ENH: Support PyPI trusted publishing #5
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: 'Build, Test, Package ITK Remote Module' | |
on: | |
workflow_call: | |
inputs: | |
cmake-options: | |
description: 'CMake configuration parameters for the module under test' | |
required: false | |
type: string | |
default: "" | |
itk-wheel-tag: | |
# See https://github.com/InsightSoftwareConsortium/ITKPythonBuilds/releases | |
description: 'Github release version tag for the ITKPythonBuilds build archive to use' | |
required: false | |
type: string | |
default: 'v5.3.0' | |
itk-python-package-tag: | |
# See https://github.com/InsightSoftwareConsortium/ITKPythonPackage | |
description: 'Git tag or commit hash for ITKPythonPackage build scripts to use' | |
required: false | |
type: string | |
default: 'dc6a18600233ac69a8f42b7489e4edf6a5d8883a' | |
itk-python-package-org: | |
description: 'Github organization name for fetching ITKPythonPackage build scripts' | |
required: false | |
type: string | |
default: 'InsightSoftwareConsortium' | |
itk-module-deps: | |
description: 'Colon-delimited list of ITK remote module dependencies to build. Format as org/module_name@tag:...' | |
# example: InsightSoftwareConsortium/ITKMeshToPolyData@3ad8f08:InsightSoftwareConsortium/ITKBSplineGradient@0.3.0 | |
required: false | |
type: string | |
python3-minor-versions: | |
description: 'JSON-formatted array of Python 3.x minor version wheel targets' | |
required: false | |
type: string | |
default: '["7","8","9","10","11"]' | |
manylinux-platforms: | |
description: 'JSON-formatted array of "<manylinux-image>-<arch>" specializations' | |
required: false | |
type: string | |
default: '["_2_28-x64","2014-x64","_2_28-aarch64"]' | |
test-notebooks: | |
description: 'Option to test Jupyter Notebooks in examples/ directory with applied changes' | |
required: false | |
type: boolean | |
default: false | |
secrets: | |
pypi_password: | |
required: false # Packages will not be uploaded to PyPI if not set | |
jobs: | |
build-linux-py: | |
runs-on: ubuntu-22.04 | |
strategy: | |
max-parallel: 2 | |
matrix: | |
python3-minor-version: ${{ fromJSON(inputs.python3-minor-versions) }} | |
manylinux-platform: ${{ fromJSON(inputs.manylinux-platforms) }} | |
steps: | |
- uses: actions/checkout@v3 | |
- name: 'Free up disk space' | |
run: | | |
# Workaround for https://github.com/actions/virtual-environments/issues/709 | |
df -h | |
sudo apt-get clean | |
sudo rm -rf "/usr/local/share/boost" | |
sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
df -h | |
- name: 'Fetch build script' | |
run: | | |
IPP_DOWNLOAD_GIT_TAG=${{ inputs.itk-python-package-tag }} | |
IPP_DOWNLOAD_ORG=${{ inputs.itk-python-package-org }} | |
curl -L https://raw.githubusercontent.com/${IPP_DOWNLOAD_ORG:=InsightSoftwareConsortium}/ITKPythonPackage/${IPP_DOWNLOAD_GIT_TAG:=master}/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh -O | |
chmod u+x dockcross-manylinux-download-cache-and-build-module-wheels.sh | |
- name: 'Build 🐍 Python 📦 package' | |
shell: bash | |
run: | | |
rm -rf dist | |
export ITK_PACKAGE_VERSION=${{ inputs.itk-wheel-tag }} | |
export ITKPYTHONPACKAGE_TAG=${{ inputs.itk-python-package-tag }} | |
export ITKPYTHONPACKAGE_ORG=${{ inputs.itk-python-package-org }} | |
export ITK_MODULE_PREQ=${{ inputs.itk-module-deps }} | |
if [ -z ${{ inputs.cmake-options }} ]; then | |
CMAKE_OPTIONS="" | |
else | |
CMAKE_OPTIONS="--cmake_options ${{ inputs.cmake-options }}" | |
fi | |
MANYLINUX_PLATFORM=${{ matrix.manylinux-platform }} | |
echo "Manylinux platform ${MANYLINUX_PLATFORM}" | |
rm -rf ITKPythonPackage | |
export MANYLINUX_VERSION=`(echo ${MANYLINUX_PLATFORM} | cut -d '-' -f 1)` | |
export TARGET_ARCH=`(echo ${MANYLINUX_PLATFORM} | cut -d '-' -f 2)` | |
echo "Building for manylinux specialization ${MANYLINUX_VERSION} and target architecture ${TARGET_ARCH}" | |
./dockcross-manylinux-download-cache-and-build-module-wheels.sh cp3${{ matrix.python3-minor-version }} $CMAKE_OPTIONS | |
- name: Set up Python 3.10 for Validation | |
uses: actions/setup-python@v4 | |
with: | |
python-version: "3.10" | |
- name: Validate build output | |
shell: bash | |
run: | | |
python -m pip install twine | |
ls dist/ | |
MANYLINUX_PLATFORM=${{ matrix.manylinux-platform }} | |
MANYLINUX_VERSION=`(echo ${MANYLINUX_PLATFORM} | cut -d '-' -f 1)` | |
TARGET_ARCH_NAME=`(echo ${MANYLINUX_PLATFORM} | cut -d '-' -f 2)` | |
if [[ ${TARGET_ARCH_NAME} == "x64" ]]; then | |
TARGET_ARCH_NAME="x86_64" # Match auditwheel naming convention | |
fi | |
WHEEL_PATTERN="dist/itk_*cp3${{ matrix.python3-minor-version }}*manylinux${MANYLINUX_VERSION}*${TARGET_ARCH_NAME}.whl" | |
echo "Searching for wheels matching pattern ${WHEEL_PATTERN}" | |
python -m twine check ${WHEEL_PATTERN} | |
- name: Publish Python package as GitHub Artifact | |
uses: actions/upload-artifact@v3 | |
with: | |
name: LinuxWheel3${{ matrix.python3-minor-version }} | |
path: dist/*.whl | |
build-macos-py: | |
runs-on: macos-12 | |
strategy: | |
max-parallel: 2 | |
matrix: | |
python3-minor-version: ${{ fromJSON(inputs.python3-minor-versions) }} | |
steps: | |
- uses: actions/checkout@v3 | |
- name: 'Specific XCode version' | |
run: | | |
sudo xcode-select -s "/Applications/Xcode_13.2.1.app" | |
- name: Get specific version of CMake, Ninja | |
uses: lukka/get-cmake@v3.24.2 | |
- name: 'Fetch build script' | |
run: | | |
IPP_DOWNLOAD_GIT_TAG=${{ inputs.itk-python-package-tag }} | |
IPP_DOWNLOAD_ORG=${{ inputs.itk-python-package-org }} | |
curl -L https://raw.githubusercontent.com/${IPP_DOWNLOAD_ORG:=InsightSoftwareConsortium}/ITKPythonPackage/${IPP_DOWNLOAD_GIT_TAG:=master}/scripts/macpython-download-cache-and-build-module-wheels.sh -O | |
chmod u+x macpython-download-cache-and-build-module-wheels.sh | |
- name: 'Build 🐍 Python 📦 package' | |
shell: bash | |
run: | | |
rm -rf dist | |
export ITK_PACKAGE_VERSION=${{ inputs.itk-wheel-tag }} | |
export ITKPYTHONPACKAGE_TAG=${{ inputs.itk-python-package-tag }} | |
export ITKPYTHONPACKAGE_ORG=${{ inputs.itk-python-package-org }} | |
export ITK_MODULE_PREQ=${{ inputs.itk-module-deps }} | |
export MACOSX_DEPLOYMENT_TARGET=10.9 | |
if [ -z ${{ inputs.cmake-options }} ]; then | |
CMAKE_OPTIONS="" | |
else | |
CMAKE_OPTIONS="--cmake_options ${{ inputs.cmake-options }}" | |
fi | |
./macpython-download-cache-and-build-module-wheels.sh $CMAKE_OPTIONS "3.${{ matrix.python3-minor-version }}" | |
- name: Set up Python 3.10 for Validation | |
uses: actions/setup-python@v4 | |
with: | |
python-version: "3.10" | |
- name: Validate build output | |
shell: bash | |
run: | | |
python -m pip install twine | |
ls dist/ | |
WHEEL_PATTERN="dist/itk_*macosx*.whl" | |
EXPECTED_WHEEL_COUNT=1 | |
WHEEL_COUNT=`(ls ${WHEEL_PATTERN} | wc -l)` | |
if (( ${WHEEL_COUNT} != ${EXPECTED_WHEEL_COUNT} )); then | |
echo "Expected ${EXPECTED_WHEEL_COUNT} wheels but found ${WHEEL_COUNT}" | |
exit 1 | |
fi | |
python -m twine check ${WHEEL_PATTERN} | |
- name: Publish Python package as GitHub Artifact | |
uses: actions/upload-artifact@v3 | |
with: | |
name: MacOSWheel3${{ matrix.python3-minor-version }} | |
path: dist/*.whl | |
build-windows-python-packages: | |
runs-on: windows-2022 | |
strategy: | |
max-parallel: 2 | |
matrix: | |
python3-minor-version: ${{ fromJSON(inputs.python3-minor-versions) }} | |
steps: | |
- name: Get specific version of CMake, Ninja | |
uses: lukka/get-cmake@v3.24.2 | |
- uses: actions/checkout@v3 | |
with: | |
path: "im" | |
- name: 'Reduce source path length' | |
shell: bash | |
run: | | |
# Move checked-out source to a shorter path to avoid Windows path length issues | |
mv im ../../ | |
- name: 'Fetch build script' | |
shell: pwsh | |
run: | | |
cd ../../im | |
$ITKPYTHONPACKAGE_TAG = "${{ inputs.itk-python-package-tag }}" | |
$ITKPYTHONPACKAGE_ORG = "${{ inputs.itk-python-package-org }}" | |
$SCRIPT_UPSTREAM = "https://raw.githubusercontent.com/$ITKPYTHONPACKAGE_ORG/ITKPythonPackage/$ITKPYTHONPACKAGE_TAG/scripts/windows-download-cache-and-build-module-wheels.ps1" | |
echo "Fetching $SCRIPT_UPSTREAM" | |
(new-object net.webclient).DownloadString($SCRIPT_UPSTREAM) > windows-download-cache-and-build-module-wheels.ps1 | |
- name: 'Build 🐍 Python 📦 package' | |
shell: pwsh | |
run: | | |
if (Test-Path dist) { rm dist -r -fo } | |
cd ../../im | |
& "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 -SkipAutomaticLocation | |
$env:CC="cl.exe" | |
$env:CXX="cl.exe" | |
$env:ITK_PACKAGE_VERSION = "${{ inputs.itk-wheel-tag }}" | |
$env:ITKPYTHONPACKAGE_TAG = "${{ inputs.itk-python-package-tag }}" | |
$env:ITKPYTHONPACKAGE_ORG = "${{ inputs.itk-python-package-org }}" | |
$env:ITK_MODULE_PREQ = "${{ inputs.itk-module-deps }}" | |
./windows-download-cache-and-build-module-wheels.ps1 "${{ matrix.python3-minor-version }}" -cmake_options "${{ inputs.cmake-options }}" | |
mkdir -p '${{ github.workspace }}\dist' | |
cp 'dist\*.whl' '${{ github.workspace }}\dist' | |
- name: Set up Python 3.10 for Validation | |
uses: actions/setup-python@v4 | |
with: | |
python-version: "3.10" | |
- name: Validate build output | |
shell: pwsh | |
run: | | |
python -m pip install twine | |
ls dist/ | |
$WHEEL_PATTERN = "dist/itk_*cp3${{ matrix.python3-minor-version }}*win*.whl" | |
echo "Searching for wheels matching pattern ${WHEEL_PATTERN}" | |
python -m twine check ${WHEEL_PATTERN} | |
- name: Publish Python package as GitHub Artifact | |
uses: actions/upload-artifact@v3 | |
with: | |
name: WindowsWheel3${{ matrix.python3-minor-version }} | |
path: dist/*.whl | |
test-linux-notebooks: | |
if: inputs.test-notebooks | |
needs: | |
- build-linux-py | |
runs-on: ubuntu-22.04 | |
steps: | |
- uses: actions/checkout@v3 | |
- uses: actions/setup-python@v4 | |
with: | |
python-version: "3.${{ fromJSON(inputs.python3-minor-versions)[0] }}" | |
- name: Install build dependencies | |
shell: bash | |
run: | | |
if [[ -f requirements.txt ]]; then | |
python -m pip install -r requirements.txt | |
elif [[ -f ./binder/requirements.txt ]]; then | |
python -m pip install -r ./binder/requirements.txt | |
fi | |
python -m pip install pytest nbmake | |
- name: Download Python Package Artifacts | |
uses: actions/download-artifact@v3 | |
with: | |
name: LinuxWheel3${{ fromJSON(inputs.python3-minor-versions)[0] }} | |
- name: Install Python Package Artifact | |
run: | | |
ls . | |
wheel_name_full=`(find . -name "*cp3${{ fromJSON(inputs.python3-minor-versions)[0] }}*-manylinux_2_28_x86_64.whl")` | |
echo "wheel_name_full ${wheel_name_full}" | |
# extract wheel name which may be an abbreviation of the module name | |
# ex. ./itk_splitcomponents-cp310..._64.whl -> itk-splitcomponents | |
wheel_name_prefix=`(echo ${wheel_name_full} | cut -d'-' -f1 | cut -d'/' -f2 | sed -e 's/_/-/g')` | |
echo "wheel_name_prefix ${wheel_name_prefix}" | |
python -m pip uninstall ${wheel_name_prefix} -y | |
python -m pip install ${wheel_name_full} | |
- name: Test notebooks | |
run: | | |
pytest --nbmake --nbmake-timeout=30000 examples/*.ipynb | |
check-secret: | |
runs-on: ubuntu-latest | |
outputs: | |
pypi-password-exists: ${{ steps.pypi-password-check.outputs.defined }} | |
steps: | |
- name: Check for Secret availability | |
id: pypi-password-check | |
# perform secret check & put boolean result as an output | |
shell: bash | |
run: | | |
if [ "${{ secrets.PYPI_PASSWORD }}" != '' ]; then | |
echo "defined=true" >> $GITHUB_OUTPUT; | |
else | |
echo "defined=false" >> $GITHUB_OUTPUT; | |
fi | |
publish-python-packages-to-pypi: | |
needs: | |
- check-secret | |
- build-linux-py | |
- build-macos-py | |
- build-windows-python-packages | |
runs-on: ubuntu-22.04 | |
permissions: | |
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing | |
steps: | |
- name: Download Python Packages | |
uses: actions/download-artifact@v3 | |
- name: Prepare packages for upload | |
run: | | |
ls -R | |
for d in */; do | |
mv ${d}/*.whl . | |
done | |
mkdir dist | |
mv *.whl dist/ | |
ls dist | |
- name: Publish 🐍 Python 📦 package to PyPI | |
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && needs.check-secret.outputs.pypi-password-exists == 'true' | |
uses: pypa/gh-action-pypi-publish@v1.8.10 | |
with: | |
skip_existing: true | |
user: __token__ | |
password: ${{ secrets.pypi_password }} | |
- name: Publish 🐍 Python 📦 package to PyPI | |
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && needs.check-secret.outputs.pypi-password-exists != 'true' |