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