Skip to content

Commit

Permalink
internal/task: add support for tagging a single repository
Browse files Browse the repository at this point in the history
To make one-off tagging less error-prone, run a miniature version of the
overall workflow to tag a specific repository.

Also includes a tiny bug fix for expansions with parameters.

For golang/go#48523.

Change-Id: Ic9089966aa28d20c716169b41121e2da21faf54a
Reviewed-on: https://go-review.googlesource.com/c/build/+/445955
Auto-Submit: Heschi Kreinick <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Run-TryBot: Heschi Kreinick <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
  • Loading branch information
heschi authored and gopherbot committed Oct 31, 2022
1 parent 193064a commit b041a95
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 44 deletions.
1 change: 1 addition & 0 deletions cmd/relui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ func main() {
ApproveAction: relui.ApproveActionDep(dbPool),
}
dh.RegisterDefinition("Tag x/ repos", tagTasks.NewDefinition())
dh.RegisterDefinition("Tag a single x/ repo", tagTasks.NewSingleDefinition())

var base *url.URL
if *baseURL != "" {
Expand Down
51 changes: 42 additions & 9 deletions internal/task/tagx.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,22 @@ func (x *TagXReposTasks) NewDefinition() *wf.Definition {
return wd
}

func (x *TagXReposTasks) NewSingleDefinition() *wf.Definition {
wd := wf.New()
repos := wf.Task0(wd, "Load all repositories", x.SelectRepos)
name := wf.Param(wd, wf.ParamDef[string]{Name: "Repository name", Example: "tools"})
wf.Expand2(wd, "Create single-repo plan", x.BuildSingleRepoPlan, repos, name)
return wd
}

// TagRepo contains information about a repo that can be tagged.
type TagRepo struct {
Name string // Gerrit project name, e.g. "tools".
ModPath string // Module path, e.g. "golang.org/x/tools".
Deps []string // Dependency module paths.
Compat string // The version to pass to go mod tidy -compat for this repository.
Version string // After a tagging decision has been made, the version dependencies should upgrade to.
Name string // Gerrit project name, e.g. "tools".
ModPath string // Module path, e.g. "golang.org/x/tools".
Deps []string // Dependency module paths.
Compat string // The Go version to pass to go mod tidy -compat for this repository.
StartVersion string // The version of the module when the workflow started.
Version string // After a tagging decision has been made, the version dependencies should upgrade to.
}

func (x *TagXReposTasks) SelectRepos(ctx *wf.TaskContext) ([]TagRepo, error) {
Expand Down Expand Up @@ -127,8 +136,9 @@ func (x *TagXReposTasks) readRepo(ctx *wf.TaskContext, project string) (*TagRepo
}

result := &TagRepo{
Name: project,
ModPath: mf.Module.Mod.Path,
Name: project,
ModPath: mf.Module.Mod.Path,
StartVersion: tag,
}

compatRe := regexp.MustCompile(`tagx:compat\s+([\d.]+)`)
Expand Down Expand Up @@ -251,8 +261,31 @@ func (x *TagXReposTasks) BuildPlan(wd *wf.Definition, repos []TagRepo) error {
return nil
}

// planRepo returns a Value containing the tagged repository's information, or
// nil, false if its dependencies haven't been planned yet.
func (x *TagXReposTasks) BuildSingleRepoPlan(wd *wf.Definition, repoSlice []TagRepo, name string) error {
repos := map[string]TagRepo{}
updatedRepos := map[string]wf.Value[TagRepo]{}
for _, r := range repoSlice {
repos[r.Name] = r

// Pretend that we've just tagged version that was live when we started.
r.Version = r.StartVersion
updatedRepos[r.ModPath] = wf.Const(r)
}
repo, ok := repos[name]
if !ok {
return fmt.Errorf("no repository %q", name)
}
tagged, ok := x.planRepo(wd, repo, updatedRepos)
if !ok {
return fmt.Errorf("%q doesn't have all of its dependencies (%q)", repo.Name, repo.Deps)
}
wf.Output(wd, "tagged repository", tagged)
return nil
}

// planRepo adds tasks to wf to update and tag repo. It returns a Value
// containing the tagged repository's information, or nil, false if its
// dependencies haven't been planned yet.
func (x *TagXReposTasks) planRepo(wd *wf.Definition, repo TagRepo, updated map[string]wf.Value[TagRepo]) (_ wf.Value[TagRepo], ready bool) {
var deps []wf.Value[TagRepo]
for _, repoDeps := range repo.Deps {
Expand Down
137 changes: 102 additions & 35 deletions internal/task/tagx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,48 +334,35 @@ case "$1" in
esac
`

func TestTagXRepos(t *testing.T) {
type tagXTestDeps struct {
ctx context.Context
gerrit *FakeGerrit
tagXTasks *TagXReposTasks
}

func newTagXTestDeps(t *testing.T, repos ...*FakeRepo) *tagXTestDeps {
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip("Requires bash shell scripting support.")
}

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

goServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ServeTarball("dl/go1.19.linux-amd64.tar.gz", map[string]string{
"go/bin/go": fakeGo,
}, w, r)
}))
t.Cleanup(goServer.Close)

fakeBuildlets := NewFakeBuildlets(t, "")

goRepo := NewFakeRepo(t, "go")
go1 := goRepo.Commit(map[string]string{
"main.go": "I'm the go command or something",
})
sys := NewFakeRepo(t, "sys")
sys1 := sys.Commit(map[string]string{
"go.mod": "module golang.org/x/sys\n",
"go.sum": "\n",
})
sys.Tag("v0.1.0", sys1)
sys2 := sys.Commit(map[string]string{
"main.go": "package main",
})
mod := NewFakeRepo(t, "mod")
mod1 := mod.Commit(map[string]string{
"go.mod": "module golang.org/x/mod\n",
"go.sum": "\n",
})
mod.Tag("v1.0.0", mod1)
tools := NewFakeRepo(t, "tools")
tools1 := tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\nrequire golang.org/x/mod v1.0.0\ngo 1.18 // tagx:compat 1.16\nrequire golang.org/x/sys v0.1.0\n",
"go.sum": "\n",
"gopls/go.mod": "module golang.org/x/tools/gopls\nrequire golang.org/x/mod v1.0.0\n",
"gopls/go.sum": "\n",
})
tools.Tag("v1.1.5", tools1)
fakeGerrit := NewFakeGerrit(t, goRepo, sys, mod, tools)
repos = append(repos, goRepo)

fakeBuildlets := NewFakeBuildlets(t, "")
fakeGerrit := NewFakeGerrit(t, repos...)
var builders, allOK []string
for _, b := range dashboard.Builders {
builders = append(builders, b.Name)
Expand All @@ -391,7 +378,7 @@ func TestTagXRepos(t *testing.T) {
},
}
}
for _, r := range []*FakeRepo{sys, mod, tools} {
for _, r := range repos {
if repo != "golang.org/x/"+r.name {
continue
}
Expand Down Expand Up @@ -427,39 +414,73 @@ func TestTagXRepos(t *testing.T) {
return nil
},
}
wd := tasks.NewDefinition()
return &tagXTestDeps{
ctx: ctx,
gerrit: fakeGerrit,
tagXTasks: tasks,
}
}

func TestTagXRepos(t *testing.T) {
sys := NewFakeRepo(t, "sys")
sys1 := sys.Commit(map[string]string{
"go.mod": "module golang.org/x/sys\n",
"go.sum": "\n",
})
sys.Tag("v0.1.0", sys1)
sys2 := sys.Commit(map[string]string{
"main.go": "package main",
})
mod := NewFakeRepo(t, "mod")
mod1 := mod.Commit(map[string]string{
"go.mod": "module golang.org/x/mod\n",
"go.sum": "\n",
})
mod.Tag("v1.0.0", mod1)
tools := NewFakeRepo(t, "tools")
tools1 := tools.Commit(map[string]string{
"go.mod": "module golang.org/x/tools\nrequire golang.org/x/mod v1.0.0\ngo 1.18 // tagx:compat 1.16\nrequire golang.org/x/sys v0.1.0\n",
"go.sum": "\n",
"gopls/go.mod": "module golang.org/x/tools/gopls\nrequire golang.org/x/mod v1.0.0\n",
"gopls/go.sum": "\n",
})
tools.Tag("v1.1.5", tools1)

deps := newTagXTestDeps(t, sys, mod, tools)

wd := deps.tagXTasks.NewDefinition()
w, err := workflow.Start(wd, nil)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
ctx, cancel := context.WithTimeout(deps.ctx, time.Minute)
defer cancel()
_, err = w.Run(ctx, &verboseListener{t: t})
if err != nil {
t.Fatal(err)
}

tag, err := fakeGerrit.GetTag(ctx, "sys", "v0.2.0")
tag, err := deps.gerrit.GetTag(ctx, "sys", "v0.2.0")
if err != nil {
t.Fatalf("sys should have been tagged with v0.2.0: %v", err)
}
if tag.Revision != sys2 {
t.Errorf("sys v0.2.0 = %v, want %v", tag.Revision, sys2)
}

tags, err := fakeGerrit.ListTags(ctx, "mod")
tags, err := deps.gerrit.ListTags(ctx, "mod")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tags, []string{"v1.0.0"}) {
t.Errorf("mod has tags %v, wanted only v1.0.0", tags)
}

tag, err = fakeGerrit.GetTag(ctx, "tools", "v1.2.0")
tag, err = deps.gerrit.GetTag(ctx, "tools", "v1.2.0")
if err != nil {
t.Fatalf("tools should have been tagged with v1.2.0: %v", err)
}
goMod, err := fakeGerrit.ReadFile(ctx, "tools", tag.Revision, "go.mod")
goMod, err := deps.gerrit.ReadFile(ctx, "tools", tag.Revision, "go.mod")
if err != nil {
t.Fatal(err)
}
Expand All @@ -469,7 +490,7 @@ func TestTagXRepos(t *testing.T) {
if !strings.Contains(string(goMod), "tidied!") {
t.Error("tools go.mod should be tidied")
}
goplsMod, err := fakeGerrit.ReadFile(ctx, "tools", tag.Revision, "gopls/go.mod")
goplsMod, err := deps.gerrit.ReadFile(ctx, "tools", tag.Revision, "gopls/go.mod")
if err != nil {
t.Fatal(err)
}
Expand All @@ -478,6 +499,52 @@ func TestTagXRepos(t *testing.T) {
}
}

func TestTagSingleRepo(t *testing.T) {
mod := NewFakeRepo(t, "mod")
mod1 := mod.Commit(map[string]string{
"go.mod": "module golang.org/x/mod\n",
"go.sum": "\n",
})
mod.Tag("v1.1.0", mod1)
foo := NewFakeRepo(t, "foo")
foo1 := foo.Commit(map[string]string{
"go.mod": "module golang.org/x/foo\nrequire golang.org/x/mod v1.0.0\n",
"go.sum": "\n",
})
foo.Tag("v1.1.5", foo1)
foo.Commit(map[string]string{
"main.go": "package main",
})

deps := newTagXTestDeps(t, mod, foo)

wd := deps.tagXTasks.NewSingleDefinition()
ctx, cancel := context.WithTimeout(deps.ctx, time.Minute)
w, err := workflow.Start(wd, map[string]interface{}{
"Repository name": "foo",
})
if err != nil {
t.Fatal(err)
}
defer cancel()
_, err = w.Run(ctx, &verboseListener{t: t})
if err != nil {
t.Fatal(err)
}

tag, err := deps.gerrit.GetTag(ctx, "foo", "v1.2.0")
if err != nil {
t.Fatalf("foo should have been tagged with v1.2.0: %v", err)
}
goMod, err := deps.gerrit.ReadFile(ctx, "foo", tag.Revision, "go.mod")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(goMod), "[email protected]") {
t.Errorf("foo should use mod v1.1.0. go.mod: %v", string(goMod))
}
}

type verboseListener struct {
t *testing.T
outputListener func(string, interface{})
Expand Down
1 change: 1 addition & 0 deletions internal/workflow/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func (d *Definition) name(name string) string {

func (d *Definition) shallowClone() *Definition {
clone := New()
clone.parameters = append([]MetaParameter(nil), d.parameters...)
for k, v := range d.tasks {
clone.tasks[k] = v
}
Expand Down

0 comments on commit b041a95

Please sign in to comment.