Jar always puts default MANIFEST.MF files in if none was specified. Copying that behavior in soong_zip causes problems with merge_zips, because it ends up taking the default manifest from the classes.jar instead of the user's manifest from res.jar. We don't want the user's manifest in the classes.jar, otherwise a change to the manifest will cause all the class files to rebuild. Instead, move the manifest insertion to the final merge_zips stage. Test: m -j checkbuild Change-Id: Id6376961dbaf743c2fb92843f9bdf2e44b963be0
324 lines
7.5 KiB
Go
324 lines
7.5 KiB
Go
// Copyright 2017 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"android/soong/jar"
|
|
"android/soong/third_party/zip"
|
|
)
|
|
|
|
type stripDir struct{}
|
|
|
|
func (s *stripDir) String() string {
|
|
return `""`
|
|
}
|
|
|
|
func (s *stripDir) Set(dir string) error {
|
|
stripDirs = append(stripDirs, filepath.Clean(dir))
|
|
|
|
return nil
|
|
}
|
|
|
|
type zipToNotStrip struct{}
|
|
|
|
func (s *zipToNotStrip) String() string {
|
|
return `""`
|
|
}
|
|
|
|
func (s *zipToNotStrip) Set(zip_path string) error {
|
|
zipsToNotStrip[zip_path] = true
|
|
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
sortEntries = flag.Bool("s", false, "sort entries (defaults to the order from the input zip files)")
|
|
emulateJar = flag.Bool("j", false, "sort zip entries using jar ordering (META-INF first)")
|
|
stripDirs []string
|
|
zipsToNotStrip = make(map[string]bool)
|
|
stripDirEntries = flag.Bool("D", false, "strip directory entries from the output zip file")
|
|
manifest = flag.String("m", "", "manifest file to insert in jar")
|
|
)
|
|
|
|
func init() {
|
|
flag.Var(&stripDir{}, "stripDir", "the prefix of file path to be excluded from the output zip")
|
|
flag.Var(&zipToNotStrip{}, "zipToNotStrip", "the input zip file which is not applicable for stripping")
|
|
}
|
|
|
|
func main() {
|
|
flag.Usage = func() {
|
|
fmt.Fprintln(os.Stderr, "usage: merge_zips [-jsD] [-m manifest] output [inputs...]")
|
|
flag.PrintDefaults()
|
|
}
|
|
|
|
// parse args
|
|
flag.Parse()
|
|
args := flag.Args()
|
|
if len(args) < 2 {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
outputPath := args[0]
|
|
inputs := args[1:]
|
|
|
|
log.SetFlags(log.Lshortfile)
|
|
|
|
// make writer
|
|
output, err := os.Create(outputPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer output.Close()
|
|
writer := zip.NewWriter(output)
|
|
defer func() {
|
|
err := writer.Close()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
// make readers
|
|
readers := []namedZipReader{}
|
|
for _, input := range inputs {
|
|
reader, err := zip.OpenReader(input)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer reader.Close()
|
|
namedReader := namedZipReader{path: input, reader: reader}
|
|
readers = append(readers, namedReader)
|
|
}
|
|
|
|
if *manifest != "" && !*emulateJar {
|
|
log.Fatal(errors.New("must specify -j when specifying a manifest via -m"))
|
|
}
|
|
|
|
// do merge
|
|
err = mergeZips(readers, writer, *manifest, *sortEntries, *emulateJar, *stripDirEntries)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// a namedZipReader reads a .zip file and can say which file it's reading
|
|
type namedZipReader struct {
|
|
path string
|
|
reader *zip.ReadCloser
|
|
}
|
|
|
|
// a zipEntryPath refers to a file contained in a zip
|
|
type zipEntryPath struct {
|
|
zipName string
|
|
entryName string
|
|
}
|
|
|
|
func (p zipEntryPath) String() string {
|
|
return p.zipName + "/" + p.entryName
|
|
}
|
|
|
|
// a zipEntry is a zipSource that pulls its content from another zip
|
|
type zipEntry struct {
|
|
path zipEntryPath
|
|
content *zip.File
|
|
}
|
|
|
|
func (ze zipEntry) String() string {
|
|
return ze.path.String()
|
|
}
|
|
|
|
func (ze zipEntry) IsDir() bool {
|
|
return ze.content.FileInfo().IsDir()
|
|
}
|
|
|
|
func (ze zipEntry) CRC32() uint32 {
|
|
return ze.content.FileHeader.CRC32
|
|
}
|
|
|
|
func (ze zipEntry) WriteToZip(dest string, zw *zip.Writer) error {
|
|
return zw.CopyFrom(ze.content, dest)
|
|
}
|
|
|
|
// a bufferEntry is a zipSource that pulls its content from a []byte
|
|
type bufferEntry struct {
|
|
fh *zip.FileHeader
|
|
content []byte
|
|
}
|
|
|
|
func (be bufferEntry) String() string {
|
|
return "internal buffer"
|
|
}
|
|
|
|
func (be bufferEntry) IsDir() bool {
|
|
return be.fh.FileInfo().IsDir()
|
|
}
|
|
|
|
func (be bufferEntry) CRC32() uint32 {
|
|
return crc32.ChecksumIEEE(be.content)
|
|
}
|
|
|
|
func (be bufferEntry) WriteToZip(dest string, zw *zip.Writer) error {
|
|
w, err := zw.CreateHeader(be.fh)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !be.IsDir() {
|
|
_, err = w.Write(be.content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type zipSource interface {
|
|
String() string
|
|
IsDir() bool
|
|
CRC32() uint32
|
|
WriteToZip(dest string, zw *zip.Writer) error
|
|
}
|
|
|
|
// a fileMapping specifies to copy a zip entry from one place to another
|
|
type fileMapping struct {
|
|
dest string
|
|
source zipSource
|
|
}
|
|
|
|
func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest string,
|
|
sortEntries, emulateJar, stripDirEntries bool) error {
|
|
|
|
sourceByDest := make(map[string]zipSource, 0)
|
|
orderedMappings := []fileMapping{}
|
|
|
|
// if dest already exists returns a non-null zipSource for the existing source
|
|
addMapping := func(dest string, source zipSource) zipSource {
|
|
mapKey := filepath.Clean(dest)
|
|
if existingSource, exists := sourceByDest[mapKey]; exists {
|
|
return existingSource
|
|
}
|
|
|
|
sourceByDest[mapKey] = source
|
|
orderedMappings = append(orderedMappings, fileMapping{source: source, dest: dest})
|
|
return nil
|
|
}
|
|
|
|
if manifest != "" {
|
|
if !stripDirEntries {
|
|
dirHeader := jar.MetaDirFileHeader()
|
|
dirSource := bufferEntry{dirHeader, nil}
|
|
addMapping(jar.MetaDir, dirSource)
|
|
}
|
|
|
|
fh, buf, err := jar.ManifestFileContents(manifest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fileSource := bufferEntry{fh, buf}
|
|
addMapping(jar.ManifestFile, fileSource)
|
|
}
|
|
|
|
for _, namedReader := range readers {
|
|
_, skipStripThisZip := zipsToNotStrip[namedReader.path]
|
|
FileLoop:
|
|
for _, file := range namedReader.reader.File {
|
|
if !skipStripThisZip {
|
|
for _, dir := range stripDirs {
|
|
if strings.HasPrefix(file.Name, dir+"/") {
|
|
if emulateJar {
|
|
if file.Name != jar.MetaDir && file.Name != jar.ManifestFile {
|
|
continue FileLoop
|
|
}
|
|
} else {
|
|
continue FileLoop
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if stripDirEntries && file.FileInfo().IsDir() {
|
|
continue
|
|
}
|
|
|
|
// check for other files or directories destined for the same path
|
|
dest := file.Name
|
|
|
|
// make a new entry to add
|
|
source := zipEntry{path: zipEntryPath{zipName: namedReader.path, entryName: file.Name}, content: file}
|
|
|
|
if existingSource := addMapping(dest, source); existingSource != nil {
|
|
// handle duplicates
|
|
if existingSource.IsDir() != source.IsDir() {
|
|
return fmt.Errorf("Directory/file mismatch at %v from %v and %v\n",
|
|
dest, existingSource, source)
|
|
}
|
|
if emulateJar &&
|
|
file.Name == jar.ManifestFile || file.Name == jar.ModuleInfoClass {
|
|
// Skip manifest and module info files that are not from the first input file
|
|
continue
|
|
}
|
|
if !source.IsDir() {
|
|
if emulateJar {
|
|
if existingSource.CRC32() != source.CRC32() {
|
|
fmt.Fprintf(os.Stdout, "WARNING: Duplicate path %v found in %v and %v\n",
|
|
dest, existingSource, source)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("Duplicate path %v found in %v and %v\n",
|
|
dest, existingSource, source)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if emulateJar {
|
|
jarSort(orderedMappings)
|
|
} else if sortEntries {
|
|
alphanumericSort(orderedMappings)
|
|
}
|
|
|
|
for _, entry := range orderedMappings {
|
|
if err := entry.source.WriteToZip(entry.dest, writer); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func jarSort(files []fileMapping) {
|
|
sort.SliceStable(files, func(i, j int) bool {
|
|
return jar.EntryNamesLess(files[i].dest, files[j].dest)
|
|
})
|
|
}
|
|
|
|
func alphanumericSort(files []fileMapping) {
|
|
sort.SliceStable(files, func(i, j int) bool {
|
|
return files[i].dest < files[j].dest
|
|
})
|
|
}
|