From c79206f99382a598be3d4a8110d0387a9cef9087 Mon Sep 17 00:00:00 2001 From: Matthew Suozzo Date: Thu, 24 Oct 2024 11:07:42 -0400 Subject: [PATCH] Add stabilize binary --- cmd/stabilize/main.go | 168 +++++++++++++++++++++++++++++++++++++++++ pkg/archive/archive.go | 13 +++- 2 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 cmd/stabilize/main.go diff --git a/cmd/stabilize/main.go b/cmd/stabilize/main.go new file mode 100644 index 00000000..1b260e7c --- /dev/null +++ b/cmd/stabilize/main.go @@ -0,0 +1,168 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/google/oss-rebuild/pkg/archive" + "github.com/pkg/errors" +) + +var ( + infile = flag.String("infile", "", "Input path to the file to be stabilized.") + outfile = flag.String("outfile", "", "Output path to which the stabilized file will be written.") + enablePasses = flag.String("enable-passes", "all", "Enable the comma-separated set of stabilizers or 'all'. -help for full list of options") + disablePasses = flag.String("disable-passes", "none", "Disable only the comma-separated set of stabilizers or 'none'. -help for full list of options") +) + +func getName(san any) string { + switch san.(type) { + case archive.TarArchiveStabilizer: + return san.(archive.TarArchiveStabilizer).Name + case archive.TarEntryStabilizer: + return san.(archive.TarEntryStabilizer).Name + case archive.ZipArchiveStabilizer: + return san.(archive.ZipArchiveStabilizer).Name + case archive.ZipEntryStabilizer: + return san.(archive.ZipEntryStabilizer).Name + default: + log.Fatalf("unknown sanitizer type: %T", san) + return "" // unreachable + } +} + +func filetype(path string) archive.Format { + ext := filepath.Ext(path) + switch ext { + case ".tar": + return archive.TarFormat + case ".tgz", ".crate", ".gz", ".Z": + return archive.TarGzFormat + case ".zip", ".whl", ".egg", ".jar": + return archive.ZipFormat + default: + return archive.RawFormat + } +} + +type StabilizerRegistry struct { + sanitizers []any + byName map[string]any +} + +func NewStabilizerRegistry(sans ...any) StabilizerRegistry { + reg := StabilizerRegistry{sanitizers: sans} + reg.byName = make(map[string]any) + for _, san := range reg.sanitizers { + reg.byName[getName(san)] = san + } + return reg +} + +func (reg StabilizerRegistry) Get(name string) (any, bool) { + val, ok := reg.byName[name] + return val, ok +} + +func (reg StabilizerRegistry) GetAll() []any { + return reg.sanitizers[:] +} + +// determinePasses returns the passes specified with the given pass specs. +// +// - Preserves the order specified in enableSpec. Order of "all" is impl-defined. +// - Disable has precedence over enable. +// - Duplicates are retained and respected. +func determinePasses(sanitizers StabilizerRegistry, enableSpec, disableSpec string) ([]any, error) { + var toRun []any + enabled := make(map[string]bool) + if *enablePasses == "all" { + for _, pass := range sanitizers.GetAll() { + toRun = append(toRun, pass) + enabled[getName(pass)] = true + } + } else { + for _, name := range strings.Split(enableSpec, ",") { + cleanName := strings.TrimSpace(name) + if san, ok := sanitizers.Get(cleanName); !ok { + return nil, fmt.Errorf("unknown pass name: %s", cleanName) + } else { + toRun = append(toRun, san) + enabled[cleanName] = true + } + } + } + if *disablePasses != "none" { + if *disablePasses == "all" { + clear(enabled) + } else { + for _, name := range strings.Split(disableSpec, ",") { + cleanName := strings.TrimSpace(name) + if _, ok := sanitizers.Get(cleanName); !ok { + return nil, fmt.Errorf("unknown pass name: %s", cleanName) + } + if _, ok := enabled[cleanName]; ok { + delete(enabled, cleanName) + } + } + } + // Apply deletions from "enabled". + toRun = slices.DeleteFunc(toRun, func(san any) bool { + _, ok := enabled[getName(san)] + return !ok + }) + } + return toRun, nil +} + +func run() error { + sanitizers := NewStabilizerRegistry(archive.AllStabilizers...) + + // Update usage to include available passes. + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "\nAvailable stabilizers (in default order of application):\n") + for _, san := range archive.AllStabilizers { + fmt.Fprintf(os.Stderr, " - %s\n", getName(san)) + } + } + + flag.Parse() + + if *infile == "" || *outfile == "" { + flag.Usage() + return errors.New("both -infile and -outfile are required") + } + toRun, err := determinePasses(sanitizers, *enablePasses, *disablePasses) + if err != nil { + flag.Usage() + return err + } + + in, err := os.Open(*infile) + if err != nil { + return errors.Wrap(err, "opening input file") + } + defer in.Close() + + out, err := os.Create(*outfile) + if err != nil { + return errors.Wrap(err, "creating output file") + } + defer out.Close() + + err = archive.StabilizeWithOpts(out, in, filetype(*infile), archive.StabilizeOpts{Stabilizers: toRun}) + return errors.Wrap(err, "stabilizing file") +} + +func main() { + if err := run(); err != nil { + log.Fatalf("Error: %v", err) + } +} diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 81c9a502..311e4ba6 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -25,9 +25,13 @@ import ( var AllStabilizers = append(AllZipStabilizers, AllTarStabilizers...) -// Stabilize selects and applies the stabilization routine for the given archive format. +// Stabilize selects and applies the default stabilization routine for the given archive format. func Stabilize(dst io.Writer, src io.Reader, f Format) error { - opts := StabilizeOpts{Stabilizers: AllStabilizers} + return StabilizeWithOpts(dst, src, f, StabilizeOpts{Stabilizers: AllStabilizers}) +} + +// StabilizeWithOpts selects and applies the provided stabilization routine for the given archive format. +func StabilizeWithOpts(dst io.Writer, src io.Reader, f Format, opts StabilizeOpts) error { switch f { case ZipFormat: srcReader, size, err := toZipCompatibleReader(src) @@ -53,6 +57,11 @@ func Stabilize(dst io.Writer, src io.Reader, f Format) error { gzw := gzip.NewWriter(dst) defer gzw.Close() err = StabilizeTar(tar.NewReader(gzr), tar.NewWriter(gzw), opts) + if err != nil { + return errors.Wrap(err, "stabilizing tar.gz") + } + case TarFormat: + err := StabilizeTar(tar.NewReader(src), tar.NewWriter(dst), opts) if err != nil { return errors.Wrap(err, "stabilizing tar") }