aboutsummaryrefslogtreecommitdiff
path: root/go/tools/releaser/file.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/releaser/file.go')
-rw-r--r--go/tools/releaser/file.go301
1 files changed, 301 insertions, 0 deletions
diff --git a/go/tools/releaser/file.go b/go/tools/releaser/file.go
new file mode 100644
index 00000000..e98fc1c1
--- /dev/null
+++ b/go/tools/releaser/file.go
@@ -0,0 +1,301 @@
+// Copyright 2021 The Bazel Authors. 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 (
+ "archive/tar"
+ "archive/zip"
+ "compress/gzip"
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "sync"
+)
+
+var repoRootState = struct {
+ once sync.Once
+ dir string
+ err error
+}{}
+
+// repoRoot returns the workspace root directory. If this program was invoked
+// with 'bazel run', repoRoot returns the BUILD_WORKSPACE_DIRECTORY environment
+// variable. Otherwise, repoRoot walks up the directory tree and finds a
+// WORKSPACE file.
+func repoRoot() (string, error) {
+ repoRootState.once.Do(func() {
+ if wsDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); wsDir != "" {
+ repoRootState.dir = wsDir
+ return
+ }
+ dir, err := os.Getwd()
+ if err != nil {
+ repoRootState.err = err
+ return
+ }
+ for {
+ _, err := os.Stat(filepath.Join(dir, "WORKSPACE"))
+ if err == nil {
+ repoRootState.dir = dir
+ return
+ }
+ if err != os.ErrNotExist {
+ repoRootState.err = err
+ return
+ }
+ parent := filepath.Dir(dir)
+ if parent == dir {
+ repoRootState.err = errors.New("could not find workspace directory")
+ return
+ }
+ dir = parent
+ }
+ })
+ return repoRootState.dir, repoRootState.err
+}
+
+// extractArchive extracts a zip or tar.gz archive opened in f, into the
+// directory dir, stripping stripPrefix from each entry before extraction.
+// name is the name of the archive, used for error reporting.
+func extractArchive(f *os.File, name, dir, stripPrefix string) (err error) {
+ if strings.HasSuffix(name, ".zip") {
+ return extractZip(f, name, dir, stripPrefix)
+ }
+ if strings.HasSuffix(name, ".tar.gz") {
+ zr, err := gzip.NewReader(f)
+ if err != nil {
+ return fmt.Errorf("extracting %s: %w", name, err)
+ }
+ defer func() {
+ if cerr := zr.Close(); err == nil && cerr != nil {
+ err = cerr
+ }
+ }()
+ return extractTar(zr, name, dir, stripPrefix)
+ }
+ return fmt.Errorf("could not determine archive format from extension: %s", name)
+}
+
+func extractZip(zf *os.File, name, dir, stripPrefix string) (err error) {
+ stripPrefix += "/"
+ fi, err := zf.Stat()
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("extracting zip %s: %w", name, err)
+ }
+ }()
+
+ zr, err := zip.NewReader(zf, fi.Size())
+ if err != nil {
+ return err
+ }
+
+ extractFile := func(f *zip.File) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("extracting %s: %w", f.Name, err)
+ }
+ }()
+ outPath, err := extractedPath(dir, stripPrefix, f.Name)
+ if err != nil {
+ return err
+ }
+ if strings.HasSuffix(f.Name, "/") {
+ return os.MkdirAll(outPath, 0777)
+ }
+ r, err := f.Open()
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ parent := filepath.Dir(outPath)
+ if err := os.MkdirAll(parent, 0777); err != nil {
+ return err
+ }
+ w, err := os.Create(outPath)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if cerr := w.Close(); err == nil && cerr != nil {
+ err = cerr
+ }
+ }()
+ _, err = io.Copy(w, r)
+ return err
+ }
+
+ for _, f := range zr.File {
+ if err := extractFile(f); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func extractTar(r io.Reader, name, dir, stripPrefix string) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("extracting tar %s: %w", name, err)
+ }
+ }()
+
+ tr := tar.NewReader(r)
+ extractFile := func(hdr *tar.Header) (err error) {
+ outPath, err := extractedPath(dir, stripPrefix, hdr.Name)
+ if err != nil {
+ return err
+ }
+ switch hdr.Typeflag {
+ case tar.TypeDir:
+ return os.MkdirAll(outPath, 0777)
+ case tar.TypeReg:
+ w, err := os.Create(outPath)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if cerr := w.Close(); err == nil && cerr != nil {
+ err = cerr
+ }
+ }()
+ _, err = io.Copy(w, tr)
+ return err
+ default:
+ return fmt.Errorf("unsupported file type %x: %q", hdr.Typeflag, hdr.Name)
+ }
+ }
+
+ stripPrefix += "/"
+ for {
+ hdr, err := tr.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return err
+ }
+ if err := extractFile(hdr); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// extractedPath returns the file path that a file in an archive should be
+// extracted to. It verifies that entryName starts with stripPrefix and does not
+// point outside dir.
+func extractedPath(dir, stripPrefix, entryName string) (string, error) {
+ if !strings.HasPrefix(entryName, stripPrefix) {
+ return "", fmt.Errorf("entry does not start with prefix %s: %q", stripPrefix, entryName)
+ }
+ entryName = entryName[len(stripPrefix):]
+ if entryName == "" {
+ return dir, nil
+ }
+ if path.IsAbs(entryName) {
+ return "", fmt.Errorf("entry has an absolute path: %q", entryName)
+ }
+ if strings.HasPrefix(entryName, "../") {
+ return "", fmt.Errorf("entry refers to something outside the archive: %q", entryName)
+ }
+ entryName = strings.TrimSuffix(entryName, "/")
+ if path.Clean(entryName) != entryName {
+ return "", fmt.Errorf("entry does not have a clean path: %q", entryName)
+ }
+ return filepath.Join(dir, entryName), nil
+}
+
+// copyDir recursively copies a directory tree.
+func copyDir(toDir, fromDir string) error {
+ if err := os.MkdirAll(toDir, 0777); err != nil {
+ return err
+ }
+ return filepath.Walk(fromDir, func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ rel, _ := filepath.Rel(fromDir, path)
+ if rel == "." {
+ return nil
+ }
+ outPath := filepath.Join(toDir, rel)
+ if fi.IsDir() {
+ return os.Mkdir(outPath, 0777)
+ } else {
+ return copyFile(outPath, path)
+ }
+ })
+}
+
+func copyFile(toFile, fromFile string) (err error) {
+ r, err := os.Open(fromFile)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ w, err := os.Create(toFile)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if cerr := w.Close(); err == nil && cerr != nil {
+ err = cerr
+ }
+ }()
+ _, err = io.Copy(w, r)
+ return err
+}
+
+func sha256SumFile(name string) (string, error) {
+ r, err := os.Open(name)
+ if err != nil {
+ return "", err
+ }
+ defer r.Close()
+ h := sha256.New()
+ if _, err := io.Copy(h, r); err != nil {
+ return "", err
+ }
+ sum := h.Sum(nil)
+ return hex.EncodeToString(sum), nil
+}
+
+// copyFileToMirror uploads a file to the GCS bucket backing mirror.bazel.build.
+// gsutil must be installed, and the user must be authenticated with
+// 'gcloud auth login' and be allowed to write files to the bucket.
+func copyFileToMirror(ctx context.Context, path, fileName string) (err error) {
+ dest := "gs://bazel-mirror/" + path
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("copying file %s to %s: %w", fileName, dest, err)
+ }
+ }()
+
+ // This function shells out to gsutil instead of using
+ // cloud.google.com/go/storage because that package has a million
+ // dependencies.
+ return runForError(ctx, ".", "gsutil", "cp", "-n", fileName, dest)
+}