cmd/go/appengine_gopath/main.go (97 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/appengine_gopath buildpack.
// The appengine_gopath buildpack sets $GOPATH and moves all gopath dependencies from _gopath/src/* to $GOPATH/src/*. The _gopath directory is created by go-app-stager during deployment.
// It then checks for _gopath/main-package-path which exists if the user's main package was originally on $GOPATH/src locally.
// If this file exists, the buildpack moves the main package to $GOPATH/src and sets the path to build $GOPATH/src/<path-to-main-package> where <path-to-main-package> is read from _gopath/main-package-path.
// If this file doesn't exist, the buildpack sets the path to build to "./..." and removes the _gopath directory because the build will fail if there's more than one go package in application root.
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/GoogleCloudPlatform/buildpacks/pkg/env"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
)
func main() {
gcp.Main(detectFn, buildFn)
}
func detectFn(ctx *gcp.Context) (gcp.DetectResult, error) {
if !env.IsGAE() && !env.IsFlex() {
return gcp.OptOut("not a GAE Standard or Flex app."), nil
}
goModExists, err := ctx.FileExists("go.mod")
if err != nil {
return nil, err
}
if goModExists {
return gcp.OptOut("go.mod found"), nil
}
atLeastOne, err := ctx.HasAtLeastOne("*.go")
if err != nil {
return nil, fmt.Errorf("finding *.go files: %w", err)
}
if !atLeastOne {
return gcp.OptOut("no .go files found"), nil
}
return gcp.OptIn("go.mod file not found, assuming GOPATH build"), nil
}
func buildFn(ctx *gcp.Context) error {
l, err := ctx.Layer("gopath", gcp.BuildLayer)
if err != nil {
return fmt.Errorf("creating gopath layer: %w", err)
}
goPath := l.Path
goPathSrc := filepath.Join(goPath, "src")
if err := ctx.MkdirAll(goPathSrc, 0755); err != nil {
return err
}
l.BuildEnvironment.Override("GOPATH", goPath)
l.BuildEnvironment.Override("GO111MODULE", "off")
stagerGoPath := filepath.Join(ctx.ApplicationRoot(), "_gopath")
stagerGoPathSrc := filepath.Join(stagerGoPath, "src")
stagerGoPathMain := filepath.Join(stagerGoPath, "main-package-path")
stagerGoPathSrcExists, err := ctx.FileExists(stagerGoPathSrc)
if err != nil {
return err
}
if stagerGoPathSrcExists {
files, err := ctx.ReadDir(stagerGoPathSrc)
if err != nil {
return err
}
for _, f := range files {
// To avoid superfluous files in root of stagerGoPathSrc, copy the subdirectories individually.
if !f.IsDir() {
continue
}
copyDir(ctx, filepath.Join(stagerGoPathSrc, f.Name()), filepath.Join(goPathSrc, f.Name()))
}
}
var buildMainPath string
stagerGoPathMainExists, err := ctx.FileExists(stagerGoPathMain)
if err != nil {
return err
}
if stagerGoPathMainExists {
goPathMainBytes, err := ctx.ReadFile(stagerGoPathMain)
if err != nil {
return err
}
buildMainPath = filepath.Join(goPathSrc, strings.TrimSpace(string(goPathMainBytes)))
// Remove stager directory prior to copying to make sure we don't copy the stager directory to $GOPATH.
if err := ctx.RemoveAll(stagerGoPath); err != nil {
return err
}
if err := ctx.MkdirAll(buildMainPath, 0755); err != nil {
return err
}
copyDir(ctx, ctx.ApplicationRoot(), buildMainPath)
} else {
buildMainPath = "./..."
// Remove stager directory to make sure there's only one go package in application root.
if err := ctx.RemoveAll(stagerGoPath); err != nil {
return err
}
}
if _, exists := os.LookupEnv(env.Buildable); !exists {
l.BuildEnvironment.Override(env.Buildable, buildMainPath)
}
// Unlike in the appengine_gomod buildpack, we do not have to compile gopath apps from a path that ends in /srv/. There are two cases:
// * _gopath/main-package-path exists and app source is put on GOPATH, which is handled by:
// https://github.com/golang/appengine/blob/553959209a20f3be281c16dd5be5c740a893978f/delay/delay.go#L136.
// * _gopath/main-package-path does not exist and the app is built from the current directory, which is handled by:
// https://github.com/golang/appengine/blob/553959209a20f3be281c16dd5be5c740a893978f/delay/delay.go#L125-L127
// TODO(b/145608768): Investigate creating and caching a GOCACHE layer.
return nil
}
func copyDir(ctx *gcp.Context, src, dst string) error {
// Trailing "/." copies the contents of src directory, but not src itself.
src = filepath.Clean(src) + string(filepath.Separator) + "."
_, err := ctx.Exec([]string{"cp", "--dereference", "-R", src, dst}, gcp.WithUserTimingAttribution)
return err
}