blob: e16cc3d3f67f7eb3e04f6d27b4604852bb4e3939 [file] [log] [blame]
#!/bin/bash
# Copyright 2023 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# build_linux_packages.sh
# One stop build of the project's Python packages for Linux. The Linux build is
# complicated because it has to be done via a docker container that has
# an LTS glibc version, all Python packages and other deps.
# This script handles all of those details.
#
# Usage:
# Build everything (all packages, all python versions):
# ./build_tools/build_linux_packages.sh
#
# Build specific Python versions and packages to custom directory:
# override_python_versions="cp38-cp38" \
# packages="plugins" \
# output_dir="/tmp/wheelhouse" \
# ./build_tools/ci/python_deploy/build_linux_packages.sh
#
# Valid Python versions match a subdirectory under /opt/python in the docker
# image. Typically:
# cp38-cp38 cp39-cp39 cp310-cp310
# Note that our Python packages are version independent so it is typical to
# build with the oldest supported Python vs multiples.
#
# Valid packages:
# plugins
# plugins_instrumented (currently does not work)
#
# Note that this script is meant to be run on CI and it will pollute both the
# output directory and in-tree build/ directories (under runtime/ and
# compiler/) with docker created, root owned builds. Sorry - there is
# no good way around it.
#
# It can be run on a workstation but recommend using a git worktree dedicated
# to packaging to avoid stomping on development artifacts.
set -xeu -o errtrace
this_dir="$(cd $(dirname $0) && pwd)"
script_name="$(basename $0)"
repo_root="$(cd "${this_dir}" && git rev-parse --show-toplevel)"
manylinux_docker_image="${manylinux_docker_image:-}"
python_versions="${override_python_versions:-cp310-cp310 cp311-cp311}"
output_dir="${output_dir:-${repo_root}/bindist}"
plugins="${plugins:-iree/integrations/pjrt/cpu/pjrt_plugin_iree_cpu.so iree/integrations/pjrt/cuda/pjrt_plugin_iree_cuda.so}"
packages="${packages:-plugins python-cpu-wheel python-cuda-wheel}"
package_suffix="${package_suffix:-}"
write_caches="${write_caches:-0}"
function run_on_host() {
echo "Running on host"
echo "Launching docker image ${manylinux_docker_image}"
if [ -z "${manylinux_docker_image}" ]; then
manylinux_docker_image="${manylinux_docker_image:-$(uname -m | awk '{print ($1 == "aarch64") ? "quay.io/pypa/manylinux_2_28_aarch64" : "gcr.io/iree-oss/manylinux2014_x86_64-release@sha256:e83893d35be4ce3558c989e9d5ccc4ff88d058bc3e74a83181059cc76e2cf1f8" }')}"
if [ -z "${manylinux_docker_image}" ]; then
echo "ERROR: Could not determine manylinux docker image"
exit 1
fi
echo "Using default docker image: $manylinux_docker_image"
fi
# Strip off the rest of the URL. Only the digest is load bearing anyway. If
# someone's not specifying the digest they may have a bad time, but the most
# likely scenario is just that the cache hits won't return anything. Actually
# writing to the cache requires access. It's surprisingly hard to get docker
# to tell you "give me the fully qualified name to the image you would use if
# I told you to `docker run` this", which is what we'd want to do this
# properly.
cache_key="${manylinux_docker_image##*/}"
# Canonicalize paths.
mkdir -p "${output_dir}"
output_dir="$(cd "${output_dir}" && pwd)"
echo "Outputting to ${output_dir}"
mkdir -p "${output_dir}"
# Mount one level up to get the entire workspace.
mount_dir="$(cd $repo_root/.. && pwd)"
docker run --rm \
-v "${mount_dir}:${mount_dir}" \
-v "${output_dir}:${output_dir}" \
-e __MANYLINUX_BUILD_WHEELS_IN_DOCKER=1 \
-e "override_python_versions=${python_versions}" \
-e "plugins=${plugins}" \
-e "packages=${packages}" \
-e "package_suffix=${package_suffix}" \
-e "output_dir=${output_dir}" \
-e "write_caches=${write_caches}" \
-e "cache_key=${cache_key}" \
-e "GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS:-}" \
"${manylinux_docker_image}" \
-- ${this_dir}/${script_name}
echo "******************** BUILD COMPLETE ********************"
echo "Generated binaries:"
ls -l "${output_dir}"
}
function run_in_docker() {
echo "Running in docker"
echo "Using python versions: ${python_versions}"
local orig_path="${PATH}"
# Build phase.
for package in ${packages}; do
echo "******************** BUILDING PACKAGE ${package} ********************"
for python_version in ${python_versions}; do
python_dir="/opt/python/${python_version}"
if ! [ -x "${python_dir}/bin/python" ]; then
echo "ERROR: Could not find python: ${python_dir} (skipping)"
continue
fi
cd $repo_root
export PATH="${python_dir}/bin:${orig_path}"
echo ":::: Python version $(python --version)"
echo "::: Running from $(pwd)"
echo "::: Installing CUDA SDK..."
cuda_sdk_dir="$($repo_root/../iree/third_party/nvidia_sdk_download/fetch_cuda_toolkit.py /tmp/cuda_sdk)"
echo "CUDA SDK installed at $cuda_sdk_dir"
echo "::: Installing python dependencies"
pip install -r requirements.txt
echo "::: Configuring bazel"
python configure.py --cuda-sdk-dir=${cuda_sdk_dir}
# replace dashes with underscores
declare -a bazel_flags=(
--compilation_mode=opt
--remote_cache="https://storage.googleapis.com/openxla-bazel-cache/${cache_key}"
)
if (( write_caches == 1 )); then
bazel_flags+=(--google_default_credentials)
else
bazel_flags+=(--noremote_upload_local_results)
fi
package_suffix="${package_suffix//-/_}"
case "${package}" in
plugins)
build_plugins
;;
plugins_instrumented)
build_plugins_instrumented
;;
python-cpu-wheel)
build_python_cpu_wheel
;;
python-cuda-wheel)
build_python_cuda_wheel
;;
*)
echo "Unrecognized package '${package}'"
exit 1
;;
esac
done
done
}
function build_plugins() {
local f
local dest="${output_dir}/pjrt_plugins"
mkdir -p $dest
bazel build "${bazel_flags[@]}" ${plugins}
for f in ${plugins}; do
cp -fv bazel-bin/$f $dest
done
}
function build_plugins_instrumented() {
# TODO: Currently does not compile.
local f
local dest="${output_dir}/pjrt_plugins_instrumented"
mkdir -p $dest
bazel build "${bazel_flags[@]}" --iree_enable_runtime_tracing ${plugins}
for f in ${plugins}; do
cp -fv bazel-bin/$f $dest
done
}
function build_python_cpu_wheel() {
# Note that these wheels are Python version independent. We build them
# with each listed Python version to make sure that the setup machinery
# works, but the most recent one persists.
bazel build "${bazel_flags[@]}" iree/integrations/pjrt/cpu/pjrt_plugin_iree_cpu.so
clean_wheels "openxla_pjrt_plugin_cpu${package_suffix}" "py3-none"
build_wheel python_packages/openxla_cpu_plugin
run_audit_wheel "openxla_pjrt_plugin_cpu${package_suffix}" "py3-none"
}
function build_python_cuda_wheel() {
# Note that these wheels are Python version independent. We build them
# with each listed Python version to make sure that the setup machinery
# works, but the most recent one persists.
bazel build -c opt iree/integrations/pjrt/cuda/pjrt_plugin_iree_cuda.so
clean_wheels "openxla_pjrt_plugin_cuda${package_suffix}" "py3-none"
build_wheel python_packages/openxla_cuda_plugin
run_audit_wheel "openxla_pjrt_plugin_cuda${package_suffix}" "py3-none"
}
function build_wheel() {
python -m pip wheel --disable-pip-version-check \
-f https://openxla.github.io/iree/pip-release-links.html \
-f https://storage.googleapis.com/jax-releases/jaxlib_nightly_releases.html \
-v -w "${output_dir}" "${repo_root}/$@"
}
function run_audit_wheel() {
local wheel_basename="$1"
local python_version="$2"
# Force wildcard expansion here
generic_wheel="$(echo "${output_dir}/${wheel_basename}-"*"-${python_version}-linux_x86_64.whl")"
ls "${generic_wheel}"
echo ":::: Auditwheel ${generic_wheel}"
auditwheel repair -w "${output_dir}" "${generic_wheel}"
rm -v "${generic_wheel}"
}
function clean_wheels() {
local wheel_basename="$1"
local python_version="$2"
echo ":::: Clean wheels ${wheel_basename} ${python_version}"
rm -f -v "${output_dir}/${wheel_basename}-"*"-${python_version}-"*".whl"
}
# Trampoline to the docker container if running on the host.
if [ -z "${__MANYLINUX_BUILD_WHEELS_IN_DOCKER-}" ]; then
run_on_host "$@"
else
run_in_docker "$@"
fi