mirror of https://github.com/knative/func.git
Enable arm64 python buildpack (#2789)
* Enable Python buildpack build for arm64 Signed-off-by: Matej Vašek <mvasek@redhat.com> * Fix: force arm64 arch for Go buildpack Signed-off-by: Matej Vašek <mvasek@redhat.com> * Fix: ensure correct arch for sub-buildpacks Signed-off-by: Matej Vašek <mvasek@redhat.com> * Fixup linter issues Signed-off-by: Matej Vašek <mvasek@redhat.com> --------- Signed-off-by: Matej Vašek <mvasek@redhat.com>
This commit is contained in:
parent
8e0b752edc
commit
33b9f15654
|
@ -16,9 +16,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
@ -106,6 +108,19 @@ func buildBuilderImage(ctx context.Context, variant, version, arch, builderTomlP
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot fix Go buildpack: %w", err)
|
return "", fmt.Errorf("cannot fix Go buildpack: %w", err)
|
||||||
}
|
}
|
||||||
|
if variant == "base" {
|
||||||
|
err = fixupPythonBuildpackARM64(ctx, &builderConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("cannot fix Python buildpack: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by URI. This ensures locally build buildpacks (URI starting with 'file://') are last.
|
||||||
|
// This is needed so locally build "sub buildpacks" are not overridden by upstream buildpacks (with bad arch).
|
||||||
|
sort.Slice(builderConfig.Buildpacks, func(i, j int) bool {
|
||||||
|
a := builderConfig.Buildpacks[i].URI
|
||||||
|
b := builderConfig.Buildpacks[j].URI
|
||||||
|
return a < b
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = updateJavaBuildpacks(ctx, &builderConfig, arch)
|
err = updateJavaBuildpacks(ctx, &builderConfig, arch)
|
||||||
|
@ -279,7 +294,7 @@ func buildBuilderImageMultiArch(ctx context.Context, variant string) error {
|
||||||
"org.opencontainers.image.version": *release.Name,
|
"org.opencontainers.image.version": *release.Name,
|
||||||
}).(v1.ImageIndex)
|
}).(v1.ImageIndex)
|
||||||
for _, arch := range []string{"arm64", "amd64"} {
|
for _, arch := range []string{"arm64", "amd64"} {
|
||||||
if arch == "arm64" && variant != "tiny" {
|
if arch == "arm64" && variant == "full" {
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "skipping arm64 build for variant: %q\n", variant)
|
_, _ = fmt.Fprintf(os.Stderr, "skipping arm64 build for variant: %q\n", variant)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -726,7 +741,10 @@ func downloadTarball(tarballUrl, destDir string) error {
|
||||||
return fmt.Errorf("cannot read from tar reader: %w", err)
|
return fmt.Errorf("cannot read from tar reader: %w", err)
|
||||||
}
|
}
|
||||||
case tar.TypeSymlink:
|
case tar.TypeSymlink:
|
||||||
return fmt.Errorf("symlinks are not supported yet")
|
err = os.Symlink(hdr.Linkname, dest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create a symlink: %w", err)
|
||||||
|
}
|
||||||
case tar.TypeDir:
|
case tar.TypeDir:
|
||||||
err = os.MkdirAll(dest, 0755)
|
err = os.MkdirAll(dest, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -847,6 +865,18 @@ func fixupGoBuildpackARM64(ctx context.Context, config *builder.Config) error {
|
||||||
return fmt.Errorf("cannot download Go buildpack source code: %w", err)
|
return fmt.Errorf("cannot download Go buildpack source code: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set targets in Go package.toml to linux/arm64
|
||||||
|
f, err := os.OpenFile(filepath.Join(goBuildpackSrcDir, "package.toml"), os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot open package toml: %w", err)
|
||||||
|
}
|
||||||
|
targets := "\n[[targets]]\n arch = \"arm64\"\n os = \"linux\"\n"
|
||||||
|
_, err = f.Write([]byte(targets))
|
||||||
|
_ = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannout update package toml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cfgReader := buildpackage.NewConfigReader()
|
cfgReader := buildpackage.NewConfigReader()
|
||||||
packageConfig, err := cfgReader.Read(filepath.Join(goBuildpackSrcDir, "package.toml"))
|
packageConfig, err := cfgReader.Read(filepath.Join(goBuildpackSrcDir, "package.toml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1186,3 +1216,291 @@ func patchStack(stackTomlPath string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixupPythonBuildpackARM64(ctx context.Context, config *builder.Config) error {
|
||||||
|
var (
|
||||||
|
pythonBuildpackIndex int
|
||||||
|
pythonBuildpackVersion string
|
||||||
|
)
|
||||||
|
for i, moduleConfig := range config.Buildpacks {
|
||||||
|
uri := moduleConfig.URI
|
||||||
|
if strings.Contains(uri, "buildpacks/python:") {
|
||||||
|
pythonBuildpackIndex = i
|
||||||
|
pythonBuildpackVersion = uri[strings.LastIndex(uri, ":")+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pythonBuildpackVersion == "" {
|
||||||
|
return fmt.Errorf("python buildpack not found in the config")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDir, err := os.MkdirTemp("", "build-dir-*")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create temp dir: %w", err)
|
||||||
|
}
|
||||||
|
// sic! do not defer remove
|
||||||
|
|
||||||
|
pythonBuildpackSrcDir := filepath.Join(buildDir, "python")
|
||||||
|
|
||||||
|
pythonBuildpackRelease, err := getReleaseByVersion(ctx, "python", pythonBuildpackVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get Python release: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = downloadTarball(*pythonBuildpackRelease.TarballURL, pythonBuildpackSrcDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot download Python buildpack source code: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set targets in Python package.toml to linux/arm64
|
||||||
|
f, err := os.OpenFile(filepath.Join(pythonBuildpackSrcDir, "package.toml"), os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot open package toml: %w", err)
|
||||||
|
}
|
||||||
|
targets := "\n[[targets]]\n arch = \"arm64\"\n os = \"linux\"\n"
|
||||||
|
_, err = f.Write([]byte(targets))
|
||||||
|
_ = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannout update package toml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgReader := buildpackage.NewConfigReader()
|
||||||
|
packageConfig, err := cfgReader.Read(filepath.Join(pythonBuildpackSrcDir, "package.toml"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot read Python buildpack config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildBuildpack := func(name, version string) error {
|
||||||
|
srcDir := filepath.Join(buildDir, name)
|
||||||
|
cmd := exec.CommandContext(ctx, "./scripts/package.sh", "--version", version)
|
||||||
|
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||||
|
cmd.Dir = srcDir
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=arm64")
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("build of buildpack %q failed: %w", name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type patchSourceFn = func(srcDir string) error
|
||||||
|
// these buildpacks need rebuild since they are only amd64 in paketo upstream
|
||||||
|
needsRebuild := map[string]patchSourceFn{
|
||||||
|
"cpython": func(srcDir string) error {
|
||||||
|
return fixupCPythonDistPkgRefs(filepath.Join(srcDir, "buildpack.toml"))
|
||||||
|
},
|
||||||
|
"miniconda": func(srcDir string) error {
|
||||||
|
return fixupMinicondaDistPkgRefs(filepath.Join(srcDir, "buildpack.toml"))
|
||||||
|
},
|
||||||
|
"conda-env-update": nil,
|
||||||
|
"pip": nil,
|
||||||
|
"pip-install": nil,
|
||||||
|
"pipenv": nil,
|
||||||
|
"pipenv-install": nil,
|
||||||
|
"poetry": nil,
|
||||||
|
"poetry-install": nil,
|
||||||
|
"poetry-run": nil,
|
||||||
|
"python": nil,
|
||||||
|
"python-start": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`^urn:cnb:registry:paketo-buildpacks/([\w-]+)@([\d.]+)$`)
|
||||||
|
for i, dep := range packageConfig.Dependencies {
|
||||||
|
m := re.FindStringSubmatch(dep.URI)
|
||||||
|
if len(m) != 3 {
|
||||||
|
return fmt.Errorf("cannot match buildpack name")
|
||||||
|
}
|
||||||
|
buildpackName := m[1]
|
||||||
|
buildpackVersion := m[2]
|
||||||
|
|
||||||
|
patch, ok := needsRebuild[buildpackName]
|
||||||
|
if !ok {
|
||||||
|
// this dependency does not require rebuild for arm64
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var rel *github.RepositoryRelease
|
||||||
|
rel, err = getReleaseByVersion(ctx, buildpackName, buildpackVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get release: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcDir := filepath.Join(buildDir, buildpackName)
|
||||||
|
|
||||||
|
err = downloadTarball(*rel.TarballURL, srcDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get tarball: %w", err)
|
||||||
|
}
|
||||||
|
if patch != nil {
|
||||||
|
err = patch(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot patch source code: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buildBuildpack(buildpackName, buildpackVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
packageConfig.Dependencies[i].URI = "file://" + filepath.Join(srcDir, "build", "buildpackage.cnb")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := toml.Marshal(&packageConfig)
|
||||||
|
err = os.WriteFile(filepath.Join(pythonBuildpackSrcDir, "package.toml"), bs, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update package.toml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buildBuildpack("python", pythonBuildpackVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Buildpacks[pythonBuildpackIndex].URI = "file://" + filepath.Join(pythonBuildpackSrcDir, "build", "buildpackage.cnb")
|
||||||
|
fmt.Println(pythonBuildpackSrcDir)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixupCPythonDistPkgRefs(buildpackToml string) error {
|
||||||
|
tomlBytes, err := os.ReadFile(buildpackToml)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config any
|
||||||
|
err = toml.Unmarshal(tomlBytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := config.(map[string]any)["metadata"].(map[string]any)["dependencies"].([]map[string]any)
|
||||||
|
|
||||||
|
// Since there are no cpython arm64 packages we set uri to source.
|
||||||
|
// This will cause cpython compilation to be done during the build process.
|
||||||
|
// This takes a while, but it's done only once per project.
|
||||||
|
for _, dep := range deps {
|
||||||
|
dep["checksum"] = dep["source-checksum"]
|
||||||
|
dep["uri"] = dep["source"]
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := toml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(buildpackToml, bs, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixupMinicondaDistPkgRefs(buildpackToml string) error {
|
||||||
|
tomlBytes, err := os.ReadFile(buildpackToml)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config any
|
||||||
|
err = toml.Unmarshal(tomlBytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := config.(map[string]any)["metadata"].(map[string]any)["dependencies"].([]map[string]any)
|
||||||
|
for _, dep := range deps {
|
||||||
|
if dep["name"] == "Miniconda.sh" {
|
||||||
|
newURI := strings.ReplaceAll(dep["uri"].(string), "x86_64", "aarch64")
|
||||||
|
dep["uri"] = newURI
|
||||||
|
|
||||||
|
parts := strings.Split(newURI, "/")
|
||||||
|
basename := parts[len(parts)-1]
|
||||||
|
var sum string
|
||||||
|
sum, err = getMinicondaHash(basename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot find hash for the dependency: %w", err)
|
||||||
|
}
|
||||||
|
dep["sha256"] = sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := toml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(buildpackToml, bs, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMinicondaHash(basename string) (string, error) {
|
||||||
|
//nolint:bodyclose
|
||||||
|
resp, err := http.Get("https://repo.anaconda.com/miniconda/")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
_ = Body.Close()
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("bad http code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := html.Parse(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody := findNode(n, func(n *html.Node) bool {
|
||||||
|
return n.Data == "tbody"
|
||||||
|
})
|
||||||
|
if tbody == nil {
|
||||||
|
return "", fmt.Errorf("table body not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for tr := range tbody.ChildNodes() {
|
||||||
|
f, l := firstLastTD(tr)
|
||||||
|
if f != l &&
|
||||||
|
f != nil && f.FirstChild != nil && f.FirstChild.FirstChild != nil &&
|
||||||
|
l != nil && l.FirstChild != nil {
|
||||||
|
if f.FirstChild.FirstChild.Data == basename {
|
||||||
|
return l.FirstChild.Data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("hash not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNode(node *html.Node, pred func(*html.Node) bool) *html.Node {
|
||||||
|
if pred(node) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
for ch := range node.ChildNodes() {
|
||||||
|
n := findNode(ch, pred)
|
||||||
|
if n != nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstLastTD(tr *html.Node) (*html.Node, *html.Node) {
|
||||||
|
var f, l *html.Node
|
||||||
|
for ch := range tr.ChildNodes() {
|
||||||
|
if ch.Data == "td" {
|
||||||
|
if f == nil {
|
||||||
|
f = ch
|
||||||
|
}
|
||||||
|
l = ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, l
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue