Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: Release
on:
push:
branches:
- default
# To simplify the release process, the publishing is triggered on tag.
# We should make sure to only push tags for new releases.
# If we start using tags for non-release purposes,
# this needs to be updated.
#
# We need to explicitly configure an expression that matches anything.
tags: [ "**" ]
pull_request:


env:
LINUX_URL: "https://bin.chevah.com:20443/third-party-stuff/ibm-mqc-redist/9.4.3.1-IBM-MQC-Redist-LinuxX64.tar.gz"
WINDOWS_URL: "https://bin.chevah.com:20443/third-party-stuff/ibm-mqc-redist/9.4.3.1-IBM-MQC-Redist-Win64.zip"

defaults:
run:
# Use bash on Windows for consistency.
shell: bash


jobs:
build_wheels:
name: Build ${{ matrix.runs-on }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, windows-latest]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
name: Install Python
with:
python-version: '3.9'

- name: Install deps
run: |
python -m pip install -r tools/requirements-dev.txt

- name: Get IBM MQ C Linux Redistributables
if: matrix.runs-on == 'ubuntu-latest'
run: |
python tools/getRedistributables.py "$LINUX_URL" ibm-mq-c

- name: Get IBM MQ C Windows Redistributables
if: matrix.runs-on == 'windows-latest'
run: |
python tools/getRedistributables.py "$WINDOWS_URL" ibm-mq-c

- name: Build wheels
run: |
export MQ_FILE_PATH=`pwd`/ibm-mq-c
python -m build --wheel

- name: Check files
run: ls -al dist/

- name: Audit ABI3 wheels
run: |
abi3audit -vsS dist/*.whl

- uses: actions/upload-artifact@v4
with:
name: artifact-wheels-${{ matrix.runs-on }}
path: ./dist/*.whl


build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
name: Install Python
with:
python-version: '3.9'

- name: Install build
run: |
python -m pip install build

- name: Build sdist
run: python -m build --sdist

- uses: actions/upload-artifact@v4
with:
name: artifact-sdist
path: dist/*.tar.gz


upload_pypi:
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: artifact-*
merge-multiple: true
path: dist

- name: Check files
run: ls -al dist/

- name: Publish to PyPI - on tag
# Skip upload to PyPI if we don't have a tag
if: startsWith(github.ref, 'refs/tags/')
uses: pypa/gh-action-pypi-publish@release/v1
33 changes: 33 additions & 0 deletions docs/Release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Release

GitHub Action is used to build the release files and publish them on PyPi.
This is automatically triggered when a new tag is created.


## Pre-release steps

* Create a new branch with a name that starts with `release-`.
Ex: `release-2.1.0`
* Update the version inside setup.py
* Update CHANGELOG.md with the latest version and release date.
Add a note to the IBM MQ C Redistributables version used for this release.
* Create a pull request and make sure all checks pass.
The wheels are generated as part of the PR checks,
but they are not yet published to PyPI.


## Release steps

* Use [GitHub Release](https://github.com/ibm-messaging/mq-mqi-python/releases/new) to create a new release together with a new tag.
* You don't have to create a GitHub Release, the important part is to
create a new tag.
* The tag value is the version. Without any prefix.
* Once a tag is pushed to the repo, GitHub Action will re-run all the jobs
and will publish to PyPI.


## Post-release steps

* Update the version inside setup.py to the next development version.
Increment the micro version and add a .dev0 suffix.
* Merge the pull request
21 changes: 17 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
# to indicate alpha/beta/release-candidate versions.
VERSION = '2.0.0'

_ABI_LIMITS = {
# Minimum Python version that this package supports.
'python_requires': ">=3.9",
# Used as a macro.
'Py_LIMITED_API': 0x03090000,
# Used for bdist_wheel option.
"py_limited_api": "cp39",
}

# If the MQ SDK is in a non-default location, set MQ_FILE_PATH environment variable.
custom_path = os.environ.get('MQ_FILE_PATH', None)

Expand Down Expand Up @@ -138,12 +147,14 @@ def get_locations_by_command_path(command_path):

# Can we find the MQ C header files? If not, there's no point in continuing, and we can
# give a reasonable error message immediately instead of trying to decode C compiler errors.
# If we are in the CI environment, we still build the package as we want
# to be able to build the source package without the MQ C headers.
found_headers = False # pylint: disable=invalid-name
for d in include_dirs:
p = os.path.join(d, "cmqc.h")
if os.path.isfile(p):
found_headers = True
if not found_headers:
if not found_headers and not os.environ.get('CI', ''):
msg = "Cannot find MQ C header files.\n"
msg += "Ensure you have already installed the MQ Client and SDK.\n"
msg += "Use the MQ_FILE_PATH environment variable to identify a non-default location."
Expand Down Expand Up @@ -195,7 +206,7 @@ def get_locations_by_command_path(command_path):
# Limited API which should make the binary extension forwards compatible.
mqi_extension = Extension('ibmmq.ibmmqc', c_source,
define_macros=[('PYVERSION', '"' + VERSION + '"'),
('Py_LIMITED_API', 0x03090000)
('Py_LIMITED_API', _ABI_LIMITS['Py_LIMITED_API'])
],
py_limited_api=True,
library_dirs=library_dirs,
Expand All @@ -214,7 +225,7 @@ def get_locations_by_command_path(command_path):
platforms='OS Independent',
package_dir={'': 'code'},
packages=['ibmmq'],
python_requires=">=3.9",
python_requires=_ABI_LIMITS['python_requires'],
license_files=['LICENSE*'],
license='Python-2.0',
keywords=('pymqi IBMMQ MQ WebSphere WMQ MQSeries IBM middleware messaging queueing asynchronous SOA EAI ESB integration'),
Expand All @@ -225,4 +236,6 @@ def get_locations_by_command_path(command_path):
'Programming Language :: C',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules'],
ext_modules=[mqi_extension])
ext_modules=[mqi_extension],
options={"bdist_wheel": {"py_limited_api": _ABI_LIMITS["py_limited_api"]}},
)
62 changes: 62 additions & 0 deletions tools/getRedistributables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Download and extract the redistributable package to support building
# the wheels on Windows and Linux.
#
# Pass the source URL as the first argument and the destination directory
# as the second argument.
#
# The source URL should end with .tar.gz or .zip
#
import os
import sys
import tarfile
import zipfile

import requests

if len(sys.argv) != 3:
print("Usage: this_script.py <source_url> <destination_path>")
sys.exit(1)

SOURCE_URL = sys.argv[1]
DESTINATION_PATH = sys.argv[2]

if SOURCE_URL.endswith('.tar.gz'):
temp_path = DESTINATION_PATH + '-temp.tar.gz'
elif SOURCE_URL.endswith('.zip'):
temp_path = DESTINATION_PATH + '-temp.zip'
else:
print("Unsupported archive format")
sys.exit(1)

if os.path.exists(DESTINATION_PATH):
print(
f"Destination path {DESTINATION_PATH} already exists. "
"Remove it to use this script.")
sys.exit(1)

_CHUNK_SIZE = 64 * 1024


def download_file(source_url, destination_path):
with requests.get(source_url, stream=True) as r:
r.raise_for_status()
with open(destination_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=_CHUNK_SIZE):
f.write(chunk)

def extract_archive(archive_path, extract_to):
if archive_path.endswith('.tar.gz'):
with tarfile.open(archive_path, 'r:gz') as tar:
tar.extractall(path=extract_to)
elif archive_path.endswith('.zip'):
with zipfile.ZipFile(archive_path, 'r') as zip_ref:
zip_ref.extractall(extract_to)
else:
raise ValueError("Unsupported archive format")


print(f'Downloading "{SOURCE_URL}" to "{DESTINATION_PATH}"')
download_file(SOURCE_URL, temp_path)
extract_archive(temp_path, DESTINATION_PATH)
os.unlink(temp_path)
print(f'Done via "{temp_path}"')
7 changes: 7 additions & 0 deletions tools/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#
# Python packages required during the dev,test,release process.
#
requests==2.32.5
abi3audit==0.0.22
wheel==0.45.1
build==1.3.0