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  }