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