blob: 1eeb52c82afff53aab834be98156d791428dc559 [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
Lorenz Brunc2e3b1b2021-11-11 11:06:41 +010056 useImportAliasNaming bool
Serge Bazanski216fe7b2021-05-21 18:36:16 +020057 // replace is an importpath that this dependency will replace. If this is set, this
58 // dependency will be visible in the build as 'importpath', but downloaded at
59 // 'replace'/'version'. This might be slighly confusing, but follows the semantics
60 // of what Gazelle exposes via 'replace' in 'go_repository'.
Serge Bazanski14cf7502020-05-28 14:29:56 +020061 replace string
62}
63
64func (d *dependency) remoteImportpath() string {
65 if d.replace != "" {
66 return d.replace
67 }
68 return d.importpath
Serge Bazanskif369cfa2020-05-22 18:36:42 +020069}
70
Serge Bazanski216fe7b2021-05-21 18:36:16 +020071// locked is information about a dependency resolved from the go module system. It
72// is expensive to get, and as such it is cached both in memory (as .locked in a
73// dependency) and in the shelf.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020074type locked struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020075 // bazelName is the external workspace name that Bazel should use for this
76 // dependency, eg. com_github_google_glog.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020077 bazelName string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020078 // sum is the gomod compatible checksum of the depdendency,
79 // egh1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020080 sum string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020081 // semver is the gomod-compatible version of this dependency. If the dependency was
82 // requested by git hash that does not resolve to a particular release, this will
83 // be in the form of v0.0.0-20200520133742-deadbeefcafe.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020084 semver string
85}
86
Serge Bazanski216fe7b2021-05-21 18:36:16 +020087// child creates a new child dependence for this dependency, ie. one where the
88// 'parent' pointer points to the dependency on which this method is called.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020089func (d *dependency) child(importpath, version string) *dependency {
90 return &dependency{
91 importpath: importpath,
92 version: version,
93 shelf: d.shelf,
94 parent: d,
95 }
96}
97
98func (d *dependency) String() string {
Serge Bazanski14cf7502020-05-28 14:29:56 +020099 if d.replace != "" {
100 return fmt.Sprintf("%s@%s (replacing %s)", d.replace, d.version, d.importpath)
101 }
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200102 return fmt.Sprintf("%s@%s", d.importpath, d.version)
103}
104
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200105// lock ensures that this dependency is locked, which means that it has been
106// resolved to a particular, stable version and VCS details. We lock a dependency
107// by either asking the go module subsystem (via a go module proxy or a download),
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200108// or by consulting the shelf as a cache.
109func (d *dependency) lock() error {
110 // If already locked in-memory, use that.
111 if d.locked != nil {
112 return nil
113 }
114
115 // If already locked in the shelf, use that.
Serge Bazanski14cf7502020-05-28 14:29:56 +0200116 if shelved := d.shelf.get(d.remoteImportpath(), d.version); shelved != nil {
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200117 d.locked = shelved
118 return nil
119 }
120
121 // Otherwise, download module.
122 semver, _, sum, err := d.download()
123 if err != nil {
124 return fmt.Errorf("could not download: %v", err)
125 }
126
127 // And resolve its bazelName.
128 name := label.ImportPathToBazelRepoName(d.importpath)
129
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200130 d.locked = &locked{
131 bazelName: name,
132 sum: sum,
133 semver: semver,
134 }
135 log.Printf("%s: locked to %s", d, d.locked)
136
137 // Save locked version to shelf.
Serge Bazanski14cf7502020-05-28 14:29:56 +0200138 d.shelf.put(d.remoteImportpath(), d.version, d.locked)
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200139 return d.shelf.save()
140}
141
142func (l *locked) String() string {
143 return fmt.Sprintf("%s@%s", l.bazelName, l.sum)
144}
145
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200146// download ensures that this dependency is download locally, and returns the
147// download location and the dependency's gomod-compatible sum.
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200148func (d *dependency) download() (version, dir, sum string, err error) {
149 goroot := os.Getenv("GOROOT")
150 if goroot == "" {
151 err = fmt.Errorf("GOROOT must be set")
152 return
153 }
154 goTool := filepath.Join(goroot, "bin", "go")
155
Serge Bazanski14cf7502020-05-28 14:29:56 +0200156 query := fmt.Sprintf("%s@%s", d.remoteImportpath(), d.version)
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200157 cmd := exec.Command(goTool, "mod", "download", "-json", "--", query)
158 out, err := cmd.Output()
159 if err != nil {
160 log.Printf("go mod returned: %q", out)
161 err = fmt.Errorf("go mod failed: %v", err)
162 return
163 }
164
165 var res struct{ Version, Sum, Dir string }
166 err = json.Unmarshal(out, &res)
167 if err != nil {
168 return
169 }
170
171 version = res.Version
172 dir = res.Dir
173 sum = res.Sum
174 return
175}