in go/tools/releaser/upgradedep.go [243:490]
func upgradeDepDecl(ctx context.Context, gh *githubClient, workDir, name string, call *bzl.CallExpr, uploadToMirror bool) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("upgrading %s: %w", name, err)
}
}()
// Find a '# releaser:upgrade-dep org repo' comment. We could probably
// figure this out from URLs but this also serves to mark a dependency as
// being automatically upgradeable.
orgName, repoName, err := parseUpgradeDepDirective(call)
if err != nil {
return err
}
// Find attributes we'll need to read or write. We'll modify these directly
// in the AST. Nothing else should read or write them while we're working.
attrs := map[string]*bzl.Expr{
"patches": nil,
"sha256": nil,
"strip_prefix": nil,
"urls": nil,
}
var urlsKwarg *bzl.AssignExpr
for _, arg := range call.List {
kwarg, ok := arg.(*bzl.AssignExpr)
if !ok {
continue
}
key := kwarg.LHS.(*bzl.Ident) // required by parser
if _, ok := attrs[key.Name]; ok {
attrs[key.Name] = &kwarg.RHS
}
if key.Name == "urls" {
urlsKwarg = kwarg
}
}
for key := range attrs {
if key == "patches" {
// Don't add optional attributes.
continue
}
if attrs[key] == nil {
kwarg := &bzl.AssignExpr{LHS: &bzl.Ident{Name: key}, Op: "="}
call.List = append(call.List, kwarg)
attrs[key] = &kwarg.RHS
}
}
// Find the highest tag in semver order, ignoring whether the version has a
// leading "v" or not. If there are no tags, find the commit at the tip of the
// default branch.
tags, err := gh.listTags(ctx, orgName, repoName)
if err != nil {
return err
}
vname := func(name string) string {
if !strings.HasPrefix(name, "v") {
return "v" + name
}
return name
}
w := 0
for r := range tags {
name := vname(*tags[r].Name)
if name != semver.Canonical(name) {
continue
}
tags[w] = tags[r]
w++
}
tags = tags[:w]
var highestTag *github.RepositoryTag
var highestVname string
for _, tag := range tags {
name := vname(*tag.Name)
if highestTag == nil || semver.Compare(name, highestVname) > 0 {
highestTag = tag
highestVname = name
}
}
var ghURL, stripPrefix, urlComment string
date := time.Now().Format("2006-01-02")
if highestTag != nil {
// If the tag is part of a release, check whether there is a release
// artifact we should use.
release, _, err := gh.Repositories.GetReleaseByTag(ctx, orgName, repoName, *highestTag.Name)
if err == nil {
wantNames := []string{
fmt.Sprintf("%s-%s.tar.gz", repoName, *highestTag.Name),
fmt.Sprintf("%s-%s.zip", repoName, *highestTag.Name),
}
AssetName:
for _, asset := range release.Assets {
for _, wantName := range wantNames {
if *asset.Name == wantName {
ghURL = asset.GetBrowserDownloadURL()
stripPrefix = "" // may not always be correct
break AssetName
}
}
}
}
if ghURL == "" {
ghURL = fmt.Sprintf("https://github.com/%s/%s/archive/%s.zip", orgName, repoName, *highestTag.Name)
stripPrefix = repoName + "-" + strings.TrimPrefix(*highestTag.Name, "v")
}
urlComment = fmt.Sprintf("%s, latest as of %s", *highestTag.Name, date)
} else {
repo, _, err := gh.Repositories.Get(ctx, orgName, repoName)
if err != nil {
return err
}
defaultBranchName := "main"
if repo.DefaultBranch != nil {
defaultBranchName = *repo.DefaultBranch
}
branch, _, err := gh.Repositories.GetBranch(ctx, orgName, repoName, defaultBranchName)
if err != nil {
return err
}
ghURL = fmt.Sprintf("https://github.com/%s/%s/archive/%s.zip", orgName, repoName, *branch.Commit.SHA)
stripPrefix = repoName + "-" + *branch.Commit.SHA
urlComment = fmt.Sprintf("%s, as of %s", defaultBranchName, date)
}
ghURLWithoutScheme := ghURL[len("https://"):]
mirrorURL := "https://mirror.bazel.build/" + ghURLWithoutScheme
// Download the archive and find the SHA.
archiveFile, err := os.CreateTemp("", "")
if err != nil {
return err
}
defer func() {
archiveFile.Close()
if rerr := os.Remove(archiveFile.Name()); err == nil && rerr != nil {
err = rerr
}
}()
resp, err := http.Get(ghURL)
if err != nil {
return err
}
hw := sha256.New()
mw := io.MultiWriter(hw, archiveFile)
if _, err := io.Copy(mw, resp.Body); err != nil {
resp.Body.Close()
return err
}
if err := resp.Body.Close(); err != nil {
return err
}
sha256Sum := hex.EncodeToString(hw.Sum(nil))
if _, err := archiveFile.Seek(0, io.SeekStart); err != nil {
return err
}
// Upload the archive to mirror.bazel.build.
if uploadToMirror {
if err := copyFileToMirror(ctx, ghURLWithoutScheme, archiveFile.Name()); err != nil {
return err
}
}
// If there are patches, re-apply or re-generate them.
// Patch labels may have "# releaser:patch-cmd name args..." directives
// that instruct this program to generate the patch by running a commnad
// in the directory. If there is no such directive, we apply the old patch
// using "patch". In either case, we'll generate a new patch with "diff".
// We'll scrub the timestamps to avoid excessive diffs in the PR that
// updates dependencies.
rootDir, err := repoRoot()
if err != nil {
return err
}
if attrs["patches"] != nil {
if err != nil {
return err
}
patchDir := filepath.Join(workDir, name, "a")
if err := extractArchive(archiveFile, path.Base(ghURL), patchDir, stripPrefix); err != nil {
return err
}
patchesList, ok := (*attrs["patches"]).(*bzl.ListExpr)
if !ok {
return fmt.Errorf("\"patches\" attribute is not a list")
}
for patchIndex, patchLabelExpr := range patchesList.List {
patchLabelValue, comments, err := parsePatchesItem(patchLabelExpr)
if err != nil {
return fmt.Errorf("parsing expr %#v : %w", patchLabelExpr, err)
}
if !strings.HasPrefix(patchLabelValue, "//third_party:") {
return fmt.Errorf("patch does not start with '//third_party:': %q", patchLabelValue)
}
patchName := patchLabelValue[len("//third_party:"):]
patchPath := filepath.Join(rootDir, "third_party", patchName)
prevDir := filepath.Join(workDir, name, string('a'+patchIndex))
patchDir := filepath.Join(workDir, name, string('a'+patchIndex+1))
var patchCmd []string
for _, c := range comments.Before {
words := strings.Fields(strings.TrimPrefix(c.Token, "#"))
if len(words) > 0 && words[0] == "releaser:patch-cmd" {
patchCmd = words[1:]
break
}
}
if err := copyDir(patchDir, prevDir); err != nil {
return err
}
if patchCmd == nil {
if err := runForError(ctx, patchDir, "patch", "-Np1", "-i", patchPath); err != nil {
return err
}
} else {
if err := runForError(ctx, patchDir, patchCmd[0], patchCmd[1:]...); err != nil {
return err
}
}
patch, _ := runForOutput(ctx, filepath.Join(workDir, name), "diff", "-urN", string('a'+patchIndex), string('a'+patchIndex+1))
patch = sanitizePatch(patch)
if err := os.WriteFile(patchPath, patch, 0666); err != nil {
return err
}
}
}
// Update the attributes.
*attrs["sha256"] = &bzl.StringExpr{Value: sha256Sum}
*attrs["strip_prefix"] = &bzl.StringExpr{Value: stripPrefix}
*attrs["urls"] = &bzl.ListExpr{
List: []bzl.Expr{
&bzl.StringExpr{Value: mirrorURL},
&bzl.StringExpr{Value: ghURL},
},
ForceMultiLine: true,
}
urlsKwarg.Before = []bzl.Comment{{Token: "# " + urlComment}}
return nil
}