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

     1  // Copyright 2016 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  	"flag"
    19  	"fmt"
    20  	"log"
    21  	"os"
    22  	"path/filepath"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/google/blueprint/pathtools"
    28  
    29  	"android/soong/jar"
    30  	"android/soong/third_party/zip"
    31  )
    32  
    33  var (
    34  	input     = flag.String("i", "", "zip file to read from")
    35  	output    = flag.String("o", "", "output file")
    36  	sortGlobs = flag.Bool("s", false, "sort matches from each glob (defaults to the order from the input zip file)")
    37  	sortJava  = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)")
    38  	setTime   = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00")
    39  
    40  	staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
    41  
    42  	excludes excludeArgs
    43  )
    44  
    45  func init() {
    46  	flag.Var(&excludes, "x", "exclude a filespec from the output")
    47  }
    48  
    49  func main() {
    50  	flag.Usage = func() {
    51  		fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...")
    52  		flag.PrintDefaults()
    53  		fmt.Fprintln(os.Stderr, "  filespec:")
    54  		fmt.Fprintln(os.Stderr, "    <name>")
    55  		fmt.Fprintln(os.Stderr, "    <in_name>:<out_name>")
    56  		fmt.Fprintln(os.Stderr, "    <glob>[:<out_dir>]")
    57  		fmt.Fprintln(os.Stderr, "")
    58  		fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match")
    59  		fmt.Fprintln(os.Stderr, "")
    60  		fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to")
    61  		fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.")
    62  		fmt.Fprintln(os.Stderr, "")
    63  		fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.")
    64  	}
    65  
    66  	flag.Parse()
    67  
    68  	if *input == "" || *output == "" {
    69  		flag.Usage()
    70  		os.Exit(1)
    71  	}
    72  
    73  	log.SetFlags(log.Lshortfile)
    74  
    75  	reader, err := zip.OpenReader(*input)
    76  	if err != nil {
    77  		log.Fatal(err)
    78  	}
    79  	defer reader.Close()
    80  
    81  	output, err := os.Create(*output)
    82  	if err != nil {
    83  		log.Fatal(err)
    84  	}
    85  	defer output.Close()
    86  
    87  	writer := zip.NewWriter(output)
    88  	defer func() {
    89  		err := writer.Close()
    90  		if err != nil {
    91  			log.Fatal(err)
    92  		}
    93  	}()
    94  
    95  	if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
    96  		flag.Args(), excludes); err != nil {
    97  
    98  		log.Fatal(err)
    99  	}
   100  }
   101  
   102  type pair struct {
   103  	*zip.File
   104  	newName string
   105  }
   106  
   107  func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
   108  	includes []string, excludes []string) error {
   109  
   110  	matches := []pair{}
   111  
   112  	sortMatches := func(matches []pair) {
   113  		if sortJava {
   114  			sort.SliceStable(matches, func(i, j int) bool {
   115  				return jar.EntryNamesLess(matches[i].newName, matches[j].newName)
   116  			})
   117  		} else if sortOutput {
   118  			sort.SliceStable(matches, func(i, j int) bool {
   119  				return matches[i].newName < matches[j].newName
   120  			})
   121  		}
   122  	}
   123  
   124  	for _, include := range includes {
   125  		// Reserve escaping for future implementation, so make sure no
   126  		// one is using \ and expecting a certain behavior.
   127  		if strings.Contains(include, "\\") {
   128  			return fmt.Errorf("\\ characters are not currently supported")
   129  		}
   130  
   131  		input, output := includeSplit(include)
   132  
   133  		var includeMatches []pair
   134  
   135  		for _, file := range reader.File {
   136  			var newName string
   137  			if match, err := pathtools.Match(input, file.Name); err != nil {
   138  				return err
   139  			} else if match {
   140  				if output == "" {
   141  					newName = file.Name
   142  				} else {
   143  					if pathtools.IsGlob(input) {
   144  						// If the input is a glob then the output is a directory.
   145  						_, name := filepath.Split(file.Name)
   146  						newName = filepath.Join(output, name)
   147  					} else {
   148  						// Otherwise it is a file.
   149  						newName = output
   150  					}
   151  				}
   152  				includeMatches = append(includeMatches, pair{file, newName})
   153  			}
   154  		}
   155  
   156  		sortMatches(includeMatches)
   157  		matches = append(matches, includeMatches...)
   158  	}
   159  
   160  	if len(includes) == 0 {
   161  		// implicitly match everything
   162  		for _, file := range reader.File {
   163  			matches = append(matches, pair{file, file.Name})
   164  		}
   165  		sortMatches(matches)
   166  	}
   167  
   168  	var matchesAfterExcludes []pair
   169  	seen := make(map[string]*zip.File)
   170  
   171  	for _, match := range matches {
   172  		// Filter out matches whose original file name matches an exclude filter
   173  		excluded := false
   174  		for _, exclude := range excludes {
   175  			if excludeMatch, err := pathtools.Match(exclude, match.File.Name); err != nil {
   176  				return err
   177  			} else if excludeMatch {
   178  				excluded = true
   179  				break
   180  			}
   181  		}
   182  
   183  		if excluded {
   184  			continue
   185  		}
   186  
   187  		// Check for duplicate output names, ignoring ones that come from the same input zip entry.
   188  		if prev, exists := seen[match.newName]; exists {
   189  			if prev != match.File {
   190  				return fmt.Errorf("multiple entries for %q with different contents", match.newName)
   191  			}
   192  			continue
   193  		}
   194  		seen[match.newName] = match.File
   195  
   196  		matchesAfterExcludes = append(matchesAfterExcludes, match)
   197  	}
   198  
   199  	for _, match := range matchesAfterExcludes {
   200  		if setTime {
   201  			match.File.SetModTime(staticTime)
   202  		}
   203  		if err := writer.CopyFrom(match.File, match.newName); err != nil {
   204  			return err
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func includeSplit(s string) (string, string) {
   212  	split := strings.SplitN(s, ":", 2)
   213  	if len(split) == 2 {
   214  		return split[0], split[1]
   215  	} else {
   216  		return split[0], ""
   217  	}
   218  }
   219  
   220  type excludeArgs []string
   221  
   222  func (e *excludeArgs) String() string {
   223  	return strings.Join(*e, " ")
   224  }
   225  
   226  func (e *excludeArgs) Set(s string) error {
   227  	*e = append(*e, s)
   228  	return nil
   229  }