github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/jarcat/main.go (about)

     1  // Package main implements jarcat, a program to efficiently concatenate .zip files.
     2  // Originally this was pretty simple and that was all it could do, over time it's
     3  // gained a bunch more features on a more or less as needed basis.
     4  //
     5  // It's now used for most general-purpose zip and tar manipulation in Please, since
     6  // the standard tools either differ between implementations (e.g. GNU tar vs. BSD tar)
     7  // or introduce indeterminacy, often in regard to timestamps.
     8  package main
     9  
    10  import (
    11  	"bufio"
    12  	"io/ioutil"
    13  	"os"
    14  
    15  	"gopkg.in/op/go-logging.v1"
    16  
    17  	"cli"
    18  	"tools/jarcat/tar"
    19  	"tools/jarcat/unzip"
    20  	"tools/jarcat/zip"
    21  )
    22  
    23  var javaExcludePrefixes = []string{
    24  	"META-INF/LICENSE", "META-INF/NOTICE", "META-INF/maven/*", "META-INF/MANIFEST.MF",
    25  	// Unsign all jars by default, after concatenation the signatures will no longer be valid.
    26  	"META-INF/*.SF", "META-INF/*.RSA", "META-INF/*.LIST",
    27  }
    28  
    29  var log = logging.MustGetLogger("jarcat")
    30  
    31  func must(err error) {
    32  	if err != nil {
    33  		log.Fatalf("%s", err)
    34  	}
    35  }
    36  
    37  // mustReadPreamble reads and returns the first line of a file.
    38  func mustReadPreamble(path string) string {
    39  	f, err := os.Open(path)
    40  	if err != nil {
    41  		log.Fatalf("%s", err)
    42  	}
    43  	defer f.Close()
    44  	r := bufio.NewReader(f)
    45  	s, err := r.ReadString('\n')
    46  	if err != nil {
    47  		log.Fatalf("%s", err)
    48  	}
    49  	return s
    50  }
    51  
    52  var opts = struct {
    53  	Usage     string
    54  	Verbosity int `short:"v" long:"verbose" default:"1" description:"Verbosity of output (higher number = more output, default 1 -> warnings and errors only)"`
    55  
    56  	Zip struct {
    57  		In                    cli.StdinStrings  `short:"i" long:"input" description:"Input directory" required:"true"`
    58  		Out                   string            `short:"o" long:"output" env:"OUT" description:"Output filename" required:"true"`
    59  		Suffix                []string          `short:"s" long:"suffix" default:".jar" description:"Suffix of files to include"`
    60  		ExcludeSuffix         []string          `short:"e" long:"exclude_suffix" default:"src.jar" description:"Suffix of files to exclude"`
    61  		ExcludeJavaPrefixes   bool              `short:"j" long:"exclude_java_prefixes" description:"Use default Java exclusions"`
    62  		ExcludeInternalPrefix []string          `short:"x" long:"exclude_internal_prefix" description:"Prefix of files to exclude"`
    63  		IncludeInternalPrefix []string          `short:"t" long:"include_internal_prefix" description:"Prefix of files to include"`
    64  		StripPrefix           string            `long:"strip_prefix" description:"Prefix to strip off file names"`
    65  		Preamble              string            `short:"p" long:"preamble" description:"Leading string to prepend to written zip file"`
    66  		PreambleFrom          string            `long:"preamble_from" description:"Read the first line of this file and use as --preamble."`
    67  		PreambleFile          string            `long:"preamble_file" description:"Concatenate zip file onto the end of this file"`
    68  		MainClass             string            `short:"m" long:"main_class" description:"Write a Java manifest file containing the given main class."`
    69  		Manifest              string            `long:"manifest" description:"Use the given file as a Java manifest"`
    70  		Align                 int               `short:"a" long:"align" description:"Align zip members to a multiple of this number of bytes."`
    71  		Strict                bool              `long:"strict" description:"Disallow duplicate files"`
    72  		IncludeOther          bool              `long:"include_other" description:"Add files that are not jar files as well"`
    73  		AddInitPy             bool              `long:"add_init_py" description:"Adds __init__.py files to all directories"`
    74  		DumbMode              bool              `short:"d" long:"dumb" description:"Dumb mode, an alias for --suffix='' --exclude_suffix='' --include_other"`
    75  		NoDirEntries          bool              `short:"n" long:"nodir_entries" description:"Don't add directory entries to zip"`
    76  		RenameDirs            map[string]string `short:"r" long:"rename_dir" description:"Rename directories within zip file"`
    77  		StoreSuffix           []string          `short:"u" long:"store_suffix" description:"Suffix of filenames to store instead of deflate (i.e. without compression). Note that this only affects files found with --include_other."`
    78  		Prefix                string            `long:"prefix" description:"Prefix all entries with this directory name."`
    79  	} `command:"zip" alias:"z" description:"Writes an output zipfile"`
    80  
    81  	Tar struct {
    82  		Gzip   bool     `short:"z" long:"gzip" description:"Apply gzip compression to the tar file. Only has an effect if --tar is passed."`
    83  		Out    string   `short:"o" long:"output" env:"OUT" description:"Output filename" required:"true"`
    84  		Srcs   []string `long:"srcs" env:"SRCS" env-delim:" " description:"Source files for the tarball."`
    85  		Prefix string   `long:"prefix" description:"Prefix all entries with this directory name."`
    86  	} `command:"tar" alias:"t" description:"Builds a tarball instead of a zipfile."`
    87  
    88  	Unzip struct {
    89  		Args struct {
    90  			In   string `positional-arg-name:"input" required:"true" description:"Input zipfile"`
    91  			File string `positional-arg-name:"file" description:"File to extract"`
    92  		} `positional-args:"true"`
    93  		StripPrefix string `short:"s" long:"strip_prefix" description:"Strip this prefix from extracted files"`
    94  		OutDir      string `short:"o" long:"out" description:"Output directory"`
    95  		Out         string `long:"out_file" hidden:"true" env:"OUT"`
    96  	} `command:"unzip" alias:"u" alias:"x" description:"Unzips a zipfile"`
    97  }{
    98  	Usage: `
    99  Jarcat is a binary shipped with Please that helps it operate on .jar and .zip files.
   100  
   101  Its original and most useful feature is performing efficient concatenation of .jar files
   102  when compiling Java code. This is possible with zip files because each file is compressed
   103  individually so it's possible to combine them without decompressing and recompressing each one.
   104  
   105  It now has a number of other features to help in compilation and serves as a general-purpose
   106  zip manipulator for Please. To help us maintain reproduceability of builds it is able to strip
   107  timestamps from files, and also has a bunch of Python-specific functionality to help with .pex files.
   108  
   109  Typically you don't invoke this directly, Please will run it when individual rules need it.
   110  You're welcome to use it separately if you find it useful, although be aware that we do not
   111  aim to maintain compatibility very strongly.
   112  
   113  Any apparent relationship between the name of this tool and bonsai kittens is completely coincidental.
   114  `,
   115  }
   116  
   117  func main() {
   118  	command := cli.ParseFlagsOrDie("Jarcat", "12.1.4", &opts)
   119  	if opts.Zip.DumbMode {
   120  		opts.Zip.Suffix = nil
   121  		opts.Zip.ExcludeSuffix = nil
   122  		opts.Zip.IncludeOther = true
   123  	}
   124  	cli.InitLogging(opts.Verbosity)
   125  
   126  	if command == "tar" {
   127  		if err := tar.Write(opts.Tar.Out, opts.Tar.Srcs, opts.Tar.Prefix, opts.Tar.Gzip); err != nil {
   128  			log.Fatalf("Error writing tarball: %s\n", err)
   129  		}
   130  		os.Exit(0)
   131  	} else if command == "unzip" {
   132  		// This comes up if we're in the root directory. Ignore it.
   133  		if opts.Unzip.StripPrefix == "." {
   134  			opts.Unzip.StripPrefix = ""
   135  		}
   136  		if opts.Unzip.Args.File != "" && opts.Unzip.OutDir == "" {
   137  			opts.Unzip.OutDir = opts.Unzip.Out
   138  		}
   139  		if err := unzip.Extract(opts.Unzip.Args.In, opts.Unzip.OutDir, opts.Unzip.Args.File, opts.Unzip.StripPrefix); err != nil {
   140  			log.Fatalf("Error extracting zipfile: %s", err)
   141  		}
   142  		os.Exit(0)
   143  	}
   144  
   145  	if opts.Zip.ExcludeJavaPrefixes {
   146  		opts.Zip.ExcludeInternalPrefix = javaExcludePrefixes
   147  	}
   148  
   149  	f := zip.NewFile(opts.Zip.Out, opts.Zip.Strict)
   150  	defer f.Close()
   151  	f.RenameDirs = opts.Zip.RenameDirs
   152  	f.Include = opts.Zip.IncludeInternalPrefix
   153  	f.Exclude = opts.Zip.ExcludeInternalPrefix
   154  	f.StripPrefix = opts.Zip.StripPrefix
   155  	f.Suffix = opts.Zip.Suffix
   156  	f.ExcludeSuffix = opts.Zip.ExcludeSuffix
   157  	f.StoreSuffix = opts.Zip.StoreSuffix
   158  	f.IncludeOther = opts.Zip.IncludeOther
   159  	f.AddInitPy = opts.Zip.AddInitPy
   160  	f.DirEntries = !opts.Zip.NoDirEntries
   161  	f.Align = opts.Zip.Align
   162  	f.Prefix = opts.Zip.Prefix
   163  
   164  	if opts.Zip.PreambleFrom != "" {
   165  		opts.Zip.Preamble = mustReadPreamble(opts.Zip.PreambleFrom)
   166  	}
   167  	if opts.Zip.Preamble != "" {
   168  		must(f.WritePreamble([]byte(opts.Zip.Preamble + "\n")))
   169  	}
   170  	if opts.Zip.PreambleFile != "" {
   171  		b, err := ioutil.ReadFile(opts.Zip.PreambleFile)
   172  		must(err)
   173  		must(f.WritePreamble(b))
   174  	}
   175  	if opts.Zip.MainClass != "" {
   176  		must(f.AddManifest(opts.Zip.MainClass))
   177  	}
   178  	if opts.Zip.Manifest != "" {
   179  		b, err := ioutil.ReadFile(opts.Zip.Manifest)
   180  		must(err)
   181  		must(f.WriteFile("META-INF/MANIFEST.MF", b, 0644))
   182  	}
   183  	for _, filename := range opts.Zip.In.Get() {
   184  		must(f.AddFiles(filename))
   185  	}
   186  }