| // 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 main | 
 |  | 
 | 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 | 
 | 	// 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 | 
 | } |