From 6f918803a693fe623a056be4275801cd8a13ef8e Mon Sep 17 00:00:00 2001 From: Lukas Berger Date: Fri, 21 Aug 2020 17:30:31 -0700 Subject: [PATCH] Build application from a srv directory in Go App Engine gomod buildpack This fixes an incompatiblity with the appengine package which expects files to be compiled from srv for go.mod apps: https://github.com/golang/appengine/blob/553959209a20f3be281c16dd5be5c740a893978f/delay/delay.go#L136. All source code is moved from /workspace to /srv and `go build` is then invoked with the latter as the work directory. The resulting binary will correctly have /srv/ in the stored file paths. PiperOrigin-RevId: 327901991 Change-Id: Ic08452e67af3f6c20741e6661931cec717460930 --- .../gae/go111/acceptance/acceptance_test.go | 5 +- .../go/gomod_appengine/_main-package-path | 1 + builders/testdata/go/gomod_appengine/go.mod | 5 ++ builders/testdata/go/gomod_appengine/main.go | 56 +++++++++++++++++++ cmd/go/appengine_gomod/BUILD.bazel | 1 + cmd/go/appengine_gomod/main.go | 9 +++ cmd/go/appengine_gopath/main.go | 8 ++- cmd/go/build/main.go | 9 ++- pkg/golang/golang.go | 2 + 9 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 builders/testdata/go/gomod_appengine/_main-package-path create mode 100644 builders/testdata/go/gomod_appengine/go.mod create mode 100644 builders/testdata/go/gomod_appengine/main.go diff --git a/builders/gae/go111/acceptance/acceptance_test.go b/builders/gae/go111/acceptance/acceptance_test.go index 0c8a3969c..a85982d6f 100644 --- a/builders/gae/go111/acceptance/acceptance_test.go +++ b/builders/gae/go111/acceptance/acceptance_test.go @@ -66,7 +66,6 @@ func TestAcceptance(t *testing.T) { Name: "gopath no dependencies", App: "gopath", }, - // Test that GOOGLE_BUILDABLE takes precedence over app.yaml and go-app-stager. { Name: "gomod GOOGLE_BUILDABLE vs go-app-stager vs app.yaml main package", @@ -107,6 +106,10 @@ func TestAcceptance(t *testing.T) { Name: "gomod no dependencies", App: "gomod", }, + { + Name: "gomod appengine", + App: "gomod_appengine", + }, } for _, tc := range testCases { tc := tc diff --git a/builders/testdata/go/gomod_appengine/_main-package-path b/builders/testdata/go/gomod_appengine/_main-package-path new file mode 100644 index 000000000..945c9b46d --- /dev/null +++ b/builders/testdata/go/gomod_appengine/_main-package-path @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/builders/testdata/go/gomod_appengine/go.mod b/builders/testdata/go/gomod_appengine/go.mod new file mode 100644 index 000000000..c3a90f0b1 --- /dev/null +++ b/builders/testdata/go/gomod_appengine/go.mod @@ -0,0 +1,5 @@ +module example.com/package + +go 1.11 + +require rsc.io/quote v1.5.2 \ No newline at end of file diff --git a/builders/testdata/go/gomod_appengine/main.go b/builders/testdata/go/gomod_appengine/main.go new file mode 100644 index 000000000..4dfc8f84b --- /dev/null +++ b/builders/testdata/go/gomod_appengine/main.go @@ -0,0 +1,56 @@ +// 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. + +// Package main tests building source that has a go.mod file without gomod dependencies. +// It also tests compatiblity with the golang/appengine package. +package main + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + + "rsc.io/quote" +) + +func handler(w http.ResponseWriter, r *http.Request) { + if got, want := quote.Hello(), "Hello, world."; got != want { + fmt.Fprintf(w, "FAIL: quote.Hello() = %s, want %s\n", got, want) + return + } + + // Check that the path at compile time includes /srv/. This is done for compatibility with the + // appengine package, which requires it for historical reasons. See the go/appengine_gomod + // buildpack. + _, file, _, ok := runtime.Caller(0) + if !ok { + fmt.Fprintln(w, "FAIL: runtime.Caller(0) not ok") + return + } + sep := string(filepath.Separator) + srv := sep + "srv" + sep + if !strings.Contains(file, srv) { + fmt.Fprintf(w, "FAIL: caller file path %s does not contain %s\n", file, srv) + return + } + fmt.Fprintln(w, "PASS") +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":"+os.Getenv("PORT"), nil) +} diff --git a/cmd/go/appengine_gomod/BUILD.bazel b/cmd/go/appengine_gomod/BUILD.bazel index a9abd25e5..e09352b6a 100644 --- a/cmd/go/appengine_gomod/BUILD.bazel +++ b/cmd/go/appengine_gomod/BUILD.bazel @@ -26,6 +26,7 @@ go_binary( deps = [ "//pkg/env", "//pkg/gcpbuildpack", + "//pkg/golang", ], ) diff --git a/cmd/go/appengine_gomod/main.go b/cmd/go/appengine_gomod/main.go index 2ea627041..f72fd01eb 100644 --- a/cmd/go/appengine_gomod/main.go +++ b/cmd/go/appengine_gomod/main.go @@ -24,6 +24,7 @@ import ( "github.com/GoogleCloudPlatform/buildpacks/pkg/env" gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack" + "github.com/GoogleCloudPlatform/buildpacks/pkg/golang" ) const ( @@ -66,6 +67,14 @@ func buildFn(ctx *gcp.Context) error { l := ctx.Layer("main_env", gcp.BuildLayer) l.BuildEnvironment.Override(env.Buildable, buildMainPath) + // HACK: For backwards compatibility on App Engine Go 1.11: + // Copy all files to a layer directory that ends with /srv because the appengine package relies on the name: + // https://github.com/golang/appengine/blob/553959209a20f3be281c16dd5be5c740a893978f/delay/delay.go#L136 + // We change the work directory instead of modifying env.Buildable, because the latter is not necessarily a filesystem path. + srvl := ctx.Layer("srv", gcp.BuildLayer) + srvl.BuildEnvironment.Override(golang.BuildDirEnv, srvl.Path) + ctx.Exec([]string{"cp", "--dereference", "-R", ".", srvl.Path}, gcp.WithUserTimingAttribution) + return nil } diff --git a/cmd/go/appengine_gopath/main.go b/cmd/go/appengine_gopath/main.go index 7cbb281ba..54f3e619b 100644 --- a/cmd/go/appengine_gopath/main.go +++ b/cmd/go/appengine_gopath/main.go @@ -84,13 +84,17 @@ func buildFn(ctx *gcp.Context) error { 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) { - ctx.Debugf("copying %q to %q", src, dst) - // Trailing "/." copies the contents of src directory, but not src itself. src = filepath.Clean(src) + string(filepath.Separator) + "." ctx.Exec([]string{"cp", "--dereference", "-R", src, dst}, gcp.WithUserTimingAttribution) diff --git a/cmd/go/build/main.go b/cmd/go/build/main.go index 4a045006f..53ac18744 100644 --- a/cmd/go/build/main.go +++ b/cmd/go/build/main.go @@ -67,9 +67,14 @@ func buildFn(ctx *gcp.Context) error { bld = append(bld, goBuildFlags()...) bld = append(bld, "-o", outBin) bld = append(bld, buildable) - ctx.Exec(bld, gcp.WithEnv("GOCACHE="+cl.Path), gcp.WithMessageProducer(printTipsAndKeepStderrTail(ctx)), gcp.WithUserAttribution) + // BuildDirEnv should only be set by App Engine buildpacks. + workdir := os.Getenv(golang.BuildDirEnv) + if workdir == "" { + workdir = ctx.ApplicationRoot() + } + ctx.Exec(bld, gcp.WithEnv("GOCACHE="+cl.Path), gcp.WithWorkDir(workdir), gcp.WithMessageProducer(printTipsAndKeepStderrTail(ctx)), gcp.WithUserAttribution) - // Configure the entrypoint for production. Use the full path to save `skaffold debug` + // Configure the entrypoint for production. Use the full path to save `skaffold debug` // from fetching the remote container image (tens to hundreds of megabytes), which is slow. if !devmode.Enabled(ctx) { ctx.AddWebProcess([]string{outBin}) diff --git a/pkg/golang/golang.go b/pkg/golang/golang.go index d4c4a5e0c..4af4386fb 100644 --- a/pkg/golang/golang.go +++ b/pkg/golang/golang.go @@ -26,6 +26,8 @@ import ( const ( // OutBin is the name of the final compiled binary produced by Go buildpacks. OutBin = "main" + // BuildDirEnv is an environment variable that buildpacks can use to communicate the working directory to `go build`. + BuildDirEnv = "GOOGLE_INTERNAL_BUILD_DIR" ) var (