#!/usr/bin/env python3 from __future__ import annotations import os import shutil import sys import textwrap import time import typing from glob import glob from pathlib import Path from subprocess import run from urllib.parse import quote import click def shell(cmd, *, check: bool, **kwargs): return run([cmd], shell=True, check=check, **kwargs) def git_repo_has_changes(): unstaged_changes = shell("git diff-index --quiet HEAD --", check=False).returncode != 0 staged_changes = shell("git diff-index --quiet --cached HEAD --", check=False).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) class CIService(typing.NamedTuple): name: str dst_config_path: str badge_md: str services = [ CIService( name="appveyor", dst_config_path="appveyor.yml", badge_md="[![Build status](https://ci.appveyor.com/api/projects/status/gt3vwl88yt0y3hur/branch/{branch}?svg=true)](https://ci.appveyor.com/project/joerick/cibuildwheel/branch/{branch})", ), CIService( name="azure-pipelines", dst_config_path="azure-pipelines.yml", badge_md="[![Build Status](https://dev.azure.com/joerick0429/cibuildwheel/_apis/build/status/pypa.cibuildwheel?branchName={branch})](https://dev.azure.com/joerick0429/cibuildwheel/_build/latest?definitionId=2&branchName={branch})", ), CIService( name="circleci", dst_config_path=".circleci/config.yml", badge_md="[![CircleCI](https://circleci.com/gh/pypa/cibuildwheel/tree/{branch_escaped}.svg?style=svg)](https://circleci.com/gh/pypa/cibuildwheel/tree/{branch})", ), CIService( name="github", dst_config_path=".github/workflows/example.yml", badge_md="[![Build](https://github.com/pypa/cibuildwheel/workflows/Build/badge.svg?branch={branch})](https://github.com/pypa/cibuildwheel/actions)", ), CIService( name="travis-ci", dst_config_path=".travis.yml", badge_md="[![Build Status](https://app.travis-ci.com/pypa/cibuildwheel.svg?branch={branch})](https://app.travis-ci.com/pypa/cibuildwheel)", ), CIService( name="gitlab", 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})", ), CIService( name="cirrus-ci", dst_config_path=".cirrus.yml", badge_md="[![Cirrus CI](https://api.cirrus-ci.com/github/pypa/cibuildwheel.svg?branch={branch})](https://cirrus-ci.com/github/pypa/cibuildwheel/{branch})", ), ] def ci_service_for_config_file(config_file): filename = Path(config_file).name try: return next(s for s in services if filename.startswith(s.name)) except StopIteration: msg = f"unknown ci service for config file {config_file}" raise ValueError(msg) from None @click.command() @click.argument("config_files", nargs=-1, type=click.Path()) def run_example_ci_configs(config_files=None): """ Test the example configs. If no files are specified, will test examples/*-minimal.yml """ if len(config_files) == 0: config_files = glob("examples/*-minimal.yml") # check each CI service has at most 1 config file configs_by_service = {} for config_file in config_files: service = ci_service_for_config_file(config_file) if service.name in configs_by_service: msg = "You cannot specify more than one config per CI service" raise Exception(msg) configs_by_service[service.name] = config_file if git_repo_has_changes(): print("Your git repo has uncommitted changes. Commit or stash before continuing.") sys.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 config_file in config_files: service = ci_service_for_config_file(config_file) src_config_file = Path(config_file) 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) message = textwrap.dedent( f"""\ Test example minimal configs Testing files: {config_files} Generated from branch: {previous_branch} Time: {timestamp} """ ) run(["git", "commit", "--no-verify", "--message", message], 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/pypa/cibuildwheel/tree/{branch_name})") print("> ") print("> | Service | Config | Status |") print("> |---|---|---|") for config_file in config_files: service = ci_service_for_config_file(config_file) badge = service.badge_md.format( branch=branch_name, branch_escaped=quote(branch_name, safe="") ) print(f"> | {service.name} | `{config_file}` | {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 -- .", check=True) 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)