package buildstep import ( "fmt" "os" "path" "path/filepath" "strings" ) // narrowKept are the files of a module's directory a narrowed build keeps beside its named paths: its manifest and // its lock files, each an input of the module's key (ModuleTree's Manifest and Locks). package.json and go.mod are // not kept: neither is in the named tree, so a build that needs one names it among its artifact's paths, which puts // it in the key (novox/hq ADR 0499 clause 4). var narrowKept = append([]string{"module.json"}, lockFiles...) // Narrow leaves in the fetched tree only what a build of an artifact that names its paths may see (novox/hq ADR 0499 // clause 4): r.Dir's named paths, its manifest and lock files, and .git; everything else is removed, so a file outside // them cannot change what is built without changing its key. A named path absent at the tree is an error naming it, // and nothing is removed then. With no paths in r it changes nothing. Both build modules' fetch steps call it. func Narrow(tree string, r FetchRequest) error { if len(r.Paths) == 0 { return nil } keep := map[string]bool{".git": true} for _, p := range r.Paths { rel := path.Join(r.Dir, p) if strings.HasPrefix(rel, "../") || rel == ".." || path.IsAbs(p) { return fmt.Errorf("the named path %q leaves the module's directory", p) } if _, err := os.Lstat(filepath.Join(tree, filepath.FromSlash(rel))); err != nil { return fmt.Errorf("the named path %q is not in the fetched tree: %w", p, err) } keep[rel] = true } for _, f := range narrowKept { keep[path.Join(r.Dir, f)] = true } return prune(tree, "", keep) } // prune removes from dir (rel within the tree) every entry that is neither kept nor on the way to one. func prune(root, rel string, keep map[string]bool) error { entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(rel))) if err != nil { return err } for _, e := range entries { p := path.Join(rel, e.Name()) if keep[p] { continue } ancestor := false for k := range keep { if strings.HasPrefix(k, p+"/") { ancestor = true break } } if ancestor && e.IsDir() { if err := prune(root, p, keep); err != nil { return err } continue } if err := os.RemoveAll(filepath.Join(root, filepath.FromSlash(p))); err != nil { return err } } return nil }