blob: e12e272ebb458873e8228fd05f3d6dc862dc5d0b [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
17package main
18
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
30// dependency is an external Go package/module, requested by the user of Fietsje directly or indirectly.
31type dependency struct {
32 // importpath is the Go import path that was used to import this dependency.
33 importpath string
34 // version at which this dependency has been requested. This can be in any form that `go get` or the go module
35 // system understands.
36 version string
37
38 // locked is the 'resolved' version of a dependency, containing information about the dependency's hash, etc.
39 locked *locked
40
41 // parent is the dependency that pulled in this one, or nil if pulled in by the user.
42 parent *dependency
43
44 shelf *shelf
45
46 // Build specific settings passed to gazelle.
Serge Bazanski14cf7502020-05-28 14:29:56 +020047 disableProtoBuild bool
48 forceBazelGeneration bool
49 buildTags []string
50 patches []string
51 buildExtraArgs []string
52 // replace is an importpath that this dependency will replace. If this is set, this dependency will be visible
53 // in the build as 'importpath', but downloaded at 'replace'/'version'. This might be slighly confusing, but
54 // follows the semantics of what Gazelle exposes via 'replace' in 'go_repository'.
55 replace string
56}
57
58func (d *dependency) remoteImportpath() string {
59 if d.replace != "" {
60 return d.replace
61 }
62 return d.importpath
Serge Bazanskif369cfa2020-05-22 18:36:42 +020063}
64
65// locked is information about a dependency resolved from the go module system. It is expensive to get, and as such
66// it is cached both in memory (as .locked in a dependency) and in the shelf.
67type locked struct {
68 // bazelName is the external workspace name that Bazel should use for this dependency, eg. com_github_google_glog.
69 bazelName string
70 // sum is the gomod compatible checksum of the depdendency, egh1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=.
71 sum string
72 // semver is the gomod-compatible version of this dependency. If the dependency was requested by git hash that does
73 // not resolve to a particular release, this will be in the form of v0.0.0-20200520133742-deadbeefcafe.
74 semver string
75}
76
77// child creates a new child dependence for this dependency, ie. one where the 'parent' pointer points to the dependency
78// on which this method is called.
79func (d *dependency) child(importpath, version string) *dependency {
80 return &dependency{
81 importpath: importpath,
82 version: version,
83 shelf: d.shelf,
84 parent: d,
85 }
86}
87
88func (d *dependency) String() string {
Serge Bazanski14cf7502020-05-28 14:29:56 +020089 if d.replace != "" {
90 return fmt.Sprintf("%s@%s (replacing %s)", d.replace, d.version, d.importpath)
91 }
Serge Bazanskif369cfa2020-05-22 18:36:42 +020092 return fmt.Sprintf("%s@%s", d.importpath, d.version)
93}
94
95// lock ensures that this dependency is locked, which means that it has been resolved to a particular, stable version
96// and VCS details. We lock a dependency by either asking the go module subsystem (via a go module proxy or a download),
97// or by consulting the shelf as a cache.
98func (d *dependency) lock() error {
99 // If already locked in-memory, use that.
100 if d.locked != nil {
101 return nil
102 }
103
104 // If already locked in the shelf, use that.
Serge Bazanski14cf7502020-05-28 14:29:56 +0200105 if shelved := d.shelf.get(d.remoteImportpath(), d.version); shelved != nil {
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200106 d.locked = shelved
107 return nil
108 }
109
110 // Otherwise, download module.
111 semver, _, sum, err := d.download()
112 if err != nil {
113 return fmt.Errorf("could not download: %v", err)
114 }
115
116 // And resolve its bazelName.
117 name := label.ImportPathToBazelRepoName(d.importpath)
118
119 // Hack for github.com/google/gvisor: it requests @com_github_opencontainers_runtime-spec.
120 // We fix the generated name for this repo so it conforms to what gvisor expects.
121 // TODO(q3k): instead of this, patch gvisor?
122 if name == "com_github_opencontainers_runtime_spec" {
123 name = "com_github_opencontainers_runtime-spec"
124 }
125
126 d.locked = &locked{
127 bazelName: name,
128 sum: sum,
129 semver: semver,
130 }
131 log.Printf("%s: locked to %s", d, d.locked)
132
133 // Save locked version to shelf.
Serge Bazanski14cf7502020-05-28 14:29:56 +0200134 d.shelf.put(d.remoteImportpath(), d.version, d.locked)
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200135 return d.shelf.save()
136}
137
138func (l *locked) String() string {
139 return fmt.Sprintf("%s@%s", l.bazelName, l.sum)
140}
141
142// download ensures that this dependency is download locally, and returns the download location and the dependency's
143// gomod-compatible sum.
144func (d *dependency) download() (version, dir, sum string, err error) {
145 goroot := os.Getenv("GOROOT")
146 if goroot == "" {
147 err = fmt.Errorf("GOROOT must be set")
148 return
149 }
150 goTool := filepath.Join(goroot, "bin", "go")
151
Serge Bazanski14cf7502020-05-28 14:29:56 +0200152 query := fmt.Sprintf("%s@%s", d.remoteImportpath(), d.version)
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200153 cmd := exec.Command(goTool, "mod", "download", "-json", "--", query)
154 out, err := cmd.Output()
155 if err != nil {
156 log.Printf("go mod returned: %q", out)
157 err = fmt.Errorf("go mod failed: %v", err)
158 return
159 }
160
161 var res struct{ Version, Sum, Dir string }
162 err = json.Unmarshal(out, &res)
163 if err != nil {
164 return
165 }
166
167 version = res.Version
168 dir = res.Dir
169 sum = res.Sum
170 return
171}