diff options
| author | Ethan Lee <ethanalee@google.com> | 2026-03-25 18:18:15 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2026-03-26 14:02:59 -0700 |
| commit | 34edebc0803b121acd1cbf363eef074e0a13ab6d (patch) | |
| tree | 72af43d88fd8e6c83d93beae0dba3cc62395e70d /internal | |
| parent | 8378ff811c1228f6a50808cb7557e6c08782205a (diff) | |
| download | go-x-pkgsite-34edebc0803b121acd1cbf363eef074e0a13ab6d.tar.xz | |
internal: consolidate build context and unit resolution logic
- Centralize logic for resolving units and selecting the best matching
build context.
- New helpers MatchingBuildContext, SortedBuildContexts provide a
consistent, efficient approach to build context selection.
- DocumentationForBuildContext now finds the best match based on
precdence rules rather than just returning the first match.
- A new getUnitContext helper resolves unit metadata and the best build
context in a single query.
- GetSymbols and GetUnit are now both optimized to use pre-resolved IDs,
simplifying their queries and removing redundant joins.
- Replaced redundant matchingDoc functions in fetchdatasource and
fakedatasource.
Change-Id: I9207d3bfe03404483c816090a0b99666f14a36e3
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/759162
Reviewed-by: Jonathan Amsterdam <jba@google.com>
kokoro-CI: kokoro <noreply+kokoro@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ethan Lee <ethanalee@google.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/build_context.go | 58 | ||||
| -rw-r--r-- | internal/fetch/unit.go | 6 | ||||
| -rw-r--r-- | internal/fetchdatasource/fetchdatasource.go | 21 | ||||
| -rw-r--r-- | internal/postgres/package_symbol.go | 104 | ||||
| -rw-r--r-- | internal/postgres/unit.go | 153 | ||||
| -rw-r--r-- | internal/testing/fakedatasource/fakedatasource.go | 34 |
6 files changed, 205 insertions, 171 deletions
diff --git a/internal/build_context.go b/internal/build_context.go index 63b1b77e..91c051b4 100644 --- a/internal/build_context.go +++ b/internal/build_context.go @@ -4,7 +4,10 @@ package internal -import "fmt" +import ( + "fmt" + "slices" +) // A BuildContext describes a build context for the Go tool: information needed // to build a Go package. For our purposes, we only care about the information @@ -41,7 +44,11 @@ var ( // BuildContexts are the build contexts we check when loading a package (see // internal/fetch/load.go). // We store documentation for all of the listed contexts. -// The order determines which environment's docs we will show as the default. +// +// The order of this slice determines the precedence when multiple build +// contexts match a request. For example, if a user provides no GOOS or GOARCH, +// the first context in this list that is available in the database will be +// chosen as the default (typically linux/amd64). var BuildContexts = []BuildContext{ BuildContextLinux, BuildContextWindows, @@ -82,17 +89,46 @@ func (d *Documentation) BuildContext() BuildContext { return BuildContext{GOOS: d.GOOS, GOARCH: d.GOARCH} } -// DocumentationForBuildContext returns the first Documentation the list that -// matches the BuildContext, or nil if none does. A Documentation matches if its -// GOOS and GOARCH fields are the same as those of the BuildContext, or if the -// Documentation field is "all", or if the BuildContext field is empty. That is, -// empty BuildContext fields act as wildcards. So the zero BuildContext will -// match the first element of docs, if there is one. +// DocumentationForBuildContext returns the Documentation from the list that +// matches the BuildContext and has the highest precedence according to +// CompareBuildContexts. It returns nil if no match is found. +// Empty BuildContext fields act as wildcards. func DocumentationForBuildContext(docs []*Documentation, bc BuildContext) *Documentation { + var ( + bestDoc *Documentation + bestBC BuildContext + ) for _, d := range docs { - if bc.Match(BuildContext{d.GOOS, d.GOARCH}) { - return d + dbc := d.BuildContext() + if bc.Match(dbc) && (bestDoc == nil || CompareBuildContexts(dbc, bestBC) < 0) { + bestDoc = d + bestBC = dbc + } + } + return bestDoc +} + +// MatchingBuildContext returns the best BuildContext from the given bcs that matches bc, +// according to CompareBuildContexts. It returns zero BuildContext and false if no match is +// found. +func MatchingBuildContext(bcs []BuildContext, bc BuildContext) (BuildContext, bool) { + var best BuildContext + found := false + for _, c := range bcs { + if bc.Match(c) { + if !found || CompareBuildContexts(c, best) < 0 { + best = c + found = true + } } } - return nil + return best, found +} + +// SortedBuildContexts returns a copy of the given slice of BuildContexts, sorted by +// precedence according to CompareBuildContexts. +func SortedBuildContexts(bcs []BuildContext) []BuildContext { + sorted := slices.Clone(bcs) + slices.SortFunc(sorted, CompareBuildContexts) + return sorted } diff --git a/internal/fetch/unit.go b/internal/fetch/unit.go index e0c9c6f3..301d2b7d 100644 --- a/internal/fetch/unit.go +++ b/internal/fetch/unit.go @@ -6,7 +6,6 @@ package fetch import ( "path" - "sort" "golang.org/x/pkgsite/internal" "golang.org/x/pkgsite/internal/licenses" @@ -45,10 +44,7 @@ func moduleUnit(modulePath string, unitMeta *internal.UnitMeta, for _, d := range unit.Documentation { bcs = append(bcs, internal.BuildContext{GOOS: d.GOOS, GOARCH: d.GOARCH}) } - sort.Slice(bcs, func(i, j int) bool { - return internal.CompareBuildContexts(bcs[i], bcs[j]) < 0 - }) - unit.BuildContexts = bcs + unit.BuildContexts = internal.SortedBuildContexts(bcs) } return unit } diff --git a/internal/fetchdatasource/fetchdatasource.go b/internal/fetchdatasource/fetchdatasource.go index 7a5f5669..dfe30fc9 100644 --- a/internal/fetchdatasource/fetchdatasource.go +++ b/internal/fetchdatasource/fetchdatasource.go @@ -220,7 +220,7 @@ func (ds *FetchDataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, f // Since we cache the module and its units, we have to copy this unit before we modify it. // It can be a shallow copy, since we're only modifying the Unit.Documentation field. u2 := *u - if d := matchingDoc(u.Documentation, bc); d != nil { + if d := internal.DocumentationForBuildContext(u.Documentation, bc); d != nil { u2.Documentation = []*internal.Documentation{d} } else { u2.Documentation = nil @@ -252,23 +252,6 @@ func findUnitMeta(m *fetch.LazyModule, path string) (*internal.UnitMeta, error) return nil, derrors.NotFound } -// matchingDoc returns the Documentation that matches the given build context -// and comes earliest in build-context order. It returns nil if there is none. -func matchingDoc(docs []*internal.Documentation, bc internal.BuildContext) *internal.Documentation { - var ( - dMin *internal.Documentation - bcMin = internal.BuildContext{GOOS: "unk", GOARCH: "unk"} // sorts last - ) - for _, d := range docs { - dbc := d.BuildContext() - if bc.Match(dbc) && internal.CompareBuildContexts(dbc, bcMin) < 0 { - dMin = d - bcMin = dbc - } - } - return dMin -} - // GetLatestInfo returns latest information for unitPath and modulePath. func (ds *FetchDataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) (latest internal.LatestInfo, err error) { defer derrors.Wrap(&err, "FetchDataSource.GetLatestInfo(ctx, %q, %q)", unitPath, modulePath) @@ -447,7 +430,7 @@ func (ds *FetchDataSource) GetSymbols(ctx context.Context, pkgPath, modulePath, return nil, err } - doc := matchingDoc(unit.Documentation, bc) + doc := internal.DocumentationForBuildContext(unit.Documentation, bc) if doc == nil || len(doc.API) == 0 { return nil, derrors.NotFound } diff --git a/internal/postgres/package_symbol.go b/internal/postgres/package_symbol.go index 4d99b07c..d77b60a6 100644 --- a/internal/postgres/package_symbol.go +++ b/internal/postgres/package_symbol.go @@ -8,7 +8,6 @@ import ( "context" "database/sql" "fmt" - "sort" "github.com/Masterminds/squirrel" "golang.org/x/pkgsite/internal" @@ -22,16 +21,22 @@ func (db *DB) GetSymbols(ctx context.Context, pkgPath, modulePath, version strin defer derrors.Wrap(&err, "DB.GetSymbols(ctx, %q, %q, %q, %v)", pkgPath, modulePath, version, bc) defer stats.Elapsed(ctx, "DB.GetSymbols")() - query := packageSymbolQueryJoin( + uc, err := db.getUnitContext(ctx, pkgPath, modulePath, version, bc) + if err != nil { + return nil, err + } + if uc.docID == 0 { + return nil, derrors.NotFound + } + + query := packageSymbolQuery( squirrel.Select( "s1.name AS symbol_name", "s2.name AS parent_symbol_name", "ps.section", "ps.type", - "ps.synopsis", - "d.goos", - "d.goarch"), pkgPath, modulePath). - Where(squirrel.Eq{"m.version": version}). + "ps.synopsis")). + Where(squirrel.Eq{"ds.documentation_id": uc.docID}). OrderBy("CASE WHEN ps.type='Type' THEN 0 ELSE 1 END"). OrderBy("s1.name") @@ -40,66 +45,47 @@ func (db *DB) GetSymbols(ctx context.Context, pkgPath, modulePath, version strin return nil, err } - resultsByBC := make(map[internal.BuildContext][]internal.SymbolMeta) + var symbols []*internal.Symbol + symbolMap := make(map[string]*internal.Symbol) collect := func(rows *sql.Rows) error { var ( - name, parentName, synopsis, goos, goarch string - section internal.SymbolSection - kind internal.SymbolKind + name, parentName, synopsis string + section internal.SymbolSection + kind internal.SymbolKind ) - if err := rows.Scan(&name, &parentName, §ion, &kind, &synopsis, &goos, &goarch); err != nil { + if err := rows.Scan(&name, &parentName, §ion, &kind, &synopsis); err != nil { return fmt.Errorf("row.Scan(): %v", err) } - rowBC := internal.BuildContext{GOOS: goos, GOARCH: goarch} - if bc.Match(rowBC) { - resultsByBC[rowBC] = append(resultsByBC[rowBC], internal.SymbolMeta{ - Name: name, - ParentName: parentName, - Section: section, - Kind: kind, - Synopsis: synopsis, - }) + sm := internal.SymbolMeta{ + Name: name, + ParentName: parentName, + Section: section, + Kind: kind, + Synopsis: synopsis, } - return nil - } - - if err := db.db.RunQuery(ctx, q, collect, args...); err != nil { - return nil, err - } - - if len(resultsByBC) == 0 { - return nil, derrors.NotFound - } - - // Find the best build context among those that matched. - var matchedBCs []internal.BuildContext - for b := range resultsByBC { - matchedBCs = append(matchedBCs, b) - } - sort.Slice(matchedBCs, func(i, j int) bool { - return internal.CompareBuildContexts(matchedBCs[i], matchedBCs[j]) < 0 - }) - bestBC := matchedBCs[0] - - var symbols []*internal.Symbol - symbolMap := make(map[string]*internal.Symbol) - rows := resultsByBC[bestBC] - for i := range rows { - sm := rows[i] if sm.ParentName != "" && sm.ParentName != sm.Name { if parent, ok := symbolMap[sm.ParentName]; ok { - parent.Children = append(parent.Children, &rows[i]) - continue + parent.Children = append(parent.Children, &sm) + return nil } } // Treat as top-level if no parent or parent not found in this build context. s := &internal.Symbol{ SymbolMeta: sm, - GOOS: bestBC.GOOS, - GOARCH: bestBC.GOARCH, + GOOS: uc.bestBC.GOOS, + GOARCH: uc.bestBC.GOARCH, } symbols = append(symbols, s) symbolMap[sm.Name] = s + return nil + } + + if err := db.db.RunQuery(ctx, q, collect, args...); err != nil { + return nil, err + } + + if len(symbols) == 0 { + return nil, derrors.NotFound } return symbols, nil } @@ -144,15 +130,19 @@ func getPackageSymbols(ctx context.Context, ddb *database.DB, packagePath, modul return sh, nil } -func packageSymbolQueryJoin(query squirrel.SelectBuilder, pkgPath, modulePath string) squirrel.SelectBuilder { - return query.From("modules m"). - Join("units u on u.module_id = m.id"). - Join("documentation d ON d.unit_id = u.id"). - Join("documentation_symbols ds ON ds.documentation_id = d.id"). +func packageSymbolQuery(query squirrel.SelectBuilder) squirrel.SelectBuilder { + return query.From("documentation_symbols ds"). Join("package_symbols ps ON ps.id = ds.package_symbol_id"). - Join("paths p1 ON u.path_id = p1.id"). Join("symbol_names s1 ON ps.symbol_name_id = s1.id"). - Join("symbol_names s2 ON ps.parent_symbol_name_id = s2.id"). + Join("symbol_names s2 ON ps.parent_symbol_name_id = s2.id") +} + +func packageSymbolQueryJoin(query squirrel.SelectBuilder, pkgPath, modulePath string) squirrel.SelectBuilder { + return packageSymbolQuery(query). + Join("documentation d ON d.id = ds.documentation_id"). + Join("units u on u.id = d.unit_id"). + Join("modules m ON m.id = u.module_id"). + Join("paths p1 ON u.path_id = p1.id"). Where(squirrel.Eq{"p1.path": pkgPath}). Where(squirrel.Eq{"m.module_path": modulePath}) } diff --git a/internal/postgres/unit.go b/internal/postgres/unit.go index fee52439..3e13143b 100644 --- a/internal/postgres/unit.go +++ b/internal/postgres/unit.go @@ -418,17 +418,33 @@ func getPackagesInUnit(ctx context.Context, db *database.DB, fullPath, modulePat return packages, nil } -func (db *DB) getUnitWithAllFields(ctx context.Context, um *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) { - defer derrors.WrapStack(&err, "getUnitWithAllFields(ctx, %q, %q, %q)", um.Path, um.ModulePath, um.Version) - defer stats.Elapsed(ctx, "getUnitWithAllFields")() +// unitContext contains information for a unit that is used to fetch its +// documentation or symbols. +type unitContext struct { + unitID int + pathID int + moduleID int + isRedistributable bool + bcs []internal.BuildContext + bestBC internal.BuildContext + docID int + licenseMetas []*licenses.Metadata +} - // Get build contexts and unit ID. - var pathID, unitID, moduleID int - var bcs []internal.BuildContext - var licenseMetas []*licenses.Metadata - var isRedistributable bool +// getUnitContext returns a unitContext for the given package path, module path, +// version and build context. It finds all available build contexts for the +// unit, selects the best matching one, and identifies the corresponding +// documentation ID. +func (db *DB) getUnitContext(ctx context.Context, pkgPath, modulePath, version string, bc internal.BuildContext) (_ *unitContext, err error) { + defer derrors.WrapStack(&err, "getUnitContext(ctx, %q, %q, %q, %v)", pkgPath, modulePath, version, bc) + + var ( + uc = &unitContext{} + // Map from build context to documentation ID. + docIDs = make(map[internal.BuildContext]int) + ) err = db.db.RunQuery(ctx, ` - SELECT d.goos, d.goarch, u.id, p.id, u.module_id, u.license_types, u.license_paths, u.redistributable + SELECT d.goos, d.goarch, u.id, p.id, u.module_id, u.license_types, u.license_paths, u.redistributable, d.id FROM units u INNER JOIN paths p ON p.id = u.path_id INNER JOIN modules m ON m.id = u.module_id @@ -438,43 +454,83 @@ func (db *DB) getUnitWithAllFields(ctx context.Context, um *internal.UnitMeta, f AND m.module_path = $2 AND m.version = $3 `, func(rows *sql.Rows) error { - var bc internal.BuildContext - // GOOS and GOARCH will be NULL if there are no documentation rows for - // the unit, but we still want the unit ID. var ( - licenseTypes []string - licensePaths []string + rowBC internal.BuildContext + docID sql.NullInt64 ) - if err := rows.Scan(database.NullIsEmpty(&bc.GOOS), database.NullIsEmpty(&bc.GOARCH), &unitID, &pathID, &moduleID, pq.Array(&licenseTypes), pq.Array(&licensePaths), &isRedistributable); err != nil { + // We need to scan all columns, but some are unit-level (constant across rows) + // and some are documentation-level (vary across rows). + var ( + uIDCol, pIDCol, mIDCol int + licTypesCol, licPathsCol []string + isRedistCol bool + ) + + if err := rows.Scan( + database.NullIsEmpty(&rowBC.GOOS), + database.NullIsEmpty(&rowBC.GOARCH), + &uIDCol, + &pIDCol, + &mIDCol, + pq.Array(&licTypesCol), + pq.Array(&licPathsCol), + &isRedistCol, + &docID, + ); err != nil { return err } - if db.bypassLicenseCheck { - isRedistributable = true + // Assign unit-level metadata only once. + if uc.unitID == 0 { + uc.unitID = uIDCol + uc.pathID = pIDCol + uc.moduleID = mIDCol + uc.isRedistributable = isRedistCol + if db.bypassLicenseCheck { + uc.isRedistributable = true + } + lics, err := zipLicenseMetadata(licTypesCol, licPathsCol) + if err != nil { + return err + } + uc.licenseMetas = lics } - if bc.GOOS != "" && bc.GOARCH != "" { - bcs = append(bcs, bc) - } - lics, err := zipLicenseMetadata(licenseTypes, licensePaths) - if err != nil { - return err + if rowBC.GOOS != "" && rowBC.GOARCH != "" { + uc.bcs = append(uc.bcs, rowBC) + if docID.Valid { + docIDs[rowBC] = int(docID.Int64) + } } - licenseMetas = lics return nil - }, um.Path, um.ModulePath, um.Version) + }, pkgPath, modulePath, version) if err != nil { return nil, err } - sort.Slice(bcs, func(i, j int) bool { return internal.CompareBuildContexts(bcs[i], bcs[j]) < 0 }) - var bcMatched internal.BuildContext - for _, c := range bcs { - if bc.Match(c) { - bcMatched = c - break - } + if uc.unitID == 0 { + return nil, derrors.NotFound + } + + uc.bcs = internal.SortedBuildContexts(uc.bcs) + + var ok bool + uc.bestBC, ok = internal.MatchingBuildContext(uc.bcs, bc) + if ok { + uc.docID = docIDs[uc.bestBC] + } + return uc, nil +} + +func (db *DB) getUnitWithAllFields(ctx context.Context, um *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) { + defer derrors.WrapStack(&err, "getUnitWithAllFields(ctx, %q, %q, %q)", um.Path, um.ModulePath, um.Version) + defer stats.Elapsed(ctx, "getUnitWithAllFields")() + + uc, err := db.getUnitContext(ctx, um.Path, um.ModulePath, um.Version, bc) + if err != nil { + return nil, err } + docSelect := "CAST(NULL AS bytea)," if fields&internal.WithDocsSource != 0 { docSelect = "d.source," @@ -503,27 +559,22 @@ func (db *DB) getUnitWithAllFields(ctx context.Context, um *internal.UnitMeta, f LEFT JOIN readmes r ON r.unit_id = u.id - LEFT JOIN ( - SELECT synopsis, source, goos, goarch, unit_id - FROM documentation d - WHERE d.GOOS = $3 AND d.GOARCH = $4 - ) d - ON d.unit_id = u.id + LEFT JOIN documentation d + ON d.id = $3 WHERE u.id = $2 `, docSelect) var ( r internal.Readme u internal.Unit ) - u.BuildContexts = bcs - var goos, goarch any - if bcMatched.GOOS != "" { - goos = bcMatched.GOOS - goarch = bcMatched.GOARCH - } - doc := &internal.Documentation{GOOS: bcMatched.GOOS, GOARCH: bcMatched.GOARCH} + u.BuildContexts = uc.bcs + doc := &internal.Documentation{GOOS: uc.bestBC.GOOS, GOARCH: uc.bestBC.GOARCH} end := stats.Elapsed(ctx, "getUnitWithAllFields-readme-and-imports") - err = db.db.QueryRow(ctx, query, pathID, unitID, goos, goarch).Scan( + var docID any + if uc.docID != 0 { + docID = uc.docID + } + err = db.db.QueryRow(ctx, query, uc.pathID, uc.unitID, docID).Scan( database.NullIsEmpty(&r.Filepath), database.NullIsEmpty(&r.Contents), database.NullIsEmpty(&doc.Synopsis), @@ -546,17 +597,17 @@ func (db *DB) getUnitWithAllFields(ctx context.Context, um *internal.UnitMeta, f } end() // Get other info. - pkgs, err := db.getPackagesInUnit(ctx, um.Path, moduleID) + pkgs, err := db.getPackagesInUnit(ctx, um.Path, uc.moduleID) if err != nil { return nil, err } u.Subdirectories = pkgs u.UnitMeta = *um - u.Licenses = licenseMetas - u.IsRedistributable = isRedistributable + u.Licenses = uc.licenseMetas + u.IsRedistributable = uc.isRedistributable - if um.IsPackage() && !um.IsCommand() && bcMatched.GOOS != "" { - u.SymbolHistory, err = GetSymbolHistoryForBuildContext(ctx, db.db, pathID, um.ModulePath, bcMatched) + if um.IsPackage() && !um.IsCommand() && uc.bestBC.GOOS != "" { + u.SymbolHistory, err = GetSymbolHistoryForBuildContext(ctx, db.db, uc.pathID, um.ModulePath, uc.bestBC) if err != nil { return nil, err } diff --git a/internal/testing/fakedatasource/fakedatasource.go b/internal/testing/fakedatasource/fakedatasource.go index b44212a0..1075d147 100644 --- a/internal/testing/fakedatasource/fakedatasource.go +++ b/internal/testing/fakedatasource/fakedatasource.go @@ -140,13 +140,13 @@ func (ds *FakeDataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, fi // It can be a shallow copy, since we're only modifying the Unit.Documentation field. u2 := *u if fields&internal.WithDocsSource != 0 { - if d := matchingDoc(u.Documentation, bc); d != nil { + if d := internal.DocumentationForBuildContext(u.Documentation, bc); d != nil { u2.Documentation = []*internal.Documentation{d} } else { u2.Documentation = nil } } else if fields&internal.WithMain != 0 { - if d := matchingDoc(u.Documentation, bc); d != nil { + if d := internal.DocumentationForBuildContext(u.Documentation, bc); d != nil { u2.Documentation = []*internal.Documentation{{GOOS: d.GOOS, GOARCH: d.GOARCH, Synopsis: d.Synopsis}} } else { u2.Documentation = nil @@ -157,23 +157,6 @@ func (ds *FakeDataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, fi return &u2, nil } -// matchingDoc returns the Documentation that matches the given build context -// and comes earliest in build-context order. It returns nil if there is none. -func matchingDoc(docs []*internal.Documentation, bc internal.BuildContext) *internal.Documentation { - var ( - dMin *internal.Documentation - bcMin *internal.BuildContext // sorts last - ) - for _, d := range docs { - dbc := d.BuildContext() - if bc.Match(dbc) && (bcMin == nil || internal.CompareBuildContexts(dbc, *bcMin) < 0) { - dMin = d - bcMin = &dbc - } - } - return dMin -} - // GetUnitMeta returns information about a path. func (ds *FakeDataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) { module := ds.findModule(path, requestedModulePath, requestedVersion) @@ -350,19 +333,14 @@ func (ds *FakeDataSource) GetSymbols(ctx context.Context, pkgPath, modulePath, v var bcs []internal.BuildContext for b := range u.Symbols { - if bc.Match(b) { - bcs = append(bcs, b) - } + bcs = append(bcs, b) } - if len(bcs) == 0 { + matchedBC, ok := internal.MatchingBuildContext(bcs, bc) + if !ok { return nil, derrors.NotFound } - sort.Slice(bcs, func(i, j int) bool { - return internal.CompareBuildContexts(bcs[i], bcs[j]) < 0 - }) - - return u.Symbols[bcs[0]], nil + return u.Symbols[matchedBC], nil } // SearchSupport reports the search types supported by this datasource. |
