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 }