diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..7398bd3f --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,15 @@ +linux: + image: python:3.8 + services: + - name: docker:dind + entrypoint: ["env", "-u", "DOCKER_HOST"] + command: ["dockerd-entrypoint.sh"] + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + # See https://github.com/docker-library/docker/pull/166 + DOCKER_TLS_CERTDIR: "" + script: + - curl -sSL https://get.docker.com/ | sh + - python -m pip install -r requirements-dev.txt + - python ./bin/run_tests.py diff --git a/README.md b/README.md index 3a87011f..68d14a53 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ What does it do? Usage ----- -`cibuildwheel` currently works on **Travis CI**, **Azure Pipelines**, **AppVeyor** and **GitHub Actions** to build wheels for all three supported platforms (Linux, macOS, Windows). On **CircleCI** Linux and macOS wheels can be built. +`cibuildwheel` currently works on **Travis CI**, **Azure Pipelines**, **AppVeyor**, **GitHub Actions**, **CircleCI**, and **Gitlab CI**. Check the table below for supported platforms on each service: | | Linux | macOS | Windows | |-----------------|-------|-------|---------| @@ -47,8 +47,9 @@ Usage | AppVeyor | ✅ | ✅ | ✅ | | GitHub Actions | ✅ | ✅ | ✅ | | CircleCI | ✅ | ✅ | | +| Gitlab CI | ✅ | | | -`cibuildwheel` is not intended to run on your development machine. Because it uses system Python from Python.org it will try to install packages globally - not what you expect from a build tool! Instead, isolated CI services like Travis CI, CircleCI, Azure Pipelines and AppVeyor are ideal. +`cibuildwheel` is not intended to run on your development machine. Because it uses system Python from Python.org it will try to install packages globally - not what you expect from a build tool! Instead, isolated CI services like those mentioned above are ideal. diff --git a/bin/run_example_ci_configs.py b/bin/run_example_ci_configs.py new file mode 100755 index 00000000..33f54de3 --- /dev/null +++ b/bin/run_example_ci_configs.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path +import shutil +from subprocess import run +import sys +import textwrap +import time +import click +from collections import namedtuple +from urllib.parse import quote + + +def shell(cmd, **kwargs): + return run([cmd], shell=True, **kwargs) + + +def git_repo_has_changes(): + unstaged_changes = shell('git diff-index --quiet HEAD --').returncode != 0 + staged_changes = shell('git diff-index --quiet --cached HEAD --').returncode != 0 + return unstaged_changes or staged_changes + + +def generate_basic_project(path): + sys.path.insert(0, '') + from test.test_projects.c import new_c_project + + project = new_c_project() + project.generate(path) + + +CIService = namedtuple('CIService', 'name src_config_path dst_config_path badge_md') +services = [ + CIService( + name='appveyor', + src_config_path='examples/appveyor-minimal.yml', + dst_config_path='appveyor.yml', + badge_md='[![Build status](https://ci.appveyor.com/api/projects/status/wbsgxshp05tt1tif/branch/{branch}?svg=true)](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/{branch})', + ), + CIService( + name='azure-pipelines', + src_config_path='examples/azure-pipelines-minimal.yml', + dst_config_path='azure-pipelines.yml', + badge_md='[![Build Status](https://dev.azure.com/joerick0429/cibuildwheel/_apis/build/status/joerick.cibuildwheel?branchName={branch})](https://dev.azure.com/joerick0429/cibuildwheel/_build/latest?definitionId=2&branchName={branch})', + ), + CIService( + name='circle-ci', + src_config_path='examples/circleci-minimal.yml', + dst_config_path='.circleci/config.yml', + badge_md='[![CircleCI](https://circleci.com/gh/joerick/cibuildwheel/tree/{branch_escaped}.svg?style=svg)](https://circleci.com/gh/joerick/cibuildwheel/tree/{branch})', + ), + CIService( + name='github', + src_config_path='examples/github-minimal.yml', + dst_config_path='.github/workflows/example.yml', + badge_md='[![Build](https://github.com/joerick/cibuildwheel/workflows/Build/badge.svg?branch={branch})](https://github.com/joerick/cibuildwheel/actions)', + ), + CIService( + name='travis-ci', + src_config_path='examples/travis-ci-minimal.yml', + dst_config_path='.travis.yml', + badge_md='[![Build Status](https://travis-ci.org/joerick/cibuildwheel.svg?branch={branch})](https://travis-ci.org/joerick/cibuildwheel)', + ), + CIService( + name='gitlab', + src_config_path='examples/gitlab-minimal.yml', + dst_config_path='.gitlab-ci.yml', + badge_md='[![Gitlab](https://gitlab.com/joerick/cibuildwheel/badges/{branch}/pipeline.svg)](https://gitlab.com/joerick/cibuildwheel/-/commits/{branch})' + ), +] + + +@click.command() +def run_example_ci_configs(): + if git_repo_has_changes(): + print('Your git repo has uncommitted changes. Commit or stash before continuing.') + exit(1) + + previous_branch = shell('git rev-parse --abbrev-ref HEAD', + check=True, + capture_output=True, + encoding='utf8').stdout.strip() + + timestamp = time.strftime('%Y-%m-%dT%H-%M-%S', time.gmtime()) + branch_name = f'example-config-test---{previous_branch}-{timestamp}' + + try: + shell(f'git checkout --orphan {branch_name}', check=True) + + example_project = Path('example_root') + generate_basic_project(example_project) + + for service in services: + src_config_file = Path(service.src_config_path) + dst_config_file = example_project / service.dst_config_path + + dst_config_file.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src_config_file, dst_config_file) + + run(['git', 'add', example_project], check=True) + run(['git', 'commit', '-m', textwrap.dedent(f''' + Test example minimal configs + + Testing files: {[s.src_config_path for s in services]} + Generated from branch: {previous_branch} + Time: {timestamp} + ''')], check=True) + shell(f'git subtree --prefix={example_project} push origin {branch_name}', check=True) + + print('---') + print() + print('> **Examples test run**') + print('> ') + print(f'> Branch: [{branch_name}](https://github.com/joerick/cibuildwheel/tree/{branch_name})') + print('> ') + print('> | Service | Config | Status |') + print('> |---|---|---|') + for service in services: + badge = service.badge_md.format(branch=branch_name, branch_escaped=quote(branch_name, safe='')) + print(f'> | {service.name} | `{service.src_config_path}` | {badge} |') + print('> ') + print('> Generated by `bin/run_example_ci_config.py`') + print() + print('---') + finally: + # remove any local changes + shutil.rmtree(example_project, ignore_errors=True) + shell('git checkout -- .') + shell(f'git checkout {previous_branch}', check=True) + shell(f'git branch -D --force {branch_name}', check=True) + + +if __name__ == '__main__': + os.chdir(os.path.dirname(__file__)) + os.chdir('..') + run_example_ci_configs(standalone_mode=True) diff --git a/docs/contributing.md b/docs/contributing.md index c3988004..52edf315 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -28,3 +28,39 @@ cibuildwheel doesn't really do anything itself - it's always deferring to other We're not responsible for errors in those tools, for fixing errors/crashes there. But cibuildwheel's job is providing users with an 'integrated' user experience across those tools. We provide an abstraction. The user says 'build me some wheels', not 'open the docker container, build a wheel with pip, fix up the symbols with auditwheel' etc. However, errors have a habit of breaking abstractions. And this is where users get confused, because the mechanism of cibuildwheel is laid bare, and they must understand a little bit how it works to debug. So, if we can, I'd like to improve the experience on errors as well. In [this](https://github.com/joerick/cibuildwheel/issues/139) case, it takes a bit of knowledge to understand that the linux builds are happening in a totally different OS via docker, that the linked symbols won't match, that auditwheel will fail because of this. A problem with how the tools fit together, instead of the tools themselves. + +Maintainer notes +---------------- + +## Testing minimal configs + +cibuildwheel's _minimal_ example configs can be tested on a simple project on cibuildwheel's existing CI. These should be run whenever the minimal configs change. + +To test minimal configs, make sure you have a clean git repo, then run the script: + + bin/run_example_ci_configs.py + +The script will create an isolated 'orphan' commit containing all the minimal config CI files, and a simple C extension project, and push that to a branch on the `origin` repo. The project's CI is already set up to run on branch push, so will begin testing. + +The script then outputs a Markdown table that can be copy/pasted into a PR to monitor and record the test. + +## Making a release + +Before making a release, ensure pinned dependencies are up-to-date. Run the script: + + bin/make_dependency_update_pr.py + +If updates are needed, this will push a PR with those updates for the CI to test. Once green, merge this PR. + +Then, increment the project version number using: + + bin/bump_version.py + +You'll be prompted to enter the new version number. Update the changelog when prompted. The script will create a 'bump version' commit and version tag. + +Finally, cut the release and upload to PyPI/Github. + + rm -rf dist + python setup.py sdist bdist_wheel + twine upload dist/* + git push && git push --tags diff --git a/docs/setup.md b/docs/setup.md index 1ff2bc55..241bc4ea 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -66,6 +66,19 @@ Commit this file, enable building of your repo on CircleCI, and push. CircleCI will store the built wheels for you - you can access them from the project console. Check out the CircleCI [docs](https://circleci.com/docs/2.0/configuration-reference/#section=configuration) for more info on this config file. +# Gitlab CI [linux] {: #gitlab-ci} + +To build Linux wheels on Gitlab CI, create a `.gitlab-ci.yml` file in your repo, + +> .gitlab-ci.yml +```yaml +{% include "../examples/gitlab-minimal.yml" %} +``` + +Commit this file, and push to Gitlab. The pipeline should start automatically. + +Gitlab will store the built wheels for you - you can access them from the Pipelines view. Check out the Gitlab [docs](https://docs.gitlab.com/ee/ci/yaml/) for more info on this config file. + # AppVeyor [linux/mac/windows] {: #appveyor} To build Linux, Mac, and Windows wheels on AppVeyor, create an `appveyor.yml` file in your repo. diff --git a/examples/circleci-minimal.yml b/examples/circleci-minimal.yml index a7e71c5a..f39a4d29 100644 --- a/examples/circleci-minimal.yml +++ b/examples/circleci-minimal.yml @@ -25,7 +25,7 @@ jobs: - run: name: Build the OS X wheels. command: | - pip3 install --user cibuildwheel==1.5.5 + pip3 install cibuildwheel==1.5.5 cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ diff --git a/examples/gitlab-minimal.yml b/examples/gitlab-minimal.yml new file mode 100644 index 00000000..9444ee23 --- /dev/null +++ b/examples/gitlab-minimal.yml @@ -0,0 +1,19 @@ +linux: + image: python:3.8 + # make a docker daemon available for cibuildwheel to use + services: + - name: docker:dind + entrypoint: ["env", "-u", "DOCKER_HOST"] + command: ["dockerd-entrypoint.sh"] + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + # See https://github.com/docker-library/docker/pull/166 + DOCKER_TLS_CERTDIR: "" + script: + - curl -sSL https://get.docker.com/ | sh + - python -m pip install cibuildwheel + - cibuildwheel --output-dir wheelhouse + artifacts: + paths: + - wheelhouse/