github.com/tiagovtristao/plz@v13.4.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 "github.com/thought-machine/please/src/cli" 18 "github.com/thought-machine/please/tools/jarcat/ar" 19 "github.com/thought-machine/please/tools/jarcat/tar" 20 "github.com/thought-machine/please/tools/jarcat/unzip" 21 "github.com/thought-machine/please/tools/jarcat/zip" 22 ) 23 24 var javaExcludePrefixes = []string{ 25 "META-INF/LICENSE", "META-INF/NOTICE", "META-INF/maven/*", "META-INF/MANIFEST.MF", 26 // Unsign all jars by default, after concatenation the signatures will no longer be valid. 27 "META-INF/*.SF", "META-INF/*.RSA", "META-INF/*.LIST", 28 } 29 30 var log = logging.MustGetLogger("jarcat") 31 32 func must(err error) { 33 if err != nil { 34 log.Fatalf("%s", err) 35 } 36 } 37 38 // mustReadPreamble reads and returns the first line of a file. 39 func mustReadPreamble(path string) string { 40 f, err := os.Open(path) 41 if err != nil { 42 log.Fatalf("%s", err) 43 } 44 defer f.Close() 45 r := bufio.NewReader(f) 46 s, err := r.ReadString('\n') 47 if err != nil { 48 log.Fatalf("%s", err) 49 } 50 return s 51 } 52 53 var opts = struct { 54 Usage string 55 Verbosity cli.Verbosity `short:"v" long:"verbosity" default:"warning" description:"Verbosity of output (higher number = more output)"` 56 57 Zip struct { 58 In cli.StdinStrings `short:"i" long:"input" description:"Input directory" required:"true"` 59 Out string `short:"o" long:"output" env:"OUT" description:"Output filename" required:"true"` 60 Suffix []string `short:"s" long:"suffix" default:".jar" description:"Suffix of files to include"` 61 ExcludeSuffix []string `short:"e" long:"exclude_suffix" default:"src.jar" description:"Suffix of files to exclude"` 62 ExcludeJavaPrefixes bool `short:"j" long:"exclude_java_prefixes" description:"Use default Java exclusions"` 63 ExcludeInternalPrefix []string `short:"x" long:"exclude_internal_prefix" description:"Prefix of files to exclude"` 64 IncludeInternalPrefix []string `short:"t" long:"include_internal_prefix" description:"Prefix of files to include"` 65 StripPrefix string `long:"strip_prefix" description:"Prefix to strip off file names"` 66 Preamble string `short:"p" long:"preamble" description:"Leading string to prepend to written zip file"` 67 PreambleFrom string `long:"preamble_from" description:"Read the first line of this file and use as --preamble."` 68 PreambleFile string `long:"preamble_file" description:"Concatenate zip file onto the end of this file"` 69 MainClass string `short:"m" long:"main_class" description:"Write a Java manifest file containing the given main class."` 70 Manifest string `long:"manifest" description:"Use the given file as a Java manifest"` 71 Align int `short:"a" long:"align" description:"Align zip members to a multiple of this number of bytes."` 72 Strict bool `long:"strict" description:"Disallow duplicate files"` 73 IncludeOther bool `long:"include_other" description:"Add files that are not jar files as well"` 74 AddInitPy bool `long:"add_init_py" description:"Adds __init__.py files to all directories"` 75 StripPy bool `long:"strip_py" description:"Strips .py files when there is a corresponding .pyc"` 76 DumbMode bool `short:"d" long:"dumb" description:"Dumb mode, an alias for --suffix='' --exclude_suffix='' --include_other"` 77 NoDirEntries bool `short:"n" long:"nodir_entries" description:"Don't add directory entries to zip"` 78 RenameDirs map[string]string `short:"r" long:"rename_dir" description:"Rename directories within zip file"` 79 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."` 80 Prefix string `long:"prefix" description:"Prefix all entries with this directory name."` 81 } `command:"zip" alias:"z" description:"Writes an output zipfile"` 82 83 Tar struct { 84 Gzip bool `short:"z" long:"gzip" description:"Apply gzip compression to the tar file."` 85 Xzip bool `short:"x" long:"xzip" description:"Apply gzip compression to the tar file."` 86 Out string `short:"o" long:"output" env:"OUT" description:"Output filename" required:"true"` 87 Srcs []string `long:"srcs" env:"SRCS" env-delim:" " description:"Source files for the tarball."` 88 Prefix string `long:"prefix" description:"Prefix all entries with this directory name."` 89 } `command:"tar" alias:"t" description:"Builds a tarball instead of a zipfile."` 90 91 Unzip struct { 92 Args struct { 93 In string `positional-arg-name:"input" required:"true" description:"Input zipfile"` 94 File string `positional-arg-name:"file" description:"File to extract"` 95 } `positional-args:"true"` 96 StripPrefix string `short:"s" long:"strip_prefix" description:"Strip this prefix from extracted files"` 97 OutDir string `short:"o" long:"out" description:"Output directory"` 98 Out string `long:"out_file" hidden:"true" env:"OUT"` 99 } `command:"unzip" alias:"u" alias:"x" description:"Unzips a zipfile"` 100 101 Ar struct { 102 Srcs []string `long:"srcs" env:"SRCS_SRCS" env-delim:" " description:"Source .ar files to combine"` 103 Out string `long:"out" env:"OUT" description:"Output filename"` 104 Rename bool `short:"r" long:"rename" description:"Rename source files as gcc would (i.e. change extension to .o)"` 105 Combine bool `short:"c" long:"combine" description:"Treat source files as .a files and combines them"` 106 Find bool `short:"f" long:"find" description:"Find all .a files under the current directory & combine those (implies --combine)"` 107 } `command:"ar" alias:"a" description:"Creates a new ar archive."` 108 }{ 109 Usage: ` 110 Jarcat is a binary shipped with Please that helps it operate on .jar and .zip files. 111 112 Its original and most useful feature is performing efficient concatenation of .jar files 113 when compiling Java code. This is possible with zip files because each file is compressed 114 individually so it's possible to combine them without decompressing and recompressing each one. 115 116 It now has a number of other features to help in compilation and serves as a general-purpose 117 zip manipulator for Please. To help us maintain reproduceability of builds it is able to strip 118 timestamps from files, and also has a bunch of Python-specific functionality to help with .pex files. 119 120 Typically you don't invoke this directly, Please will run it when individual rules need it. 121 You're welcome to use it separately if you find it useful, although be aware that we do not 122 aim to maintain compatibility very strongly. 123 124 Any apparent relationship between the name of this tool and bonsai kittens is completely coincidental. 125 `, 126 } 127 128 func main() { 129 command := cli.ParseFlagsOrDie("Jarcat", &opts) 130 if opts.Zip.DumbMode { 131 opts.Zip.Suffix = nil 132 opts.Zip.ExcludeSuffix = nil 133 opts.Zip.IncludeOther = true 134 } 135 cli.InitLogging(opts.Verbosity) 136 137 if command == "tar" { 138 if opts.Tar.Xzip && opts.Tar.Gzip { 139 log.Fatalf("Can't pass --xzip and --gzip simultaneously") 140 } 141 if err := tar.Write(opts.Tar.Out, opts.Tar.Srcs, opts.Tar.Prefix, opts.Tar.Gzip, opts.Tar.Xzip); err != nil { 142 log.Fatalf("Error writing tarball: %s\n", err) 143 } 144 os.Exit(0) 145 } else if command == "unzip" { 146 // This comes up if we're in the root directory. Ignore it. 147 if opts.Unzip.StripPrefix == "." { 148 opts.Unzip.StripPrefix = "" 149 } 150 if opts.Unzip.Args.File != "" && opts.Unzip.OutDir == "" { 151 opts.Unzip.OutDir = opts.Unzip.Out 152 } 153 if err := unzip.Extract(opts.Unzip.Args.In, opts.Unzip.OutDir, opts.Unzip.Args.File, opts.Unzip.StripPrefix); err != nil { 154 log.Fatalf("Error extracting zipfile: %s", err) 155 } 156 os.Exit(0) 157 } else if command == "ar" { 158 if opts.Ar.Find { 159 srcs, err := ar.Find() 160 if err != nil { 161 log.Fatalf("%s", err) 162 } 163 opts.Ar.Srcs = srcs 164 opts.Ar.Combine = true 165 } 166 if err := ar.Create(opts.Ar.Srcs, opts.Ar.Out, opts.Ar.Combine, opts.Ar.Rename); err != nil { 167 log.Fatalf("Error combining archives: %s", err) 168 } 169 os.Exit(0) 170 } 171 172 if opts.Zip.ExcludeJavaPrefixes { 173 opts.Zip.ExcludeInternalPrefix = javaExcludePrefixes 174 } 175 176 f := zip.NewFile(opts.Zip.Out, opts.Zip.Strict) 177 defer f.Close() 178 f.RenameDirs = opts.Zip.RenameDirs 179 f.Include = opts.Zip.IncludeInternalPrefix 180 f.Exclude = opts.Zip.ExcludeInternalPrefix 181 f.StripPrefix = opts.Zip.StripPrefix 182 f.Suffix = opts.Zip.Suffix 183 f.ExcludeSuffix = opts.Zip.ExcludeSuffix 184 f.StoreSuffix = opts.Zip.StoreSuffix 185 f.IncludeOther = opts.Zip.IncludeOther 186 f.AddInitPy = opts.Zip.AddInitPy 187 f.StripPy = opts.Zip.StripPy 188 f.DirEntries = !opts.Zip.NoDirEntries 189 f.Align = opts.Zip.Align 190 f.Prefix = opts.Zip.Prefix 191 192 if opts.Zip.PreambleFrom != "" { 193 opts.Zip.Preamble = mustReadPreamble(opts.Zip.PreambleFrom) 194 } 195 if opts.Zip.Preamble != "" { 196 must(f.WritePreamble([]byte(opts.Zip.Preamble + "\n"))) 197 } 198 if opts.Zip.PreambleFile != "" { 199 b, err := ioutil.ReadFile(opts.Zip.PreambleFile) 200 must(err) 201 must(f.WritePreamble(b)) 202 } 203 if opts.Zip.MainClass != "" { 204 must(f.AddManifest(opts.Zip.MainClass)) 205 } 206 if opts.Zip.Manifest != "" { 207 b, err := ioutil.ReadFile(opts.Zip.Manifest) 208 must(err) 209 must(f.WriteFile("META-INF/MANIFEST.MF", b, 0644)) 210 } 211 for _, filename := range opts.Zip.In.Get() { 212 must(f.AddFiles(filename)) 213 } 214 }