xref: /aosp_15_r20/build/soong/cmd/zipsync/zipsync.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2018 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
15package main
16
17import (
18	"archive/zip"
19	"flag"
20	"fmt"
21	"io"
22	"io/ioutil"
23	"log"
24	"os"
25	"path/filepath"
26	"strings"
27)
28
29var (
30	outputDir  = flag.String("d", "", "output dir")
31	outputFile = flag.String("l", "", "output list file")
32	zipPrefix  = flag.String("zip-prefix", "", "optional prefix within the zip file to extract, stripping the prefix")
33	filter     multiFlag
34)
35
36func init() {
37	flag.Var(&filter, "f", "optional filter pattern")
38}
39
40func must(err error) {
41	if err != nil {
42		log.Fatal(err)
43	}
44}
45
46func writeFile(filename string, in io.Reader, perm os.FileMode) error {
47	out, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
48	if err != nil {
49		return err
50	}
51	_, err = io.Copy(out, in)
52	if err != nil {
53		out.Close()
54		return err
55	}
56
57	return out.Close()
58}
59
60func writeSymlink(filename string, in io.Reader) error {
61	b, err := ioutil.ReadAll(in)
62	if err != nil {
63		return err
64	}
65	dest := string(b)
66	err = os.Symlink(dest, filename)
67	return err
68}
69
70func main() {
71	flag.Usage = func() {
72		fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...")
73		flag.PrintDefaults()
74	}
75
76	flag.Parse()
77
78	if *outputDir == "" {
79		flag.Usage()
80		os.Exit(1)
81	}
82
83	inputs := flag.Args()
84
85	// For now, just wipe the output directory and replace its contents with the zip files
86	// Eventually this could only modify the directory contents as necessary to bring it up
87	// to date with the zip files.
88	must(os.RemoveAll(*outputDir))
89
90	must(os.MkdirAll(*outputDir, 0777))
91
92	var files []string
93	seen := make(map[string]string)
94
95	if *zipPrefix != "" {
96		*zipPrefix = filepath.Clean(*zipPrefix) + "/"
97	}
98
99	for _, input := range inputs {
100		reader, err := zip.OpenReader(input)
101		if err != nil {
102			log.Fatal(err)
103		}
104		defer reader.Close()
105
106		for _, f := range reader.File {
107			name := f.Name
108			if *zipPrefix != "" {
109				if !strings.HasPrefix(name, *zipPrefix) {
110					continue
111				}
112				name = strings.TrimPrefix(name, *zipPrefix)
113			}
114
115			if filter != nil {
116				if match, err := filter.Match(filepath.Base(name)); err != nil {
117					log.Fatal(err)
118				} else if !match {
119					continue
120				}
121			}
122
123			if filepath.IsAbs(name) {
124				log.Fatalf("%q in %q is an absolute path", name, input)
125			}
126
127			if prev, exists := seen[name]; exists {
128				log.Fatalf("%q found in both %q and %q", name, prev, input)
129			}
130			seen[name] = input
131
132			filename := filepath.Join(*outputDir, name)
133			if f.FileInfo().IsDir() {
134				must(os.MkdirAll(filename, 0777))
135			} else {
136				must(os.MkdirAll(filepath.Dir(filename), 0777))
137				in, err := f.Open()
138				if err != nil {
139					log.Fatal(err)
140				}
141				if f.FileInfo().Mode()&os.ModeSymlink != 0 {
142					must(writeSymlink(filename, in))
143				} else {
144					must(writeFile(filename, in, f.FileInfo().Mode()))
145				}
146				in.Close()
147				files = append(files, filename)
148			}
149		}
150	}
151
152	if *outputFile != "" {
153		data := strings.Join(files, "\n")
154		if len(files) > 0 {
155			data += "\n"
156		}
157		must(ioutil.WriteFile(*outputFile, []byte(data), 0666))
158	}
159}
160
161type multiFlag []string
162
163func (m *multiFlag) String() string {
164	return strings.Join(*m, " ")
165}
166
167func (m *multiFlag) Set(s string) error {
168	*m = append(*m, s)
169	return nil
170}
171
172func (m *multiFlag) Match(s string) (bool, error) {
173	if m == nil {
174		return false, nil
175	}
176	for _, f := range *m {
177		if match, err := filepath.Match(f, s); err != nil {
178			return false, err
179		} else if match {
180			return true, nil
181		}
182	}
183	return false, nil
184}
185