Skip to content

Commit 481f2b5

Browse files
committed
build: add GCB release workflow
This workflow is meant to replace the GitHub action workflow defined in https://github.com/golang/vscode-go/blob/master/.github/workflows/release.yml The GH workflow was a collection of command line tools invocation and some bash scripting to parse the version tag and adjust the workflow depending on whether it's for rc or not. I figured it's easier to work with go, so ported the logic to build/release.go. TODO: convert most part of build/all.bash and the nightly release workflow with go code. release.go uses `gh` (GitHub CLI tool) to post the release note and the artifacts (vsix) to GitHub. Dockerfile is updated to include `gh`. Reference: https://stackoverflow.com/a/69477930 The workflow needs to access GitHub personal access token. To access the release API, write permission for Content & Workflow is necessary. https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens?apiVersion=2022-11-28#repository-permissions-for-contents Tested it by triggering the GCB workflow against a forked repo. For b/216321767 Change-Id: I1eec6b4b896fe1f1565bbf9e160f0368cade36c7 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/553115 Reviewed-by: Robert Findley <[email protected]> Reviewed-by: Suzy Mueller <[email protected]> Commit-Queue: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: kokoro <[email protected]>
1 parent 4d53cdc commit 481f2b5

File tree

4 files changed

+283
-4
lines changed

4 files changed

+283
-4
lines changed

build/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,17 @@ ENV DEBIAN_FRONTEND noninteractive
2525
# TODO(hyangah): remove this when the platform works with ipv6.
2626
ENV NODE_OPTIONS --dns-result-order=ipv4first
2727

28+
# Install xvfb jq
2829
RUN apt-get -qq update && apt-get install -qq -y libnss3 libgtk-3-dev libxss1 libasound2 xvfb libsecret-1-0 jq > /dev/null
2930

31+
# Install gh https://stackoverflow.com/a/69477930
32+
RUN apt update && apt install -y \
33+
curl \
34+
gpg
35+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg;
36+
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null;
37+
RUN apt update && apt install -y gh;
38+
3039
USER node
3140
WORKDIR /workspace
3241
ENTRYPOINT ["build/all.bash"]

build/release-nightly.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ steps:
2929
entrypoint: npm
3030
- name: us-docker.pkg.dev/$PROJECT_ID/vscode-go-docker-repo/ci-image
3131
args:
32-
- build/all.bash
32+
- build/all.bash // TODO: replace all.bash with release.go
3333
- prepare_nightly
3434
dir: vscode-go
3535
id: prepare nightly release
@@ -61,14 +61,14 @@ steps:
6161
args:
6262
- '-c'
6363
- >
64-
npx vsce publish -i $(cat /workspace/vsix_name.txt) -p $$VSCE_TOKEN
64+
npx vsce publish -i $(cat /workspace/vsix_name.txt) -p $$VSCE_PAT
6565
--baseContentUrl=https://github.com/golang/vscode-go
6666
--baseImagesUrl=https://github.com/golang/vscode-go
6767
dir: vscode-go
6868
id: publish nightly extension
6969
entrypoint: bash
7070
secretEnv:
71-
- VSCE_TOKEN
71+
- VSCE_PAT
7272
timeout: 1800s
7373
options:
7474
machineType: E2_HIGHCPU_8
@@ -81,4 +81,4 @@ artifacts:
8181
availableSecrets:
8282
secretManager:
8383
- versionName: projects/$PROJECT_ID/secrets/$_VSCE_TOKEN/versions/latest
84-
env: VSCE_TOKEN
84+
env: VSCE_PAT

build/release.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This script is used to build and publish VS Code Go extension.
6+
// The script should be run from the root of the repository where package.json is located.
7+
//
8+
// The script requires the following environment variables to be set:
9+
//
10+
// TAG_NAME: the name of the tag to be released.
11+
// COMMIT_SHA: the commit SHA to be released (optional. if not set, it will be retrieved from git)
12+
// VSCE_PAT: the Personal Access Token for the VS Code Marketplace.
13+
// GITHUB_TOKEN: the GitHub token for the Go repository.
14+
//
15+
// This script requires the following tools to be installed:
16+
//
17+
// jq, npx, gh, git
18+
//
19+
// Usage:
20+
//
21+
// // package the extension (based on TAG_NAME).
22+
// go run build/release.go package
23+
// // publish the extension.
24+
// go run build/release.go publish
25+
package main
26+
27+
import (
28+
"bytes"
29+
"fmt"
30+
"os"
31+
"os/exec"
32+
"regexp"
33+
"strings"
34+
)
35+
36+
func main() {
37+
if len(os.Args) != 2 {
38+
fatalf("usage: %s [package|publish]", os.Args[0])
39+
}
40+
cmd := os.Args[1]
41+
42+
checkWD()
43+
requireTools("jq", "npx", "gh", "git")
44+
requireEnvVars("TAG_NAME")
45+
46+
tagName, version, isRC := releaseVersionInfo()
47+
vsix := fmt.Sprintf("go-%s.vsix", version)
48+
49+
switch cmd {
50+
case "package":
51+
buildPackage(version, vsix)
52+
case "publish":
53+
requireEnvVars("VSCE_PAT", "GITHUB_TOKEN")
54+
publish(tagName, vsix, isRC)
55+
default:
56+
fatalf("usage: %s [package|publish]", os.Args[0])
57+
}
58+
}
59+
60+
func fatalf(format string, args ...any) {
61+
fmt.Fprintf(os.Stderr, format, args...)
62+
fmt.Fprintf(os.Stderr, "\n")
63+
os.Exit(1)
64+
}
65+
66+
func requireTools(tools ...string) {
67+
for _, tool := range tools {
68+
if _, err := exec.LookPath(tool); err != nil {
69+
fatalf("required tool %q not found", tool)
70+
}
71+
}
72+
}
73+
74+
func requireEnvVars(vars ...string) {
75+
for _, v := range vars {
76+
if os.Getenv(v) == "" {
77+
fatalf("required environment variable %q not set", v)
78+
}
79+
}
80+
}
81+
82+
// checkWD checks if the working directory is the root of the repository where package.json is located.
83+
func checkWD() {
84+
wd, err := os.Getwd()
85+
if err != nil {
86+
fatalf("failed to get working directory")
87+
}
88+
// check if package.json is in the working directory
89+
if _, err := os.Stat("package.json"); os.IsNotExist(err) {
90+
fatalf("package.json not found in working directory %q", wd)
91+
}
92+
}
93+
94+
// releaseVersionInfo computes the version and label information for this release.
95+
// It requires the TAG_NAME environment variable to be set and the tag matches the version info embedded in package.json.
96+
func releaseVersionInfo() (tagName, version string, isPrerelease bool) {
97+
tagName = os.Getenv("TAG_NAME")
98+
if tagName == "" {
99+
fatalf("TAG_NAME environment variable is not set")
100+
}
101+
// versionTag should be of the form vMajor.Minor.Patch[-rc.N].
102+
// e.g. v1.1.0-rc.1, v1.1.0
103+
// The MajorMinorPatch part should match the version in package.json.
104+
// The optional `-rc.N` part is captured as the `Label` group
105+
// and the validity is checked below.
106+
versionTagRE := regexp.MustCompile(`^v(?P<MajorMinorPatch>\d+\.\d+\.\d+)(?P<Label>\S*)$`)
107+
m := versionTagRE.FindStringSubmatch(tagName)
108+
if m == nil {
109+
fatalf("TAG_NAME environment variable %q is not a valid version", tagName)
110+
}
111+
mmp := m[versionTagRE.SubexpIndex("MajorMinorPatch")]
112+
label := m[versionTagRE.SubexpIndex("Label")]
113+
if label != "" {
114+
if !strings.HasPrefix(label, "-rc.") {
115+
fatalf("TAG_NAME environment variable %q is not a valid release candidate version", tagName)
116+
}
117+
isPrerelease = true
118+
}
119+
120+
cmd := exec.Command("jq", "-r", ".version", "package.json")
121+
cmd.Stderr = os.Stderr
122+
versionInPackageJSON, err := cmd.Output()
123+
if err != nil {
124+
fatalf("failed to read package.json version")
125+
}
126+
if got := string(bytes.TrimSpace(versionInPackageJSON)); got != mmp {
127+
fatalf("package.json version %q does not match TAG_NAME %q", got, tagName)
128+
}
129+
130+
return tagName, mmp + label, isPrerelease
131+
}
132+
133+
// buildPackage builds the extension of the given version, using npx vsce package.
134+
func buildPackage(version, output string) {
135+
cmd := exec.Command("npx", "vsce", "package",
136+
"-o", output,
137+
"--baseContentUrl", "https://github.com/golang/vscode-go",
138+
"--baseImagesUrl", "https://github.com/golang/vscode-go",
139+
"--no-update-package-json",
140+
"--no-git-tag-version",
141+
version)
142+
143+
cmd.Stderr = os.Stderr
144+
if err := cmd.Run(); err != nil {
145+
fatalf("failed to build package")
146+
}
147+
148+
cmd = exec.Command("git", "add", output)
149+
cmd.Stderr = os.Stderr
150+
if err := cmd.Run(); err != nil {
151+
fatalf("failed to build package")
152+
}
153+
}
154+
155+
// publish publishes the extension to the VS Code Marketplace and GitHub, using npx vsce and gh release create.
156+
func publish(tagName, packageFile string, isPrerelease bool) {
157+
// check if the package file exists.
158+
if _, err := os.Stat(packageFile); os.IsNotExist(err) {
159+
fatalf("package file %q does not exist. Did you run 'go run build/release.go package'?", packageFile)
160+
}
161+
162+
// publish release to GitHub. This will create a draft release - manually publish it after reviewing the draft.
163+
// TODO(hyangah): populate the changelog (the first section of CHANGELOG.md) and pass it using --notes-file instead of --generate-notes.
164+
ghArgs := []string{"release", "create", "--generate-notes", "--target", commitSHA(), "--title", "Release " + tagName, "--draft"}
165+
fmt.Printf("%s\n", strings.Join(ghArgs, " "))
166+
if isPrerelease {
167+
ghArgs = append(ghArgs, "--prerelease")
168+
}
169+
ghArgs = append(ghArgs, "-R", "github.com/golang/vscode-go")
170+
ghArgs = append(ghArgs, tagName, packageFile)
171+
cmd := exec.Command("gh", ghArgs...)
172+
cmd.Stderr = os.Stderr
173+
if err := cmd.Run(); err != nil {
174+
fatalf("failed to publish release")
175+
}
176+
177+
if isPrerelease {
178+
return // TODO: release with the -pre-release flag if isPrerelease is set.
179+
}
180+
181+
/* TODO(hyangah): uncomment this to finalize the release workflow migration.
182+
npxVsceArgs := []string{"vsce", "publish", "-i", packageFile}
183+
184+
cmd2 := exec.Command("npx", npxVsceArgs...)
185+
cmd2.Stderr = os.Stderr
186+
if err := cmd2.Run(); err != nil {
187+
fatalf("failed to publish release")
188+
}
189+
*/
190+
}
191+
192+
// commitSHA returns COMMIT_SHA environment variable, or the commit SHA of the current branch.
193+
func commitSHA() string {
194+
if commitSHA := os.Getenv("COMMIT_SHA"); commitSHA != "" {
195+
return commitSHA
196+
}
197+
198+
cmd := exec.Command("git", "rev-parse", "HEAD")
199+
cmd.Stderr = os.Stderr
200+
commitSHA, err := cmd.Output()
201+
if err != nil {
202+
fatalf("failed to get commit SHA")
203+
}
204+
return strings.TrimSpace(string(commitSHA))
205+
}

build/release.yaml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# This workflow will be triggered when a new release tag (of the form vX.Y.Z) is
2+
# pushed to the vscode-go repo.)
3+
# For local testing, run:
4+
# gcloud builds submit --config release.yaml --no-source --substitutions=_TAG_NAME="v0.40.1-rc.1",_GITHUB_TOKEN="alias1",_VSCE_TOKEN="alias2"
5+
#
6+
# WARNING: this will publish the extension.
7+
#
8+
# This will check out the vscode-go repo on the specified tag, build the extension,
9+
# and publish it to the VS Code Marketplace and the GitHub Releases page if not published already.
10+
steps:
11+
# TODO: check build/test status
12+
# TODO: configure failure notification https://cloud.google.com/build/docs/configuring-notifications/notifiers
13+
- name: gcr.io/cloud-builders/git
14+
args:
15+
- clone
16+
- '--branch=$TAG_NAME'
17+
- '--single-branch'
18+
- '--depth=1'
19+
- 'https://go.googlesource.com/vscode-go'
20+
- vscode-go
21+
id: clone vscode-go repo
22+
- name: gcr.io/cloud-builders/docker
23+
args:
24+
- '-R'
25+
- '1000:1000'
26+
- /workspace
27+
- /builder/home
28+
dir: /
29+
id: adjust file permissions
30+
entrypoint: chown
31+
- name: us-docker.pkg.dev/$PROJECT_ID/vscode-go-docker-repo/ci-image
32+
args:
33+
- ci
34+
dir: vscode-go
35+
id: install npm dependencies
36+
entrypoint: npm
37+
- name: us-docker.pkg.dev/$PROJECT_ID/vscode-go-docker-repo/ci-image
38+
args:
39+
- -c
40+
- |
41+
go run build/release.go package &&
42+
go run build/release.go publish
43+
dir: vscode-go
44+
id: package and publish the extension
45+
entrypoint: bash
46+
env:
47+
- 'TAG_NAME=$TAG_NAME'
48+
- 'COMMIT_SHA=$COMMIT_SHA'
49+
secretEnv:
50+
- VSCE_PAT
51+
- GITHUB_TOKEN
52+
timeout: 1800s
53+
options:
54+
substitutionOption: ALLOW_LOOSE
55+
artifacts:
56+
objects:
57+
location: 'gs://$PROJECT_ID/releases/$TAG_NAME'
58+
paths:
59+
- vscode-go/*.vsix
60+
availableSecrets:
61+
secretManager:
62+
- versionName: projects/$PROJECT_ID/secrets/$_VSCE_TOKEN/versions/latest
63+
env: VSCE_PAT
64+
- versionName: projects/$PROJECT_ID/secrets/$_GITHUB_TOKEN/versions/latest
65+
env: GITHUB_TOKEN

0 commit comments

Comments
 (0)