| // 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 | 
 | } |