golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/task/vscodego.go (about)

     1  // Copyright 2024 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  package task
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/fs"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	wf "golang.org/x/build/internal/workflow"
    17  	"golang.org/x/sync/errgroup"
    18  )
    19  
    20  // VSCodeGoReleaseTask releases vscode go.
    21  //
    22  // 1. cross-compile github.com/golang/vscode-go/vscgo and store the artifacts in the scratchFS.
    23  // 2. (TODO) sign the artifacts.
    24  // 3. (TODO) tag the repository.
    25  // 4. (TODO) trigger the GCB workflow that reads the signed artifacts, packages, and publishes them.
    26  // 5. (TODO) announce (SNS)
    27  type VSCodeGoReleaseTask struct {
    28  	CloudBuild CloudBuildClient
    29  	*ScratchFS
    30  	Revision string
    31  }
    32  
    33  func checkVersion(v string) error {
    34  	if len(v) < 2 || v[0] != 'v' {
    35  		return errors.New("release version must start with 'v'")
    36  	}
    37  	return nil
    38  }
    39  
    40  var vscgoVersionParam = wf.ParamDef[string]{
    41  	Name:    "Extension version to release",
    42  	Example: "v0.0.0-rc.1", Check: checkVersion,
    43  }
    44  
    45  func (t *VSCodeGoReleaseTask) NewDefinition() *wf.Definition {
    46  	wd := wf.New()
    47  
    48  	version := wf.Param(wd, vscgoVersionParam)
    49  	unsignedArtifacts := wf.Task1(wd, "build vscgo", t.buildVSCGO, version)
    50  	wf.Output(wd, "build artifacts", unsignedArtifacts)
    51  
    52  	// TODO: sign
    53  	return wd
    54  }
    55  
    56  var vscgoPlatforms = []struct {
    57  	Platform string
    58  	Env      []string
    59  }{
    60  	{Platform: "win32-x64", Env: []string{"GOOS=windows", "GOARCH=amd64"}},
    61  	{Platform: "win32-arm64", Env: []string{"GOOS=windows", "GOARCH=arm64"}},
    62  	{Platform: "darwin-x64", Env: []string{"GOOS=darwin", "GOARCH=amd64"}},
    63  	{Platform: "darwin-arm64", Env: []string{"GOOS=darwin", "GOARCH=arm64"}},
    64  	{Platform: "linux-x64", Env: []string{"GOOS=linux", "GOARCH=amd64"}},
    65  	{Platform: "linux-arm64", Env: []string{"GOOS=linux", "GOARCH=arm64"}},
    66  	// { Platform: "linux-arm", Env: []string{"GOOS=linux", "GOARCH=arm"}},
    67  	// { Platform:"linux-armhf", Env: []string{"GOOS=linux", "GOARCH=arm64", "GOARM=7"}},
    68  }
    69  
    70  type goBuildArtifact struct {
    71  	Platform string
    72  	Filename string
    73  }
    74  
    75  func (t *VSCodeGoReleaseTask) buildVSCGO(ctx *wf.TaskContext, version string) ([]goBuildArtifact, error) {
    76  	// TODO: version stamping won't use the tagged version with go build.
    77  
    78  	// TODO: encode it in vscode-go's build script. Then,
    79  	// we can just "go run -C extension tools/release/release.go build-vscgo".
    80  	var b strings.Builder
    81  	fmt.Fprintf(&b, "git fetch && git switch %v\n", t.Revision)
    82  	fmt.Fprintf(&b, "export OUT=$(mktemp -d /tmp/vscgo-XXXXXXXX)\n")
    83  	fmt.Fprintf(&b, "export CGO_ENABLED=0\n")
    84  	for _, info := range vscgoPlatforms {
    85  		envs := strings.Join(info.Env, " ")
    86  		base := "vscgo"
    87  		if strings.HasPrefix(info.Platform, "win32") {
    88  			base = "vscgo.exe"
    89  		}
    90  		fmt.Fprintf(&b, "mkdir ${OUT}/%v\n", info.Platform)
    91  		fmt.Fprintf(&b, "%v go build -o ${OUT}/%v/%v github.com/golang/vscode-go/vscgo\n", envs, info.Platform, base)
    92  	}
    93  	fmt.Fprintf(&b, "mkdir out && mv ${OUT}/* out/\n")
    94  	script := b.String()
    95  
    96  	build, err := t.CloudBuild.RunScript(ctx, script, "vscode-go", []string{"out"})
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	if _, err := AwaitCondition(ctx, 30*time.Second, func() (string, bool, error) {
   101  		return t.CloudBuild.Completed(ctx, build)
   102  	}); err != nil {
   103  		return nil, err
   104  	}
   105  	outfs, err := t.CloudBuild.ResultFS(ctx, build)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	var artifacts []goBuildArtifact
   110  	err = fs.WalkDir(outfs, ".", func(path string, d fs.DirEntry, err error) error {
   111  		if err != nil {
   112  			return err
   113  		}
   114  		if d.IsDir() {
   115  			return nil
   116  		}
   117  		if name := d.Name(); name != "vscgo" && name != "vscgo.exe" {
   118  			return nil
   119  		}
   120  		platform := filepath.Base(filepath.Dir(path)) // platform name is the parent name.
   121  		artifacts = append(artifacts, goBuildArtifact{
   122  			Platform: platform,
   123  			Filename: path,
   124  		})
   125  		return nil
   126  	})
   127  
   128  	var eg errgroup.Group
   129  	for i := range artifacts {
   130  		idx := i
   131  		eg.Go(func() error {
   132  			platform, path := artifacts[idx].Platform, artifacts[idx].Filename
   133  
   134  			in, err := outfs.Open(path)
   135  			if err != nil {
   136  				return err
   137  			}
   138  			defer in.Close()
   139  			name, out, err := t.ScratchFS.OpenWrite(ctx, platform+"-"+filepath.Base(path))
   140  			if err != nil {
   141  				return err
   142  			}
   143  			if _, err := io.Copy(out, in); err != nil {
   144  				out.Close()
   145  				return err
   146  			}
   147  			if err := out.Close(); err != nil {
   148  				return err
   149  			}
   150  			// replace artifacts
   151  			artifacts[idx] = goBuildArtifact{
   152  				Platform: platform,
   153  				Filename: name,
   154  			}
   155  			return nil
   156  		})
   157  	}
   158  	if err := eg.Wait(); err != nil {
   159  		return nil, err
   160  	}
   161  	return artifacts, nil
   162  }