Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testing: initial code for release helper tool #2114

Merged
merged 10 commits into from
May 28, 2019
143 changes: 143 additions & 0 deletions internal/testing/releasehelper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright 2019 The Go Cloud Development Kit Authors
//
// 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
//
// https://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.

// Helper tool for creating new releases of the Go CDK.
// Run from the repository root:
// $ go run internal/testing/releasehelper.go <flags>
vangent marked this conversation as resolved.
Show resolved Hide resolved
package main
vangent marked this conversation as resolved.
Show resolved Hide resolved

import (
"bufio"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
)

// TODO(eliben): add bumpversion flag that bumps version by 1, or to a version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling that when we add these it will be better to use commands rather than flags. Either commands that are high level like prep which package up a bunch of lower-level things, or low-level commands that you do one at a time (releasehelper bumpversion, releasehelper addreplace). Otherwise it may be tricky to define the semantics for various combinations of flags (e.g., these two existing flags already depend on each other in some way; it doesn't make sense to set both or neither. With more flags, that may get more complex).

But, we can revisit later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I see what you're saying, been thinking about this. It's hard to predict what future functionailty we'll want though and I don't want to over-YAGNI it too much right now. We'll enhance it as we go and learn the ropes of the new process

// given on the command line
var doReplace = flag.Bool("replace", false, "add replace directives to root")
vangent marked this conversation as resolved.
Show resolved Hide resolved
var doDropReplace = flag.Bool("dropreplace", false, "drop replace directives to root")

// cmdCheck invokes the command given in s, echoing the invocation to stdout.
// It checks that the command was successful and returns its standard output.
// If the command returned a non-0 status, log.Fatal is invoked.
func cmdCheck(s string) []byte {
fmt.Printf(" -> %s\n", s)
fields := strings.Fields(s)
if len(fields) < 1 {
log.Fatal("Expected \"command <arguments>\"")
eliben marked this conversation as resolved.
Show resolved Hide resolved
}
b, err := exec.Command(fields[0], fields[1:]...).Output()
if exiterr, ok := err.(*exec.ExitError); ok {
log.Fatalf("%s; stderr: %s\n", err, string(exiterr.Stderr))
} else if err != nil {
log.Fatal("exec.Command", err)
}
vangent marked this conversation as resolved.
Show resolved Hide resolved
return b
}

// These types are taken from "go mod help edit", in order to parse the JSON
// output of `go mod edit -json`.
type GoMod struct {
Module Module
Go string
Require []Require
Exclude []Module
Replace []Replace
}

type Module struct {
Path string
Version string
}

type Require struct {
Path string
Version string
Indirect bool
}

type Replace struct {
Old Module
New Module
}

// parseModuleInfo parses module information from a go.mod file at path.
func parseModuleInfo(path string) GoMod {
rawJson := cmdCheck("go mod edit -json " + path)
var modInfo GoMod
err := json.Unmarshal(rawJson, &modInfo)
if err != nil {
log.Fatal(err)
}
return modInfo
}

func runOnGomod(path string) {
vangent marked this conversation as resolved.
Show resolved Hide resolved
fmt.Println("Processing", path)
modInfo := parseModuleInfo(path)

for _, r := range modInfo.Require {
// Find requirements on modules within the gocloud.dev tree.
if strings.HasPrefix(r.Path, "gocloud.dev") {
// Find the relative path from 'path' (the module file we're processing)
// and the module required here.
var reqPath string
if r.Path == "gocloud.dev" {
reqPath = "."
} else {
reqPath = r.Path[12:]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, something like this:

if reqPath := strings.TrimPrefix(r.Path, "gocloud.dev"); reqPath != r.Path {
  if reqPath == "" {
    reqPath = "."
  }
  ...
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. Mine feels a bit simpler, so I'll stick with it if you don't mind

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I don't really find the 12: that readable, maybe TrimPrefix there?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done

}
rel, err := filepath.Rel(filepath.Dir(path), reqPath)
if err != nil {
log.Fatal(err)
}
if strings.HasSuffix(rel, "/.") {
vangent marked this conversation as resolved.
Show resolved Hide resolved
rel, _ = filepath.Split(rel)
}

if *doReplace {
cmdCheck(fmt.Sprintf("go mod edit -replace=%s=%s %s", r.Path, rel, path))
} else if *doDropReplace {
cmdCheck(fmt.Sprintf("go mod edit -dropreplace=%s %s", r.Path, path))
}
}
}
}

func main() {
flag.Parse()
f, err := os.Open("allmodules")
if err != nil {
log.Fatal(err)
}

input := bufio.NewScanner(f)
input.Split(bufio.ScanLines)
for input.Scan() {
if len(input.Text()) > 0 && !strings.HasPrefix(input.Text(), "#") {
runOnGomod(path.Join(input.Text(), "go.mod"))
eliben marked this conversation as resolved.
Show resolved Hide resolved
}
}

if input.Err() != nil {
log.Fatal(input.Err())
}
}
vangent marked this conversation as resolved.
Show resolved Hide resolved