diff --git a/README.md b/README.md index c70d40f9b..11643ccbc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ cibuildwheel Python wheels are great. Building them across **Mac, Linux, Windows**, on **multiple versions of Python**, is not. -`cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, and GitLab CI - and it builds and tests your wheels across all of your platforms. +`cibuildwheel` is here to help. `cibuildwheel` runs on your CI server - currently it supports GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, GitLab CI, Cirrus CI, and Bitbucket Pipelines - and it builds and tests your wheels across all of your platforms. What does it do? @@ -44,7 +44,7 @@ While cibuildwheel itself requires a recent Python version to run (we support th ⁵ manylinux armv7l support is experimental. As there are no RHEL based image for this architecture, it's using an Ubuntu based image instead.
- Builds manylinux, musllinux, macOS 10.9+ (10.13+ for Python 3.12+), and Windows wheels for CPython and PyPy -- Works on GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, GitLab CI, and Cirrus CI +- Works on GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, GitLab CI, Cirrus CI, and Bitbucket Pipelines - Bundles shared library dependencies on Linux and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate) - Runs your library's tests against the wheel-installed version of your library @@ -55,15 +55,16 @@ Usage `cibuildwheel` runs inside a CI service. Supported platforms depend on which service you're using: -| | Linux | macOS | Windows | Linux ARM | macOS ARM | Windows ARM | iOS | -|-----------------|-------|-------|---------|-----------|-----------|-------------|-----| -| GitHub Actions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅³ | -| Azure Pipelines | ✅ | ✅ | ✅ | | ✅ | ✅² | ✅³ | -| Travis CI | ✅ | | ✅ | ✅ | | | | -| AppVeyor | ✅ | ✅ | ✅ | | ✅ | ✅² | ✅³ | -| CircleCI | ✅ | ✅ | | ✅ | ✅ | | ✅³ | -| Gitlab CI | ✅ | ✅ | ✅ | ✅¹ | ✅ | | ✅³ | -| Cirrus CI | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅³ | +| | Linux | macOS | Windows | Linux ARM | macOS ARM | Windows ARM | iOS | +|----------------------|-------|-------|---------|-----------|-----------|-------------|-----| +| GitHub Actions | ✅ | ✅ | ✅ | ✅ | ✅ | ✅² | ✅³ | +| Azure Pipelines | ✅ | ✅ | ✅ | | ✅ | ✅² | ✅³ | +| Travis CI | ✅ | | ✅ | ✅ | | | | +| AppVeyor | ✅ | ✅ | ✅ | | ✅ | ✅² | ✅³ | +| CircleCI | ✅ | ✅ | | ✅ | ✅ | | ✅³ | +| Gitlab CI | ✅ | ✅ | ✅ | ✅¹ | ✅ | | ✅³ | +| Cirrus CI | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅³ | +| Bitbucket Pipelines | ✅ | ✅ | ✅ | ✅¹ | ✅ | | ✅³ | ¹ [Requires emulation](https://cibuildwheel.pypa.io/en/stable/faq/#emulation), distributed separately. Other services may also support Linux ARM through emulation or third-party build hosts, but these are not tested in our CI.
² [Uses cross-compilation](https://cibuildwheel.pypa.io/en/stable/faq/#windows-arm64). It is not possible to test `arm64` on this CI platform.
diff --git a/cibuildwheel/ci.py b/cibuildwheel/ci.py index a7ba04ee1..33ecf0fe4 100644 --- a/cibuildwheel/ci.py +++ b/cibuildwheel/ci.py @@ -13,6 +13,7 @@ class CIProvider(Enum): github_actions = "github_actions" gitlab = "gitlab" cirrus_ci = "cirrus_ci" + bitbucket_pipelines = "bitbucket_pipelines" other = "other" @@ -31,6 +32,8 @@ def detect_ci_provider() -> CIProvider | None: return CIProvider.gitlab elif "CIRRUS_CI" in os.environ: return CIProvider.cirrus_ci + elif "BITBUCKET_PIPELINE_UUID" in os.environ: + return CIProvider.bitbucket_pipelines elif strtobool(os.environ.get("CI", "false")): return CIProvider.other else: diff --git a/cibuildwheel/logger.py b/cibuildwheel/logger.py index 391786a04..a703001f8 100644 --- a/cibuildwheel/logger.py +++ b/cibuildwheel/logger.py @@ -13,6 +13,7 @@ "azure": ("##[group]{name}", "##[endgroup]"), "travis": ("travis_fold:start:{identifier}\n{name}", "travis_fold:end:{identifier}"), "github": ("::group::{name}", "::endgroup::{name}"), + "bitbucket": ("--- {name} ---", "--- End {name} ---"), } PLATFORM_IDENTIFIER_DESCRIPTIONS: Final[dict[str, str]] = { @@ -98,6 +99,10 @@ def __init__(self) -> None: self.fold_mode = "travis" self.colors_enabled = True + elif ci_provider == CIProvider.bitbucket_pipelines: + self.fold_mode = "bitbucket" + self.colors_enabled = True + elif ci_provider == CIProvider.appveyor: self.fold_mode = "disabled" self.colors_enabled = True diff --git a/docs/setup.md b/docs/setup.md index e82f5a3e8..32b083a23 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -213,6 +213,25 @@ Commit this file, enable building of your repo on Cirrus CI, and push. Cirrus CI will store the built wheels for you - you can access them from the individual task view. Check out the Cirrus CI [docs](https://cirrus-ci.org/guide/writing-tasks/) for more info on this config file. +### Bitbucket Pipelines [linux/mac/windows] {: #bitbucket-pipelines} + +To build Linux, Mac, and Windows wheels on Bitbucket Pipelines, create a `bitbucket-pipelines.yml` file in your repo, + +> bitbucket-pipelines.yml + +```yaml +{% include "../examples/bitbucket-pipelines-minimal.yml" %} +``` + +Commit this file, enable building of your repo on Bitbucket Pipelines, and push. + +Bitbucket Pipelines will store the built wheels for you - you can access them from the artifacts tab. Check out the Bitbucket Pipelines [docs](https://support.atlassian.com/bitbucket-cloud/docs/configure-bitbucket-pipelinesyml/) for more info on this config file. + +[`examples/bitbucket-pipelines-deploy.yml`](https://github.com/pypa/cibuildwheel/blob/main/examples/bitbucket-pipelines-deploy.yml) extends this minimal example to include iOS and Pyodide builds, and a demonstration of how to automatically upload the built wheels to PyPI when a tag is pushed. + +!!! note + To enable Windows and macOS runners in Bitbucket Pipelines, you may need to enable these resources in your Bitbucket settings. See the [Bitbucket documentation](https://support.atlassian.com/bitbucket-cloud/docs/windows-and-macos-build-environments/) for more information. + > ⚠️ Got an error? Check the [FAQ](faq.md). ## Next steps diff --git a/examples/bitbucket-pipelines-deploy.yml b/examples/bitbucket-pipelines-deploy.yml new file mode 100644 index 000000000..eea28ddd9 --- /dev/null +++ b/examples/bitbucket-pipelines-deploy.yml @@ -0,0 +1,123 @@ +image: python:3.12 + +pipelines: + default: + - parallel: + - step: + name: Build Linux Wheels + services: + - docker + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse + artifacts: + - wheelhouse/** + + - step: + name: Build Windows Wheels + runs-on: + - windows + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform windows + artifacts: + - wheelhouse/** + + - step: + name: Build macOS Wheels + runs-on: + - macos + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform macos + artifacts: + - wheelhouse/** + + - step: + name: Build iOS Wheels + runs-on: + - macos + script: + - python -m pip install cibuildwheel==2.23.2 + # iOS is only available on macOS runners + - cibuildwheel --output-dir wheelhouse --platform ios + artifacts: + - wheelhouse/** + + - step: + name: Build Pyodide Wheels + services: + - docker + script: + - python -m pip install cibuildwheel==2.23.2 + # Pyodide can build on any platform, but linux is typically used + - cibuildwheel --output-dir wheelhouse --platform pyodide + artifacts: + - wheelhouse/** + + tags: + release-*: + - parallel: + - step: + name: Build Linux Wheels + services: + - docker + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse + artifacts: + - wheelhouse/** + + - step: + name: Build Windows Wheels + runs-on: + - windows + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform windows + artifacts: + - wheelhouse/** + + - step: + name: Build macOS Wheels + runs-on: + - macos + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform macos + artifacts: + - wheelhouse/** + + - step: + name: Build iOS Wheels + runs-on: + - macos + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform ios + artifacts: + - wheelhouse/** + + - step: + name: Build Pyodide Wheels + services: + - docker + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform pyodide + artifacts: + - wheelhouse/** + + - step: + name: Deploy to PyPI + trigger: manual # Requires manual approval + script: + - python -m pip install twine + # Download artifacts from previous parallel steps + - twine upload wheelhouse/*.whl + deployment: production + +definitions: + services: + docker: + memory: 2048 \ No newline at end of file diff --git a/examples/bitbucket-pipelines-minimal.yml b/examples/bitbucket-pipelines-minimal.yml new file mode 100644 index 000000000..eb5d0b792 --- /dev/null +++ b/examples/bitbucket-pipelines-minimal.yml @@ -0,0 +1,42 @@ +image: python:3.12 + +pipelines: + default: + - parallel: + - step: + name: Linux Wheels + services: + - docker + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse + artifacts: + - wheelhouse/** + + - step: + name: Windows Wheels + runs-on: + - windows + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform windows + artifacts: + - wheelhouse/** + + - step: + name: macOS Wheels + runs-on: + - macos + script: + - python -m pip install cibuildwheel==2.23.2 + - cibuildwheel --output-dir wheelhouse --platform macos + artifacts: + - wheelhouse/** + +# To enable Windows and macOS builds, you need to enable these resources in your Bitbucket settings +# See: https://support.atlassian.com/bitbucket-cloud/docs/windows-and-macos-build-environments/ + +definitions: + services: + docker: + memory: 2048 \ No newline at end of file diff --git a/unit_test/ci_detection_test.py b/unit_test/ci_detection_test.py new file mode 100644 index 000000000..f2f4e9528 --- /dev/null +++ b/unit_test/ci_detection_test.py @@ -0,0 +1,88 @@ +import os +from unittest import mock + +import pytest + +from cibuildwheel.ci import CIProvider, detect_ci_provider + + +def test_detect_ci_provider_none(): + """Test that None is returned when no CI environment variables are set.""" + with mock.patch.dict(os.environ, {}, clear=True): + assert detect_ci_provider() is None + + +def test_detect_ci_provider_generic(): + """Test that generic CI is detected when only CI=true is set.""" + with mock.patch.dict(os.environ, {"CI": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.other + + +def test_detect_ci_provider_travis(): + """Test that Travis CI is detected.""" + with mock.patch.dict(os.environ, {"TRAVIS": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.travis_ci + + +def test_detect_ci_provider_appveyor(): + """Test that AppVeyor is detected.""" + with mock.patch.dict(os.environ, {"APPVEYOR": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.appveyor + + +def test_detect_ci_provider_circle_ci(): + """Test that Circle CI is detected.""" + with mock.patch.dict(os.environ, {"CIRCLECI": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.circle_ci + + +def test_detect_ci_provider_azure_pipelines(): + """Test that Azure Pipelines is detected.""" + with mock.patch.dict(os.environ, {"AZURE_HTTP_USER_AGENT": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.azure_pipelines + + +def test_detect_ci_provider_github_actions(): + """Test that GitHub Actions is detected.""" + with mock.patch.dict(os.environ, {"GITHUB_ACTIONS": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.github_actions + + +def test_detect_ci_provider_gitlab(): + """Test that GitLab CI is detected.""" + with mock.patch.dict(os.environ, {"GITLAB_CI": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.gitlab + + +def test_detect_ci_provider_cirrus_ci(): + """Test that Cirrus CI is detected.""" + with mock.patch.dict(os.environ, {"CIRRUS_CI": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.cirrus_ci + + +def test_detect_ci_provider_bitbucket_pipelines(): + """Test that Bitbucket Pipelines is detected.""" + with mock.patch.dict(os.environ, {"BITBUCKET_PIPELINE_UUID": "true"}, clear=True): + assert detect_ci_provider() == CIProvider.bitbucket_pipelines + + +def test_detect_ci_provider_order(): + """Test that CI providers are detected in the correct order.""" + # Setting multiple CI environment variables + with mock.patch.dict( + os.environ, + { + "TRAVIS": "true", + "APPVEYOR": "true", + "CIRCLECI": "true", + "AZURE_HTTP_USER_AGENT": "true", + "GITHUB_ACTIONS": "true", + "GITLAB_CI": "true", + "CIRRUS_CI": "true", + "BITBUCKET_PIPELINE_UUID": "true", + "CI": "true", + }, + clear=True, + ): + # Travis CI should be detected first based on the order in detect_ci_provider + assert detect_ci_provider() == CIProvider.travis_ci \ No newline at end of file diff --git a/unit_test/logger_test.py b/unit_test/logger_test.py new file mode 100644 index 000000000..1ec1258ed --- /dev/null +++ b/unit_test/logger_test.py @@ -0,0 +1,25 @@ +import os +from unittest import mock + +from cibuildwheel.ci import CIProvider +from cibuildwheel.logger import Logger + + +def test_logger_fold_pattern_for_bitbucket_pipelines(): + """Test that the logger uses the correct fold pattern for Bitbucket Pipelines.""" + with mock.patch("cibuildwheel.logger.detect_ci_provider", return_value=CIProvider.bitbucket_pipelines): + # Set up a logger that thinks it's running on Bitbucket Pipelines + logger = Logger() + + # Check that it has the expected fold mode + assert logger.fold_mode == "bitbucket" + + # Check that colors are enabled + assert logger.colors_enabled is True + + +def test_logger_fold_mode_default(): + """Test that the logger defaults to disabled fold mode for unknown CI providers.""" + with mock.patch("cibuildwheel.logger.detect_ci_provider", return_value=CIProvider.other): + logger = Logger() + assert logger.fold_mode == "disabled" \ No newline at end of file