internal/fetch: support multiple build contexts
Extend the goPackage type to handle multiple documentation build
contexts (GOOS/GOARCH combinations).
When goPackages are coverted to Units, ignore all but the first.
For golang/go#37232
Change-Id: I706e6e407c2b3c31963032f1db5aa9a297e7ad8a
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/279458
Trust: Jonathan Amsterdam <jba@google.com>
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/fetch/fetch_test.go b/internal/fetch/fetch_test.go
index b7fd746..897efb1 100644
--- a/internal/fetch/fetch_test.go
+++ b/internal/fetch/fetch_test.go
@@ -88,13 +88,13 @@
}
t.Run(fmt.Sprintf("%s:%s", fetcher.name, test.name), func(t *testing.T) {
ctx := context.Background()
- ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
+ ctx, cancel := context.WithTimeout(ctx, 150*time.Second)
defer cancel()
got, d := fetcher.fetch(t, true, ctx, test.mod, test.fetchVersion)
defer got.Defer()
if got.Error != nil {
- t.Fatal("fetching failed: %w", got.Error)
+ t.Fatalf("fetching failed: %v", got.Error)
}
if !test.cleaned {
test.mod.fr = cleanFetchResult(t, test.mod.fr, d)
diff --git a/internal/fetch/fetchlocal.go b/internal/fetch/fetchlocal.go
index 6325f60..8f171b3 100644
--- a/internal/fetch/fetchlocal.go
+++ b/internal/fetch/fetchlocal.go
@@ -19,7 +19,6 @@
"golang.org/x/mod/modfile"
"golang.org/x/pkgsite/internal/derrors"
- "golang.org/x/pkgsite/internal/log"
"golang.org/x/pkgsite/internal/source"
)
@@ -55,8 +54,6 @@
if fi != nil {
finishFetchInfo(fi, fr.Status, fr.Error)
}
- log.Debugf(ctx, "memory after fetch of %s: %dM", fr.ModulePath, allocMeg())
-
}()
info, err := os.Stat(localPath)
diff --git a/internal/fetch/load.go b/internal/fetch/load.go
index 1e62873..be5b7ad 100644
--- a/internal/fetch/load.go
+++ b/internal/fetch/load.go
@@ -54,16 +54,18 @@
}
// loadPackage loads a Go package by calling loadPackageWithBuildContext, trying
-// several build contexts in turn. The first build context in the list to produce
-// a non-empty package is used. If none of them result in a package, then
-// loadPackage returns nil, nil.
+// several build contexts in turn. It returns a goPackage with documentation
+// information for each build context that results in a valid package, in the
+// same order that the build contexts are listed. If none of them result in a
+// package, then loadPackage returns nil, nil.
//
-// If the package is fine except that its documentation is too large, loadPackage
-// returns a package whose err field is a non-nil error with godoc.ErrTooLarge in its chain.
+// If a package is fine except that its documentation is too large, loadPackage
+// returns a goPackage whose err field is a non-nil error with godoc.ErrTooLarge in its chain.
func loadPackage(ctx context.Context, zipGoFiles []*zip.File, innerPath string, sourceInfo *source.Info, modInfo *godoc.ModuleInfo) (_ *goPackage, err error) {
defer derrors.Wrap(&err, "loadPackage(ctx, zipGoFiles, %q, sourceInfo, modInfo)", innerPath)
ctx, span := trace.StartSpan(ctx, "fetch.loadPackage")
defer span.End()
+ var pkgs []*goPackage
for _, env := range goEnvs {
pkg, err := loadPackageWithBuildContext(ctx, env.GOOS, env.GOARCH, zipGoFiles, innerPath, sourceInfo, modInfo)
if err != nil && !errors.Is(err, godoc.ErrTooLarge) && !errors.Is(err, derrors.NotFound) {
@@ -71,10 +73,39 @@
}
if pkg != nil {
pkg.err = err
+ pkgs = append(pkgs, pkg)
+ }
+ }
+ return mergePackages(pkgs)
+}
+
+// mergePackages combines multiple packages from different build contexts into
+// one.
+func mergePackages(pkgs []*goPackage) (*goPackage, error) {
+ if len(pkgs) == 0 {
+ return nil, nil
+ }
+ // If any build context has an error, return that one.
+ for _, pkg := range pkgs {
+ if pkg.err != nil {
return pkg, nil
}
}
- return nil, nil
+
+ // Use everything from the first package, just merge the docs.
+ result := pkgs[0]
+ for _, pkg := range pkgs[1:] {
+ if pkg.name != result.name {
+ // All the build environments should use the same package name. Although
+ // it's technically legal for different build tags to result in different
+ // package names, it's not something we support.
+ return nil, &BadPackageError{
+ Err: fmt.Errorf("more than one package name (%q and %q)", result.name, pkg.name),
+ }
+ }
+ result.docs = append(result.docs, pkg.docs[0])
+ }
+ return result, nil
}
// httpPost allows package fetch tests to stub out playground URL fetches.
@@ -132,14 +163,16 @@
}
v1path := internal.V1Path(importPath, modulePath)
return &goPackage{
- path: importPath,
- name: packageName,
- synopsis: synopsis,
- v1path: v1path,
- imports: imports,
- goos: goos,
- goarch: goarch,
- source: src,
+ path: importPath,
+ name: packageName,
+ v1path: v1path,
+ imports: imports,
+ docs: []*internal.Documentation{{
+ GOOS: goos,
+ GOARCH: goarch,
+ Synopsis: synopsis,
+ Source: src,
+ }},
}, err
}
diff --git a/internal/fetch/load_test.go b/internal/fetch/load_test.go
index 94f90d2..166a369 100644
--- a/internal/fetch/load_test.go
+++ b/internal/fetch/load_test.go
@@ -10,6 +10,9 @@
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "golang.org/x/pkgsite/internal"
+ "golang.org/x/pkgsite/internal/godoc"
"golang.org/x/pkgsite/internal/testing/testhelper"
)
@@ -95,3 +98,85 @@
})
}
}
+
+func TestMergePackages(t *testing.T) {
+ doc1 := &internal.Documentation{
+ GOOS: "linux",
+ GOARCH: "amd64",
+ Synopsis: "s1",
+ }
+ doc2 := &internal.Documentation{
+ GOOS: "js",
+ GOARCH: "wasm",
+ Synopsis: "s2",
+ }
+
+ pkg := func(name, imp string, doc *internal.Documentation, err error) *goPackage {
+ return &goPackage{
+ name: name,
+ imports: []string{imp},
+ docs: []*internal.Documentation{doc},
+ err: err,
+ }
+ }
+
+ for _, test := range []struct {
+ name string
+ pkgs []*goPackage
+ want *goPackage
+ wantErr bool
+ }{
+ {
+ name: "no packages",
+ pkgs: nil,
+ want: nil,
+ wantErr: false,
+ },
+ {
+ name: "one package",
+ pkgs: []*goPackage{pkg("name1", "imp1", doc1, nil)},
+ want: pkg("name1", "imp1", doc1, nil),
+ },
+ {
+ name: "two packages",
+ pkgs: []*goPackage{
+ pkg("name1", "imp1", doc1, nil),
+ pkg("name1", "imp2", doc2, nil),
+ },
+ want: &goPackage{
+ name: "name1",
+ imports: []string{"imp1"}, // keep the first one
+ docs: []*internal.Documentation{doc1, doc2}, // keep both, in order
+ },
+ },
+ {
+ name: "one package has err",
+ pkgs: []*goPackage{
+ pkg("name1", "imp1", doc1, nil),
+ pkg("name1", "imp2", doc2, godoc.ErrTooLarge),
+ },
+ want: pkg("name1", "imp2", doc2, godoc.ErrTooLarge), // return one with err
+ },
+ {
+ name: "different names",
+ pkgs: []*goPackage{
+ pkg("name1", "imp1", doc1, nil),
+ pkg("name2", "imp2", doc2, nil),
+ },
+ wantErr: true,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := mergePackages(test.pkgs)
+ if err != nil {
+ if !test.wantErr {
+ t.Fatalf("got %v, want no error", err)
+ }
+ return
+ }
+ if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(goPackage{}), cmpopts.EquateErrors()); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+ })
+ }
+}
diff --git a/internal/fetch/package.go b/internal/fetch/package.go
index b4bed4c..8acee3b 100644
--- a/internal/fetch/package.go
+++ b/internal/fetch/package.go
@@ -30,20 +30,14 @@
type goPackage struct {
path string
name string
- synopsis string
imports []string
isRedistributable bool
licenseMeta []*licenses.Metadata // metadata of applicable licenses
- // goos and goarch are environment variables used to parse the
- // package.
- goos string
- goarch string
-
// v1path is the package path of a package with major version 1 in a given
// series.
v1path string
- source []byte // the source files of the package, for generating doc at serving time
- err error // non-fatal error when loading the package (e.g. documentation is too large)
+ docs []*internal.Documentation // doc for different build contexts
+ err error // non-fatal error when loading the package (e.g. documentation is too large)
}
// extractPackagesFromZip returns a slice of packages from the module zip r.
diff --git a/internal/fetch/unit.go b/internal/fetch/unit.go
index a33f7f1..7bce5bb 100644
--- a/internal/fetch/unit.go
+++ b/internal/fetch/unit.go
@@ -61,12 +61,8 @@
if pkg, ok := pkgLookup[dirPath]; ok {
dir.Name = pkg.name
dir.Imports = pkg.imports
- dir.Documentation = &internal.Documentation{
- GOOS: pkg.goos,
- GOARCH: pkg.goarch,
- Synopsis: pkg.synopsis,
- Source: pkg.source,
- }
+ // TODO(golang/go#37232): keep all docs
+ dir.Documentation = pkg.docs[0]
}
units = append(units, dir)
}
diff --git a/internal/fetch/unit_test.go b/internal/fetch/unit_test.go
index b14f862..1bd8440 100644
--- a/internal/fetch/unit_test.go
+++ b/internal/fetch/unit_test.go
@@ -85,12 +85,14 @@
name: path.Base(p),
path: p,
v1path: internal.V1Path(p, modulePath),
- synopsis: sample.Synopsis,
isRedistributable: true,
licenseMeta: sample.LicenseMetadata,
imports: sample.Imports,
- goos: sample.GOOS,
- goarch: sample.GOARCH,
+ docs: []*internal.Documentation{{
+ GOOS: sample.GOOS,
+ GOARCH: sample.GOARCH,
+ Synopsis: sample.Synopsis,
+ }},
}
}