Package: dh-golang
Version: 1.39
Tags: patch
User: debian-cr...@lists.debian.org
Usertags: cross-satisfiability ftcbfs

I was recently asked how to cross build Debian packages with go sources
and figured that presently it wouldn't work at all. Effectively, all go
packages are cross bd-uninstallable due to the way Multi-Arch works.

Packages typically Build-Depend on dh-golang. Such a dependency is
interpreted as a host architecture dependency. Now dh-golang is
Architecture: all, which is treated as a "native" architecture (for
dependency resolution). "native" is formally defined as the architecture
of the installed "dpkg" package and usually matches the build
architecture. When build and host architecture differ (aka cross
compiling), dh-golang:$host does not have any installation candidate.
The usual fix is to mark it Multi-Arch: foreign. But doing so would be
wrong in its present form.

For getting started with cross building go packages, I looked for very
simple go packages. Note that go libraries actually package source code
and thus typically are Architecture: all. Such packages are irrelevant
for cross building. What I looked for was a go package containing an
executable. About the simplest package I could find was "canid". Now for
actually trying a cross build, I simply installed the relevant packages
for the build architecture and made dpkg-checkbuilddeps shut up using
-d. Dependency satisfiability still needs to be taken care of somehow,
but let's defer that for now.

Martín Ferrari kindly pointed me to the GOOS and GOARCH environment
variables. These need to be set. For Arm architectures one also needs to
set GOARM. The values are ... not very obvious. The attached patch takes
care of setting them up. As far as I understand it, go might also call a
C compiler, so I also provide a CC variable. Once all these variables
are set, I get a successful build.

Unfortunately the contents of the build are broken. Instead of
/usr/bin/canid, I now have /usr/bin/${GOOS}_${GOARCH}/canid. According
to github user ishanjain28 this is an expected convention. Therefore my
patch corrects the binary installation directory in the install step.

Another aspect is that canid is now statically linked. This also is
expected behaviour as https://golang.org/cmd/cgo/ explains:

| The cgo tool is enabled by default for native builds on systems where it
| is expected to work. It is disabled by default when cross-compiling. You
| can control this by setting the CGO_ENABLED environment variable when
| running the go tool:

In the interest of reproducible builds, we need to match the native
behaviour and set CGO_ENABLED=1.

I also tried building mtail (again running dpkg-buildpackage with -d)
and it appeared to produce reasonable packages.

Since dh-golang now actually honours the dpkg architecture variables,
marking it Multi-Arch: foreign is a reasonable thing to do.

If you upload a dh-golang with these changes, suddenly those go packages
that don't have any library dependencies will become cross satisfiable
(due to the Multi-Arch: foreign marking).  However their dependency on
golang-any will use the host architecture go, which cannot be run. We'll
have to somehow figure out what to do about this. I don't have an answer
yet.

In the mean time, please consider applying the attached patch as an
incremental step after buster is released.

Helmut
diff --minimal -Nru dh-golang-1.39/debian/changelog 
dh-golang-1.39+nmu1/debian/changelog
--- dh-golang-1.39/debian/changelog     2018-12-30 02:00:34.000000000 +0100
+++ dh-golang-1.39+nmu1/debian/changelog        2019-06-07 20:06:44.000000000 
+0200
@@ -1,3 +1,15 @@
+dh-golang (1.39+nmu1) UNRELEASED; urgency=medium
+
+  * Non-maintainer upload.
+  * Make dh-golang work with cross compiling. (Closes: #-1)
+    + Set up cross environment variables GOOS, GOARCH and GOARM.
+    + Set up a C compiler in CC.
+    + Always use cgo (enabled by default for native builds).
+    + Cross go install installs to GOPATH/bin/GOOS_GOARCH.
+    + Mark dh-golang Multi-Arch: foreign.
+
+ -- Helmut Grohne <hel...@subdivi.de>  Fri, 07 Jun 2019 20:06:44 +0200
+
 dh-golang (1.39) unstable; urgency=medium
 
   * Team upload.
diff --minimal -Nru dh-golang-1.39/debian/control 
dh-golang-1.39+nmu1/debian/control
--- dh-golang-1.39/debian/control       2018-12-30 01:28:09.000000000 +0100
+++ dh-golang-1.39+nmu1/debian/control  2019-06-07 20:06:41.000000000 +0200
@@ -11,6 +11,7 @@
 
 Package: dh-golang
 Architecture: all
+Multi-Arch: foreign
 Depends: ${perl:Depends}, ${misc:Depends},
          debhelper,
          libdpkg-perl,
diff --minimal -Nru dh-golang-1.39/lib/Debian/Debhelper/Buildsystem/golang.pm 
dh-golang-1.39+nmu1/lib/Debian/Debhelper/Buildsystem/golang.pm
--- dh-golang-1.39/lib/Debian/Debhelper/Buildsystem/golang.pm   2018-12-30 
01:27:48.000000000 +0100
+++ dh-golang-1.39+nmu1/lib/Debian/Debhelper/Buildsystem/golang.pm      
2019-06-07 20:06:44.000000000 +0200
@@ -309,6 +309,55 @@
     $ENV{GO111MODULE} = "off";
 }
 
+my %GOOS_MAPPING = (
+    'linux' => 'linux',
+);
+my %GOARCH_MAPPING = (
+    'amd64/base'     => {'GOARCH' => 'amd64'},
+    'amd64/x32'      => {'GOARCH' => 'amd64p32'},
+    'arm/eabi'       => {'GOARCH' => 'arm', 'GOARM' => '5'},
+    'arm/eabihf'     => {'GOARCH' => 'arm', 'GOARM' => '7'},
+    'arm64/base'     => {'GOARCH' => 'arm64'},
+    'i386/base'      => {'GOARCH' => '386'},
+    'mips/base'      => {'GOARCH' => 'mips'},
+    'mips64/abi64'   => {'GOARCH' => 'mips64'},
+    'mips64el/abi64' => {'GOARCH' => 'mips64le'},
+    'mipsel/base'    => {'GOARCH' => 'mipsle'},
+    'powerpc/base'   => {'GOARCH' => 'ppc'},
+    'ppc64/base'     => {'GOARCH' => 'ppc64'},
+    'ppc64el/base'   => {'GOARCH' => 'ppc64le'},
+    'riscv64/base'   => {'GOARCH' => 'riscv64'},
+    's390/base'      => {'GOARCH' => 's390'},
+    's390x/base'     => {'GOARCH' => 's390x'},
+    'sparc/base'     => {'GOARCH' => 'sparc'},
+    'sparc64/base'   => {'GOARCH' => 'sparc64'},
+);
+
+sub _set_gocross {
+    return unless is_cross_compiling();
+
+    unless ($ENV{CC}) {
+        $ENV{CC} = dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-gcc";
+    }
+
+    $ENV{CGO_ENABLED} = "1";
+
+    my $host_os = dpkg_architecture_value("DEB_HOST_ARCH_OS");
+    if (defined(my $goos = $GOOS_MAPPING{$host_os})) {
+        $ENV{GOOS} = $goos;
+    } else {
+        error("Cannot cross-compile: Missing entry for HOST OS ${host_os}.");
+    }
+
+    my $host_cpu_abi = dpkg_architecture_value("DEB_HOST_ARCH_CPU") . "/" .
+                       dpkg_architecture_value("DEB_HOST_ARCH_ABI");
+    if (defined(my $cpu_vars = $GOARCH_MAPPING{$host_cpu_abi})) {
+        @ENV{keys %{$cpu_vars}} = @{$cpu_vars}{keys %{$cpu_vars}};
+    } else {
+        error("Cannot cross-compile: Missing entry for CPU/ABI 
${host_cpu_abi}.");
+    }
+}
+
 sub _link_contents {
     my ($src, $dst) = @_;
 
@@ -475,6 +524,7 @@
     $this->_set_gopath();
     $this->_set_gocache();
     $this->_set_go111module();
+    $this->_set_gocross();
     if (exists($ENV{DH_GOLANG_GO_GENERATE}) && $ENV{DH_GOLANG_GO_GENERATE} == 
1) {
         $this->doit_in_builddir("go", "generate", "-v", @_, get_targets());
     }
@@ -526,7 +576,12 @@
     my @binaries = glob "$builddir/bin/*";
     if ($install_binaries and @binaries > 0) {
         $this->doit_in_builddir('mkdir', '-p', "$destdir/usr");
-        $this->doit_in_builddir('cp', '-r', 'bin', "$destdir/usr");
+        if (is_cross_compiling()) {
+            $this->_set_gocross();
+            $this->doit_in_builddir('cp', '-r', '-T', 
"bin/$ENV{GOOS}_$ENV{GOARCH}", "$destdir/usr/bin");
+        } else {
+            $this->doit_in_builddir('cp', '-r', 'bin', "$destdir/usr");
+        }
     }
 
     if ($install_source) {

Reply via email to