cmd/python/pip/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 python/pip buildpack.
// The pip buildpack installs dependencies using pip.
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
"github.com/GoogleCloudPlatform/buildpacks/pkg/python"
"github.com/buildpacks/libcnb/v2"
)
const (
layerName = "pip"
)
// metadata represents metadata stored for a dependencies layer.
type metadata struct {
PythonVersion string `toml:"python_version"`
DependencyHash string `toml:"dependency_hash"`
ExpiryTimestamp string `toml:"expiry_timestamp"`
}
func main() {
gcp.Main(detectFn, buildFn)
}
func detectFn(ctx *gcp.Context) (gcp.DetectResult, error) {
plan := libcnb.BuildPlan{Requires: python.RequirementsRequires}
// If a requirement.txt file exists, the buildpack needs to provide the Requirements dependency.
// If the dependency is not provided by any buildpacks, lifecycle will exclude the pip
// buildpack from the build.
requirementsExists, err := ctx.FileExists("requirements.txt")
if err != nil {
return nil, err
}
if requirementsExists {
plan.Provides = python.RequirementsProvides
}
return gcp.OptInAlways(gcp.WithBuildPlans(plan)), nil
}
func buildFn(ctx *gcp.Context) error {
// Remove leading and trailing : because otherwise SplitList will add empty strings.
reqs := filepath.SplitList(strings.Trim(os.Getenv(python.RequirementsFilesEnv), string(os.PathListSeparator)))
ctx.Debugf("Found requirements.txt files provided by other buildpacks: %s", reqs)
// The workspace requirements.txt file should be installed last.
requirementsExists, err := ctx.FileExists("requirements.txt")
if err != nil {
return err
}
if requirementsExists {
reqs = append(reqs, "requirements.txt")
}
l, err := ctx.Layer(layerName, gcp.BuildLayer, gcp.CacheLayer, gcp.LaunchLayer)
if err != nil {
return fmt.Errorf("creating %v layer: %w", layerName, err)
}
if err := python.InstallRequirements(ctx, l, reqs...); err != nil {
return fmt.Errorf("installing dependencies: %w", err)
}
ctx.Logf("Checking for incompatible dependencies.")
result, err := ctx.Exec([]string{"python3", "-m", "pip", "check"}, gcp.WithUserAttribution)
if result == nil {
return fmt.Errorf("pip check: %w", err)
}
if result.ExitCode == 0 {
return nil
}
pyVer, err := python.Version(ctx)
if err != nil {
return err
}
// HACK: For backwards compatibility on App Engine and Cloud Functions Python 3.7 only report a warning.
if strings.HasPrefix(pyVer, "Python 3.7") {
ctx.Warnf("Found incompatible dependencies: %q", result.Stdout)
return nil
}
return gcp.UserErrorf("found incompatible dependencies: %q", result.Stdout)
}