|
5 | 5 | from textwrap import dedent |
6 | 6 | from subprocess import TimeoutExpired |
7 | 7 | from shlex import quote |
| 8 | +import platform |
8 | 9 |
|
9 | 10 | from alibuild_helpers.log import debug, error, dieOnError |
10 | 11 |
|
@@ -70,6 +71,52 @@ def execute(command, printer=debug, timeout=None): |
70 | 71 |
|
71 | 72 | BASH = "bash" if getstatusoutput("/bin/bash --version")[0] else "/bin/bash" |
72 | 73 |
|
| 74 | +class AppleContainerRunner: |
| 75 | + """A context manager for running commands inside a Apple Container (see https://github.com/apple/container) |
| 76 | + If the image given is None or empty, the commands are run on the host instead. |
| 77 | + """ |
| 78 | + def __init__(self, container_image, container_run_args, extra_env, extra_volumes) -> None: |
| 79 | + self._container_image = container_image |
| 80 | + self._container_run_args = container_run_args |
| 81 | + self._container = None |
| 82 | + self._extra_env = extra_env |
| 83 | + self._extra_volumes = extra_volumes |
| 84 | + |
| 85 | + def __enter__(self): |
| 86 | + def getstatusoutput_host(cmd, cwd=None): |
| 87 | + command_prefix="" |
| 88 | + if self._extra_env: |
| 89 | + command_prefix="env " + " ".join("{}={}".format(k, quote(v)) for (k,v) in self._extra_env.items()) + " " |
| 90 | + return getstatusoutput("{}{} -c {}".format(command_prefix, BASH, quote(cmd)) |
| 91 | + , cwd=cwd) |
| 92 | + |
| 93 | + if not self._container_image: |
| 94 | + return getstatusoutput_host |
| 95 | + |
| 96 | + envOpts = [opt for k, v in self._extra_env.items() for opt in ("-e", f"{k}={v}")] |
| 97 | + volumes = [opt for v in self._extra_volumes for opt in ("-v", v)] |
| 98 | + # Apple Container is more picky about missing entrypoints, so we always override it |
| 99 | + # with /bin/sleep. |
| 100 | + cmd = ["container", "run", "--detach"] + envOpts + volumes + ["--rm", "--entrypoint=/bin/sleep"] |
| 101 | + cmd += self._container_run_args |
| 102 | + cmd += [self._container_image, "inf"] |
| 103 | + self._container = getoutput(cmd).strip() |
| 104 | + |
| 105 | + def getstatusoutput_container(cmd, cwd=None): |
| 106 | + if self._container is None: |
| 107 | + return getstatusoutput_host(cmd, cwd=cwd) |
| 108 | + envOpts = [opt for k, v in self._extra_env.items() for opt in ("-e", f"{k}={v}")] |
| 109 | + exec_cmd = ["container", "exec"] + envOpts + [self._container, "bash", "-c", cmd] |
| 110 | + return getstatusoutput(exec_cmd, cwd=cwd) |
| 111 | + |
| 112 | + return getstatusoutput_container |
| 113 | + |
| 114 | + def __exit__(self, exc_type, exc_value, traceback): |
| 115 | + if self._container is not None: |
| 116 | + getstatusoutput("container kill " + quote(self._container)) |
| 117 | + self._container = None |
| 118 | + return False # propagate any exception that may have occurred |
| 119 | + |
73 | 120 |
|
74 | 121 | class DockerRunner: |
75 | 122 | """A context manager for running commands inside a Docker container. |
@@ -146,3 +193,85 @@ def install_wrapper_script(name, work_dir): |
146 | 193 | "rerunning this command inside a login shell (e.g. `bash -l`). " |
147 | 194 | "If that doesn't work, run `export PATH` manually.") |
148 | 195 | os.environ["PATH"] = script_dir + ":" + os.environ["PATH"] |
| 196 | + |
| 197 | +def to_int(s: str) -> int: |
| 198 | + try: |
| 199 | + return int(float(s)) |
| 200 | + except (ValueError, TypeError): |
| 201 | + return 0 |
| 202 | + |
| 203 | + |
| 204 | +def _apple_run_string(dockerImage, workDir, configDir, scriptDir, docker_extra_args, spec, specs, volumes, buildEnvironment): |
| 205 | + build_command = ( |
| 206 | + "container run --rm --entrypoint=/bin/bash --user $(id -u):$(id -g) " |
| 207 | + "--mount type=bind,source={workdir},target=/sw " |
| 208 | + "--mount type=bind,source={configDir},target=/alidist,readonly " |
| 209 | + "--mount type=bind,source={scriptDir},target=/scripts,readonly " |
| 210 | + "{mirrorVolume} {develVolumes} {additionalEnv} {additionalVolumes} " |
| 211 | + "-e WORK_DIR_OVERRIDE=/sw -e ALIBUILD_CONFIG_DIR_OVERRIDE=/alidist {extraArgs} {image} -ex /scripts/build.sh" |
| 212 | + ).format( |
| 213 | + image=quote(dockerImage), |
| 214 | + workdir=quote(os.path.abspath(workDir)), |
| 215 | + configDir=quote(os.path.abspath(configDir)), |
| 216 | + scriptDir=quote(scriptDir), |
| 217 | + extraArgs=" ".join(map(quote, docker_extra_args)), |
| 218 | + additionalEnv=" ".join( |
| 219 | + "-e {}={}".format(var, quote(value)) for var, value in buildEnvironment |
| 220 | + ), |
| 221 | + # Used e.g. by O2DPG-sim-tests to find the O2DPG repository. |
| 222 | + develVolumes=" ".join( |
| 223 | + '--mount type=bind,source="$PWD/$(readlink {pkg} || echo {pkg})",target=/{pkg},readonly'.format( |
| 224 | + pkg=quote(s["package"]) |
| 225 | + ) |
| 226 | + for s in specs.values() |
| 227 | + if s["is_devel_pkg"] |
| 228 | + ), |
| 229 | + additionalVolumes=" ".join("--mount type=bind,source=%s" % quote(volume) for volume in volumes), |
| 230 | + mirrorVolume=( |
| 231 | + "--mount source=%s,target=/mirror" % quote(os.path.dirname(spec["reference"])) |
| 232 | + if "reference" in spec |
| 233 | + else "" |
| 234 | + ), |
| 235 | + ) |
| 236 | + print(build_command) |
| 237 | + return build_command |
| 238 | + |
| 239 | +def _docker_run_string( dockerImage, workDir, configDir, scriptDir, docker_extra_args, spec, specs, volumes, buildEnvironment): |
| 240 | + build_command = ( |
| 241 | + "docker run --rm --entrypoint=/bin/bash --user $(id -u):$(id -g) " |
| 242 | + "-v {workdir}:/sw -v{configDir}:/alidist:ro -v {scriptDir}/build.sh:/build.sh:ro " |
| 243 | + "{mirrorVolume} {develVolumes} {additionalEnv} {additionalVolumes} " |
| 244 | + "-e WORK_DIR_OVERRIDE=/sw -e ALIBUILD_CONFIG_DIR_OVERRIDE=/alidist {extraArgs} --network=host {image} bash -ex /build.sh" |
| 245 | + ).format( |
| 246 | + image=quote(dockerImage), |
| 247 | + workdir=quote(os.path.abspath(workDir)), |
| 248 | + configDir=quote(os.path.abspath(configDir)), |
| 249 | + scriptDir=quote(scriptDir), |
| 250 | + extraArgs=" ".join(map(quote, docker_extra_args)), |
| 251 | + additionalEnv=" ".join( |
| 252 | + "-e {}={}".format(var, quote(value)) for var, value in buildEnvironment |
| 253 | + ), |
| 254 | + # Used e.g. by O2DPG-sim-tests to find the O2DPG repository. |
| 255 | + develVolumes=" ".join( |
| 256 | + '-v "$PWD/$(readlink {pkg} || echo {pkg})":/{pkg}:rw'.format( |
| 257 | + pkg=quote(spec["package"]) |
| 258 | + ) |
| 259 | + for spec in specs.values() |
| 260 | + if spec["is_devel_pkg"] |
| 261 | + ), |
| 262 | + additionalVolumes=" ".join("-v %s" % quote(volume) for volume in volumes), |
| 263 | + mirrorVolume=( |
| 264 | + "-v %s:/mirror" % quote(os.path.dirname(spec["reference"])) |
| 265 | + if "reference" in spec |
| 266 | + else "" |
| 267 | + ), |
| 268 | + ) |
| 269 | + return build_command |
| 270 | + |
| 271 | + |
| 272 | +if to_int(platform.mac_ver()[0].split(".")[0]) >= 26: |
| 273 | + ContainerRunner = AppleContainerRunner |
| 274 | + container_run_string = _apple_run_string |
| 275 | +else: |
| 276 | + ContainerRunner = DockerRunner |
| 277 | + container_run_string = _docker_run_string |
0 commit comments