blob: e3520a48097046b01e244df8f4d69bd6e8ed4a42 [file] [log] [blame]
Serge Bazanskif369cfa2020-05-22 18:36:42 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski4b1e37c2021-09-28 12:49:15 +020017package fietsje
Serge Bazanskif369cfa2020-05-22 18:36:42 +020018
19import (
20 "encoding/json"
21 "fmt"
22 "log"
23 "os"
24 "os/exec"
25 "path/filepath"
26
27 "github.com/bazelbuild/bazel-gazelle/label"
28)
29
Serge Bazanski216fe7b2021-05-21 18:36:16 +020030// dependency is an external Go package/module, requested by the user of Fietsje
31// directly or indirectly.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020032type dependency struct {
33 // importpath is the Go import path that was used to import this dependency.
34 importpath string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020035 // version at which this dependency has been requested. This can be in any form
36 // that `go get` or the go module system understands.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020037 version string
38
Serge Bazanski216fe7b2021-05-21 18:36:16 +020039 // locked is the 'resolved' version of a dependency, containing information about
40 // the dependency's hash, etc.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020041 locked *locked
42
Serge Bazanski216fe7b2021-05-21 18:36:16 +020043 // parent is the dependency that pulled in this one, or nil if pulled in by the
44 // user.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020045 parent *dependency
46
47 shelf *shelf
48
49 // Build specific settings passed to gazelle.
Serge Bazanski14cf7502020-05-28 14:29:56 +020050 disableProtoBuild bool
51 forceBazelGeneration bool
52 buildTags []string
53 patches []string
Lorenz Brunefb028f2020-07-28 17:04:49 +020054 prePatches []string
Serge Bazanski14cf7502020-05-28 14:29:56 +020055 buildExtraArgs []string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020056 // replace is an importpath that this dependency will replace. If this is set, this
57 // dependency will be visible in the build as 'importpath', but downloaded at
58 // 'replace'/'version'. This might be slighly confusing, but follows the semantics
59 // of what Gazelle exposes via 'replace' in 'go_repository'.
Serge Bazanski14cf7502020-05-28 14:29:56 +020060 replace string
61}
62
63func (d *dependency) remoteImportpath() string {
64 if d.replace != "" {
65 return d.replace
66 }
67 return d.importpath
Serge Bazanskif369cfa2020-05-22 18:36:42 +020068}
69
Serge Bazanski216fe7b2021-05-21 18:36:16 +020070// locked is information about a dependency resolved from the go module system. It
71// is expensive to get, and as such it is cached both in memory (as .locked in a
72// dependency) and in the shelf.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020073type locked struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020074 // bazelName is the external workspace name that Bazel should use for this
75 // dependency, eg. com_github_google_glog.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020076 bazelName string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020077 // sum is the gomod compatible checksum of the depdendency,
78 // egh1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020079 sum string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020080 // semver is the gomod-compatible version of this dependency. If the dependency was
81 // requested by git hash that does not resolve to a particular release, this will
82 // be in the form of v0.0.0-20200520133742-deadbeefcafe.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020083 semver string
84}
85
Serge Bazanski216fe7b2021-05-21 18:36:16 +020086// child creates a new child dependence for this dependency, ie. one where the
87// 'parent' pointer points to the dependency on which this method is called.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020088func (d *dependency) child(importpath, version string) *dependency {
89 return &dependency{
90 importpath: importpath,
91 version: version,
92 shelf: d.shelf,
93 parent: d,
94 }
95}
96
97func (d *dependency) String() string {
Serge Bazanski14cf7502020-05-28 14:29:56 +020098 if d.replace != "" {
99 return fmt.Sprintf("%s@%s (replacing %s)", d.replace, d.version, d.importpath)
100 }
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200101 return fmt.Sprintf("%s@%s", d.importpath, d.version)
102}
103
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200104// lock ensures that this dependency is locked, which means that it has been
105// resolved to a particular, stable version and VCS details. We lock a dependency
106// by either asking the go module subsystem (via a go module proxy or a download),
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200107// or by consulting the shelf as a cache.
108func (d *dependency) lock() error {
109 // If already locked in-memory, use that.
110 if d.locked != nil {
111 return nil
112 }
113
114 // If already locked in the shelf, use that.
Serge Bazanski14cf7502020-05-28 14:29:56 +0200115 if shelved := d.shelf.get(d.remoteImportpath(), d.version); shelved != nil {
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200116 d.locked = shelved
117 return nil
118 }
119
120 // Otherwise, download module.
121 semver, _, sum, err := d.download()
122 if err != nil {
123 return fmt.Errorf("could not download: %v", err)
124 }
125
126 // And resolve its bazelName.
127 name := label.ImportPathToBazelRepoName(d.importpath)
128
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200129 d.locked = &locked{
130 bazelName: name,
131 sum: sum,
132 semver: semver,
133 }
134 log.Printf("%s: locked to %s", d, d.locked)
135
136 // Save locked version to shelf.
Serge Bazanski14cf7502020-05-28 14:29:56 +0200137 d.shelf.put(d.remoteImportpath(), d.version, d.locked)
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200138 return d.shelf.save()
139}
140
141func (l *locked) String() string {
142 return fmt.Sprintf("%s@%s", l.bazelName, l.sum)
143}
144
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200145// download ensures that this dependency is download locally, and returns the
146// download location and the dependency's gomod-compatible sum.
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200147func (d *dependency) download() (version, dir, sum string, err error) {
148 goroot := os.Getenv("GOROOT")
149 if goroot == "" {
150 err = fmt.Errorf("GOROOT must be set")
151 return
152 }
153 goTool := filepath.Join(goroot, "bin", "go")
154
Serge Bazanski14cf7502020-05-28 14:29:56 +0200155 query := fmt.Sprintf("%s@%s", d.remoteImportpath(), d.version)
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200156 cmd := exec.Command(goTool, "mod", "download", "-json", "--", query)
157 out, err := cmd.Output()
158 if err != nil {
159 log.Printf("go mod returned: %q", out)
160 err = fmt.Errorf("go mod failed: %v", err)
161 return
162 }
163
164 var res struct{ Version, Sum, Dir string }
165 err = json.Unmarshal(out, &res)
166 if err != nil {
167 return
168 }
169
170 version = res.Version
171 dir = res.Dir
172 sum = res.Sum
173 return
174}