Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/u-root/gobusybox/src

go 1.25.0
go 1.26.0

require (
github.com/dustin/go-humanize v1.0.1
Expand Down
110 changes: 110 additions & 0 deletions src/pkg/bb/bbinternal/bb.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,106 @@ func (p *Package) rewriteFile(f *ast.File) bool {
return hasMain
}

// ambiguousImportDir parses an "ambiguous import" error message and returns
// the directory of the most specific module that provides the package.
//
// When a package exists in multiple modules (e.g. both google.golang.org/genproto
// and its sub-module google.golang.org/genproto/googleapis/rpc), Go reports an
// "ambiguous import" error. In GOPATH mode (used for the final busybox build),
// there is no such ambiguity, so we resolve it by picking the module whose path
// is the longest prefix of the package path (i.e. the most specific sub-module).
//
// Returns ("", nil) if none of the errors are ambiguous import errors.
func ambiguousImportDir(pkgPath string, errs []packages.Error) (string, error) {
for _, e := range errs {
if !strings.HasPrefix(e.Msg, "ambiguous import:") {
continue
}

// Error message format:
// ambiguous import: found package P in multiple modules:
// M1 v1 (dir1)
// M2 v2 (dir2)
lines := strings.Split(e.Msg, "\n")
type candidate struct {
modPath string
dir string
}
var candidates []candidate
for _, line := range lines[1:] {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// Each line has the form: "modulepath version (directory)"
openParen := strings.LastIndex(line, " (")
closeParen := strings.LastIndex(line, ")")
if openParen < 0 || closeParen <= openParen+1 {
continue
}
dir := line[openParen+2 : closeParen]
modPart := strings.TrimSpace(line[:openParen])
// modPart: "modulepath version"; version is the last space-separated word
spaceIdx := strings.LastIndex(modPart, " ")
if spaceIdx < 0 {
continue
}
modPath := modPart[:spaceIdx]
candidates = append(candidates, candidate{modPath: modPath, dir: dir})
}

if len(candidates) == 0 {
return "", nil
}

// Choose the candidate whose module path is the longest prefix of pkgPath.
// This prefers sub-modules (e.g. google.golang.org/genproto/googleapis/rpc)
// over monolithic parent modules (e.g. google.golang.org/genproto).
best := ""
bestLen := -1
for _, c := range candidates {
if strings.HasPrefix(pkgPath+"/", c.modPath+"/") && len(c.modPath) > bestLen {
bestLen = len(c.modPath)
best = c.dir
}
}
if best == "" {
// Fall back to the first candidate if none had a matching prefix.
best = candidates[0].dir
}
return best, nil
}
return "", nil
}

// copyDirGoFiles copies Go source files and assembly files from srcDir into
// destDir. It skips test files (_test.go) and non-source files.
func copyDirGoFiles(srcDir, destDir string) error {
if err := os.MkdirAll(destDir, 0755); err != nil {
return err
}
entries, err := os.ReadDir(srcDir)
if err != nil {
return fmt.Errorf("reading directory %q: %w", srcDir, err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if strings.HasSuffix(name, "_test.go") {
continue
}
if !strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, ".s") {
continue
}
if err := cp.Copy(filepath.Join(srcDir, name), filepath.Join(destDir, name)); err != nil {
return fmt.Errorf("copy %s: %w", name, err)
}
}
return nil
}

// WritePkg writes p's files into destDir.
func WritePkg(p *packages.Package, destDir string) error {
// TODO(hugelgupf):
Expand All @@ -416,6 +516,16 @@ func WritePkg(p *packages.Package, destDir string) error {
// should check when these packages are queried? first used?
// - test
if len(p.Errors) > 0 {
// "Ambiguous import" errors occur when a package is provided by multiple
// modules in the dependency graph. In GOPATH mode (used for the final
// busybox compilation), there is no ambiguity since each import path maps
// to exactly one directory. Resolve the ambiguity by copying files from
// the most specific module (longest module-path prefix of the package path).
if dir, err := ambiguousImportDir(p.PkgPath, p.Errors); err != nil {
return err
} else if dir != "" {
return copyDirGoFiles(dir, destDir)
}
return p.Errors[0]
}

Expand Down
140 changes: 140 additions & 0 deletions src/pkg/bb/bbinternal/bb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2015-2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bbinternal

import (
"os"
"path/filepath"
"testing"

"golang.org/x/tools/go/packages"
)

func TestAmbiguousImportDir(t *testing.T) {
for _, tt := range []struct {
name string
pkgPath string
errs []packages.Error
wantDir string // relative dir name (we check suffix)
wantNil bool // expect empty string (not an ambiguous import)
}{
{
name: "not an ambiguous import error",
pkgPath: "example.com/foo",
errs: []packages.Error{
{Msg: "build constraints exclude all Go files"},
},
wantNil: true,
},
{
name: "ambiguous import prefers sub-module",
pkgPath: "google.golang.org/genproto/googleapis/rpc/status",
errs: []packages.Error{
{
Msg: "ambiguous import: found package google.golang.org/genproto/googleapis/rpc/status in multiple modules:\n" +
"\tgoogle.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 (/mod/genproto@v0.0.0-20230410155749-daa745c078e1/googleapis/rpc/status)\n" +
"\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 (/mod/genproto/googleapis/rpc@v0.0.0-20260401024825-9d38bb4040a9/status)",
},
},
// Should prefer the sub-module (longer prefix)
wantDir: "/mod/genproto/googleapis/rpc@v0.0.0-20260401024825-9d38bb4040a9/status",
},
{
name: "ambiguous import falls back to first when no prefix match",
pkgPath: "example.com/pkg",
errs: []packages.Error{
{
Msg: "ambiguous import: found package example.com/pkg in multiple modules:\n" +
"\texample.com/other v1.0.0 (/mod/other@v1.0.0/pkg)\n" +
"\texample.com/another v2.0.0 (/mod/another@v2.0.0/pkg)",
},
},
// Neither module path is a prefix of the pkg path; fall back to first
wantDir: "/mod/other@v1.0.0/pkg",
},
{
name: "ambiguous import with empty error list",
pkgPath: "example.com/foo",
errs: []packages.Error{},
wantNil: true,
},
{
name: "ambiguous import first error not ambiguous second is",
pkgPath: "example.com/a/b",
errs: []packages.Error{
{Msg: "some other error"},
{
Msg: "ambiguous import: found package example.com/a/b in multiple modules:\n" +
"\texample.com/a v1.0.0 (/mod/a@v1.0.0/b)\n" +
"\texample.com/a/b v1.0.0 (/mod/a_b@v1.0.0)",
},
},
// Should find the ambiguous import in the second error
wantDir: "/mod/a_b@v1.0.0",
},
} {
t.Run(tt.name, func(t *testing.T) {
got, err := ambiguousImportDir(tt.pkgPath, tt.errs)
if err != nil {
t.Fatalf("ambiguousImportDir(%q, ...) returned error: %v", tt.pkgPath, err)
}
if tt.wantNil {
if got != "" {
t.Errorf("ambiguousImportDir(%q, ...) = %q, want empty", tt.pkgPath, got)
}
return
}
if got != tt.wantDir {
t.Errorf("ambiguousImportDir(%q, ...) = %q, want %q", tt.pkgPath, got, tt.wantDir)
}
})
}
}

func TestCopyDirGoFiles(t *testing.T) {
// Create a source directory with various files.
srcDir := t.TempDir()
destDir := t.TempDir()

files := map[string]string{
"foo.go": "package foo\n",
"bar.go": "package foo\n",
"foo_test.go": "package foo_test\n",
"foo.s": "// assembly\n",
"README.md": "readme\n",
"data.json": "{}\n",
}
for name, content := range files {
if err := os.WriteFile(filepath.Join(srcDir, name), []byte(content), 0644); err != nil {
t.Fatal(err)
}
}

if err := copyDirGoFiles(srcDir, destDir); err != nil {
t.Fatalf("copyDirGoFiles: %v", err)
}

// Check that Go source files and assembly files were copied, but not test
// files or other files.
wantCopied := map[string]bool{
"foo.go": true,
"bar.go": true,
"foo.s": true,
}
wantNotCopied := []string{"foo_test.go", "README.md", "data.json"}

for name := range wantCopied {
path := filepath.Join(destDir, name)
if _, err := os.Stat(path); err != nil {
t.Errorf("expected %s to be copied, but got error: %v", name, err)
}
}
for _, name := range wantNotCopied {
path := filepath.Join(destDir, name)
if _, err := os.Stat(path); err == nil {
t.Errorf("expected %s not to be copied, but it was", name)
}
}
}
11 changes: 0 additions & 11 deletions test/resolve-modules/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
Expand Down Expand Up @@ -73,7 +71,6 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw=
github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo=
github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4=
github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0=
github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4=
Expand All @@ -98,7 +95,6 @@ github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Go
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e h1:IQpunlq7T+NiJJMO7ODYV2YWBiv/KnObR3gofX0mWOo=
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 h1:h+RKaNPjka7LRJGoeub/IQBdXSoEaJjfADkBq02hvjw=
github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2/go.mod h1:RmeVYf9XrPRbRc3XIx0gLYA8qOFvNoPOfaEZduRlEp4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
Expand Down Expand Up @@ -132,7 +128,6 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
Expand All @@ -156,7 +151,6 @@ github.com/nanmu42/limitio v1.0.0/go.mod h1:8H40zQ7pqxzbwZ9jxsK2hDoE06TH5ziybtAp
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/orangecms/go-framebuffer v0.0.0-20200613202404-a0700d90c330/go.mod h1:3Myb/UszJY32F2G7yGkUtcW/ejHpjlGfYLim7cv2uKA=
github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA=
github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
Expand All @@ -179,9 +173,7 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rck/unit v0.0.3 h1:q3/Ui9gcrFKpEneZXw2gNmNEbzv5jLrZnH6qhX1ypZ0=
github.com/rck/unit v0.0.3/go.mod h1:jTOnzP4s1OjIP1vdxb4n76b23QPKS4EurYg7sYMr2DM=
github.com/rekby/gpt v0.0.0-20200219180433-a930afbc6edc h1:goZGTwEEn8mWLcY012VouWZWkJ8GrXm9tS3VORMxT90=
github.com/rekby/gpt v0.0.0-20200219180433-a930afbc6edc/go.mod h1:scrOqOnnHVKCHENvFw8k9ajCb88uqLQDA4BvuJNJ2ew=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
Expand Down Expand Up @@ -217,7 +209,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
github.com/twitchtv/twirp v5.8.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
github.com/u-root/gobusybox/src v0.0.0-20220728145311-85dc1fd1bc75 h1:pCQLzZyKqQnYn/lTpROHlQJO8LrBQqrHE0gar1TnGOA=
github.com/u-root/gobusybox/src v0.0.0-20220728145311-85dc1fd1bc75/go.mod h1:xvfGSdbSMKh7LhDFEoyPPrlWdEbqkM16DEEoOsMvRoI=
github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a h1:A0sK7WEodak7eVd21MOEatnh2pfAAwZaEPSIEEsjctQ=
github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a/go.mod h1:RWIgJWqm9/0gjBZ0Hl8iR6MVGzZ+yAda2uqqLmetE2I=
github.com/u-root/u-root v0.10.0 h1:nz3jSORXAxTl6bNXhRh5s9HT5oRo5hmCJXUJ0q/BK7s=
github.com/u-root/u-root v0.10.0/go.mod h1:lqAiThZZ0/yg0rj49gxpSCK8hfv86NSc+wPhGp5idb4=
Expand Down Expand Up @@ -387,7 +378,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
mvdan.cc/sh/v3 v3.4.1/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY=
pack.ag/tftp v1.0.1-0.20181129014014-07909dfbde3c h1:4DHuGX0VtxRIyjXlVpcjSGEmZ7OnIK7Hvo+INnxI8yk=
pack.ag/tftp v1.0.1-0.20181129014014-07909dfbde3c/go.mod h1:N1Pyo5YG+K90XHoR2vfLPhpRuE8ziqbgMn/r/SghZas=
src.elv.sh v0.16.0-rc1.0.20220116211855-fda62502ad7f h1:pjVeIo9Ba6K1Wy+rlwX91zT7A+xGEmxiNRBdN04gDTQ=
src.elv.sh v0.16.0-rc1.0.20220116211855-fda62502ad7f/go.mod h1:kPbhv5+fBeUh85nET3wWhHGUaUQ64nZMJ8FwA5v5Olg=