github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/cmd/merge_zips/merge_zips.go (about)

     1  // Copyright 2017 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"errors"
    19  	"flag"
    20  	"fmt"
    21  	"hash/crc32"
    22  	"io/ioutil"
    23  	"log"
    24  	"os"
    25  	"path/filepath"
    26  	"sort"
    27  	"strings"
    28  
    29  	"android/soong/jar"
    30  	"android/soong/third_party/zip"
    31  )
    32  
    33  type fileList []string
    34  
    35  func (f *fileList) String() string {
    36  	return `""`
    37  }
    38  
    39  func (f *fileList) Set(name string) error {
    40  	*f = append(*f, filepath.Clean(name))
    41  
    42  	return nil
    43  }
    44  
    45  type zipsToNotStripSet map[string]bool
    46  
    47  func (s zipsToNotStripSet) String() string {
    48  	return `""`
    49  }
    50  
    51  func (s zipsToNotStripSet) Set(zip_path string) error {
    52  	s[zip_path] = true
    53  
    54  	return nil
    55  }
    56  
    57  var (
    58  	sortEntries      = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
    59  	emulateJar       = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
    60  	emulatePar       = flag.Bool("p", false, "merge zip entries based on par format")
    61  	stripDirs        fileList
    62  	stripFiles       fileList
    63  	zipsToNotStrip   = make(zipsToNotStripSet)
    64  	stripDirEntries  = flag.Bool("D", false, "strip directory entries from the output zip file")
    65  	manifest         = flag.String("m", "", "manifest file to insert in jar")
    66  	entrypoint       = flag.String("e", "", "par entrypoint file to insert in par")
    67  	ignoreDuplicates = flag.Bool("ignore-duplicates", false, "take each entry from the first zip it exists in and don't warn")
    68  )
    69  
    70  func init() {
    71  	flag.Var(&stripDirs, "stripDir", "the prefix of file path to be excluded from the output zip")
    72  	flag.Var(&stripFiles, "stripFile", "filenames to be excluded from the output zip, accepts wildcards")
    73  	flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping")
    74  }
    75  
    76  func main() {
    77  	flag.Usage = func() {
    78  		fmt.Fprintln(os.Stderr, "usage: merge_zips [-jpsD] [-m manifest] [-e entrypoint] output [inputs...]")
    79  		flag.PrintDefaults()
    80  	}
    81  
    82  	// parse args
    83  	flag.Parse()
    84  	args := flag.Args()
    85  	if len(args) < 1 {
    86  		flag.Usage()
    87  		os.Exit(1)
    88  	}
    89  	outputPath := args[0]
    90  	inputs := args[1:]
    91  
    92  	log.SetFlags(log.Lshortfile)
    93  
    94  	// make writer
    95  	output, err := os.Create(outputPath)
    96  	if err != nil {
    97  		log.Fatal(err)
    98  	}
    99  	defer output.Close()
   100  	writer := zip.NewWriter(output)
   101  	defer func() {
   102  		err := writer.Close()
   103  		if err != nil {
   104  			log.Fatal(err)
   105  		}
   106  	}()
   107  
   108  	// make readers
   109  	readers := []namedZipReader{}
   110  	for _, input := range inputs {
   111  		reader, err := zip.OpenReader(input)
   112  		if err != nil {
   113  			log.Fatal(err)
   114  		}
   115  		defer reader.Close()
   116  		namedReader := namedZipReader{path: input, reader: reader}
   117  		readers = append(readers, namedReader)
   118  	}
   119  
   120  	if *manifest != "" && !*emulateJar {
   121  		log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
   122  	}
   123  
   124  	if *entrypoint != "" && !*emulatePar {
   125  		log.Fatal(errors.New("must specify -p when specifying a entrypoint via -e"))
   126  	}
   127  
   128  	// do merge
   129  	err = mergeZips(readers, writer, *manifest, *entrypoint, *sortEntries, *emulateJar, *emulatePar,
   130  		*stripDirEntries, *ignoreDuplicates)
   131  	if err != nil {
   132  		log.Fatal(err)
   133  	}
   134  }
   135  
   136  // a namedZipReader reads a .zip file and can say which file it's reading
   137  type namedZipReader struct {
   138  	path   string
   139  	reader *zip.ReadCloser
   140  }
   141  
   142  // a zipEntryPath refers to a file contained in a zip
   143  type zipEntryPath struct {
   144  	zipName   string
   145  	entryName string
   146  }
   147  
   148  func (p zipEntryPath) String() string {
   149  	return p.zipName + "/" + p.entryName
   150  }
   151  
   152  // a zipEntry is a zipSource that pulls its content from another zip
   153  type zipEntry struct {
   154  	path    zipEntryPath
   155  	content *zip.File
   156  }
   157  
   158  func (ze zipEntry) String() string {
   159  	return ze.path.String()
   160  }
   161  
   162  func (ze zipEntry) IsDir() bool {
   163  	return ze.content.FileInfo().IsDir()
   164  }
   165  
   166  func (ze zipEntry) CRC32() uint32 {
   167  	return ze.content.FileHeader.CRC32
   168  }
   169  
   170  func (ze zipEntry) WriteToZip(dest string, zw *zip.Writer) error {
   171  	return zw.CopyFrom(ze.content, dest)
   172  }
   173  
   174  // a bufferEntry is a zipSource that pulls its content from a []byte
   175  type bufferEntry struct {
   176  	fh      *zip.FileHeader
   177  	content []byte
   178  }
   179  
   180  func (be bufferEntry) String() string {
   181  	return "internal buffer"
   182  }
   183  
   184  func (be bufferEntry) IsDir() bool {
   185  	return be.fh.FileInfo().IsDir()
   186  }
   187  
   188  func (be bufferEntry) CRC32() uint32 {
   189  	return crc32.ChecksumIEEE(be.content)
   190  }
   191  
   192  func (be bufferEntry) WriteToZip(dest string, zw *zip.Writer) error {
   193  	w, err := zw.CreateHeader(be.fh)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	if !be.IsDir() {
   199  		_, err = w.Write(be.content)
   200  		if err != nil {
   201  			return err
   202  		}
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  type zipSource interface {
   209  	String() string
   210  	IsDir() bool
   211  	CRC32() uint32
   212  	WriteToZip(dest string, zw *zip.Writer) error
   213  }
   214  
   215  // a fileMapping specifies to copy a zip entry from one place to another
   216  type fileMapping struct {
   217  	dest   string
   218  	source zipSource
   219  }
   220  
   221  func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest, entrypoint string,
   222  	sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool) error {
   223  
   224  	sourceByDest := make(map[string]zipSource, 0)
   225  	orderedMappings := []fileMapping{}
   226  
   227  	// if dest already exists returns a non-null zipSource for the existing source
   228  	addMapping := func(dest string, source zipSource) zipSource {
   229  		mapKey := filepath.Clean(dest)
   230  		if existingSource, exists := sourceByDest[mapKey]; exists {
   231  			return existingSource
   232  		}
   233  
   234  		sourceByDest[mapKey] = source
   235  		orderedMappings = append(orderedMappings, fileMapping{source: source, dest: dest})
   236  		return nil
   237  	}
   238  
   239  	if manifest != "" {
   240  		if !stripDirEntries {
   241  			dirHeader := jar.MetaDirFileHeader()
   242  			dirSource := bufferEntry{dirHeader, nil}
   243  			addMapping(jar.MetaDir, dirSource)
   244  		}
   245  
   246  		fh, buf, err := jar.ManifestFileContents(manifest)
   247  		if err != nil {
   248  			return err
   249  		}
   250  
   251  		fileSource := bufferEntry{fh, buf}
   252  		addMapping(jar.ManifestFile, fileSource)
   253  	}
   254  
   255  	if entrypoint != "" {
   256  		buf, err := ioutil.ReadFile(entrypoint)
   257  		if err != nil {
   258  			return err
   259  		}
   260  		fh := &zip.FileHeader{
   261  			Name:               "entry_point.txt",
   262  			Method:             zip.Store,
   263  			UncompressedSize64: uint64(len(buf)),
   264  		}
   265  		fh.SetMode(0700)
   266  		fh.SetModTime(jar.DefaultTime)
   267  		fileSource := bufferEntry{fh, buf}
   268  		addMapping("entry_point.txt", fileSource)
   269  	}
   270  
   271  	if emulatePar {
   272  		// the runfiles packages needs to be populated with "__init__.py".
   273  		newPyPkgs := []string{}
   274  		// the runfiles dirs have been treated as packages.
   275  		existingPyPkgSet := make(map[string]bool)
   276  		// put existing __init__.py files to a set first. This set is used for preventing
   277  		// generated __init__.py files from overwriting existing ones.
   278  		for _, namedReader := range readers {
   279  			for _, file := range namedReader.reader.File {
   280  				if filepath.Base(file.Name) != "__init__.py" {
   281  					continue
   282  				}
   283  				pyPkg := pathBeforeLastSlash(file.Name)
   284  				if _, found := existingPyPkgSet[pyPkg]; found {
   285  					panic(fmt.Errorf("found __init__.py path duplicates during pars merging: %q.", file.Name))
   286  				} else {
   287  					existingPyPkgSet[pyPkg] = true
   288  				}
   289  			}
   290  		}
   291  		for _, namedReader := range readers {
   292  			for _, file := range namedReader.reader.File {
   293  				var parentPath string /* the path after trimming last "/" */
   294  				if filepath.Base(file.Name) == "__init__.py" {
   295  					// for existing __init__.py files, we should trim last "/" for twice.
   296  					// eg. a/b/c/__init__.py ---> a/b
   297  					parentPath = pathBeforeLastSlash(pathBeforeLastSlash(file.Name))
   298  				} else {
   299  					parentPath = pathBeforeLastSlash(file.Name)
   300  				}
   301  				populateNewPyPkgs(parentPath, existingPyPkgSet, &newPyPkgs)
   302  			}
   303  		}
   304  		for _, pkg := range newPyPkgs {
   305  			var emptyBuf []byte
   306  			fh := &zip.FileHeader{
   307  				Name:               filepath.Join(pkg, "__init__.py"),
   308  				Method:             zip.Store,
   309  				UncompressedSize64: uint64(len(emptyBuf)),
   310  			}
   311  			fh.SetMode(0700)
   312  			fh.SetModTime(jar.DefaultTime)
   313  			fileSource := bufferEntry{fh, emptyBuf}
   314  			addMapping(filepath.Join(pkg, "__init__.py"), fileSource)
   315  		}
   316  	}
   317  	for _, namedReader := range readers {
   318  		_, skipStripThisZip := zipsToNotStrip[namedReader.path]
   319  		for _, file := range namedReader.reader.File {
   320  			if !skipStripThisZip && shouldStripFile(emulateJar, file.Name) {
   321  				continue
   322  			}
   323  
   324  			if stripDirEntries && file.FileInfo().IsDir() {
   325  				continue
   326  			}
   327  
   328  			// check for other files or directories destined for the same path
   329  			dest := file.Name
   330  
   331  			// make a new entry to add
   332  			source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
   333  
   334  			if existingSource := addMapping(dest, source); existingSource != nil {
   335  				// handle duplicates
   336  				if existingSource.IsDir() != source.IsDir() {
   337  					return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
   338  						dest, existingSource, source)
   339  				}
   340  				if ignoreDuplicates {
   341  					continue
   342  				}
   343  				if emulateJar &&
   344  					file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
   345  					// Skip manifest and module info files that are not from the first input file
   346  					continue
   347  				}
   348  				if !source.IsDir() {
   349  					if emulateJar {
   350  						if existingSource.CRC32() != source.CRC32() {
   351  							fmt.Fprintf(os.Stdout, "WARNING: Duplicate path %v found in %v and %v\n",
   352  								dest, existingSource, source)
   353  						}
   354  					} else {
   355  						return fmt.Errorf("Duplicate path %v found in %v and %v\n",
   356  							dest, existingSource, source)
   357  					}
   358  				}
   359  			}
   360  		}
   361  	}
   362  
   363  	if emulateJar {
   364  		jarSort(orderedMappings)
   365  	} else if sortEntries {
   366  		alphanumericSort(orderedMappings)
   367  	}
   368  
   369  	for _, entry := range orderedMappings {
   370  		if err := entry.source.WriteToZip(entry.dest, writer); err != nil {
   371  			return err
   372  		}
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  // Sets the given directory and all its ancestor directories as Python packages.
   379  func populateNewPyPkgs(pkgPath string, existingPyPkgSet map[string]bool, newPyPkgs *[]string) {
   380  	for pkgPath != "" {
   381  		if _, found := existingPyPkgSet[pkgPath]; !found {
   382  			existingPyPkgSet[pkgPath] = true
   383  			*newPyPkgs = append(*newPyPkgs, pkgPath)
   384  			// Gets its ancestor directory by trimming last slash.
   385  			pkgPath = pathBeforeLastSlash(pkgPath)
   386  		} else {
   387  			break
   388  		}
   389  	}
   390  }
   391  
   392  func pathBeforeLastSlash(path string) string {
   393  	ret := filepath.Dir(path)
   394  	// filepath.Dir("abc") -> "." and filepath.Dir("/abc") -> "/".
   395  	if ret == "." || ret == "/" {
   396  		return ""
   397  	}
   398  	return ret
   399  }
   400  
   401  func shouldStripFile(emulateJar bool, name string) bool {
   402  	for _, dir := range stripDirs {
   403  		if strings.HasPrefix(name, dir+"/") {
   404  			if emulateJar {
   405  				if name != jar.MetaDir && name != jar.ManifestFile {
   406  					return true
   407  				}
   408  			} else {
   409  				return true
   410  			}
   411  		}
   412  	}
   413  	for _, pattern := range stripFiles {
   414  		if match, err := filepath.Match(pattern, filepath.Base(name)); err != nil {
   415  			panic(fmt.Errorf("%s: %s", err.Error(), pattern))
   416  		} else if match {
   417  			return true
   418  		}
   419  	}
   420  	return false
   421  }
   422  
   423  func jarSort(files []fileMapping) {
   424  	sort.SliceStable(files, func(i, j int) bool {
   425  		return jar.EntryNamesLess(files[i].dest, files[j].dest)
   426  	})
   427  }
   428  
   429  func alphanumericSort(files []fileMapping) {
   430  	sort.SliceStable(files, func(i, j int) bool {
   431  		return files[i].dest < files[j].dest
   432  	})
   433  }