blob: 9af35a983fcf8159d61db27ad14a6da0d0aae3c0 [file]
#!/bin/bash
# Copyright 2022 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
# For deploying to PyPI, you will need to have credentials set up.
# Googlers can access the shared releasing account "google-iree-pypi-deploy"
# at http://go/iree-pypi-password
#
# Typical usage is to use keyring or create a ~/.pypirc file with:
#
# [pypi]
# username = __token__
# password = <<API TOKEN>>
#
# You must have `gh` installed and authenticated (run `gh auth`).
#
# Usage:
# python -m venv .venv
# source .venv/bin/activate
# python -m pip install -r ./pypi_deploy_requirements.txt
# ./pypi_deploy.sh iree-3.4.0rc20250430 # dry run
# ./pypi_deploy.sh --publish iree-3.4.0rc20250430 # upload to PyPI
#
# By default the script runs as a dry run: wheels are downloaded, promoted and
# validated with `twine check`, but nothing is uploaded. Pass --publish to
# upload to PyPI.
#
# To publish the wheels from a previous dry run without downloading them again,
# pass the directory that the dry run printed:
# ./pypi_deploy.sh --publish --wheel-dir /tmp/iree_pypi_wheels.XXXXX
#
# Each package is uploaded separately. If an upload fails (e.g. because your
# credentials lack permission for that PyPI project), the remaining packages
# are still uploaded and the failures are listed in a summary at the end.
set -euo pipefail
function print_usage() {
echo "Usage: $0 [--publish] <release tag, e.g. iree-3.4.0rc20250430>"
echo " $0 [--publish] --wheel-dir <directory from a previous run>"
}
PUBLISH=0
RELEASE=""
WHEEL_DIR=""
while (( $# > 0 )); do
case "$1" in
--publish)
PUBLISH=1
;;
--wheel-dir)
if (( $# < 2 )); then
echo "--wheel-dir requires a directory."
print_usage
exit 1
fi
WHEEL_DIR="$2"
shift
;;
--wheel-dir=*)
WHEEL_DIR="${1#--wheel-dir=}"
;;
-h|--help)
print_usage
exit 0
;;
-*)
echo "Unknown option: $1"
print_usage
exit 1
;;
*)
if [[ -n "${RELEASE}" ]]; then
echo "Only one release may be given."
print_usage
exit 1
fi
RELEASE="$1"
;;
esac
shift
done
if [[ -n "${WHEEL_DIR}" && -n "${RELEASE}" ]]; then
echo "Pass either a release tag or --wheel-dir, not both."
print_usage
exit 1
fi
if [[ -z "${WHEEL_DIR}" && -z "${RELEASE}" ]]; then
print_usage
exit 1
fi
if [[ -n "${WHEEL_DIR}" && ! -d "${WHEEL_DIR}" ]]; then
echo "Wheel directory '${WHEEL_DIR}' does not exist."
exit 1
fi
# Packages that every release is expected to contain, as named in wheel files.
EXPECTED_PACKAGES=(
iree_base_compiler
iree_base_runtime
iree_tools_tf
iree_tools_tflite
)
SCRIPT_DIR="$(dirname -- "$( readlink -f -- "$0"; )")";
REQUIREMENTS_FILE="${SCRIPT_DIR}/pypi_deploy_requirements.txt"
if [[ -n "${WHEEL_DIR}" ]]; then
TMPDIR="$(readlink -f -- "${WHEEL_DIR}")"
else
TMPDIR="$(mktemp --directory --tmpdir iree_pypi_wheels.XXXXX)"
fi
function check_command_exists() {
if ! command -v "$1" > /dev/null; then
echo "$1 not found."
return 1
fi
return 0
}
function check_python_package_installed() {
if ! pip show "$1" > /dev/null; then
echo "$1 not installed."
return 1
fi
return 0
}
function check_requirements() {
while read line; do
# Read in the package, ignoring everything after the first '='
ret=0
read -rd '=' package <<< "${line}" || ret=$?
# exit code 1 means EOF (i.e. no '='), which is fine.
if (( ret!=0 && ret!=1 )); then
echo "Reading requirements file '${REQUIREMENTS_FILE}' failed."
exit "${ret}"
fi
if ! check_python_package_installed "${package}"; then
echo "Recommend installing python dependencies in a venv using pypi_deploy_requirements.txt"
exit 1
fi
done < <(cat "${REQUIREMENTS_FILE}")
}
function download_wheels() {
echo ""
echo "Downloading wheels from '${RELEASE}'..."
gh release download "${RELEASE}" --repo iree-org/iree --pattern "*.whl"
echo ""
echo "Downloaded wheels:"
ls
}
function confirm_or_exit() {
local reply=""
if ! read -r -p "$1 [y/N] " reply < /dev/tty; then
echo ""
echo "Could not read confirmation, aborting."
exit 1
fi
if [[ ! "${reply}" =~ ^[Yy]([Ee][Ss])?$ ]]; then
echo "Aborting."
exit 1
fi
}
function check_expected_packages() {
echo ""
echo "Checking for expected packages..."
local missing=()
local package
for package in "${EXPECTED_PACKAGES[@]}"; do
local wheels=( "${package}"-*.whl )
if [[ -e "${wheels[0]}" ]]; then
echo " ${package}: ${#wheels[@]} wheel(s)"
else
echo " ${package}: MISSING"
missing+=( "${package}" )
fi
done
if (( ${#missing[@]} > 0 )); then
echo ""
echo "'${RELEASE:-${TMPDIR}}' has no wheels for: ${missing[*]}"
confirm_or_exit "Continue without them?"
fi
}
function edit_release_versions() {
echo ""
echo "Editing release versions..."
for file in *
do
${SCRIPT_DIR}/promote_whl_from_rc_to_final.py ${file} --delete-old-wheel
done
echo "Edited wheels:"
ls
}
function upload_wheels() {
# In a dry run, `twine check` stands in for `twine upload` so the wheels are
# validated.
local twine_args=( upload --verbose )
local action="Uploading"
local done_label="uploaded"
echo ""
if (( PUBLISH )); then
echo "Uploading wheels..."
else
twine_args=( check --strict )
action="Checking"
done_label="ok"
echo "Dry run: checking wheels instead of uploading (pass --publish to upload)..."
fi
# Group and upload wheels by package.
local packages=()
local file
for file in *.whl; do
packages+=( "${file%%-*}" )
done
mapfile -t packages < <(printf '%s\n' "${packages[@]}" | sort -u)
local succeeded=()
local failed=()
local package
for package in "${packages[@]}"; do
echo ""
echo "${action} ${package}..."
if twine "${twine_args[@]}" "${package}"-*.whl; then
succeeded+=( "${package}" )
else
echo "${action} ${package} failed, continuing with remaining packages."
failed+=( "${package}" )
fi
done
echo ""
if (( PUBLISH )); then
echo "Upload summary:"
else
echo "Dry run. Wheels that would be uploaded:"
ls -1
echo ""
echo "Check summary:"
fi
for package in "${succeeded[@]}"; do
echo " ${done_label}: ${package}"
done
for package in "${failed[@]}"; do
echo " FAILED: ${package}"
done
if (( PUBLISH && ${#failed[@]} > 0 )); then
echo ""
echo "Some packages were not uploaded. Their wheels are kept in ${TMPDIR}."
echo " twine upload ${TMPDIR}/<package>-*.whl"
fi
if (( ! PUBLISH )); then
echo ""
echo "To upload these wheels without downloading them again, run:"
echo " $0 --publish --wheel-dir ${TMPDIR}"
fi
}
function main() {
local source="${RELEASE:-${TMPDIR}}"
if (( PUBLISH )); then
echo "Publishing '${source}' to PyPI."
else
echo "Dry run for '${source}', nothing will be uploaded (pass --publish to upload)."
fi
echo "Changing into ${TMPDIR}"
cd "${TMPDIR}"
set +e
check_requirements
if [[ -z "${WHEEL_DIR}" ]] && ! check_command_exists gh; then
echo "The GitHub CLI 'gh' is required. See https://github.com/cli/cli#installation."
echo " Googlers, the PPA should already be on your linux machine."
exit 1
fi
set -e
if [[ -z "${WHEEL_DIR}" ]]; then
download_wheels
else
echo ""
echo "Using wheels from '${TMPDIR}' instead of downloading:"
ls
fi
check_expected_packages
edit_release_versions
upload_wheels
}
main