go plugin: cross-compilation support #1253

Closed
wants to merge 44 commits into
from

Conversation

Projects
None yet
8 participants
Collaborator

kalikiana commented Apr 13, 2017

To be used with the --target-arch= argument.

Fixes bug 1664857.

@sergiusens sergiusens requested a review from chipaca Apr 13, 2017

Collaborator

sergiusens commented Apr 13, 2017

I've added someone as a reviewer who will give you some magic lines 😉

Member

chipaca commented Apr 14, 2017

At a glance, this is missing CGO_ENABLED=1 (otherwise Go runs as if CGO_ENABLED=0 were set, when cross-compiling). Without cgo enabled simple things like current user lookup, and full nssswitch support, won't work. You also probably need a few more flags to get the right ld and etc; I recommend having a simple go program that uses a shared C library for testing.

If you want to suffer, make that a simple go program that uses a shared C++ library. Mix both of these if you want to lose your mind.

The best quick read for this that I know of is still xnox's blog post from a few years ago.

Also note the intricacies of this differ between go versions, so test it on all of them.

Member

chipaca commented Apr 14, 2017

As a start, try this:

package main

/*
#cgo pkg-config: glib-2.0
#cgo LDFLAGS: -lm
#include <math.h>
#include <glib.h>

*/
import "C"

import (
	"fmt"
	"os/user"
)

func main() {
	u, err := user.Current()
	if err != nil {
		panic(err)
	}
	fmt.Println("user according to go:", u.Username)
	fmt.Println("user according to glib:", C.GoString((*C.char)(C.g_get_user_name())))
	fmt.Println("more or less 1:", C.sin(3.14/2))
}
Collaborator

sergiusens commented Apr 19, 2017

you might need to commit --reauthor your commit with christian@twotoasts.de or sign the CLA ;-)

Collaborator

sergiusens commented Apr 19, 2017

@mwhudson mind taking a look at this?

Contributor

mwhudson commented Apr 19, 2017

It all looks OK to me.

@kalikiana, please add a manual test for this, in manual_tests.md.

snapcraft/plugins/go.py
+ }
+ env['GOARCH'] = go_archs.get(self.project.deb_arch,
+ self.project.deb_arch)
+ env['GOARM'] = '7'
@sergiusens

sergiusens Apr 20, 2017

Collaborator

do you want to set GOARM even if GOARCH is not arm?

@mwhudson

mwhudson Apr 20, 2017

Contributor

It's harmless, if a little strange.

@sergiusens

sergiusens May 18, 2017

Collaborator

I would add an if self.project.debarch == 'armhf' here, GOARM is probably 8 for arm64 anyways

+ If you want to add other functionalities to this snap, please don't.
+ Make a new one.
+
+icon: icon.png
@sergiusens

sergiusens Apr 24, 2017

Collaborator

let's remove this (and the actual icon file)

integration_tests/__init__.py
@@ -31,6 +33,7 @@
from testtools.matchers import MatchesRegex
from snapcraft.tests import fixture_setup
+from snapcraft.internal.repo import _deb
@sergiusens

sergiusens Apr 24, 2017

Collaborator

this gives me a bad vibe, not sure how to solve it yet. Maybe reimplement here, @elopio ?

integration_tests/__init__.py
@@ -129,6 +132,32 @@ def run_snapcraft_parser(self, arguments):
raise
return snapcraft_output
+ @contextmanager
+ def setup_multi_arch_sources(self, arch):
@elopio

elopio Apr 24, 2017

Member

This is nice, but in tests we usually have fixtures instead of context managers. I personally would prefer to follow that practice, but I don't have a very strong opinion against this context manager.

+
+ arch = 'armhf'
+ with self.setup_multi_arch_sources(arch):
+ self.run_snapcraft('stage', 'go-cgo', '--target={}'.format(arch))
@elopio

elopio Apr 24, 2017

Member

looks good! Thanks :)

+ ('i386', dict(deb_arch='i386', go_arch='386')),
+ ('x86_64', dict(deb_arch='amd64', go_arch='amd64')),
+ ('ppc64le', dict(deb_arch='ppc64el', go_arch='ppc64le')),
+ ]
@elopio

elopio Apr 24, 2017

Member

Please move the scenarios before the test.

looks very good to me. I'm +1, with the request to move the scenarios before the test, and a comment about using a fixture instead of a context manager.

@sergiusens sergiusens changed the title from Cross-compilation support for the Go plugin to go plugin: cross-compilation support Apr 26, 2017

codecov-io commented Apr 28, 2017

Codecov Report

Merging #1253 into master will decrease coverage by 0.15%.
The diff coverage is 71.81%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1253      +/-   ##
==========================================
- Coverage   95.33%   95.17%   -0.16%     
==========================================
  Files         218      224       +6     
  Lines       20258    20880     +622     
  Branches     1612     1672      +60     
==========================================
+ Hits        19312    19873     +561     
- Misses        672      705      +33     
- Partials      274      302      +28
Impacted Files Coverage Δ
snapcraft/internal/repo/_base.py 70.96% <ø> (ø) ⬆️
snapcraft/internal/repo/errors.py 87.5% <ø> (+13.58%) ⬆️
snapcraft/_options.py 83.16% <ø> (+0.87%) ⬆️
snapcraft/tests/test_project_loader.py 100% <ø> (ø) ⬆️
snapcraft/tests/repo/test_deb.py 100% <100%> (ø) ⬆️
snapcraft/tests/plugins/test_go.py 100% <100%> (ø) ⬆️
snapcraft/internal/lifecycle.py 93% <100%> (ø) ⬆️
snapcraft/internal/repo/_deb.py 66.98% <44.44%> (-0.93%) ⬇️
snapcraft/plugins/go.py 92.92% <95%> (-0.02%) ⬇️
snapcraft/main.py 88.27% <0%> (-7.71%) ⬇️
... and 36 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1aeb1e3...5e5a806. Read the comment docs.

snapcraft/internal/parts.py
+ # Add multi-arch suffix for cross-compilation to dev packages
+ for package in part.code.build_packages:
+ if self._project_options.is_cross_compiling:
+ if package.endswith('-dev'):
@sergiusens

sergiusens Apr 29, 2017

Collaborator

to be future proof and this will happen, we need to check for : to detect a hardcoding of an arch, an all entry or an any; in the case of no : or any I think is when we can append.

@sergiusens

sergiusens Apr 29, 2017

Collaborator

@cjwatson can we pretty please get your insight here?

@cjwatson

cjwatson Apr 29, 2017

Contributor

I'm pretty sure that the whole approach of appending architecture suffixes is wrong here, not least because apt has a whole load of non-trivial metadata about which packages need which kind of multi-arch treatment that's being ignored. Just checking for -dev suffixes is going to go wrong in both directions.

Here's the relevant spec for the system-level behaviour. To use this correctly, you need to somehow arrange to call apt-get build-dep -a<target-arch> (the behaviour of apt-get build-dep behaves differently in subtle but important ways from apt-get install - for instance the :native suffix isn't defined for ordinary dependencies, only build-dependencies).

My recommendation would be a somewhat different strategy, which is a bit more work but should be a lot more robust: generate a temporary .dsc file with the contents of build-packages in its Build-Depends, and pass that to apt-get build-dep or apt-get build-dep -a<target-arch> depending on whether you're cross-compiling. On xenial you can just pass the .dsc directly to apt-get build-dep, but if you need to support trusty you need to faff about with turning it into a Sources file the same kind of way that sbuild does.

If it were me then I'd do that in a separate PR and rebase this one on top of it later ...

@chrmod chrmod referenced this pull request in chrmod/snap-mosh Apr 30, 2017

Open

armhf cross compilation on travis #1

needs a new review

@sergiusens sergiusens added this to the 2.30 milestone May 12, 2017

Looks good, nothing major on my side but I've taken the liberty to request some changes given the fact that a conflict resolution is in order anyways.

Thanks

snapcraft/internal/repo/_deb.py
@@ -186,20 +188,91 @@ def get_packages_for_source_type(cls, source_type):
return packages
@classmethod
- def install_build_packages(cls, package_names):
- unique_packages = set(package_names)
+ def _setup_multi_arch(cls, apt_cache, package_name):
@sergiusens

sergiusens May 18, 2017

Collaborator

Given the complexity (not the code, but the task, it is invasive after all), can we add a docstring?

snapcraft/internal/repo/_deb.py
+ release = platform.linux_distribution()[2]
+ sources = _format_sources_list(None, deb_arch=arch,
+ release=release, foreign=True)
+ with tempfile.NamedTemporaryFile() as sources_arch:
@sergiusens

sergiusens May 18, 2017

Collaborator

can you rename source_arch to source_arch_file`? makes the code below a faster read

snapcraft/internal/repo/_deb.py
+ return package_name in apt_cache
+
+ @classmethod
+ def _get_build_deps(cls, package_names, arch):
@sergiusens

sergiusens May 18, 2017

Collaborator

Can we get a docstring here too?

snapcraft/plugins/go.py
+ }
+ env['GOARCH'] = go_archs.get(self.project.deb_arch,
+ self.project.deb_arch)
+ env['GOARM'] = '7'
@sergiusens

sergiusens Apr 20, 2017

Collaborator

do you want to set GOARM even if GOARCH is not arm?

@mwhudson

mwhudson Apr 20, 2017

Contributor

It's harmless, if a little strange.

@sergiusens

sergiusens May 18, 2017

Collaborator

I would add an if self.project.debarch == 'armhf' here, GOARM is probably 8 for arm64 anyways

snapcraft/tests/fixture_setup.py
+ tempfile.gettempprefix())
+ if arch:
+ arch = ':{}'.format(arch)
+ prolog = '''NOTE: This is only a simulation!
@sergiusens

sergiusens May 18, 2017

Collaborator

mind using dedent here? from textwrap import dedent (look at the new-cli branch occurrences for quick inspiration)

snapcraft/tests/fixture_setup.py
+ errors.append(note.format(package, arch))
+ if not_cached or not_available:
+ self.exception = True
+ details = '''Some packages could not be installed. This may mean that you have
@sergiusens

sergiusens May 18, 2017

Collaborator

mind using dedent here?

Collaborator

sergiusens commented May 19, 2017

Seeing some issues

First try, go is not installed

sergiusens@mirkwood:~/source/snapcraft/integration_tests/snaps/go-cgo$ ../../../bin/snapcraft snap --target-arch=armhf
Setting target machine to 'armhf'
"grade" property not specified: defaulting to "stable"
Preparing to pull go-cgo 
Pulling go-cgo 
Please consider setting `go-importpath` for the 'go-cgo' part
go get -t -d ./go-cgo/...
/tmp/tmpirt49ylk: 10: exec: go: not found
....
subprocess.CalledProcessError: Command '['/bin/sh', '/tmp/tmpirt49ylk', 'go', 'get', '-t', '-d', './go-cgo/...']' returned non-zero exit status 127

Second try, first without x-compile

sergiusens@mirkwood:~/source/snapcraft/integration_tests/snaps/go-cgo$ ../../../bin/snapcraft pull
"grade" property not specified: defaulting to "stable"
Installing build dependencies: golang-1.7-go golang-1.7-src golang-go golang-src libglib2.0-dev libglib2.0-dev-bin libpcre3-dev libpcre32-3 libpcrecpp0v5
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Suggested packages:
  libglib2.0-doc
Recommended packages:
  golang-1.7-race-detector-runtime golang-race-detector-runtime
The following NEW packages will be installed:
  golang-1.7-go golang-1.7-src golang-go golang-src libglib2.0-dev
  libglib2.0-dev-bin libpcre3-dev libpcre32-3 libpcrecpp0v5
0 upgraded, 9 newly installed, 0 to remove and 1 not upgraded.
Need to get 0 B/28,0 MB of archives.
After this operation, 154 MB of additional disk space will be used.
...
Preparing to pull go-cgo 
Pulling go-cgo 
Please consider setting `go-importpath` for the 'go-cgo' part
go get -t -d ./go-cgo/...

Now I can x-compile, but wait...

sergiusens@mirkwood:~/source/snapcraft/integration_tests/snaps/go-cgo$ ../../../bin/snapcraft snap --target-arch=armhf
Setting target machine to 'armhf'
"grade" property not specified: defaulting to "stable"
Preparing to pull go-cgo 
Pulling go-cgo 
Please consider setting `go-importpath` for the 'go-cgo' part
go get -t -d ./go-cgo/...
Preparing to build go-cgo 
Building go-cgo 
Please consider setting `go-importpath` for the 'go-cgo' part
go build -o /home/sergiusens/source/snapcraft/integration_tests/snaps/go-cgo/parts/go-cgo/go/bin/go-cgo ./go-cgo/...
# runtime/cgo
exec: "arm-linux-gnueabihf-gcc": executable file not found in $PATH
...
snapcraft/internal/repo/_deb.py
+
+ @classmethod
+ def install_build_packages(cls, package_names, arch):
+ unique_packages = set(cls._get_build_deps(package_names, arch))
@elopio

elopio May 19, 2017

Member

This is crashing for me:

/home/ubuntu/workspace/canonical/snapcraft/snapcraft/internal/repo/_deb.py(286)install_build_packages()
-> new_packages = []
(Pdb) unique_packages
{'for', 'Keep', 'privileges', 'so', 'package', 'get', 'build', 'in', 'relevance', "don't", "'/tmp/tmpvnu0mvxc.dsc'", 'tree...', 'file', 'the', 'is', 'dependencies', 'depend', 'real', 'on', 'also', 'deactivated,', 'locking', 'execution.', 'Reading', 'information...', 'current', 'needs', 'Building', 'mind', 'using', 'root', 'state', 'to', 'situation!', 'lists...', 'that', 'Note,', 'dependency'}

$ apt-get build-dep -q -s -aarm64 golang-go:native
NOTE: This is only a simulation!
apt-get needs root privileges for real execution.
Keep also in mind that locking is deactivated,
so don't depend on the relevance to the real current situation!
E: No architecture information available for arm64. See apt.conf(5) APT::Architectures for setup

@sergiusens sergiusens modified the milestones: 2.31, 2.30 May 19, 2017

snapcraft/plugins/go.py
+ env['CC'] = '{}-gcc'.format(self.project.arch_triplet)
+ env['CXX'] = '{}-g++'.format(self.project.arch_triplet)
+ env['CGO_ENABLED'] = '1'
+ env['PKG_CONFIG_PATH'] = '/usr/lib/{}/pkgconfig'.format(
@kyrofa

kyrofa May 29, 2017

Member

This should not be needed. Check the environment bits in project loader for more details.

snapcraft/plugins/go.py
+ if not packages:
+ packages = ['./{}/...'.format(self._get_local_go_package())]
+ for package in packages:
+ binary = self._gopath_bin + '/' + self._binary_name(package)
@kyrofa

kyrofa May 29, 2017

Member

In order to maintain consistency with the rest of the codebase, I suggest os.path.join() here.

Can we split this PR in two, please?

  1. Go-specific changes that will only work if changing dpkg arch by hand
  2. The repo changes that add dpkg bits automatically (protected by a prompt)

@sergiusens sergiusens modified the milestones: 2.31, 2.30.1 Jun 1, 2017

@kalikiana kalikiana closed this Jun 1, 2017

@kyrofa kyrofa removed this from the 2.31 milestone Jun 9, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment