blob: ea709be74c3b3aa29d722c8fcaddd584b45e2927 [file] [log] [blame]
// Copyright 2020 The Monogon Project Authors.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"sort"
"github.com/golang/protobuf/proto"
pb "source.monogon.dev/build/fietsje/proto"
)
// The Shelf is a combined cache and dependency lockfile, not unlike go.sum. It's implemented as a text proto file on
// disk, and currently stores a single mapping of shelfKeys to shelfValues, which are in order a (importpath, version)
// tuple and the `locked` structure of a dependency.
// The resulting shelf file should be commited to the nxt repository. It can be freely deleted to force recreation from
// scratch, which can be useful as there is no garbage collection implemented for it.
// The 'lockfile' aspect of the Shelf is counter-intuitive to what readers might be used to from other dependency
// management systems. It does not lock a third-party dependency to a particular version, but only locks a well defined
// version to its checksum. As such, recreating the shelf from scratch should not bump any dependencies, unless some
// upstream-project retagged a release to a different VCS commit, or a fietsje user pinned to 'master' instead of a
// particular commit. The effective changes will always be reflected in the resulting starlark repository ruleset,
// which (also being commited to source control) can be used as a canary of a version being effectively bumped.
// shelfKey is the key into the shelf map structure.
type shelfKey struct {
importpath string
version string
}
// shelfValue is the entry of a shelf map structure.
type shelfValue struct {
l *locked
}
// shelf is an in-memory representation of the shelf loaded from disk.
type shelf struct {
path string
data map[shelfKey]shelfValue
}
func shelfLoad(path string) (*shelf, error) {
var data []byte
var err error
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Printf("Creating new shelf file at %q, this run will be slow.", path)
} else {
data, err = ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not read shelf: %v", err)
}
}
var shelfProto pb.Shelf
err = proto.UnmarshalText(string(data), &shelfProto)
if err != nil {
return nil, fmt.Errorf("could not unmarshal shelf: %v", err)
}
res := &shelf{
path: path,
data: make(map[shelfKey]shelfValue),
}
for _, e := range shelfProto.Entry {
k := shelfKey{
importpath: e.ImportPath,
version: e.Version,
}
v := shelfValue{
l: &locked{
bazelName: e.BazelName,
sum: e.Sum,
semver: e.Semver,
},
}
res.data[k] = v
}
return res, nil
}
// get retrieves a given lock entry from the in-memory shelf.
func (s *shelf) get(importpath, version string) *locked {
res, ok := s.data[shelfKey{importpath: importpath, version: version}]
if !ok {
return nil
}
return res.l
}
// put stores a given locked entry in memory. This will not be commited to disk until .save() is called.
func (s *shelf) put(importpath, version string, l *locked) {
s.data[shelfKey{importpath: importpath, version: version}] = shelfValue{l: l}
}
// save commits the shelf to disk (to the same location it was loaded from), fully overwriting from in-memory data.
func (s *shelf) save() error {
// Build proto representation of shelf data.
var shelfProto pb.Shelf
for k, v := range s.data {
shelfProto.Entry = append(shelfProto.Entry, &pb.Shelf_Entry{
ImportPath: k.importpath,
Version: k.version,
BazelName: v.l.bazelName,
Sum: v.l.sum,
Semver: v.l.semver,
})
}
// Sort shelf keys by importpath, then by version.
sort.Slice(shelfProto.Entry, func(i, j int) bool {
a := shelfProto.Entry[i]
b := shelfProto.Entry[j]
if a.ImportPath < b.ImportPath {
return true
}
if a.ImportPath > b.ImportPath {
return false
}
return a.Version < b.Version
})
// Make an in-memory representation of the marshaled shelf.
buf := bytes.NewBuffer(nil)
err := proto.MarshalText(buf, &shelfProto)
if err != nil {
return fmt.Errorf("could not serialize shelf: %v", err)
}
// And write it out.
err = ioutil.WriteFile(s.path, buf.Bytes(), 0644)
if err != nil {
return fmt.Errorf("could not write shelf: %v", err)
}
return nil
}