diff --git a/bob/devtools/build.py b/bob/devtools/build.py index 70ee86acf8f27a4984f9b7e132a471ee749b318c..59aa728f96ae6fb123e5f938fc419e9c7fa038d5 100644 --- a/bob/devtools/build.py +++ b/bob/devtools/build.py @@ -534,6 +534,7 @@ def git_clean_build(runner, verbose): exclude_from_cleanup = [ "miniconda.sh", # the installer, cached "sphinx", # build artifact -- documentation + "coverage.xml", # build artifact -- coverage report ] # artifacts diff --git a/bob/devtools/scripts/badges.py b/bob/devtools/scripts/badges.py new file mode 100644 index 0000000000000000000000000000000000000000..19f89c031e798ad30a53fcfda2122c473cb5fe7c --- /dev/null +++ b/bob/devtools/scripts/badges.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python + +import os + +import click +import gitlab + +from . import bdt +from ..release import get_gitlab_instance, update_files_at_master + +from ..log import verbosity_option, get_logger, echo_normal, echo_warning + +logger = get_logger(__name__) + + +# These show on the gitlab project landing page (not visible on PyPI) +PROJECT_BADGES = [ + { + "name": "Docs (stable)", + "link_url": "https://www.idiap.ch/software/{group}/docs/%{{project_path}}/stable/index.html", + "image_url": "https://img.shields.io/badge/docs-stable-yellow.svg", + }, + { + "name": "Docs (latest)", + "link_url": "https://www.idiap.ch/software/{group}/docs/%{{project_path}}/%{{default_branch}}/index.html", + "image_url": "https://img.shields.io/badge/docs-latest-orange.svg", + }, + { + "name": "Pipeline (status)", + "link_url": "https://gitlab.idiap.ch/%{{project_path}}/commits/%{{default_branch}}", + "image_url": "https://gitlab.idiap.ch/%{{project_path}}/badges/%{{default_branch}}/pipeline.svg", + }, + { + "name": "Coverage (latest)", + "link_url": "https://gitlab.idiap.ch/%{{project_path}}/commits/%{{default_branch}}", + "image_url": "https://gitlab.idiap.ch/%{{project_path}}/badges/%{{default_branch}}/coverage.svg", + }, + { + "name": "PyPI (version)", + "link_url": "https://pypi.python.org/pypi/{name}", + "image_url": "https://img.shields.io/pypi/v/{name}.svg", + }, +] + + +# These show on the README and will be visible in PyPI +README_BADGES = [ + { + "name": "Docs (current)", + "link_url": "https://www.idiap.ch/software/{group}/docs/{group}/{name}/master/index.html", + "image_url": "https://img.shields.io/badge/docs-available-orage.svg", + }, + { + "name": "Pipeline (current)", + "link_url": "https://gitlab.idiap.ch/{group}/{name}/commits/master", + "image_url": "https://gitlab.idiap.ch/{group}/{name}/badges/master/pipeline.svg", + }, + { + "name": "Coverage (current)", + "link_url": "https://gitlab.idiap.ch/{group}/{name}/commits/master", + "image_url": "https://gitlab.idiap.ch/{group}/{name}/badges/master/coverage.svg", + }, + { + "name": "Gitlab project", + "link_url": "https://gitlab.idiap.ch/{group}/{name}", + "image_url": "https://img.shields.io/badge/gitlab-project-0000c0.svg", + }, +] + + +def _update_readme(content, info): + """Updates the README content provided, replacing badges""" + + import re + + new_badges_text = [] + for badge in README_BADGES: + data = dict((k, v.format(**info)) for (k,v) in badge.items()) + new_badges_text.append(".. image:: {image_url}".format(**data)) + new_badges_text.append(" :target: {link_url}".format(**data)) + new_badges_text = '\n'.join(new_badges_text) + '\n' + # matches only 3 or more occurences of ..image::/:target: occurences + expression = r"(\.\.\s*image.+\n\s+:target:\s*.+\b\n){3,}" + return re.sub(expression, new_badges_text, content) + + +@click.command( + epilog=""" +Examples: + + 1. Creates (by replacing) all existing badges in a gitlab project + (bob/bob.devtools): + + $ bdt gitlab badges bob/bob.devtools + + N.B.: This command also affects the README.rst file. + +""" +) +@click.argument("package") +@click.option( + "-d", + "--dry-run/--no-dry-run", + default=False, + help="Only goes through the actions, but does not execute them " + "(combine with the verbosity flags - e.g. ``-vvv``) to enable " + "printing to help you understand what will be done", +) +@verbosity_option() +@bdt.raise_on_error +def badges(package, dry_run): + """Creates stock badges for a project repository""" + + # if we are in a dry-run mode, let's let it be known + if dry_run: + logger.warn("!!!! DRY RUN MODE !!!!") + logger.warn("Nothing is being changed at Gitlab") + + if "/" not in package: + raise RuntimeError('PACKAGE should be specified as "group/name"') + + gl = get_gitlab_instance() + + # we lookup the gitlab package once + try: + use_package = gl.projects.get(package) + logger.info( + "Found gitlab project %s (id=%d)", + use_package.attributes["path_with_namespace"], + use_package.id, + ) + + badges = use_package.badges.list() + for badge in badges: + logger.info( + "Removing badge '%s' (id=%d) => '%s'", + badge.name, + badge.id, + badge.link_url, + ) + if not dry_run: badge.delete() + + # creates all stock badges, preserve positions + info = dict(zip(("group", "name"), package.split("/", 1))) + for position, badge in enumerate(PROJECT_BADGES): + data = dict([(k,v.format(**info)) for (k,v) in badge.items()]) + data["position"] = position + logger.info( + "Creating badge '%s' => '%s'", + data["name"], + data["link_url"], + ) + if not dry_run: use_package.badges.create(data) + + # download and edit README to setup badges + readme_file = use_package.files.get(file_path="README.rst", ref="master") + readme_content = readme_file.decode().decode() + readme_content = _update_readme(readme_content, info) + # commit and push changes + logger.info("Changing README.rst badges...") + update_files_at_master(use_package, {"README.rst": readme_content}, + "Updated badges section [ci skip]", dry_run) + logger.info("All done.") + + except gitlab.GitlabGetError as e: + logger.warn("Gitlab access error - package %s does not exist?", package) + echo_warning("%s: unknown" % (package,)) diff --git a/conda/meta.yaml b/conda/meta.yaml index 838772de640254d1c2241400fb250a8ebc3aaad2..a41ac8893c86bacf1701ce5386d6b4bae861dfb2 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -106,6 +106,7 @@ test: - bdt gitlab process-pipelines --help - bdt gitlab get-pipelines --help - bdt gitlab graph --help + - bdt gitlab badges --help - sphinx-build -aEW ${PREFIX}/share/doc/{{ name }}/doc sphinx - if [ -n "${CI_PROJECT_DIR}" ]; then mv sphinx "${CI_PROJECT_DIR}/"; fi diff --git a/setup.py b/setup.py index 449ba6d330f33c97c335a99c42987d819bc7cce5..f4cb86eb71c40be0478188dc21502ddd2930b724 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ setup( ], 'bdt.gitlab.cli': [ + 'badges = bob.devtools.scripts.badges:badges', 'commitfile = bob.devtools.scripts.commitfile:commitfile', 'release = bob.devtools.scripts.release:release', 'changelog = bob.devtools.scripts.changelog:changelog', @@ -68,7 +69,7 @@ setup( 'visibility = bob.devtools.scripts.visibility:visibility', 'getpath = bob.devtools.scripts.getpath:getpath', 'process-pipelines = bob.devtools.scripts.pipelines:process_pipelines', - 'get-pipelines- = bob.devtools.scripts.pipelines:get_pipelines', + 'get-pipelines = bob.devtools.scripts.pipelines:get_pipelines', 'graph = bob.devtools.scripts.graph:graph', 'update-bob = bob.devtools.scripts.update_bob:update_bob', ],