| // Copyright 2020 The Monogon Project Authors. |
| // |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package fietsje |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "log" |
| "os" |
| "os/exec" |
| "path/filepath" |
| |
| "github.com/bazelbuild/bazel-gazelle/label" |
| ) |
| |
| // dependency is an external Go package/module, requested by the user of Fietsje |
| // directly or indirectly. |
| type dependency struct { |
| // importpath is the Go import path that was used to import this dependency. |
| importpath string |
| // version at which this dependency has been requested. This can be in any form |
| // that `go get` or the go module system understands. |
| version string |
| |
| // locked is the 'resolved' version of a dependency, containing information about |
| // the dependency's hash, etc. |
| locked *locked |
| |
| // parent is the dependency that pulled in this one, or nil if pulled in by the |
| // user. |
| parent *dependency |
| |
| shelf *shelf |
| |
| // Build specific settings passed to gazelle. |
| disableProtoBuild bool |
| forceBazelGeneration bool |
| buildTags []string |
| patches []string |
| prePatches []string |
| buildExtraArgs []string |
| useImportAliasNaming bool |
| // replace is an importpath that this dependency will replace. If this is set, this |
| // dependency will be visible in the build as 'importpath', but downloaded at |
| // 'replace'/'version'. This might be slighly confusing, but follows the semantics |
| // of what Gazelle exposes via 'replace' in 'go_repository'. |
| replace string |
| } |
| |
| func (d *dependency) remoteImportpath() string { |
| if d.replace != "" { |
| return d.replace |
| } |
| return d.importpath |
| } |
| |
| // locked is information about a dependency resolved from the go module system. It |
| // is expensive to get, and as such it is cached both in memory (as .locked in a |
| // dependency) and in the shelf. |
| type locked struct { |
| // bazelName is the external workspace name that Bazel should use for this |
| // dependency, eg. com_github_google_glog. |
| bazelName string |
| // sum is the gomod compatible checksum of the depdendency, |
| // egh1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=. |
| sum string |
| // semver is the gomod-compatible version of this dependency. If the dependency was |
| // requested by git hash that does not resolve to a particular release, this will |
| // be in the form of v0.0.0-20200520133742-deadbeefcafe. |
| semver string |
| } |
| |
| // child creates a new child dependence for this dependency, ie. one where the |
| // 'parent' pointer points to the dependency on which this method is called. |
| func (d *dependency) child(importpath, version string) *dependency { |
| return &dependency{ |
| importpath: importpath, |
| version: version, |
| shelf: d.shelf, |
| parent: d, |
| } |
| } |
| |
| func (d *dependency) String() string { |
| if d.replace != "" { |
| return fmt.Sprintf("%s@%s (replacing %s)", d.replace, d.version, d.importpath) |
| } |
| return fmt.Sprintf("%s@%s", d.importpath, d.version) |
| } |
| |
| // lock ensures that this dependency is locked, which means that it has been |
| // resolved to a particular, stable version and VCS details. We lock a dependency |
| // by either asking the go module subsystem (via a go module proxy or a download), |
| // or by consulting the shelf as a cache. |
| func (d *dependency) lock() error { |
| // If already locked in-memory, use that. |
| if d.locked != nil { |
| return nil |
| } |
| |
| // If already locked in the shelf, use that. |
| if shelved := d.shelf.get(d.remoteImportpath(), d.version); shelved != nil { |
| d.locked = shelved |
| return nil |
| } |
| |
| // Otherwise, download module. |
| semver, _, sum, err := d.download() |
| if err != nil { |
| return fmt.Errorf("could not download: %v", err) |
| } |
| |
| // And resolve its bazelName. |
| name := label.ImportPathToBazelRepoName(d.importpath) |
| |
| d.locked = &locked{ |
| bazelName: name, |
| sum: sum, |
| semver: semver, |
| } |
| log.Printf("%s: locked to %s", d, d.locked) |
| |
| // Save locked version to shelf. |
| d.shelf.put(d.remoteImportpath(), d.version, d.locked) |
| return d.shelf.save() |
| } |
| |
| func (l *locked) String() string { |
| return fmt.Sprintf("%s@%s", l.bazelName, l.sum) |
| } |
| |
| // download ensures that this dependency is download locally, and returns the |
| // download location and the dependency's gomod-compatible sum. |
| func (d *dependency) download() (version, dir, sum string, err error) { |
| goroot := os.Getenv("GOROOT") |
| if goroot == "" { |
| err = fmt.Errorf("GOROOT must be set") |
| return |
| } |
| goTool := filepath.Join(goroot, "bin", "go") |
| |
| query := fmt.Sprintf("%s@%s", d.remoteImportpath(), d.version) |
| cmd := exec.Command(goTool, "mod", "download", "-json", "--", query) |
| out, err := cmd.Output() |
| if err != nil { |
| log.Printf("go mod returned: %q", out) |
| err = fmt.Errorf("go mod failed: %v", err) |
| return |
| } |
| |
| var res struct{ Version, Sum, Dir string } |
| err = json.Unmarshal(out, &res) |
| if err != nil { |
| return |
| } |
| |
| version = res.Version |
| dir = res.Dir |
| sum = res.Sum |
| return |
| } |