-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable architecture specific dependencies #622
base: v2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,10 @@ import ( | |
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"runtime" | ||
"sort" | ||
"strings" | ||
"time" | ||
|
@@ -52,15 +54,17 @@ type ErrNoDeps struct { | |
id string | ||
version string | ||
stack string | ||
arch string | ||
supportedVersions []string | ||
} | ||
|
||
// Error implements the error.Error interface | ||
func (e *ErrNoDeps) Error() string { | ||
return fmt.Sprintf("failed to satisfy %q dependency version constraint %q: no compatible versions on %q stack. Supported versions are: [%s]", | ||
return fmt.Sprintf("failed to satisfy %q dependency version constraint %q: no compatible versions on %q stack with architecture %q. Supported versions are: [%s]", | ||
e.id, | ||
e.version, | ||
e.stack, | ||
e.arch, | ||
strings.Join(e.supportedVersions, ", "), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same on this one we should add the os attribute There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we dont use the os, this comment does not apply. |
||
) | ||
} | ||
|
@@ -147,6 +151,10 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) { | |
continue | ||
} | ||
|
||
if !isCorrectArch(dependency) { | ||
continue | ||
} | ||
|
||
sVersion, err := semver.NewVersion(dependency.Version) | ||
if err != nil { | ||
return Dependency{}, err | ||
|
@@ -160,7 +168,7 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) { | |
} | ||
|
||
if len(compatibleVersions) == 0 { | ||
return Dependency{}, &ErrNoDeps{id, version, stack, supportedVersions} | ||
return Dependency{}, &ErrNoDeps{id, version, stack, archFromSystem(), supportedVersions} | ||
} | ||
|
||
stacksForVersion := map[string][]string{} | ||
|
@@ -220,6 +228,25 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) { | |
return compatibleVersions[0], nil | ||
} | ||
|
||
func isCorrectArch(dep Dependency) bool { | ||
systemArch := archFromSystem() | ||
|
||
if systemArch == "amd64" && dep.Arch == "" { | ||
return true | ||
} | ||
|
||
return systemArch == dep.Arch | ||
} | ||
|
||
func archFromSystem() string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In terms of clean code, one function should only do one thing, I would remove from this function, the two things that is doing 1. looking if the environment variable exists 2. fetching the system architecture. Changing that function to fetching only the architecture does not make sense as there is already a function for that. Therefore, I would suggest to move this logic out in to the Parent function ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we dont want to default into the system that the user is using as pack currently fallbacks to linux/amd64, so I would choose this platform as default, in order to avoid any errors from macos users that currently building applications There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I might be completely wrong, but that is not what is done. It looks up the architecture of the runtime, meaning the buildpack that actually called I tried to keep everything as similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, so seems that it is not breaking the default behavior. LGTM |
||
archFromEnv, ok := os.LookupEnv("BP_ARCH") | ||
if !ok { | ||
archFromEnv = runtime.GOARCH | ||
} | ||
|
||
return archFromEnv | ||
} | ||
|
||
func stringSliceContains(slice []string, str string) bool { | ||
for _, s := range slice { | ||
if s == str { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ import ( | |
//nolint Ignore SA1019, usage of deprecated package within a deprecated test case | ||
"github.com/paketo-buildpacks/packit/v2/paketosbom" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
|
@@ -115,6 +116,12 @@ strip-components = 1 | |
service = postal.NewService(transport). | ||
WithDependencyMappingResolver(mappingResolver). | ||
WithDependencyMirrorResolver(mirrorResolver) | ||
|
||
Expect(os.Setenv("BP_ARCH", "amd64")).To(Succeed()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On this one, instead of having BP_ARCH and BP_OS, we could rename the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we dont use the os, this comment does not apply. |
||
}) | ||
|
||
AfterEach(func() { | ||
Expect(os.Unsetenv("BP_ARCH")).To(Succeed()) | ||
}) | ||
|
||
context("Resolve", func() { | ||
|
@@ -310,6 +317,85 @@ version = "4.5.6" | |
}) | ||
}) | ||
|
||
context("when there architecture specific versions", func() { | ||
it.Before(func() { | ||
err := os.WriteFile(path, []byte(` | ||
[metadata] | ||
[metadata.default-versions] | ||
some-entry = "1.2.x" | ||
|
||
[[metadata.dependencies]] | ||
id = "some-entry" | ||
sha256 = "some-sha-amd64" | ||
stacks = ["*"] | ||
uri = "some-uri" | ||
version = "1.2.3" | ||
arch = "amd64" | ||
|
||
[[metadata.dependencies]] | ||
id = "some-entry" | ||
sha256 = "some-sha-arm" | ||
stacks = ["*"] | ||
uri = "some-uri" | ||
version = "1.2.3" | ||
arch = "arm" | ||
|
||
[[metadata.dependencies]] | ||
id = "some-other-entry" | ||
sha256 = "some-other-sha" | ||
stacks = ["*"] | ||
uri = "some-uri" | ||
version = "1.2.4" | ||
|
||
`), 0600) | ||
Expect(err).NotTo(HaveOccurred()) | ||
}) | ||
|
||
context("BP_ARCH=amd64", func() { | ||
it.Before(func() { | ||
Expect(os.Setenv("BP_ARCH", "amd64")).To(Succeed()) | ||
}) | ||
|
||
it.After(func() { | ||
Expect(os.Unsetenv("BP_ARCH")).To(Succeed()) | ||
}) | ||
|
||
it("picks the dependency with the correct arch", func() { | ||
dependency, err := service.Resolve(path, "some-entry", "1.2.3", "some-stack") | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(dependency.SHA256).To(Equal("some-sha-amd64")) | ||
}) | ||
|
||
it("picks the dependency with the no arch", func() { | ||
dependency, err := service.Resolve(path, "some-other-entry", "1.2.4", "some-stack") | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(dependency.SHA256).To(Equal("some-other-sha")) | ||
}) | ||
}) | ||
|
||
context("BP_ARCH=arm", func() { | ||
it.Before(func() { | ||
Expect(os.Setenv("BP_ARCH", "arm")).To(Succeed()) | ||
}) | ||
|
||
it.After(func() { | ||
Expect(os.Unsetenv("BP_ARCH")).To(Succeed()) | ||
}) | ||
|
||
it("picks the dependency with the correct arch", func() { | ||
dependency, err := service.Resolve(path, "some-entry", "1.2.3", "some-stack") | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(dependency.SHA256).To(Equal("some-sha-arm")) | ||
}) | ||
|
||
it("fails if no dependency with the correct arch is found", func() { | ||
_, err := service.Resolve(path, "some-other-entry", "1.2.4", "some-stack") | ||
Expect(err).To(MatchError(ContainSubstring("failed to satisfy"))) | ||
Expect(err).To(MatchError(ContainSubstring("\"arm\""))) | ||
}) | ||
}) | ||
}) | ||
|
||
context("when both a wildcard stack constraint and a specific stack constraint exist for the same dependency version", func() { | ||
it.Before(func() { | ||
err := os.WriteFile(path, []byte(` | ||
|
@@ -429,7 +515,7 @@ version = "1.2.3" | |
expectedErr := &postal.ErrNoDeps{} | ||
_, err := service.Resolve(path, "some-entry", "9.9.9", "some-stack") | ||
Expect(errors.As(err, &expectedErr)).To(BeTrue()) | ||
Expect(err).To(MatchError(ContainSubstring("failed to satisfy \"some-entry\" dependency version constraint \"9.9.9\": no compatible versions on \"some-stack\" stack. Supported versions are: [1.2.3, 4.5.6]"))) | ||
Expect(err).To(MatchError(ContainSubstring("failed to satisfy \"some-entry\" dependency version constraint \"9.9.9\": no compatible versions on \"some-stack\" stack with architecture \"amd64\". Supported versions are: [1.2.3, 4.5.6]"))) | ||
}) | ||
}) | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several platforms, for example, pack cli has plenty of them when it comes to build and image https://github.com/buildpacks/pack/blob/5cca9c51a61cf1bb8b0b6ba479bbbf10a65f0377/internal/target/platform.go#L31-L47 For nodejs we have a subset of them https://github.com/nodejs/node/blob/main/BUILDING.md#platform-list which I think it would be good to start with. So my suggestion on this one would be to also add another variable called
OS
oroperating-system
which will indicate the operating system of the dependencyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am no expert on the whole multiarch thing, but
operating-system
would be wrong, wouldn't it? The operating system is for all dependencieslinux
- it is the same run image after all. We "only" have to differ dependencies for the architecture of the image. So I thing anything other thanArch
would be quite confusing, don't you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that we might need to support generating images for macos, but currently there is no such use case so makes sense to not over-complicate it. LGTM