blob: 8caf52b90bd489c749d3e098df9d77e6fac6bdd8 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Multi arch dockerized build tool.
"""
__author__ = 'Marco de Abreu, Kellen Sunderland, Anton Chernov, Pedro Larroy'
__version__ = '0.1'
import os
import sys
import subprocess
import logging
import argparse
from subprocess import check_call, call
import glob
import re
from typing import *
from itertools import chain
from copy import deepcopy
def get_platforms(path: Optional[str]="docker"):
"""Get a list of architectures given our dockerfiles"""
dockerfiles = glob.glob(os.path.join(path, "Dockerfile.build.*"))
dockerfiles = list(filter(lambda x: x[-1] != '~', dockerfiles))
files = list(map(lambda x: re.sub(r"Dockerfile.build.(.*)", r"\1", x), dockerfiles))
files.sort()
platforms = list(map(lambda x: os.path.split(x)[1], files))
return platforms
def get_docker_tag(platform: str) -> None:
return "mxnet/build.{0}".format(platform)
def get_dockerfile(platform: str, path="docker"):
return os.path.join(path, "Dockerfile.build.{0}".format(platform))
def get_docker_binary(use_nvidia_docker: bool):
if use_nvidia_docker:
return "nvidia-docker"
else:
return "docker"
def build_docker(platform: str, docker_binary: str) -> None:
"""Build a container for the given platform"""
tag = get_docker_tag(platform)
logging.info("Building container tagged '%s' with %s", tag, docker_binary)
cmd = [docker_binary, "build",
"-f", get_dockerfile(platform),
"--build-arg", "USER_ID={}".format(os.getuid()),
"-t", tag,
"docker"]
logging.info("Running command: '%s'", ' '.join(cmd))
check_call(cmd)
def get_mxnet_root() -> str:
curpath = os.path.abspath(os.path.dirname(__file__))
def is_mxnet_root(path: str) -> bool:
return os.path.exists(os.path.join(path, ".mxnet_root"))
while not is_mxnet_root(curpath):
parent = os.path.abspath(os.path.join(curpath, os.pardir))
if parent == curpath:
raise RuntimeError("Got to the root and couldn't find a parent folder with .mxnet_root")
curpath = parent
return curpath
def container_run(platform: str, docker_binary: str, command: List[str]) -> None:
tag = get_docker_tag(platform)
mx_root = get_mxnet_root()
local_build_folder = '{}/build'.format(mx_root)
# We need to create it first, otherwise it will be created by the docker daemon with root only permissions
os.makedirs(local_build_folder, exist_ok=True)
logging.info("Running %s in container %s", command, tag)
runlist = [docker_binary, 'run', '--rm',
'-v', "{}:/work/mxnet".format(mx_root), # mount mxnet root
'-v', "{}:/work/build".format(local_build_folder), # mount mxnet/build for storing build artifacts
'-u', '{}:{}'.format(os.getuid(), os.getgid()),
tag]
runlist.extend(command)
cmd = ' '.join(runlist)
logging.info("Executing: %s", cmd)
ret = call(runlist)
if ret != 0:
logging.error("Running of command in container failed: %s", cmd)
into_cmd = deepcopy(runlist)
idx = into_cmd.index('-u') + 2
into_cmd[idx:idx] = ['-ti', '--entrypoint', 'bash']
logging.error("You can try to get into the container by using the following command: %s", ' '.join(into_cmd))
raise subprocess.CalledProcessError(ret, cmd)
def main() -> int:
# We need to be in the same directory than the script so the commands in the dockerfiles work as
# expected. But the script can be invoked from a different path
base = os.path.split(os.path.realpath(__file__))[0]
os.chdir(base)
logging.getLogger().setLevel(logging.INFO)
def script_name() -> str:
return os.path.split(sys.argv[0])[1]
logging.basicConfig(format='{}: %(asctime)-15s %(message)s'.format(script_name()))
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--platform",
help="platform",
type=str)
parser.add_argument("-b", "--build",
help="Build the container",
action='store_true')
parser.add_argument("-n", "--nvidiadocker",
help="Use nvidia docker",
action='store_true')
parser.add_argument("-l", "--list",
help="List platforms",
action='store_true')
parser.add_argument("command",
help="command to run in the container",
nargs='*', action='append', type=str)
args = parser.parse_args()
command = list(chain(*args.command))
docker_binary = get_docker_binary(args.nvidiadocker)
if args.list:
platforms = get_platforms()
print(platforms)
elif args.platform:
platform = args.platform
if args.build:
build_docker(platform, docker_binary)
tag = get_docker_tag(platform)
if command:
container_run(platform, docker_binary, command)
else:
cmd = ["/work/mxnet/ci/docker/runtime_functions.sh", "build_{}".format(platform)]
logging.info("No command specified, trying default build: %s", ' '.join(cmd))
container_run(platform, docker_binary, cmd)
else:
platforms = get_platforms()
logging.info("Building for all architectures: {}".format(platforms))
logging.info("Artifacts will be produced in the build/ directory.")
for platform in platforms:
build_docker(platform, docker_binary)
cmd = ["/work/mxnet/ci/docker/runtime_functions.sh", "build_{}".format(platform)]
logging.info("No command specified, trying default build: %s", ' '.join(cmd))
container_run(platform, docker_binary, cmd)
return 0
if __name__ == '__main__':
sys.exit(main())