xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/releaser/file.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1*9bb1b549SSpandan Das// Copyright 2021 The Bazel Authors. All rights reserved.
2*9bb1b549SSpandan Das//
3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License");
4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License.
5*9bb1b549SSpandan Das// You may obtain a copy of the License at
6*9bb1b549SSpandan Das//
7*9bb1b549SSpandan Das//    http://www.apache.org/licenses/LICENSE-2.0
8*9bb1b549SSpandan Das//
9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software
10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS,
11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and
13*9bb1b549SSpandan Das// limitations under the License.
14*9bb1b549SSpandan Das
15*9bb1b549SSpandan Daspackage main
16*9bb1b549SSpandan Das
17*9bb1b549SSpandan Dasimport (
18*9bb1b549SSpandan Das	"archive/tar"
19*9bb1b549SSpandan Das	"archive/zip"
20*9bb1b549SSpandan Das	"compress/gzip"
21*9bb1b549SSpandan Das	"context"
22*9bb1b549SSpandan Das	"crypto/sha256"
23*9bb1b549SSpandan Das	"encoding/hex"
24*9bb1b549SSpandan Das	"errors"
25*9bb1b549SSpandan Das	"fmt"
26*9bb1b549SSpandan Das	"io"
27*9bb1b549SSpandan Das	"os"
28*9bb1b549SSpandan Das	"path"
29*9bb1b549SSpandan Das	"path/filepath"
30*9bb1b549SSpandan Das	"strings"
31*9bb1b549SSpandan Das	"sync"
32*9bb1b549SSpandan Das)
33*9bb1b549SSpandan Das
34*9bb1b549SSpandan Dasvar repoRootState = struct {
35*9bb1b549SSpandan Das	once sync.Once
36*9bb1b549SSpandan Das	dir  string
37*9bb1b549SSpandan Das	err  error
38*9bb1b549SSpandan Das}{}
39*9bb1b549SSpandan Das
40*9bb1b549SSpandan Das// repoRoot returns the workspace root directory. If this program was invoked
41*9bb1b549SSpandan Das// with 'bazel run', repoRoot returns the BUILD_WORKSPACE_DIRECTORY environment
42*9bb1b549SSpandan Das// variable. Otherwise, repoRoot walks up the directory tree and finds a
43*9bb1b549SSpandan Das// WORKSPACE file.
44*9bb1b549SSpandan Dasfunc repoRoot() (string, error) {
45*9bb1b549SSpandan Das	repoRootState.once.Do(func() {
46*9bb1b549SSpandan Das		if wsDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); wsDir != "" {
47*9bb1b549SSpandan Das			repoRootState.dir = wsDir
48*9bb1b549SSpandan Das			return
49*9bb1b549SSpandan Das		}
50*9bb1b549SSpandan Das		dir, err := os.Getwd()
51*9bb1b549SSpandan Das		if err != nil {
52*9bb1b549SSpandan Das			repoRootState.err = err
53*9bb1b549SSpandan Das			return
54*9bb1b549SSpandan Das		}
55*9bb1b549SSpandan Das		for {
56*9bb1b549SSpandan Das			_, err := os.Stat(filepath.Join(dir, "WORKSPACE"))
57*9bb1b549SSpandan Das			if err == nil {
58*9bb1b549SSpandan Das				repoRootState.dir = dir
59*9bb1b549SSpandan Das				return
60*9bb1b549SSpandan Das			}
61*9bb1b549SSpandan Das			if err != os.ErrNotExist {
62*9bb1b549SSpandan Das				repoRootState.err = err
63*9bb1b549SSpandan Das				return
64*9bb1b549SSpandan Das			}
65*9bb1b549SSpandan Das			parent := filepath.Dir(dir)
66*9bb1b549SSpandan Das			if parent == dir {
67*9bb1b549SSpandan Das				repoRootState.err = errors.New("could not find workspace directory")
68*9bb1b549SSpandan Das				return
69*9bb1b549SSpandan Das			}
70*9bb1b549SSpandan Das			dir = parent
71*9bb1b549SSpandan Das		}
72*9bb1b549SSpandan Das	})
73*9bb1b549SSpandan Das	return repoRootState.dir, repoRootState.err
74*9bb1b549SSpandan Das}
75*9bb1b549SSpandan Das
76*9bb1b549SSpandan Das// extractArchive extracts a zip or tar.gz archive opened in f, into the
77*9bb1b549SSpandan Das// directory dir, stripping stripPrefix from each entry before extraction.
78*9bb1b549SSpandan Das// name is the name of the archive, used for error reporting.
79*9bb1b549SSpandan Dasfunc extractArchive(f *os.File, name, dir, stripPrefix string) (err error) {
80*9bb1b549SSpandan Das	if strings.HasSuffix(name, ".zip") {
81*9bb1b549SSpandan Das		return extractZip(f, name, dir, stripPrefix)
82*9bb1b549SSpandan Das	}
83*9bb1b549SSpandan Das	if strings.HasSuffix(name, ".tar.gz") {
84*9bb1b549SSpandan Das		zr, err := gzip.NewReader(f)
85*9bb1b549SSpandan Das		if err != nil {
86*9bb1b549SSpandan Das			return fmt.Errorf("extracting %s: %w", name, err)
87*9bb1b549SSpandan Das		}
88*9bb1b549SSpandan Das		defer func() {
89*9bb1b549SSpandan Das			if cerr := zr.Close(); err == nil && cerr != nil {
90*9bb1b549SSpandan Das				err = cerr
91*9bb1b549SSpandan Das			}
92*9bb1b549SSpandan Das		}()
93*9bb1b549SSpandan Das		return extractTar(zr, name, dir, stripPrefix)
94*9bb1b549SSpandan Das	}
95*9bb1b549SSpandan Das	return fmt.Errorf("could not determine archive format from extension: %s", name)
96*9bb1b549SSpandan Das}
97*9bb1b549SSpandan Das
98*9bb1b549SSpandan Dasfunc extractZip(zf *os.File, name, dir, stripPrefix string) (err error) {
99*9bb1b549SSpandan Das	stripPrefix += "/"
100*9bb1b549SSpandan Das	fi, err := zf.Stat()
101*9bb1b549SSpandan Das	if err != nil {
102*9bb1b549SSpandan Das		return err
103*9bb1b549SSpandan Das	}
104*9bb1b549SSpandan Das	defer func() {
105*9bb1b549SSpandan Das		if err != nil {
106*9bb1b549SSpandan Das			err = fmt.Errorf("extracting zip %s: %w", name, err)
107*9bb1b549SSpandan Das		}
108*9bb1b549SSpandan Das	}()
109*9bb1b549SSpandan Das
110*9bb1b549SSpandan Das	zr, err := zip.NewReader(zf, fi.Size())
111*9bb1b549SSpandan Das	if err != nil {
112*9bb1b549SSpandan Das		return err
113*9bb1b549SSpandan Das	}
114*9bb1b549SSpandan Das
115*9bb1b549SSpandan Das	extractFile := func(f *zip.File) (err error) {
116*9bb1b549SSpandan Das		defer func() {
117*9bb1b549SSpandan Das			if err != nil {
118*9bb1b549SSpandan Das				err = fmt.Errorf("extracting %s: %w", f.Name, err)
119*9bb1b549SSpandan Das			}
120*9bb1b549SSpandan Das		}()
121*9bb1b549SSpandan Das		outPath, err := extractedPath(dir, stripPrefix, f.Name)
122*9bb1b549SSpandan Das		if err != nil {
123*9bb1b549SSpandan Das			return err
124*9bb1b549SSpandan Das		}
125*9bb1b549SSpandan Das		if strings.HasSuffix(f.Name, "/") {
126*9bb1b549SSpandan Das			return os.MkdirAll(outPath, 0777)
127*9bb1b549SSpandan Das		}
128*9bb1b549SSpandan Das		r, err := f.Open()
129*9bb1b549SSpandan Das		if err != nil {
130*9bb1b549SSpandan Das			return err
131*9bb1b549SSpandan Das		}
132*9bb1b549SSpandan Das		defer r.Close()
133*9bb1b549SSpandan Das		parent := filepath.Dir(outPath)
134*9bb1b549SSpandan Das		if err := os.MkdirAll(parent, 0777); err != nil {
135*9bb1b549SSpandan Das			return err
136*9bb1b549SSpandan Das		}
137*9bb1b549SSpandan Das		w, err := os.Create(outPath)
138*9bb1b549SSpandan Das		if err != nil {
139*9bb1b549SSpandan Das			return err
140*9bb1b549SSpandan Das		}
141*9bb1b549SSpandan Das		defer func() {
142*9bb1b549SSpandan Das			if cerr := w.Close(); err == nil && cerr != nil {
143*9bb1b549SSpandan Das				err = cerr
144*9bb1b549SSpandan Das			}
145*9bb1b549SSpandan Das		}()
146*9bb1b549SSpandan Das		_, err = io.Copy(w, r)
147*9bb1b549SSpandan Das		return err
148*9bb1b549SSpandan Das	}
149*9bb1b549SSpandan Das
150*9bb1b549SSpandan Das	for _, f := range zr.File {
151*9bb1b549SSpandan Das		if err := extractFile(f); err != nil {
152*9bb1b549SSpandan Das			return err
153*9bb1b549SSpandan Das		}
154*9bb1b549SSpandan Das	}
155*9bb1b549SSpandan Das
156*9bb1b549SSpandan Das	return nil
157*9bb1b549SSpandan Das}
158*9bb1b549SSpandan Das
159*9bb1b549SSpandan Dasfunc extractTar(r io.Reader, name, dir, stripPrefix string) (err error) {
160*9bb1b549SSpandan Das	defer func() {
161*9bb1b549SSpandan Das		if err != nil {
162*9bb1b549SSpandan Das			err = fmt.Errorf("extracting tar %s: %w", name, err)
163*9bb1b549SSpandan Das		}
164*9bb1b549SSpandan Das	}()
165*9bb1b549SSpandan Das
166*9bb1b549SSpandan Das	tr := tar.NewReader(r)
167*9bb1b549SSpandan Das	extractFile := func(hdr *tar.Header) (err error) {
168*9bb1b549SSpandan Das		outPath, err := extractedPath(dir, stripPrefix, hdr.Name)
169*9bb1b549SSpandan Das		if err != nil {
170*9bb1b549SSpandan Das			return err
171*9bb1b549SSpandan Das		}
172*9bb1b549SSpandan Das		switch hdr.Typeflag {
173*9bb1b549SSpandan Das		case tar.TypeDir:
174*9bb1b549SSpandan Das			return os.MkdirAll(outPath, 0777)
175*9bb1b549SSpandan Das		case tar.TypeReg:
176*9bb1b549SSpandan Das			w, err := os.Create(outPath)
177*9bb1b549SSpandan Das			if err != nil {
178*9bb1b549SSpandan Das				return err
179*9bb1b549SSpandan Das			}
180*9bb1b549SSpandan Das			defer func() {
181*9bb1b549SSpandan Das				if cerr := w.Close(); err == nil && cerr != nil {
182*9bb1b549SSpandan Das					err = cerr
183*9bb1b549SSpandan Das				}
184*9bb1b549SSpandan Das			}()
185*9bb1b549SSpandan Das			_, err = io.Copy(w, tr)
186*9bb1b549SSpandan Das			return err
187*9bb1b549SSpandan Das		default:
188*9bb1b549SSpandan Das			return fmt.Errorf("unsupported file type %x: %q", hdr.Typeflag, hdr.Name)
189*9bb1b549SSpandan Das		}
190*9bb1b549SSpandan Das	}
191*9bb1b549SSpandan Das
192*9bb1b549SSpandan Das	stripPrefix += "/"
193*9bb1b549SSpandan Das	for {
194*9bb1b549SSpandan Das		hdr, err := tr.Next()
195*9bb1b549SSpandan Das		if err == io.EOF {
196*9bb1b549SSpandan Das			break
197*9bb1b549SSpandan Das		} else if err != nil {
198*9bb1b549SSpandan Das			return err
199*9bb1b549SSpandan Das		}
200*9bb1b549SSpandan Das		if err := extractFile(hdr); err != nil {
201*9bb1b549SSpandan Das			return err
202*9bb1b549SSpandan Das		}
203*9bb1b549SSpandan Das	}
204*9bb1b549SSpandan Das	return nil
205*9bb1b549SSpandan Das}
206*9bb1b549SSpandan Das
207*9bb1b549SSpandan Das// extractedPath returns the file path that a file in an archive should be
208*9bb1b549SSpandan Das// extracted to. It verifies that entryName starts with stripPrefix and does not
209*9bb1b549SSpandan Das// point outside dir.
210*9bb1b549SSpandan Dasfunc extractedPath(dir, stripPrefix, entryName string) (string, error) {
211*9bb1b549SSpandan Das	if !strings.HasPrefix(entryName, stripPrefix) {
212*9bb1b549SSpandan Das		return "", fmt.Errorf("entry does not start with prefix %s: %q", stripPrefix, entryName)
213*9bb1b549SSpandan Das	}
214*9bb1b549SSpandan Das	entryName = entryName[len(stripPrefix):]
215*9bb1b549SSpandan Das	if entryName == "" {
216*9bb1b549SSpandan Das		return dir, nil
217*9bb1b549SSpandan Das	}
218*9bb1b549SSpandan Das	if path.IsAbs(entryName) {
219*9bb1b549SSpandan Das		return "", fmt.Errorf("entry has an absolute path: %q", entryName)
220*9bb1b549SSpandan Das	}
221*9bb1b549SSpandan Das	if strings.HasPrefix(entryName, "../") {
222*9bb1b549SSpandan Das		return "", fmt.Errorf("entry refers to something outside the archive: %q", entryName)
223*9bb1b549SSpandan Das	}
224*9bb1b549SSpandan Das	entryName = strings.TrimSuffix(entryName, "/")
225*9bb1b549SSpandan Das	if path.Clean(entryName) != entryName {
226*9bb1b549SSpandan Das		return "", fmt.Errorf("entry does not have a clean path: %q", entryName)
227*9bb1b549SSpandan Das	}
228*9bb1b549SSpandan Das	return filepath.Join(dir, entryName), nil
229*9bb1b549SSpandan Das}
230*9bb1b549SSpandan Das
231*9bb1b549SSpandan Das// copyDir recursively copies a directory tree.
232*9bb1b549SSpandan Dasfunc copyDir(toDir, fromDir string) error {
233*9bb1b549SSpandan Das	if err := os.MkdirAll(toDir, 0777); err != nil {
234*9bb1b549SSpandan Das		return err
235*9bb1b549SSpandan Das	}
236*9bb1b549SSpandan Das	return filepath.Walk(fromDir, func(path string, fi os.FileInfo, err error) error {
237*9bb1b549SSpandan Das		if err != nil {
238*9bb1b549SSpandan Das			return err
239*9bb1b549SSpandan Das		}
240*9bb1b549SSpandan Das		rel, _ := filepath.Rel(fromDir, path)
241*9bb1b549SSpandan Das		if rel == "." {
242*9bb1b549SSpandan Das			return nil
243*9bb1b549SSpandan Das		}
244*9bb1b549SSpandan Das		outPath := filepath.Join(toDir, rel)
245*9bb1b549SSpandan Das		if fi.IsDir() {
246*9bb1b549SSpandan Das			return os.Mkdir(outPath, 0777)
247*9bb1b549SSpandan Das		} else {
248*9bb1b549SSpandan Das			return copyFile(outPath, path)
249*9bb1b549SSpandan Das		}
250*9bb1b549SSpandan Das	})
251*9bb1b549SSpandan Das}
252*9bb1b549SSpandan Das
253*9bb1b549SSpandan Dasfunc copyFile(toFile, fromFile string) (err error) {
254*9bb1b549SSpandan Das	r, err := os.Open(fromFile)
255*9bb1b549SSpandan Das	if err != nil {
256*9bb1b549SSpandan Das		return err
257*9bb1b549SSpandan Das	}
258*9bb1b549SSpandan Das	defer r.Close()
259*9bb1b549SSpandan Das	w, err := os.Create(toFile)
260*9bb1b549SSpandan Das	if err != nil {
261*9bb1b549SSpandan Das		return err
262*9bb1b549SSpandan Das	}
263*9bb1b549SSpandan Das	defer func() {
264*9bb1b549SSpandan Das		if cerr := w.Close(); err == nil && cerr != nil {
265*9bb1b549SSpandan Das			err = cerr
266*9bb1b549SSpandan Das		}
267*9bb1b549SSpandan Das	}()
268*9bb1b549SSpandan Das	_, err = io.Copy(w, r)
269*9bb1b549SSpandan Das	return err
270*9bb1b549SSpandan Das}
271*9bb1b549SSpandan Das
272*9bb1b549SSpandan Dasfunc sha256SumFile(name string) (string, error) {
273*9bb1b549SSpandan Das	r, err := os.Open(name)
274*9bb1b549SSpandan Das	if err != nil {
275*9bb1b549SSpandan Das		return "", err
276*9bb1b549SSpandan Das	}
277*9bb1b549SSpandan Das	defer r.Close()
278*9bb1b549SSpandan Das	h := sha256.New()
279*9bb1b549SSpandan Das	if _, err := io.Copy(h, r); err != nil {
280*9bb1b549SSpandan Das		return "", err
281*9bb1b549SSpandan Das	}
282*9bb1b549SSpandan Das	sum := h.Sum(nil)
283*9bb1b549SSpandan Das	return hex.EncodeToString(sum), nil
284*9bb1b549SSpandan Das}
285*9bb1b549SSpandan Das
286*9bb1b549SSpandan Das// copyFileToMirror uploads a file to the GCS bucket backing mirror.bazel.build.
287*9bb1b549SSpandan Das// gsutil must be installed, and the user must be authenticated with
288*9bb1b549SSpandan Das// 'gcloud auth login' and be allowed to write files to the bucket.
289*9bb1b549SSpandan Dasfunc copyFileToMirror(ctx context.Context, path, fileName string) (err error) {
290*9bb1b549SSpandan Das	dest := "gs://bazel-mirror/" + path
291*9bb1b549SSpandan Das	defer func() {
292*9bb1b549SSpandan Das		if err != nil {
293*9bb1b549SSpandan Das			err = fmt.Errorf("copying file %s to %s: %w", fileName, dest, err)
294*9bb1b549SSpandan Das		}
295*9bb1b549SSpandan Das	}()
296*9bb1b549SSpandan Das
297*9bb1b549SSpandan Das	// This function shells out to gsutil instead of using
298*9bb1b549SSpandan Das	// cloud.google.com/go/storage because that package has a million
299*9bb1b549SSpandan Das	// dependencies.
300*9bb1b549SSpandan Das	return runForError(ctx, ".", "gsutil", "cp", "-n", fileName, dest)
301*9bb1b549SSpandan Das}
302