blob: 807ee449c4c42cf277b93e9a6c42c4650ba55939 [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 "bytes"
21 "fmt"
22 "io/ioutil"
23 "log"
24 "os"
25 "sort"
26
27 "github.com/golang/protobuf/proto"
28
Serge Bazanski31370b02021-01-07 16:31:14 +010029 pb "source.monogon.dev/build/fietsje/proto"
Serge Bazanskif369cfa2020-05-22 18:36:42 +020030)
31
Serge Bazanski216fe7b2021-05-21 18:36:16 +020032// The Shelf is a combined cache and dependency lockfile, not unlike go.sum. It's
33// implemented as a text proto file on disk, and currently stores a single mapping
34// of shelfKeys to shelfValues, which are in order a (importpath, version) tuple
35// and the `locked` structure of a dependency. The resulting shelf file should be
36// commited to the monogon repository. It can be freely deleted to force recreation
37// from scratch, which can be useful as there is no garbage collection implemented
38// for it. The 'lockfile' aspect of the Shelf is counter-intuitive to what readers
39// might be used to from other dependency management systems. It does not lock a
40// third-party dependency to a particular version, but only locks a well defined
41// version to its checksum. As such, recreating the shelf from scratch should not
42// bump any dependencies, unless some upstream-project retagged a release to a
43// different VCS commit, or a fietsje user pinned to 'master' instead of a
44// particular commit. The effective changes will always be reflected in the
45// resulting starlark repository ruleset, which (also being commited to source
46// control) can be used as a canary of a version being effectively bumped.
Serge Bazanskif369cfa2020-05-22 18:36:42 +020047
48// shelfKey is the key into the shelf map structure.
49type shelfKey struct {
50 importpath string
51 version string
52}
53
54// shelfValue is the entry of a shelf map structure.
55type shelfValue struct {
56 l *locked
57}
58
59// shelf is an in-memory representation of the shelf loaded from disk.
60type shelf struct {
61 path string
62 data map[shelfKey]shelfValue
63}
64
65func shelfLoad(path string) (*shelf, error) {
66 var data []byte
67 var err error
68
69 if _, err := os.Stat(path); os.IsNotExist(err) {
70 log.Printf("Creating new shelf file at %q, this run will be slow.", path)
71 } else {
72 data, err = ioutil.ReadFile(path)
73 if err != nil {
74 return nil, fmt.Errorf("could not read shelf: %v", err)
75 }
76 }
77 var shelfProto pb.Shelf
78 err = proto.UnmarshalText(string(data), &shelfProto)
79 if err != nil {
80 return nil, fmt.Errorf("could not unmarshal shelf: %v", err)
81 }
82
83 res := &shelf{
84 path: path,
85 data: make(map[shelfKey]shelfValue),
86 }
87
88 for _, e := range shelfProto.Entry {
89 k := shelfKey{
90 importpath: e.ImportPath,
91 version: e.Version,
92 }
93 v := shelfValue{
94 l: &locked{
95 bazelName: e.BazelName,
96 sum: e.Sum,
97 semver: e.Semver,
98 },
99 }
100 res.data[k] = v
101 }
102 return res, nil
103}
104
105// get retrieves a given lock entry from the in-memory shelf.
106func (s *shelf) get(importpath, version string) *locked {
107 res, ok := s.data[shelfKey{importpath: importpath, version: version}]
108 if !ok {
109 return nil
110 }
111 return res.l
112}
113
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200114// put stores a given locked entry in memory. This will not be commited to disk
115// until .save() is called.
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200116func (s *shelf) put(importpath, version string, l *locked) {
117 s.data[shelfKey{importpath: importpath, version: version}] = shelfValue{l: l}
118}
119
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200120// save commits the shelf to disk (to the same location it was loaded from), fully
121// overwriting from in-memory data.
Serge Bazanskif369cfa2020-05-22 18:36:42 +0200122func (s *shelf) save() error {
123 // Build proto representation of shelf data.
124 var shelfProto pb.Shelf
125 for k, v := range s.data {
126 shelfProto.Entry = append(shelfProto.Entry, &pb.Shelf_Entry{
127 ImportPath: k.importpath,
128 Version: k.version,
129 BazelName: v.l.bazelName,
130 Sum: v.l.sum,
131 Semver: v.l.semver,
132 })
133 }
134
135 // Sort shelf keys by importpath, then by version.
136 sort.Slice(shelfProto.Entry, func(i, j int) bool {
137 a := shelfProto.Entry[i]
138 b := shelfProto.Entry[j]
139
140 if a.ImportPath < b.ImportPath {
141 return true
142 }
143 if a.ImportPath > b.ImportPath {
144 return false
145 }
146 return a.Version < b.Version
147 })
148
149 // Make an in-memory representation of the marshaled shelf.
150 buf := bytes.NewBuffer(nil)
151 err := proto.MarshalText(buf, &shelfProto)
152 if err != nil {
153 return fmt.Errorf("could not serialize shelf: %v", err)
154 }
155
156 // And write it out.
157 err = ioutil.WriteFile(s.path, buf.Bytes(), 0644)
158 if err != nil {
159 return fmt.Errorf("could not write shelf: %v", err)
160 }
161
162 return nil
163}