-
-
Notifications
You must be signed in to change notification settings - Fork 661
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
go/tools/releaser: new tool to help with multiple aspects of releases (…
…#2904) For now, this has an "upgrade-dep" subcommand that automates updating a dependency in WORKSPACE or go/private/repositories.bzl. Upgradeable dependencies must be marked with a '# releaser:upgrade-dep org repo' directive that specifies the GitHub organization and repository. You can run 'bazel run //go/tools/releaser upgrade-dep all' to upgrade all dependencies.
- Loading branch information
Jay Conrod
authored
Jun 30, 2021
1 parent
46b4330
commit 4788714
Showing
7 changed files
with
1,167 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
load("@io_bazel_rules_go//go:def.bzl", "go_library") | ||
load("//go:def.bzl", "go_binary") | ||
|
||
go_binary( | ||
name = "releaser", | ||
embed = [":releaser_lib"], | ||
visibility = ["//visibility:public"], | ||
) | ||
|
||
go_library( | ||
name = "releaser_lib", | ||
srcs = [ | ||
"file.go", | ||
"github.go", | ||
"releaser.go", | ||
"run.go", | ||
"upgradedep.go", | ||
], | ||
importpath = "github.com/bazelbuild/rules_go/go/tools/releaser", | ||
visibility = ["//visibility:private"], | ||
deps = [ | ||
"@com_github_bazelbuild_buildtools//build:go_default_library", | ||
"@com_github_google_go_github_v36//github", | ||
"@org_golang_x_mod//semver", | ||
"@org_golang_x_oauth2//:oauth2", | ||
"@org_golang_x_sync//errgroup", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
// 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" | ||
"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 | ||
} | ||
|
||
// 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) | ||
return nil | ||
} |
Oops, something went wrong.