diff --git a/go.mod b/go.mod index 120a29d9a..bb13b151b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.0 require ( github.com/blang/semver v3.5.1+incompatible - github.com/cloudfoundry/libbuildpack v0.0.0-20260306121953-8ab9253c8181 + github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938 github.com/cloudfoundry/switchblade v0.9.5 github.com/golang/mock v1.6.0 github.com/kr/text v0.2.0 diff --git a/go.sum b/go.sum index ad7c64b76..e8f2e4947 100644 --- a/go.sum +++ b/go.sum @@ -744,8 +744,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudfoundry/libbuildpack v0.0.0-20260306121953-8ab9253c8181 h1:di63teVid/uT+6TAqBSXFqxNM3sAbxk6hssYppZBvbw= -github.com/cloudfoundry/libbuildpack v0.0.0-20260306121953-8ab9253c8181/go.mod h1:Qtj1XicpoDn88w2cvVCYtw1Whq+kK3bouin0xNZ9lIU= +github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938 h1:hKWGdn8UAnlVtkNG+HnvEK9f8s2NDTpq48/HxKd0SSc= +github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938/go.mod h1:Qtj1XicpoDn88w2cvVCYtw1Whq+kK3bouin0xNZ9lIU= github.com/cloudfoundry/switchblade v0.9.5 h1:GTga1Uu6kGOL+n1TRTHyZm170N5/B/ou6wU90MiKKys= github.com/cloudfoundry/switchblade v0.9.5/go.mod h1:hIEQdGAsuNnzlyQfsD5OIORt38weSBar6Wq5/JX6Omo= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= diff --git a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/data.go b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/data.go index ff8c21ed9..81f8f59f2 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/data.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/data.go @@ -3,7 +3,6 @@ package bratshelper import ( "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -36,7 +35,7 @@ func InitBpData(stack string, stackAssociationSupported bool) *BpData { Data.BpDir, err = cutlass.FindRoot() Expect(err).NotTo(HaveOccurred()) - file, err := ioutil.ReadFile(filepath.Join(Data.BpDir, "manifest.yml")) + file, err := os.ReadFile(filepath.Join(Data.BpDir, "manifest.yml")) Expect(err).ToNot(HaveOccurred()) obj := make(map[string]interface{}) Expect(yaml.Unmarshal(file, &obj)).To(Succeed()) diff --git a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/language.go b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/language.go index ba85d2d4a..dfef2f68f 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/language.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/language.go @@ -1,7 +1,7 @@ package bratshelper import ( - "io/ioutil" + "os" "path/filepath" "github.com/cloudfoundry/libbuildpack/cutlass" @@ -13,7 +13,7 @@ var cachedLanguage string func bpLanguage() string { if cachedLanguage == "" { - file, err := ioutil.ReadFile(filepath.Join(bpDir(), "manifest.yml")) + file, err := os.ReadFile(filepath.Join(bpDir(), "manifest.yml")) Expect(err).ToNot(HaveOccurred()) obj := make(map[string]interface{}) Expect(yaml.Unmarshal(file, &obj)).To(Succeed()) diff --git a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/modify.go b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/modify.go index 8d3b2a276..432e6261a 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/modify.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/modify.go @@ -4,7 +4,6 @@ import ( "archive/zip" "bytes" "io" - "io/ioutil" "os" yaml "gopkg.in/yaml.v2" @@ -70,7 +69,7 @@ func modifyZipfile(path string, cb func(path string, r io.Reader) (io.Reader, er } defer r.Close() - newfile, err := ioutil.TempFile("", "buildpack.zip") + newfile, err := os.CreateTemp("", "buildpack.zip") if err != nil { return "", err } @@ -117,7 +116,7 @@ func modifyZipfile(path string, cb func(path string, r io.Reader) (io.Reader, er } func changeManifest(r io.Reader, cb func(*Manifest)) (io.Reader, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/utils.go b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/utils.go index 97062ba25..9cc52dc99 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/utils.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/bratshelper/utils.go @@ -1,7 +1,7 @@ package bratshelper import ( - "io/ioutil" + "os" "path/filepath" "time" @@ -22,7 +22,7 @@ func DestroyApp(app *cutlass.App) { func AddDotProfileScriptToApp(dir string) { profilePath := filepath.Join(dir, ".profile") - Expect(ioutil.WriteFile(profilePath, []byte(`#!/usr/bin/env bash + Expect(os.WriteFile(profilePath, []byte(`#!/usr/bin/env bash echo PROFILE_SCRIPT_IS_PRESENT_AND_RAN `), 0755)).To(Succeed()) } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/cf.go b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/cf.go index b05f8a14f..899430419 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/cf.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/cf.go @@ -5,7 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "os" "os/exec" @@ -254,7 +254,7 @@ func (a *App) SpaceGUID() (string, error) { if cfHome == "" { cfHome = os.Getenv("HOME") } - bytes, err := ioutil.ReadFile(filepath.Join(cfHome, ".cf", "config.json")) + bytes, err := os.ReadFile(filepath.Join(cfHome, ".cf", "config.json")) if err != nil { return "", err } @@ -466,7 +466,7 @@ func (a *App) Get(path string, headers map[string]string) (string, map[string][] return "", map[string][]string{}, err } defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return "", map[string][]string{}, err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/docker.go b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/docker.go index ef87b9c7b..51f2eb88d 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/docker.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/docker.go @@ -3,7 +3,6 @@ package cutlass import ( "fmt" "io" - "io/ioutil" "math/rand" "os" "os/exec" @@ -51,7 +50,7 @@ func InternetTrafficForNetwork(networkName, fixturePath, buildpackPath string, e session := DefaultLogger.Session("internet-traffic-for-network", data) session.Debug("preparing-docker-build-context") - tmpDir, err := ioutil.TempDir("", "docker-context") + tmpDir, err := os.MkdirTemp("", "docker-context") if err != nil { return nil, false, nil, fmt.Errorf("failed to create docker context directory: %s", err) } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/glow/archiver.go b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/glow/archiver.go index 486d97afc..27b02e937 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/glow/archiver.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/glow/archiver.go @@ -2,7 +2,7 @@ package glow import ( "fmt" - "io/ioutil" + "os" "path/filepath" "regexp" ) @@ -25,7 +25,7 @@ func NewArchiver(packager Packager) Archiver { } func (a Archiver) Archive(dir, stack, tag string, cached bool) (string, error) { - version, err := ioutil.ReadFile(filepath.Join(dir, "VERSION")) + version, err := os.ReadFile(filepath.Join(dir, "VERSION")) if err != nil { return "", err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/proxy.go b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/proxy.go index f588a54b8..5af10288e 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/proxy.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/proxy.go @@ -2,10 +2,10 @@ package cutlass import ( "fmt" - "io/ioutil" "net" "net/http/httptest" "net/url" + "os" "github.com/elazarl/goproxy" ) @@ -34,7 +34,7 @@ func main() { proxyURL, err := url.Parse(p.URL) listenMsg := fmt.Sprintf("Listening on Port: %s", proxyURL.Port()) fmt.Println(listenMsg) - if err := ioutil.WriteFile("server.log", []byte(listenMsg), 0644); err != nil { + if err := os.WriteFile("server.log", []byte(listenMsg), 0644); err != nil { fmt.Println("Failed to write to log") } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/test_helpers.go b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/test_helpers.go index 30cea8b12..ddef3a53b 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/test_helpers.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/test_helpers.go @@ -5,9 +5,7 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "log" - "math/rand" "os" "path/filepath" "strings" @@ -111,7 +109,7 @@ func PackageUniquelyVersionedBuildpack(stack string, stackAssociationSupported b return VersionedBuildpackPackage{}, fmt.Errorf("Failed to find root: %v", err) } - data, err := ioutil.ReadFile(filepath.Join(bpDir, "VERSION")) + data, err := os.ReadFile(filepath.Join(bpDir, "VERSION")) if err != nil { return VersionedBuildpackPackage{}, fmt.Errorf("Failed to read VERSION file: %v", err) } @@ -220,7 +218,7 @@ func CopyCfHome() error { if cf_home == "" { cf_home = os.Getenv("HOME") } - cf_home_new, err := ioutil.TempDir("", "cf-home-copy") + cf_home_new, err := os.MkdirTemp("", "cf-home-copy") if err != nil { return err } @@ -235,8 +233,6 @@ func CopyCfHome() error { } func SeedRandom() { - seed := int64(time.Now().Nanosecond() + os.Getpid()) - rand.Seed(seed) } func RemovePackagedBuildpack(buildpack VersionedBuildpackPackage) error { @@ -265,7 +261,7 @@ func readVersionFromZip(filePath string) (string, error) { return "", err } - out, err := ioutil.ReadAll(rc) + out, err := io.ReadAll(rc) if err != nil { return "", err diff --git a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/utils.go b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/utils.go index 172b8e089..aa13ba96b 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/cutlass/utils.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/cutlass/utils.go @@ -2,7 +2,6 @@ package cutlass import ( "io" - "io/ioutil" "math/rand" "os" "path/filepath" @@ -12,7 +11,7 @@ import ( ) func CopyFixture(srcDir string) (string, error) { - destDir, err := ioutil.TempDir("", "cutlass-fixture-copy") + destDir, err := os.MkdirTemp("", "cutlass-fixture-copy") if err != nil { return "", err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/installer.go b/vendor/github.com/cloudfoundry/libbuildpack/installer.go index 365b8d11b..dbbf34774 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/installer.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/installer.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -47,7 +46,7 @@ func (i *Installer) InstallDependency(dep Dependency, outputDir string) error { func (i *Installer) InstallDependencyWithStrip(dep Dependency, outputDir string, stripComponents int) error { i.manifest.log.BeginStep("Installing %s %s", dep.Name, dep.Version) - tmpDir, err := ioutil.TempDir("", "downloads") + tmpDir, err := os.MkdirTemp("", "downloads") if err != nil { return err } @@ -111,7 +110,7 @@ func (i *Installer) InstallDependencyWithStrip(dep Dependency, outputDir string, func (i *Installer) warnNewerPatch(dep Dependency) error { - v, err := semver.NewVersion(dep.Version) + v, err := semver.NewVersion(normalizeSemver(dep.Version)) if err != nil { return nil } @@ -132,6 +131,11 @@ func (i *Installer) warnNewerPatch(dep Dependency) error { latest, err := FindMatchingVersion(constraint, versions) if err != nil { + if normalizeSemver(dep.Version) != dep.Version { + // Versions with more than three fields were never checked before; + // don't let an unparsable sibling version fail their installation. + return nil + } return err } @@ -143,13 +147,13 @@ func (i *Installer) warnNewerPatch(dep Dependency) error { } func (i *Installer) warnEndOfLife(dep Dependency) error { - matchVersion := func(versionLine, depVersion string) bool { + matchSemver := func(versionLine, depVersion string) bool { return versionLine == depVersion } - v, err := semver.NewVersion(dep.Version) + v, err := semver.NewVersion(normalizeSemver(dep.Version)) if err == nil { - matchVersion = func(versionLine, depVersion string) bool { + matchSemver = func(versionLine, depVersion string) bool { constraint, err := semver.NewConstraint(versionLine) if err != nil { return false @@ -159,6 +163,16 @@ func (i *Installer) warnEndOfLife(dep Dependency) error { } } + matchVersion := func(versionLine, depVersion string) bool { + // Exact version lines such as "21.0.12+10" or "21.0.12.1" are matched + // like exact FindMatchingVersion constraints; semver would ignore the + // build number or fail to parse more than three fields. + if c, ok := isExactJEP322Constraint(versionLine); ok { + return matchesExactJEP322(c, depVersion) + } + return matchSemver(versionLine, depVersion) + } + for _, deprecation := range i.manifest.Deprecations { if deprecation.Name != dep.Name { continue diff --git a/vendor/github.com/cloudfoundry/libbuildpack/json.go b/vendor/github.com/cloudfoundry/libbuildpack/json.go index d6d124f82..7e747cb12 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/json.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/json.go @@ -3,7 +3,7 @@ package libbuildpack import ( "bytes" "encoding/json" - "io/ioutil" + "os" ) type JSON struct { @@ -30,7 +30,7 @@ func removeBOM(b []byte) []byte { } func (j *JSON) Load(file string, obj interface{}) error { - data, err := ioutil.ReadFile(file) + data, err := os.ReadFile(file) if err != nil { return err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/manifest.go b/vendor/github.com/cloudfoundry/libbuildpack/manifest.go index 0629e0533..3381cbb06 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/manifest.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/manifest.go @@ -2,7 +2,6 @@ package libbuildpack import ( "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -179,7 +178,7 @@ func (m *Manifest) Language() string { } func (m *Manifest) Version() (string, error) { - version, err := ioutil.ReadFile(filepath.Join(m.manifestRootDir, "VERSION")) + version, err := os.ReadFile(filepath.Join(m.manifestRootDir, "VERSION")) if err != nil { return "", fmt.Errorf("unable to read VERSION file %s", err) } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/packager/README.md b/vendor/github.com/cloudfoundry/libbuildpack/packager/README.md index cb626df5f..d4533c047 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/packager/README.md +++ b/vendor/github.com/cloudfoundry/libbuildpack/packager/README.md @@ -20,3 +20,142 @@ For more on go-bindata: https://github.com/jteeuwen/go-bindata ``` ginkgo -r ``` + +--- + +## Selective Dependency Packaging + +`buildpack-packager` supports building **cached** buildpacks that contain only a named +subset of dependencies. This is useful for operators who need a smaller artifact or a +pre-defined variant (e.g. a "minimal" build without optional agent libraries). + +> **Note:** `--profile`, `--exclude`, and `--include` are only valid for **cached** +> buildpacks (`--cached` flag). Using them on an uncached build is a hard error. + +### Packaging profiles + +A buildpack author defines named profiles in `manifest.yml` under the +`packaging_profiles` key: + +```yaml +packaging_profiles: + minimal: + description: "Core runtime only — no agents or profilers" + exclude: + - agent-dep + - profiler-dep + + no-profiler: + description: "Full build minus the profiler library" + exclude: + - profiler-dep +``` + +Each profile has: + +| Field | Type | Description | +|---------------|----------------|-------------| +| `description` | string | Human-readable summary shown by `buildpack-packager summary` | +| `exclude` | list of string | Dependency names to omit when this profile is active | + +Profile names must match `^[a-z0-9_-]+$` (lowercase letters, digits, hyphens, +underscores). This keeps names safe for embedding in zip filenames. + +All dependency names listed in a profile's `exclude` list must exist in the +manifest — a typo is a hard error at packaging time. + +### CLI flags + +| Flag | Argument | Description | +|------|----------|-------------| +| `--profile` | profile name | Activate a named profile from `manifest.yml` | +| `--exclude` | `dep1,dep2,...` | Additional dependencies to exclude (comma-separated) | +| `--include` | `dep1,dep2,...` | Restore dependencies that the active profile excluded | + +#### Examples + +```sh +# Build with the "minimal" profile (omits agent-dep and profiler-dep) +buildpack-packager --cached --stack cflinuxfs4 --profile minimal + +# Build with the "minimal" profile but restore profiler-dep +buildpack-packager --cached --stack cflinuxfs4 --profile minimal --include profiler-dep + +# No profile — just exclude a specific dependency +buildpack-packager --cached --stack cflinuxfs4 --exclude agent-dep + +# Combine a profile with an extra exclusion +buildpack-packager --cached --stack cflinuxfs4 --profile no-profiler --exclude agent-dep +``` + +### Resolution order + +1. Profile's `exclude` list is applied first. +2. `--exclude` names are **unioned** with the profile exclusions. +3. `--include` names are **removed** from the exclusion set (overrides the profile). + +Excluded dependencies are neither downloaded nor written into the packaged +`manifest.yml`. + +### Output filename + +The zip filename encodes which variant was built: + +| Options used | Filename pattern | +|---|---| +| No opts | `_buildpack-cached--v.zip` | +| `--profile minimal` | `_buildpack-cached-minimal--v.zip` | +| `--profile minimal --include profiler-dep` | `_buildpack-cached-minimal+custom--v.zip` | +| `--profile minimal --exclude extra-dep` | `_buildpack-cached-minimal+custom--v.zip` | +| `--exclude agent-dep` (no profile) | `_buildpack-cached-custom--v.zip` | + +The `+custom` suffix appears only when the result deviates from a pure profile: +either an extra `--exclude` was added, or `--include` actually overrode one of +the profile's exclusions. + +### Error conditions + +All validation errors are **hard errors** — the packager exits non-zero with a +descriptive message. There are no silent no-ops or warnings. + +| Situation | Error message | +|---|---| +| `--profile` / `--exclude` / `--include` on uncached build | `--profile/--exclude/--include are only valid for cached buildpacks` | +| `--include` without `--profile` | `--include requires --profile` | +| Unknown profile name | `packaging profile "" not found in manifest` | +| Invalid profile name characters | `profile name "" is invalid: must match ^[a-z0-9_-]+$` | +| Unknown dep in `--exclude` | `dependency "" not found in manifest` | +| Unknown dep in `--include` | `dependency "" not found in manifest` | +| `--include` of dep not excluded by profile | `--include "" has no effect: dependency is not excluded by the profile or --exclude` | +| Profile's `exclude` list references unknown dep | `profile "" references unknown dependency ""` | + +### Go API + +`Package()` is unchanged and delegates to `PackageWithOptions` with zero options: + +```go +// Legacy — unchanged behaviour +zipFile, err := packager.Package(bpDir, cacheDir, version, stack, cached) + +// New — selective packaging +zipFile, err := packager.PackageWithOptions(bpDir, cacheDir, version, stack, true, + packager.PackageOptions{ + Profile: "minimal", + Include: []string{"profiler-dep"}, + }) +``` + +`PackageOptions` fields: + +```go +type PackageOptions struct { + // Profile is a packaging_profiles key from manifest.yml. + Profile string + // Exclude lists additional dependency names to skip. + Exclude []string + // Include restores dependency names excluded by Profile. + // Requires Profile to be set. Hard error if a name was not excluded. + Include []string +} +``` + diff --git a/vendor/github.com/cloudfoundry/libbuildpack/packager/bindata.go b/vendor/github.com/cloudfoundry/libbuildpack/packager/bindata.go index 7ec30cca3..4a2eb0923 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/packager/bindata.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/packager/bindata.go @@ -41,7 +41,6 @@ import ( "compress/gzip" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -995,7 +994,7 @@ func RestoreAsset(dir, name string) error { if err != nil { return err } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + err = os.WriteFile(_filePath(dir, name), data, info.Mode()) if err != nil { return err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/packager/cfbindata.go b/vendor/github.com/cloudfoundry/libbuildpack/packager/cfbindata.go index 56c17e599..85ead2f15 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/packager/cfbindata.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/packager/cfbindata.go @@ -8,7 +8,6 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -57,7 +56,7 @@ func OurRestoreAsset(dir, name string, funcMap template.FuncMap, shas map[string } oldSha256 := "" - if oldContents, err := ioutil.ReadFile(_filePath(dir, name)); err == nil { + if oldContents, err := os.ReadFile(_filePath(dir, name)); err == nil { oldSha256 = checksumHex(oldContents) } @@ -72,7 +71,7 @@ func OurRestoreAsset(dir, name string, funcMap template.FuncMap, shas map[string return err } - if err := ioutil.WriteFile(_filePath(dir, name), b.Bytes(), info.Mode()); err != nil { + if err := os.WriteFile(_filePath(dir, name), b.Bytes(), info.Mode()); err != nil { return err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/packager/models.go b/vendor/github.com/cloudfoundry/libbuildpack/packager/models.go index e9150d919..d4b309f0a 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/packager/models.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/packager/models.go @@ -15,6 +15,12 @@ type Dependency struct { type SubDependency struct{ Name string } type Dependencies []Dependency +// PackagingProfile defines a named dependency exclusion set for use at packaging time. +type PackagingProfile struct { + Description string `yaml:"description"` + Exclude []string `yaml:"exclude"` +} + type Manifest struct { Language string `yaml:"language"` Stack string `yaml:"stack"` @@ -25,6 +31,7 @@ type Manifest struct { Name string `yaml:"name"` Version string `yaml:"version"` } `yaml:"default_versions"` + PackagingProfiles map[string]PackagingProfile `yaml:"packaging_profiles"` } type File struct { diff --git a/vendor/github.com/cloudfoundry/libbuildpack/packager/packager.go b/vendor/github.com/cloudfoundry/libbuildpack/packager/packager.go index 755aa71a8..b0c61b049 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/packager/packager.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/packager/packager.go @@ -9,25 +9,29 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "log" "net/http" "net/url" "os" "os/exec" "path/filepath" + "regexp" "strings" "github.com/cloudfoundry/libbuildpack" "gopkg.in/yaml.v2" ) +// profileNameRe restricts profile names to safe characters that can be +// embedded in a zip filename without escaping or path-traversal risk. +var profileNameRe = regexp.MustCompile(`^[a-z0-9_-]+$`) + type sha struct { Sha map[string]string `yaml:"sha"` } func readManifest(bpDir string) (Manifest, error) { - data, err := ioutil.ReadFile(filepath.Join(bpDir, "manifest.yml")) + data, err := os.ReadFile(filepath.Join(bpDir, "manifest.yml")) if err != nil { return Manifest{}, err } @@ -53,7 +57,7 @@ func CompileExtensionPackage(bpDir, version string, cached bool, stack string) ( return "", fmt.Errorf("Failed to copy %s: %v", bpDir, err) } - err = ioutil.WriteFile(filepath.Join(dir, "VERSION"), []byte(version), 0644) + err = os.WriteFile(filepath.Join(dir, "VERSION"), []byte(version), 0644) if err != nil { return "", fmt.Errorf("Failed to write VERSION file: %v", err) } @@ -153,7 +157,92 @@ func downloadDependency(dependency Dependency, cacheDir string) (File, error) { return File{file, filepath.Join(cacheDir, file)}, nil } +// PackageOptions configures optional selective-packaging behaviour for PackageWithOptions. +// All fields are optional; zero values produce identical output to the legacy Package function. +type PackageOptions struct { + // Profile is the name of a packaging_profiles entry in manifest.yml. + // Its exclude list is applied before Exclude and Include are processed. + Profile string + // Exclude is an additional list of dependency names to skip, unioned with + // any exclusions implied by Profile. + Exclude []string + // Include restores specific dependency names that would otherwise be excluded + // by Profile. It is a no-op (with a warning) when Profile is empty. + Include []string +} + +// resolveExclusions returns the set of dependency names that should be skipped +// during packaging, and the count of include names that actually overrode a +// profile exclusion (used by the caller to decide the +custom filename suffix). +// Resolution order: +// 1. Profile's exclude list (if a profile is named). +// 2. Explicit Exclude names are unioned in. +// 3. Explicit Include names are removed (overrides profile exclusions). +// +// An error is returned if the profile name is unknown or if any exclude/include +// name does not exist in the manifest (including names in the profile's exclude +// list itself). +func resolveExclusions(manifest Manifest, profile string, exclude []string, include []string) (map[string]struct{}, int, error) { + // Build a set of all known dependency names for validation. + depNames := make(map[string]struct{}, len(manifest.Dependencies)) + for _, d := range manifest.Dependencies { + depNames[d.Name] = struct{}{} + } + + // 1. Start with profile exclusions. + result := make(map[string]struct{}) + if profile != "" { + if !profileNameRe.MatchString(profile) { + return nil, 0, fmt.Errorf("profile name %q is invalid: must match ^[a-z0-9_-]+$", profile) + } + p, ok := manifest.PackagingProfiles[profile] + if !ok { + return nil, 0, fmt.Errorf("packaging profile %q not found in manifest", profile) + } + for _, name := range p.Exclude { + if _, ok := depNames[name]; !ok { + return nil, 0, fmt.Errorf("profile %q references unknown dependency %q", profile, name) + } + result[name] = struct{}{} + } + } + + // 2. Union with explicitly excluded names. + for _, name := range exclude { + if _, ok := depNames[name]; !ok { + return nil, 0, fmt.Errorf("dependency %q not found in manifest", name) + } + result[name] = struct{}{} + } + + // 3. Remove explicitly included names (overrides profile). + // Count how many of these actually removed something from the set. + // It is a hard error to --include a name that was never excluded (likely a typo). + effectiveIncludes := 0 + for _, name := range include { + if _, ok := depNames[name]; !ok { + return nil, 0, fmt.Errorf("dependency %q not found in manifest", name) + } + if _, wasExcluded := result[name]; !wasExcluded { + return nil, 0, fmt.Errorf("--include %q has no effect: dependency is not excluded by the profile or --exclude", name) + } + effectiveIncludes++ + delete(result, name) + } + + return result, effectiveIncludes, nil +} + +// Package is the legacy entry point. It delegates to PackageWithOptions with +// zero-value options so all existing callers remain unaffected. func Package(bpDir, cacheDir, version, stack string, cached bool) (string, error) { + return PackageWithOptions(bpDir, cacheDir, version, stack, cached, PackageOptions{}) +} + +// PackageWithOptions creates a buildpack zip, optionally filtering dependencies +// according to opts (profile, exclude, include). When opts is zero-value the +// behaviour is identical to the legacy Package function. +func PackageWithOptions(bpDir, cacheDir, version, stack string, cached bool, opts PackageOptions) (string, error) { bpDir, err := filepath.Abs(bpDir) if err != nil { return "", err @@ -168,7 +257,7 @@ func Package(bpDir, cacheDir, version, stack string, cached bool) (string, error } defer os.RemoveAll(dir) - err = ioutil.WriteFile(filepath.Join(dir, "VERSION"), []byte(version), 0644) + err = os.WriteFile(filepath.Join(dir, "VERSION"), []byte(version), 0644) if err != nil { return "", err } @@ -178,6 +267,28 @@ func Package(bpDir, cacheDir, version, stack string, cached bool) (string, error return "", err } + // --profile/--exclude/--include only apply to cached buildpacks. + if !cached && (opts.Profile != "" || len(opts.Exclude) > 0 || len(opts.Include) > 0) { + return "", fmt.Errorf("--profile/--exclude/--include are only valid for cached buildpacks") + } + + // --include requires --profile (nothing to override otherwise). + if opts.Profile == "" && len(opts.Include) > 0 { + return "", fmt.Errorf("--include requires --profile") + } + + // Resolve which dependency names to skip before the download loop. + // On uncached builds the exclusion set is never used, so skip validation. + excluded := map[string]struct{}{} + effectiveIncludes := 0 + if cached { + var err2 error + excluded, effectiveIncludes, err2 = resolveExclusions(manifest, opts.Profile, opts.Exclude, opts.Include) + if err2 != nil { + return "", err2 + } + } + if manifest.PrePackage != "" { cmd := exec.Command(manifest.PrePackage) cmd.Dir = dir @@ -208,6 +319,12 @@ func Package(bpDir, cacheDir, version, stack string, cached bool) (string, error } dependenciesForStack := []interface{}{} for idx, d := range manifest.Dependencies { + // Skip excluded dependencies — they are not downloaded and are not + // written into the packaged manifest.yml. + if _, skip := excluded[d.Name]; skip { + continue + } + for _, s := range d.Stacks { if stack == "" || s == stack { dependencyMap := deps[idx] @@ -243,7 +360,21 @@ func Package(bpDir, cacheDir, version, stack string, cached bool) (string, error cachedPart = "-cached" } - fileName := fmt.Sprintf("%s_buildpack%s%s-v%s.zip", manifest.Language, cachedPart, stackPart, version) + // Build the profile/exclusion suffix for the filename. + // +custom is only appended when there is genuine customisation on top of + // the profile: either an extra --exclude, or an --include that actually + // overrode one of the profile's exclusions (effectiveIncludes > 0). + profilePart := "" + if opts.Profile != "" { + profilePart = "-" + opts.Profile + if len(opts.Exclude) > 0 || effectiveIncludes > 0 { + profilePart += "+custom" + } + } else if len(opts.Exclude) > 0 { + profilePart = "-custom" + } + + fileName := fmt.Sprintf("%s_buildpack%s%s%s-v%s.zip", manifest.Language, cachedPart, profilePart, stackPart, version) zipFile := filepath.Join(bpDir, fileName) if err := ZipFiles(zipFile, files); err != nil { @@ -304,7 +435,7 @@ func DownloadFromURI(uri, fileName string) error { } func checkSha256(filePath, expectedSha256 string) error { - content, err := ioutil.ReadFile(filePath) + content, err := os.ReadFile(filePath) if err != nil { return err } @@ -373,7 +504,7 @@ func ZipFiles(filename string, files []File) error { } func CopyDirectory(srcDir string) (string, error) { - destDir, err := ioutil.TempDir("", "buildpack-packager") + destDir, err := os.MkdirTemp("", "buildpack-packager") if err != nil { return "", err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/packager/summary.go b/vendor/github.com/cloudfoundry/libbuildpack/packager/summary.go index 468855092..67237f8d3 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/packager/summary.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/packager/summary.go @@ -2,7 +2,7 @@ package packager import ( "fmt" - "io/ioutil" + "os" "path/filepath" "sort" "strings" @@ -12,7 +12,7 @@ import ( func Summary(bpDir string) (string, error) { manifest := Manifest{} - data, err := ioutil.ReadFile(filepath.Join(bpDir, "manifest.yml")) + data, err := os.ReadFile(filepath.Join(bpDir, "manifest.yml")) if err != nil { return "", err } @@ -62,5 +62,17 @@ func Summary(bpDir string) (string, error) { } } + if len(manifest.PackagingProfiles) > 0 { + out += "\nPackaging profiles:\n\n" + profileNames := make([]string, 0, len(manifest.PackagingProfiles)) + for name := range manifest.PackagingProfiles { + profileNames = append(profileNames, name) + } + sort.Strings(profileNames) + for _, name := range profileNames { + out += fmt.Sprintf(" %-12s %s\n", name, manifest.PackagingProfiles[name].Description) + } + } + return out, nil } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/stager.go b/vendor/github.com/cloudfoundry/libbuildpack/stager.go index f9c63e3f7..38a9d54a8 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/stager.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/stager.go @@ -3,7 +3,6 @@ package libbuildpack import ( "errors" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -95,7 +94,7 @@ func (s *Stager) WriteEnvFile(envVar, envVal string) error { } - return ioutil.WriteFile(filepath.Join(envDir, envVar), []byte(envVal), 0644) + return os.WriteFile(filepath.Join(envDir, envVar), []byte(envVal), 0644) } func (s *Stager) LinkDirectoryInDepDir(destDir, depSubDir string) error { @@ -104,7 +103,7 @@ func (s *Stager) LinkDirectoryInDepDir(destDir, depSubDir string) error { return err } - files, err := ioutil.ReadDir(destDir) + files, err := os.ReadDir(destDir) if err != nil { return err } @@ -153,7 +152,7 @@ func (s *Stager) StagingComplete() { } func (s *Stager) ClearCache() error { - files, err := ioutil.ReadDir(s.cacheDir) + files, err := os.ReadDir(s.cacheDir) if err != nil { if os.IsNotExist(err) { return nil @@ -172,7 +171,7 @@ func (s *Stager) ClearCache() error { } func (s *Stager) ClearDepDir() error { - files, err := ioutil.ReadDir(s.DepDir()) + files, err := os.ReadDir(s.DepDir()) if err != nil { return err } @@ -234,14 +233,14 @@ func (s *Stager) SetStagingEnvironment() error { } for _, dir := range depsPaths { - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { return err } for _, file := range files { - if file.Mode().IsRegular() { - val, err := ioutil.ReadFile(filepath.Join(dir, file.Name())) + if file.Type().IsRegular() { + val, err := os.ReadFile(filepath.Join(dir, file.Name())) if err != nil { return err } @@ -293,13 +292,13 @@ func (s *Stager) SetLaunchEnvironment() error { depsIdx := sections[len(sections)-2] - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { return err } for _, file := range files { - if file.Mode().IsRegular() { + if file.Type().IsRegular() { src := filepath.Join(dir, file.Name()) dest := filepath.Join(s.profileDir, depsIdx+"_"+file.Name()) @@ -322,7 +321,7 @@ func (s *Stager) BuildpackVersion() (string, error) { } func existingDepsDirs(depsDir, subDir, prefix string) ([]string, error) { - files, err := ioutil.ReadDir(depsDir) + files, err := os.ReadDir(depsDir) if err != nil { return nil, err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/util.go b/vendor/github.com/cloudfoundry/libbuildpack/util.go index 686103266..93b442863 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/util.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/util.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "math/rand" "net/http" "net/url" @@ -22,17 +21,13 @@ import ( backoff "github.com/cenkalti/backoff/v4" ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - func MoveDirectory(srcDir, destDir string) error { destExists, _ := FileExists(destDir) if !destExists { return os.Rename(srcDir, destDir) } - files, err := ioutil.ReadDir(srcDir) + files, err := os.ReadDir(srcDir) if err != nil { return err } @@ -43,7 +38,7 @@ func MoveDirectory(srcDir, destDir string) error { if exists, err := FileExists(dest); err != nil { return err } else if !exists { - if m := f.Mode(); m&os.ModeSymlink != 0 { + if m := f.Type(); m&os.ModeSymlink != 0 { if err = moveSymlinks(src, dest); err != nil { return err } @@ -69,7 +64,7 @@ func CopyDirectory(srcDir, destDir string) error { return errors.New("destination dir must exist") } - files, err := ioutil.ReadDir(srcDir) + files, err := os.ReadDir(srcDir) if err != nil { return err } @@ -78,12 +73,16 @@ func CopyDirectory(srcDir, destDir string) error { src := filepath.Join(srcDir, f.Name()) dest := filepath.Join(destDir, f.Name()) - if m := f.Mode(); m&os.ModeSymlink != 0 { + if m := f.Type(); m&os.ModeSymlink != 0 { if err = moveSymlinks(src, dest); err != nil { return err } } else if f.IsDir() { - err = os.MkdirAll(dest, f.Mode()) + fi, err := f.Info() + if err != nil { + return err + } + err = os.MkdirAll(dest, fi.Mode()) if err != nil { return err } @@ -96,7 +95,13 @@ func CopyDirectory(srcDir, destDir string) error { return err } - err = writeToFile(rc, dest, f.Mode()) + fi, err := f.Info() + if err != nil { + rc.Close() + return err + } + + err = writeToFile(rc, dest, fi.Mode()) if err != nil { rc.Close() return err @@ -500,7 +505,7 @@ func filterURI(rawURL string) (string, error) { } func CheckSha256(filePath, expectedSha256 string) error { - content, err := ioutil.ReadFile(filePath) + content, err := os.ReadFile(filePath) if err != nil { return err } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/versions.go b/vendor/github.com/cloudfoundry/libbuildpack/versions.go index f510997e0..0b9f34bc9 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/versions.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/versions.go @@ -2,7 +2,10 @@ package libbuildpack import ( "fmt" + "regexp" "sort" + "strconv" + "strings" semver2 "github.com/Masterminds/semver" semver1 "github.com/blang/semver" @@ -14,9 +17,163 @@ type versionWithOriginal struct { } type versionsWithOriginal []versionWithOriginal -func (v versionsWithOriginal) Len() int { return len(v) } -func (v versionsWithOriginal) Swap(i, j int) { v[i], v[j] = v[j], v[i] } -func (v versionsWithOriginal) Less(i, j int) bool { return v[i].version.LT(v[j].version) } +func (v versionsWithOriginal) Len() int { return len(v) } +func (v versionsWithOriginal) Swap(i, j int) { v[i], v[j] = v[j], v[i] } +func (v versionsWithOriginal) Less(i, j int) bool { + if v[i].version.EQ(v[j].version) { + return compareJEP322(v[i].original, v[j].original) < 0 + } + return v[i].version.LT(v[j].version) +} + +// jep322Regex matches numeric JEP 322 style versions: any number of numeric +// fields, optionally followed by a numeric build number, e.g. "21", "21.0.12", +// "21.0.12.1", "21.0.10.0.1", "21.0.12+10" or "21.0.12.1+1". +// See https://openjdk.org/jeps/322. +var jep322Regex = regexp.MustCompile(`^(\d+(?:\.\d+)*)(?:\+(\d+))?$`) + +type jep322Version struct { + fields []int + build int // -1 if absent +} + +// parseJEP322 parses a numeric JEP 322 style version. It returns false for +// anything else, e.g. versions with pre-release identifiers or non-numeric +// build metadata, which are left to the semver libraries. +func parseJEP322(ver string) (jep322Version, bool) { + m := jep322Regex.FindStringSubmatch(ver) + if m == nil { + return jep322Version{}, false + } + parts := strings.Split(m[1], ".") + v := jep322Version{fields: make([]int, len(parts)), build: -1} + for i, p := range parts { + n, err := strconv.Atoi(p) + if err != nil { + return jep322Version{}, false + } + v.fields[i] = n + } + if m[2] != "" { + n, err := strconv.Atoi(m[2]) + if err != nil { + return jep322Version{}, false + } + v.build = n + } + return v, true +} + +// compareFields compares numeric fields, treating missing fields as zero, +// so it reports "21.0.12" and "21.0.12.0" as equal. compareJEP322 breaks +// such ties by number of fields; matchesExactJEP322 requires equal counts. +func (a jep322Version) compareFields(b jep322Version) int { + for i := 0; i < len(a.fields) || i < len(b.fields); i++ { + var x, y int + if i < len(a.fields) { + x = a.fields[i] + } + if i < len(b.fields) { + y = b.fields[i] + } + if x != y { + if x < y { + return -1 + } + return 1 + } + } + return 0 +} + +// compareJEP322 is the tie-breaker for versions that are equal according to +// semver. It orders two numeric JEP 322 versions by all numeric fields +// (trailing zeros insignificant), then by numeric build number +// (absent < +1 < +2), and finally by number of fields so that ordering stays +// deterministic ("21.0.12" < "21.0.12.0"). Other versions (e.g. with +// non-numeric build metadata such as "21.0.12+foo") are equal to each other +// and sort before numeric JEP 322 versions, keeping the ordering transitive. +func compareJEP322(a, b string) int { + va, okA := parseJEP322(a) + vb, okB := parseJEP322(b) + if !okA || !okB { + switch { + case okA: + return 1 + case okB: + return -1 + default: + return 0 + } + } + if c := va.compareFields(vb); c != 0 { + return c + } + if va.build != vb.build { + if va.build < vb.build { + return -1 + } + return 1 + } + return len(va.fields) - len(vb.fields) +} + +// normalizeSemver truncates a numeric JEP 322 version with more than three +// fields (e.g. "21.0.12.1", "21.0.12.1+1" or "21.0.10.0.1") to its first three +// fields ("21.0.12") so that the semver libraries can parse it. The original +// string is preserved separately for output and ordering (see compareJEP322). +// All other strings, including 3-part versions with build metadata +// ("21.0.12+10") and pre-release versions ("8.0.100-preview.7.23376.3"), are +// returned unchanged. +func normalizeSemver(ver string) string { + if v, ok := parseJEP322(ver); ok && len(v.fields) > 3 { + return fmt.Sprintf("%d.%d.%d", v.fields[0], v.fields[1], v.fields[2]) + } + return ver +} + +// isExactJEP322Constraint reports whether constraint is an exact numeric +// version that the semver libraries cannot handle correctly as a constraint: +// more than three fields ("21.0.12.1") or an explicit build number +// ("21.0.12+10", "21.0.12.1+1"). +func isExactJEP322Constraint(constraint string) (jep322Version, bool) { + v, ok := parseJEP322(constraint) + if !ok || (len(v.fields) <= 3 && v.build < 0) { + return jep322Version{}, false + } + return v, true +} + +// matchesExactJEP322 reports whether ver equals the exact constraint c. The +// numeric fields must be identical, including their number ("21.0.12.0" does +// not match "21.0.12"). Without a build number in the constraint any build +// matches ("21.0.12.1" matches "21.0.12.1+1" and "21.0.12.1+2"); with one, +// the build must be equal. +func matchesExactJEP322(c jep322Version, ver string) bool { + v, ok := parseJEP322(ver) + if !ok || len(v.fields) != len(c.fields) || v.compareFields(c) != 0 { + return false + } + return c.build < 0 || v.build == c.build +} + +// matchExactJEP322 returns all versions matching the exact constraint, see +// matchesExactJEP322. +func matchExactJEP322(constraint string, c jep322Version, versions []string) ([]string, error) { + var matched []string + for _, ver := range versions { + if matchesExactJEP322(c, ver) { + matched = append(matched, ver) + } + } + if len(matched) == 0 { + return []string{}, fmt.Errorf("no match found for %s in %v", constraint, versions) + } + sort.SliceStable(matched, func(i, j int) bool { + return compareJEP322(matched[i], matched[j]) < 0 + }) + return matched, nil +} func FindMatchingVersion(constraint string, versions []string) (string, error) { vs, err := FindMatchingVersions(constraint, versions) @@ -27,6 +184,13 @@ func FindMatchingVersion(constraint string, versions []string) (string, error) { } func FindMatchingVersions(constraint string, versions []string) ([]string, error) { + // Short-circuit: exact JEP 322 version constraint. Neither blang/semver nor + // Masterminds/semver accept more than three fields, and both ignore build + // metadata when comparing. + if c, ok := isExactJEP322Constraint(constraint); ok { + return matchExactJEP322(constraint, c, versions) + } + matchedVersions, err := matchSemver1(constraint, versions) if err == nil { return matchedVersions, nil @@ -43,17 +207,16 @@ func matchSemver1(constraint string, versions []string) ([]string, error) { } for _, ver := range versions { - depVersion, err := semver1.Parse(ver) + depVersion, err := semver1.Parse(normalizeSemver(ver)) if err != nil { return []string{}, err } - versionWithOriginal := versionWithOriginal{ - original: ver, - version: depVersion, - } if versionConstraint(depVersion) { - depVersions = append(depVersions, versionWithOriginal) + depVersions = append(depVersions, versionWithOriginal{ + original: ver, + version: depVersion, + }) } } @@ -70,28 +233,40 @@ func matchSemver1(constraint string, versions []string) ([]string, error) { } func matchSemver2(constraint string, versions []string) ([]string, error) { - var depVersions []*semver2.Version + type versionEntry struct { + original string + parsed *semver2.Version + } + var depVersions []versionEntry versionConstraint, err := semver2.NewConstraint(constraint) if err != nil { return []string{}, err } for _, ver := range versions { - depVersion, err := semver2.NewVersion(ver) + depVersion, err := semver2.NewVersion(normalizeSemver(ver)) if err != nil { return []string{}, err } if versionConstraint.Check(depVersion) { - depVersions = append(depVersions, depVersion) + depVersions = append(depVersions, versionEntry{ + original: ver, + parsed: depVersion, + }) } } if len(depVersions) != 0 { - sort.Sort(semver2.Collection(depVersions)) + sort.Slice(depVersions, func(i, j int) bool { + if depVersions[i].parsed.Equal(depVersions[j].parsed) { + return compareJEP322(depVersions[i].original, depVersions[j].original) < 0 + } + return depVersions[i].parsed.LessThan(depVersions[j].parsed) + }) var vs []string - for _, depV := range depVersions { - vs = append(vs, depV.Original()) + for _, e := range depVersions { + vs = append(vs, e.original) } return vs, nil } diff --git a/vendor/github.com/cloudfoundry/libbuildpack/yaml.go b/vendor/github.com/cloudfoundry/libbuildpack/yaml.go index 8cbe8cb75..368ce12d0 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/yaml.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/yaml.go @@ -2,7 +2,7 @@ package libbuildpack import ( "bytes" - "io/ioutil" + "os" yaml "gopkg.in/yaml.v2" ) @@ -15,7 +15,7 @@ func NewYAML() *YAML { } func (y *YAML) Load(file string, obj interface{}) error { - data, err := ioutil.ReadFile(file) + data, err := os.ReadFile(file) if err != nil { return err } diff --git a/vendor/modules.txt b/vendor/modules.txt index f136dabe7..32b587d36 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -20,7 +20,7 @@ github.com/blang/semver # github.com/cenkalti/backoff/v4 v4.3.0 ## explicit; go 1.18 github.com/cenkalti/backoff/v4 -# github.com/cloudfoundry/libbuildpack v0.0.0-20260306121953-8ab9253c8181 +# github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938 ## explicit; go 1.22.5 github.com/cloudfoundry/libbuildpack github.com/cloudfoundry/libbuildpack/ansicleaner