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