Make platform configurable instead of only auto-detecting

pull/1228/head
Simon Li 2023-01-02 12:11:34 +00:00
rodzic 064d91ae22
commit 8f3f57ada7
13 zmienionych plików z 80 dodań i 32 usunięć

Wyświetl plik

@ -15,6 +15,7 @@ import shutil
import sys import sys
import tempfile import tempfile
import time import time
import warnings
from urllib.parse import urlparse from urllib.parse import urlparse
import entrypoints import entrypoints
@ -36,7 +37,7 @@ from .buildpacks import (
RBuildPack, RBuildPack,
) )
from .engine import BuildError, ContainerEngineException, ImageLoadError from .engine import BuildError, ContainerEngineException, ImageLoadError
from .utils import ByteSpecification, R2dState, chdir from .utils import ByteSpecification, R2dState, chdir, get_platform
class Repo2Docker(Application): class Repo2Docker(Application):
@ -250,6 +251,27 @@ class Repo2Docker(Application):
config=True, config=True,
) )
platform = Unicode(
config=True,
help="""
Platform to build for, linux/amd64 (recommended) or linux/arm64 (experimental).
""",
)
@default("platform")
def _platform_default(self):
"""
Default platform
"""
p = get_platform()
if p == "linux/arm64":
warnings.warn(
"Building for linux/arm64 is experimental. "
"To use the recommended platform set --Repo2Docker.platform=linux/amd64. "
"To silence this warning set --Repo2Docker.platform=linux/arm64."
)
return p
extra_build_args = Dict( extra_build_args = Dict(
{}, {},
help=""" help="""
@ -778,6 +800,7 @@ class Repo2Docker(Application):
else: else:
picked_buildpack = self.default_buildpack() picked_buildpack = self.default_buildpack()
picked_buildpack.platform = self.platform
picked_buildpack.appendix = self.appendix picked_buildpack.appendix = self.appendix
# Add metadata labels # Add metadata labels
picked_buildpack.labels["repo2docker.version"] = self.version picked_buildpack.labels["repo2docker.version"] = self.version
@ -819,6 +842,7 @@ class Repo2Docker(Application):
build_args, build_args,
self.cache_from, self.cache_from,
self.extra_build_kwargs, self.extra_build_kwargs,
platform=self.platform,
): ):
if docker_client.string_output: if docker_client.string_output:
self.log.info(l, extra=dict(phase=R2dState.BUILDING)) self.log.info(l, extra=dict(phase=R2dState.BUILDING))

Wyświetl plik

@ -4,15 +4,11 @@ Base information for using R in BuildPacks.
Keeping this in r.py would lead to cyclic imports. Keeping this in r.py would lead to cyclic imports.
""" """
from ..semver import parse_version as V from ..semver import parse_version as V
from ..utils import get_platform
def rstudio_base_scripts(r_version): def rstudio_base_scripts(r_version):
"""Base steps to install RStudio and shiny-server.""" """Base steps to install RStudio and shiny-server."""
if get_platform() != "linux-64":
raise RuntimeError("RStudio is only available for linux-64")
# Shiny server (not the package!) seems to be the same version for all R versions # Shiny server (not the package!) seems to be the same version for all R versions
shiny_server_url = "https://download3.rstudio.org/ubuntu-14.04/x86_64/shiny-server-1.5.17.973-amd64.deb" shiny_server_url = "https://download3.rstudio.org/ubuntu-14.04/x86_64/shiny-server-1.5.17.973-amd64.deb"
shiny_proxy_version = "1.1" shiny_proxy_version = "1.1"

Wyświetl plik

@ -11,8 +11,6 @@ import textwrap
import escapism import escapism
import jinja2 import jinja2
from ..utils import get_platform
# Only use syntax features supported by Docker 17.09 # Only use syntax features supported by Docker 17.09
TEMPLATE = r""" TEMPLATE = r"""
FROM buildpack-deps:bionic FROM buildpack-deps:bionic
@ -231,7 +229,7 @@ class BuildPack:
"Windows environment detected. Note that Windows " "Windows environment detected. Note that Windows "
"support is experimental in repo2docker." "support is experimental in repo2docker."
) )
self.platform = get_platform() self.platform = ""
def get_packages(self): def get_packages(self):
""" """
@ -562,6 +560,7 @@ class BuildPack:
build_args, build_args,
cache_from, cache_from,
extra_build_kwargs, extra_build_kwargs,
platform=None,
): ):
tarf = io.BytesIO() tarf = io.BytesIO()
tar = tarfile.open(fileobj=tarf, mode="w") tar = tarfile.open(fileobj=tarf, mode="w")
@ -616,6 +615,7 @@ class BuildPack:
buildargs=build_args, buildargs=build_args,
container_limits=limits, container_limits=limits,
cache_from=cache_from, cache_from=cache_from,
platform=platform,
) )
build_kwargs.update(extra_build_kwargs) build_kwargs.update(extra_build_kwargs)

Wyświetl plik

@ -35,6 +35,14 @@ class CondaBuildPack(BaseImage):
# extra pip requirements.txt for the notebook env # extra pip requirements.txt for the notebook env
_nb_requirements_file = "" _nb_requirements_file = ""
def _conda_platform(self):
"""Return the conda platform name for the current platform"""
if self.platform == "linux/amd64":
return "linux-64"
if self.platform == "linux/arm64":
return "linux-aarch64"
raise ValueError(f"Unknown platform {self.platform}")
def get_build_env(self): def get_build_env(self):
"""Return environment variables to be set. """Return environment variables to be set.
@ -59,7 +67,7 @@ class CondaBuildPack(BaseImage):
# this exe should be used for installs after bootstrap with micromamba # this exe should be used for installs after bootstrap with micromamba
# switch this to /usr/local/bin/micromamba to use it for all installs # switch this to /usr/local/bin/micromamba to use it for all installs
("MAMBA_EXE", "${CONDA_DIR}/bin/mamba"), ("MAMBA_EXE", "${CONDA_DIR}/bin/mamba"),
("CONDA_PLATFORM", self.platform), ("CONDA_PLATFORM", self._conda_platform()),
] ]
if self._nb_requirements_file: if self._nb_requirements_file:
env.append(("NB_REQUIREMENTS_FILE", self._nb_requirements_file)) env.append(("NB_REQUIREMENTS_FILE", self._nb_requirements_file))
@ -161,7 +169,7 @@ class CondaBuildPack(BaseImage):
# major Python versions during upgrade. # major Python versions during upgrade.
# If no version is specified or no matching X.Y version is found, # If no version is specified or no matching X.Y version is found,
# the default base environment is used. # the default base environment is used.
frozen_name = f"environment-{self.platform}.lock" frozen_name = f"environment-{self._conda_platform()}.lock"
pip_frozen_name = "requirements.txt" pip_frozen_name = "requirements.txt"
if py_version: if py_version:
if self.python_version == "2.7": if self.python_version == "2.7":
@ -177,13 +185,15 @@ class CondaBuildPack(BaseImage):
self._kernel_requirements_file self._kernel_requirements_file
) = "/tmp/env/kernel-requirements.txt" ) = "/tmp/env/kernel-requirements.txt"
else: else:
py_frozen_name = f"environment.py-{py_version}-{self.platform}.lock" py_frozen_name = (
f"environment.py-{py_version}-{self._conda_platform()}.lock"
)
if os.path.exists(os.path.join(HERE, py_frozen_name)): if os.path.exists(os.path.join(HERE, py_frozen_name)):
frozen_name = py_frozen_name frozen_name = py_frozen_name
pip_frozen_name = f"requirements.py-{py_version}.pip" pip_frozen_name = f"requirements.py-{py_version}.pip"
else: else:
raise ValueError( raise ValueError(
f"Python version {py_version} {self.platform} is not supported!" f"Python version {py_version} {self._conda_platform()} is not supported!"
) )
files[ files[
"conda/" + frozen_name "conda/" + frozen_name
@ -369,6 +379,10 @@ class CondaBuildPack(BaseImage):
""", """,
) )
) )
if self.platform != "linux/amd64":
raise RuntimeError(
f"RStudio is only available for linux/amd64 ({self.platform})"
)
scripts += rstudio_base_scripts(self.r_version) scripts += rstudio_base_scripts(self.r_version)
scripts += [ scripts += [
( (

Wyświetl plik

@ -30,6 +30,7 @@ class DockerBuildPack(BuildPack):
build_args, build_args,
cache_from, cache_from,
extra_build_kwargs, extra_build_kwargs,
platform=None,
): ):
"""Build a Docker image based on the Dockerfile in the source repo.""" """Build a Docker image based on the Dockerfile in the source repo."""
# If you work on this bit of code check the corresponding code in # If you work on this bit of code check the corresponding code in
@ -55,6 +56,7 @@ class DockerBuildPack(BuildPack):
container_limits=limits, container_limits=limits,
cache_from=cache_from, cache_from=cache_from,
labels=self.get_labels(), labels=self.get_labels(),
platform=platform,
) )
build_kwargs.update(extra_build_kwargs) build_kwargs.update(extra_build_kwargs)

Wyświetl plik

@ -87,7 +87,7 @@ class JuliaProjectTomlBuildPack(PythonBuildPack):
For example, a tuple may be `('JULIA_VERSION', '0.6.0')`. For example, a tuple may be `('JULIA_VERSION', '0.6.0')`.
""" """
if self.platform == "linux-aarch64": if self.platform == "linux/arm64":
julia_arch = julia_arch_short = "aarch64" julia_arch = julia_arch_short = "aarch64"
else: else:
julia_arch = "x86_64" julia_arch = "x86_64"

Wyświetl plik

@ -77,7 +77,7 @@ class JuliaRequireBuildPack(PythonBuildPack):
For example, a tuple may be `('JULIA_VERSION', '0.6.0')`. For example, a tuple may be `('JULIA_VERSION', '0.6.0')`.
""" """
if self.platform == "linux-aarch64": if self.platform == "linux/arm64":
julia_arch = julia_arch_short = "aarch64" julia_arch = julia_arch_short = "aarch64"
else: else:
julia_arch = "x86_64" julia_arch = "x86_64"

Wyświetl plik

@ -31,7 +31,7 @@ class NixBuildPack(BaseImage):
- install nix package manager for user - install nix package manager for user
""" """
if self.platform == "linux-aarch64": if self.platform == "linux/arm64":
nix_arch = "aarch64" nix_arch = "aarch64"
else: else:
nix_arch = "x86_64" nix_arch = "x86_64"

Wyświetl plik

@ -5,7 +5,6 @@ import re
import requests import requests
from ..semver import parse_version as V from ..semver import parse_version as V
from ..utils import get_platform
from ._r_base import rstudio_base_scripts from ._r_base import rstudio_base_scripts
from .python import PythonBuildPack from .python import PythonBuildPack
@ -278,8 +277,10 @@ class RBuildPack(PythonBuildPack):
cran_mirror_url = self.get_cran_mirror_url(self.checkpoint_date) cran_mirror_url = self.get_cran_mirror_url(self.checkpoint_date)
if get_platform() != "linux-64": if self.platform != "linux/amd64":
raise RuntimeError("RStudio is only available for linux-64") raise RuntimeError(
f"RStudio is only available for linux/amd64 ({self.platform})"
)
scripts = [ scripts = [
( (
"root", "root",

Wyświetl plik

@ -8,7 +8,6 @@ from traitlets import Dict
import docker import docker
from .engine import Container, ContainerEngine, ContainerEngineException, Image from .engine import Container, ContainerEngine, ContainerEngineException, Image
from .utils import get_platform
class DockerContainer(Container): class DockerContainer(Container):
@ -92,14 +91,9 @@ class DockerEngine(ContainerEngine):
fileobj=None, fileobj=None,
path="", path="",
labels=None, labels=None,
platform=None,
**kwargs, **kwargs,
): ):
platform = get_platform()
if platform == "linux-aarch64":
docker_platform = "linux/arm64"
else:
docker_platform = "linux/amd64"
return self._apiclient.build( return self._apiclient.build(
buildargs=buildargs, buildargs=buildargs,
cache_from=cache_from, cache_from=cache_from,
@ -113,7 +107,7 @@ class DockerEngine(ContainerEngine):
fileobj=fileobj, fileobj=fileobj,
path=path, path=path,
labels=labels, labels=labels,
platform=docker_platform, platform=platform,
**kwargs, **kwargs,
) )

Wyświetl plik

@ -177,6 +177,7 @@ class ContainerEngine(LoggingConfigurable):
fileobj=None, fileobj=None,
path="", path="",
labels=None, labels=None,
platform=None,
**kwargs, **kwargs,
): ):
""" """
@ -207,6 +208,8 @@ class ContainerEngine(LoggingConfigurable):
path to the Dockerfile path to the Dockerfile
labels : dict labels : dict
Dictionary of labels to set on the image Dictionary of labels to set on the image
platform: str
Platform to build for
Returns Returns
------- -------

Wyświetl plik

@ -529,15 +529,15 @@ def is_local_pip_requirement(line):
def get_platform(): def get_platform():
"""Return the platform of the image """Return the target platform of the container image
Returns either `linux-64` or `linux-aarch64` Returns either `linux/amd64` or `linux/arm64`
""" """
m = platform.machine() m = platform.machine()
if m == "x86_64": if m == "x86_64":
return "linux-64" return "linux/amd64"
elif m == "aarch64": elif m == "aarch64":
return "linux-aarch64" return "linux/arm64"
else: else:
warnings.warn(f"Unexpected platform '{m}', defaulting to linux-64") warnings.warn(f"Unexpected platform '{m}', defaulting to linux/amd64")
return "linux-64" return "linux/amd64"

Wyświetl plik

@ -2,6 +2,7 @@
Tests for repo2docker/utils.py Tests for repo2docker/utils.py
""" """
import os import os
import platform
import subprocess import subprocess
import tempfile import tempfile
@ -160,3 +161,16 @@ def test_open_guess_encoding():
) )
def test_local_pip_requirement(req, is_local): def test_local_pip_requirement(req, is_local):
assert utils.is_local_pip_requirement(req) == is_local assert utils.is_local_pip_requirement(req) == is_local
@pytest.mark.parametrize(
"machine_name,expected",
[
("x86_64", "linux/amd64"),
("aarch64", "linux/arm64"),
("other", "linux/amd64"),
],
)
def test_get_platform(monkeypatch, machine_name, expected):
monkeypatch.setattr(platform, "machine", lambda: machine_name)
assert utils.get_platform() == expected