cmd/go/gomod/main.go (67 lines of code) (raw):
// Copyright 2020 Google LLC
//
// 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
//
// http://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.
// Implements go/gomod buildpack.
// The gomod buildpack downloads modules specified in go.mod.
package main
import (
"fmt"
"os"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
"github.com/GoogleCloudPlatform/buildpacks/pkg/golang"
)
func main() {
gcp.Main(detectFn, buildFn)
}
func detectFn(ctx *gcp.Context) (gcp.DetectResult, error) {
goModExists, err := ctx.FileExists("go.mod")
if err != nil {
return nil, err
}
if goModExists {
return gcp.OptInFileFound("go.mod"), nil
}
return gcp.OptOutFileNotFound("go.mod"), nil
}
func buildFn(ctx *gcp.Context) error {
l, err := golang.NewGoWorkspaceLayer(ctx)
if err != nil {
return fmt.Errorf("creating GOPATH layer: %w", err)
}
vendorExists, err := ctx.FileExists("vendor")
if err != nil {
return err
}
// When there's a vendor folder and go is 1.14+, we shouldn't download the modules
// and let go build use the vendored dependencies.
if vendorExists {
avSupport, err := golang.SupportsAutoVendor(ctx)
if err != nil {
return fmt.Errorf("checking for auto vendor support: %w", err)
}
if avSupport {
ctx.Logf("Not downloading modules because there's a `vendor` directory")
return nil
}
ctx.Warnf(`Ignoring "vendor" directory: To use vendor directory, the Go runtime must be 1.14+ and go.mod must contain a "go 1.14"+ entry. See https://cloud.google.com/appengine/docs/standard/go/specifying-dependencies#vendoring_dependencies.`)
}
goModIsWriteable, err := ctx.IsWritable("go.mod")
if err != nil {
return err
}
if !goModIsWriteable {
// Preempt an obscure failure mode: if go.mod is not writable then `go list -m` can fail saying:
// go: updates to go.sum needed, disabled by -mod=readonly
return gcp.UserErrorf("go.mod exists but is not writable")
}
env := []string{"GOPATH=" + l.Path, "GO111MODULE=on"}
// BuildDirEnv should only be set by App Engine buildpacks.
workdir := os.Getenv(golang.BuildDirEnv)
if workdir == "" {
workdir = ctx.ApplicationRoot()
}
goSumExists, err := ctx.FileExists("go.sum")
if err != nil {
return err
}
// Go 1.16+ requires a go.sum file. If one does not exist, generate it.
// go build -mod=readonly requires a complete graph of modules which `go mod download` does not produce in all cases (https://golang.org/issue/35832).
if !goSumExists {
ctx.Logf(`go.sum not found, generating using "go mod tidy"`)
if _, err := golang.ExecWithGoproxyFallback(ctx, []string{"go", "mod", "tidy"}, gcp.WithEnv(env...), gcp.WithWorkDir(workdir), gcp.WithUserAttribution); err != nil {
return fmt.Errorf("running go mod tidy: %w", err)
}
}
if _, err := golang.ExecWithGoproxyFallback(ctx, []string{"go", "mod", "download"}, gcp.WithEnv(env...), gcp.WithUserAttribution); err != nil {
return fmt.Errorf("running go mod download: %w", err)
}
return nil
}