Skip to content

Commit 7974851

Browse files
authored
Ensure modules are correctly handled including remote modules (#189)
There was an unfortunate misunderstanding in #180 which did not properly take account of how xgo.go handled remote builds as compared to local builds, and modules. This PR adjusts the way xgo handles GO111MODULE and the way it sets GO111MODULE to more closely match the way go 1.16+ handles GO111MODULE - and it makes remote builds work again (albeit there is a problem here, see below...) From go1.16 module aware builds are assumed by default with GO111MODULE. This means GO111MODULE="" means GO111MODULE="on". This differs from the previous state where GO111MODULE="" meant GO111MODULE="auto". `xgo` will now respect the GO111MODULE environment variable in the context it run in. If it is empty, a module aware build is performed by default. If it is "auto" `xgo` will interrogate the repository to discover it should be "on" or "off" in a similar way to `go`. HOWEVER, "auto" is not supported for remote builds as source code is not available to it. (It would be possible to move some of this into the build.sh or make it a separate command if this was required.) Next, when doing a module-aware build of a remote repository the bug in #180 has been fixed, and because the change in go1.18 that causes `go get` to not work outside of a module by default - the `go get` command is explicitly run with `GO111MODULE="off"` to get the old GOPATH `go get` variant. Potential issues: * `GO111MODULE="auto"` is not supported for remote builds - this could be made supportable as described above. * The hack above using `GO111MODULE="off"` to make `go get` work for remote builds is probably going to stop working within a few more releases of go. Fix #187 Signed-off-by: Andrew Thornton <[email protected]> Signed-off-by: Andrew Thornton <[email protected]>
1 parent 452da85 commit 7974851

File tree

2 files changed

+126
-74
lines changed

2 files changed

+126
-74
lines changed

docker/base/build.sh

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ else
6666
fi
6767
fi
6868

69-
# Either set a local build environemnt, or pull any remote imports
69+
# Either set a local build environment, or pull any remote imports
7070
if [ "$EXT_GOPATH" != "" ]; then
7171
# If local builds are requested, inject the sources
7272
echo "Building locally $1..."
@@ -77,13 +77,9 @@ if [ "$EXT_GOPATH" != "" ]; then
7777
cd "$(go list -e -f "{{.Dir}}" "$1")"
7878
GODEPS_WORKSPACE="$(pwd)/Godeps/_workspace"
7979
export GOPATH="$GOPATH":"$GODEPS_WORKSPACE"
80-
elif [[ "$USEMODULES" == true ]]; then
81-
# Go module builds should assume a local repository
82-
# at mapped to /source containing at least a go.mod file.
83-
if [[ ! -d /source ]]; then
84-
echo "Go modules are enabled but go.mod was not found in the source folder."
85-
exit 10
86-
fi
80+
elif [[ "$USEMODULES" == true && -d /source ]]; then
81+
# Go module build with a local repository mapped to /source containing at least a go.mod file.
82+
8783
# Change into the repo/source folder
8884
cd /source
8985
echo "Building /source/go.mod..."
@@ -98,7 +94,7 @@ else
9894

9995
# Otherwise download the canonical import path (may fail, don't allow failures beyond)
10096
echo "Fetching main repository $1..."
101-
go get -v -d "$1"
97+
GO111MODULE="off" go get -v -d "$1"
10298
set -e
10399

104100
cd "$GOPATH_ROOT/$1"
@@ -162,21 +158,23 @@ shopt -u nullglob
162158

163159

164160
# Configure some global build parameters
165-
NAME=$(basename "$1/$PACK")
161+
NAME="$OUT"
166162

167-
# Go module-based builds error with 'cannot find main module'
168-
# when $PACK is defined
169-
if [[ "$USEMODULES" = true ]]; then
170-
NAME=$(sed -n 's/module\ \(.*\)/\1/p' /source/go.mod)
163+
if [ "$NAME" == "" ]; then
164+
if [[ "$USEMODULES" = true ]]; then
165+
# Go module-based builds error with 'cannot find main module'
166+
# when $PACK is defined
167+
NAME="$(sed -n 's/module\ \(.*\)/\1/p' /source/go.mod)"
168+
fi
169+
fi
170+
171+
if [ "$NAME" == "" ]; then
172+
NAME="$(basename "$1/$PACK")"
171173
fi
172174

173175
# Support go module package
174176
PACK_RELPATH="./$PACK"
175177

176-
if [ "$OUT" != "" ]; then
177-
NAME=$OUT
178-
fi
179-
180178
if [ "$FLAG_V" == "true" ]; then V=-v; LD+='-v'; fi
181179
if [ "$FLAG_X" == "true" ]; then X=-x; fi
182180
if [ "$FLAG_RACE" == "true" ]; then R=-race; fi

xgo.go

Lines changed: 110 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ func init() {
4040
}
4141

4242
// Cross compilation docker containers
43-
var dockerBase = "techknowlogick/xgo:base"
44-
var dockerDist = "techknowlogick/xgo:"
43+
var (
44+
dockerDist = "techknowlogick/xgo:"
45+
)
4546

4647
// Command line arguments to fine tune the compilation
4748
var (
@@ -240,7 +241,6 @@ func checkDockerImage(image string) (bool, error) {
240241

241242
// compare output of docker images and image name
242243
func compareOutAndImage(out []byte, image string) (bool, error) {
243-
244244
if strings.Contains(image, ":") {
245245
// get repository and tag
246246
res := strings.SplitN(image, ":", 2)
@@ -251,7 +251,6 @@ func compareOutAndImage(out []byte, image string) (bool, error) {
251251

252252
// default find repository without tag
253253
return bytes.Contains(out, []byte(image)), nil
254-
255254
}
256255

257256
// Pulls an image from the docker registry.
@@ -265,8 +264,24 @@ func pullDockerImage(image string) error {
265264
func compile(image string, config *ConfigFlags, flags *BuildFlags, folder string) error {
266265
// If a local build was requested, find the import path and mount all GOPATH sources
267266
locals, mounts, paths := []string{}, []string{}, []string{}
268-
var usesModules bool
269-
if strings.HasPrefix(config.Repository, string(filepath.Separator)) || strings.HasPrefix(config.Repository, ".") {
267+
268+
usesModules := true
269+
localBuild := strings.HasPrefix(config.Repository, string(filepath.Separator)) || strings.HasPrefix(config.Repository, ".")
270+
271+
// We need to consider our module-aware status
272+
go111module := os.Getenv("GO111MODULE")
273+
if go111module == "off" {
274+
usesModules = false
275+
} else if go111module == "auto" {
276+
// we need to look at the current config and determine if we should use modules...
277+
278+
if !localBuild {
279+
// This implies that we are using an url or module name for `go get`.
280+
// We can't run `go get` here! So we cannot determine if this needs to be module-aware or not!
281+
log.Fatalf("Can only compile directories with GO111MODULE=auto")
282+
}
283+
284+
usesModules = false
270285
if _, err := os.Stat(config.Repository + "/go.mod"); err == nil {
271286
usesModules = true
272287
}
@@ -278,58 +293,92 @@ func compile(image string, config *ConfigFlags, flags *BuildFlags, folder string
278293
usesModules = true
279294
}
280295
}
296+
if !usesModules {
297+
// Walk the parents looking for a go.mod file!
298+
goModDir, err := filepath.Abs(config.Repository)
299+
if err != nil {
300+
log.Fatalf("Failed to locate requested package: %v.", err)
301+
}
302+
// now walk backwards as per go behaviour
303+
for {
304+
if stat, err := os.Stat(filepath.Join(goModDir, "go.mod")); err == nil {
305+
usesModules = true
306+
break
307+
} else if stat.IsDir() {
308+
break
309+
}
310+
parent := filepath.Dir(goModDir)
311+
if len(parent) >= len(goModDir) {
312+
break
313+
}
314+
goModDir = parent
315+
}
316+
}
317+
}
281318

319+
if localBuild && !usesModules {
320+
// If we're performing a local build and we're not using modules we need to map the gopath over to the docker
321+
322+
// First determine the GOPATH
282323
gopathEnv := os.Getenv("GOPATH")
283-
if gopathEnv == "" && !usesModules {
324+
if gopathEnv == "" {
284325
log.Printf("No $GOPATH is set - defaulting to %s", build.Default.GOPATH)
285326
gopathEnv = build.Default.GOPATH
286327
}
287328

288-
// Iterate over all the local libs and export the mount points
289-
if gopathEnv == "" && !usesModules {
329+
if gopathEnv == "" {
290330
log.Fatalf("No $GOPATH is set or forwarded to xgo")
291331
}
292-
if !usesModules {
293332

294-
for _, gopath := range strings.Split(gopathEnv, string(os.PathListSeparator)) {
295-
// Since docker sandboxes volumes, resolve any symlinks manually
296-
sources := filepath.Join(gopath, "src")
297-
filepath.Walk(sources, func(path string, info os.FileInfo, err error) error {
298-
// Skip any folders that errored out
299-
if err != nil {
300-
log.Printf("Failed to access GOPATH element %s: %v", path, err)
301-
return nil
302-
}
303-
// Skip anything that's not a symlink
304-
if info.Mode()&os.ModeSymlink == 0 {
305-
return nil
306-
}
307-
// Resolve the symlink and skip if it's not a folder
308-
target, err := filepath.EvalSymlinks(path)
309-
if err != nil {
310-
return nil
311-
}
312-
if info, err = os.Stat(target); err != nil || !info.IsDir() {
313-
return nil
314-
}
315-
// Skip if the symlink points within GOPATH
316-
if filepath.HasPrefix(target, sources) {
333+
// Iterate over all the local libs and export the mount points
334+
for _, gopath := range strings.Split(gopathEnv, string(os.PathListSeparator)) {
335+
// Since docker sandboxes volumes, resolve any symlinks manually
336+
sources := filepath.Join(gopath, "src")
337+
absSources, err := filepath.Abs(sources)
338+
if err != nil {
339+
log.Fatalf("Unable to generate absolute path for source directory %s. %v", sources, err)
340+
}
341+
absSources = filepath.ToSlash(filepath.Join(absSources, string(filepath.Separator)))
342+
_ = filepath.Walk(sources, func(path string, info os.FileInfo, err error) error {
343+
// Skip any folders that errored out
344+
if err != nil {
345+
log.Printf("Failed to access GOPATH element %s: %v", path, err)
346+
return nil
347+
}
348+
// Skip anything that's not a symlink
349+
if info.Mode()&os.ModeSymlink == 0 {
350+
return nil
351+
}
352+
// Resolve the symlink and skip if it's not a folder
353+
target, err := filepath.EvalSymlinks(path)
354+
if err != nil {
355+
return nil
356+
}
357+
if info, err = os.Stat(target); err != nil || !info.IsDir() {
358+
return nil
359+
}
360+
// Skip if the symlink points within GOPATH
361+
absTarget, err := filepath.Abs(target)
362+
if err == nil {
363+
absTarget = filepath.ToSlash(filepath.Join(absTarget, string(filepath.Separator)))
364+
if strings.HasPrefix(absTarget, absSources) {
317365
return nil
318366
}
367+
}
319368

320-
// Folder needs explicit mounting due to docker symlink security
321-
locals = append(locals, target)
322-
mounts = append(mounts, filepath.Join("/ext-go", strconv.Itoa(len(locals)), "src", strings.TrimPrefix(path, sources)))
323-
paths = append(paths, filepath.Join("/ext-go", strconv.Itoa(len(locals))))
324-
return nil
325-
})
326-
// Export the main mount point for this GOPATH entry
327-
locals = append(locals, sources)
328-
mounts = append(mounts, filepath.Join("/ext-go", strconv.Itoa(len(locals)), "src"))
369+
// Folder needs explicit mounting due to docker symlink security
370+
locals = append(locals, target)
371+
mounts = append(mounts, filepath.Join("/ext-go", strconv.Itoa(len(locals)), "src", strings.TrimPrefix(path, sources)))
329372
paths = append(paths, filepath.Join("/ext-go", strconv.Itoa(len(locals))))
330-
}
373+
return nil
374+
})
375+
// Export the main mount point for this GOPATH entry
376+
locals = append(locals, sources)
377+
mounts = append(mounts, filepath.Join("/ext-go", strconv.Itoa(len(locals)), "src"))
378+
paths = append(paths, filepath.Join("/ext-go", strconv.Itoa(len(locals))))
331379
}
332380
}
381+
333382
// Assemble and run the cross compilation command
334383
fmt.Printf("Cross compiling %s...\n", config.Repository)
335384

@@ -383,27 +432,32 @@ func compile(image string, config *ConfigFlags, flags *BuildFlags, folder string
383432
// Set ssh agent socket environment variable
384433
args = append(args, "-e", fmt.Sprintf("SSH_AUTH_SOCK=%s", os.Getenv("SSH_AUTH_SOCK")))
385434
}
435+
386436
if usesModules {
387437
args = append(args, []string{"-e", "GO111MODULE=on"}...)
388438
args = append(args, []string{"-v", build.Default.GOPATH + ":/go"}...)
389-
390-
// Map this repository to the /source folder
391-
absRepository, err := filepath.Abs(config.Repository)
392-
if err != nil {
393-
log.Fatalf("Failed to locate requested module repository: %v.", err)
394-
}
395-
args = append(args, []string{"-v", absRepository + ":/source"}...)
439+
// FIXME: consider GOMODCACHE?
396440

397441
fmt.Printf("Enabled Go module support\n")
398442

399-
// Check whether it has a vendor folder, and if so, use it
400-
vendorPath := absRepository + "/vendor"
401-
vendorfolder, err := os.Stat(vendorPath)
402-
if !os.IsNotExist(err) && vendorfolder.Mode().IsDir() {
403-
args = append(args, []string{"-e", "FLAG_MOD=vendor"}...)
404-
fmt.Printf("Using vendored Go module dependencies\n")
443+
if localBuild {
444+
// Map this repository to the /source folder
445+
absRepository, err := filepath.Abs(config.Repository)
446+
if err != nil {
447+
log.Fatalf("Failed to locate requested module repository: %v.", err)
448+
}
449+
450+
args = append(args, []string{"-v", absRepository + ":/source"}...)
451+
// Check whether it has a vendor folder, and if so, use it
452+
vendorPath := absRepository + "/vendor"
453+
vendorfolder, err := os.Stat(vendorPath)
454+
if !os.IsNotExist(err) && vendorfolder.Mode().IsDir() {
455+
args = append(args, []string{"-e", "FLAG_MOD=vendor"}...)
456+
fmt.Printf("Using vendored Go module dependencies\n")
457+
}
405458
}
406459
} else {
460+
args = append(args, []string{"-e", "GO111MODULE=off"}...)
407461
for i := 0; i < len(locals); i++ {
408462
args = append(args, []string{"-v", fmt.Sprintf("%s:%s:ro", locals[i], mounts[i])}...)
409463
}

0 commit comments

Comments
 (0)