cmd/java/gradle/main.go (159 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 java/gradle buildpack. // The gradle buildpack builds Gradle applications. package main import ( "fmt" "net/http" "os" "path/filepath" "strings" "github.com/GoogleCloudPlatform/buildpacks/pkg/devmode" "github.com/GoogleCloudPlatform/buildpacks/pkg/env" "github.com/GoogleCloudPlatform/buildpacks/pkg/fileutil" gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack" "github.com/GoogleCloudPlatform/buildpacks/pkg/java" ) const ( gradleDistroURL = "https://services.gradle.org/distributions/gradle-%s-bin.zip" gradleLayer = "gradle" cacheLayer = "cache" versionKey = "version" ) func main() { gcp.Main(detectFn, buildFn) } func detectFn(ctx *gcp.Context) (gcp.DetectResult, error) { files := []string{ "build.gradle", "build.gradle.kts", "settings.gradle.kts", "settings.gradle", } for _, f := range files { exists, err := ctx.FileExists(f) if err != nil { return nil, err } if exists { return gcp.OptInFileFound(f), nil } } return gcp.OptOut(fmt.Sprintf("none of the following found: %s", strings.Join(files, ", "))), nil } func buildFn(ctx *gcp.Context) error { gradleCachedRepo, err := ctx.Layer(cacheLayer, gcp.CacheLayer, gcp.LaunchLayerIfDevMode) if err != nil { return fmt.Errorf("creating %v layer: %w", cacheLayer, err) } if err := java.CheckCacheExpiration(ctx, gradleCachedRepo); err != nil { return fmt.Errorf("validating the cache: %w", err) } homeGradle := filepath.Join(ctx.HomeDir(), ".gradle") // Symlink the gradle-cache layer into ~/.gradle. If ~/.gradle already exists, delete it first. // If it exists as a symlink, RemoveAll will remove the link, not anything it's linked to. if err := ctx.RemoveAll(homeGradle); err != nil { return err } if err := ctx.Symlink(gradleCachedRepo.Path, homeGradle); err != nil { return err } gradle, err := provisionOrDetectGradle(ctx) if err != nil { return err } command := []string{gradle, "clean", "assemble", "-x", "test", "--build-cache"} if buildArgs := os.Getenv(env.BuildArgs); buildArgs != "" { if strings.Contains(buildArgs, "project-cache-dir") { ctx.Warnf("Detected project-cache-dir property set in GOOGLE_BUILD_ARGS. Dependency caching may not work properly.") } command = append(command, buildArgs) } if gradleBuildArgs := os.Getenv(java.GradleBuildArgs); gradleBuildArgs != "" { command = append([]string{gradle}, strings.Fields(gradleBuildArgs)...) } if !ctx.Debug() && !devmode.Enabled(ctx) { command = append(command, "--quiet") } if _, err := ctx.Exec(command, gcp.WithUserAttribution); err != nil { return err } // Store the build steps in a script to be run on each file change. if devmode.Enabled(ctx) { devmode.WriteBuildScript(ctx, gradleCachedRepo.Path, "~/.gradle", command) } return nil } func provisionOrDetectGradle(ctx *gcp.Context) (string, error) { gradlewExists, err := ctx.FileExists("gradlew") if err != nil { return "", err } if gradlewExists { // With CRLF endings, the "\r" gets seen as part of the shebang target, which doesn't exist. if err := fileutil.EnsureUnixLineEndings("gradlew"); err != nil { return "", fmt.Errorf("ensuring unix newline characters: %w", err) } return "./gradlew", nil } installed, err := gradleInstalled(ctx) if err != nil { return "", err } if installed { return "gradle", nil } gradle, err := installGradle(ctx) if err != nil { return "", fmt.Errorf("installing Gradle: %w", err) } return gradle, nil } func gradleInstalled(ctx *gcp.Context) (bool, error) { result, err := ctx.Exec([]string{"bash", "-c", "command -v gradle || true"}) if err != nil { return false, err } return result.Stdout != "", nil } // installGradle installs Gradle and returns the path of the gradle binary func installGradle(ctx *gcp.Context) (string, error) { gradlel, err := ctx.Layer(gradleLayer, gcp.CacheLayer, gcp.BuildLayer, gcp.LaunchLayerIfDevMode) if err != nil { return "", fmt.Errorf("creating %v layer: %w", gradleLayer, err) } metaVersion := ctx.GetMetadata(gradlel, versionKey) // Check the metadata in the cache layer to determine if we need to proceed. gradleVersion, err := java.GetLatestGradleVersion() if err != nil { return "", fmt.Errorf("getting latest gradle version: %w", err) } if gradleVersion == metaVersion { ctx.CacheHit(gradleLayer) ctx.Logf("Gradle cache hit, skipping installation.") return filepath.Join(gradlel.Path, "bin", "gradle"), nil } ctx.CacheMiss(gradleLayer) if err := ctx.ClearLayer(gradlel); err != nil { return "", fmt.Errorf("clearing layer %q: %w", gradlel.Name, err) } downloadURL := fmt.Sprintf(gradleDistroURL, gradleVersion) // Download and install gradle in layer. ctx.Logf("Installing Gradle v%s", gradleVersion) code, err := ctx.HTTPStatus(downloadURL) if err != nil { return "", err } if code != http.StatusOK { return "", fmt.Errorf("Gradle version %s does not exist at %s (status %d)", gradleVersion, downloadURL, code) } tmpDir := "/tmp" gradleZip := filepath.Join(tmpDir, "gradle.zip") defer ctx.RemoveAll(gradleZip) curl := fmt.Sprintf("curl --fail --show-error --silent --location --retry 3 %s --output %s", downloadURL, gradleZip) if _, err := ctx.Exec([]string{"bash", "-c", curl}); err != nil { return "", err } unzip := fmt.Sprintf("unzip -q %s -d %s", gradleZip, tmpDir) if _, err := ctx.Exec([]string{"bash", "-c", unzip}); err != nil { return "", err } gradleExtracted := filepath.Join(tmpDir, fmt.Sprintf("gradle-%s", gradleVersion)) defer ctx.RemoveAll(gradleExtracted) install := fmt.Sprintf("mv %s/* %s", gradleExtracted, gradlel.Path) if _, err := ctx.Exec([]string{"bash", "-c", install}); err != nil { return "", err } ctx.SetMetadata(gradlel, versionKey, gradleVersion) return filepath.Join(gradlel.Path, "bin", "gradle"), nil }