diff --git a/bob/devtools/build.py b/bob/devtools/build.py index b04b7becf6cdc20b999a6fffdbdbec23be1cd086..9881ca58a0668518ced1ff557a4e39a5bc1a2745 100644 --- a/bob/devtools/build.py +++ b/bob/devtools/build.py @@ -314,6 +314,77 @@ def get_docserver_setup(public, stable, server, intranet): return '|'.join(entries) +def check_version(workdir, envtag): + '''Checks if the version being built and the value reported match + + This method will read the contents of the file ``version.txt`` and compare it + to the potentially set ``envtag`` (may be ``None``). If the value of + ``envtag`` is different than ``None``, ensure it matches the value in + ``version.txt`` or raises an exception. + + + Args: + + workdir: The work directory where the repo of the package being built was + checked-out + envtag: The output of os.environ.get('CI_COMMIT_TAG') (may be ``None``) + + + Returns: A tuple with the version of the package that we're currently + building and a boolean flag indicating if the version number represents a + pre-release or a stable release. + ''' + + version = open(os.path.join(workdir, "version.txt"), 'rt').read().rstrip() + + # if we're building a stable release, ensure a tag is set + parsed_version = distutils.version.LooseVersion(version).version + is_prerelease = any([isinstance(k, str) for k in parsed_version]) + if is_prerelease: + if envtag is not None: + raise EnvironmentError('"version.txt" indicates version is a ' \ + 'pre-release (v%s) - but os.environ["CI_COMMIT_TAG"]="%s", ' \ + 'which indicates this is a **stable** build. ' \ + 'Have you created the tag using ``bdt release``?' % (version, + envtag)) + else: #it is a stable build + if envtag is None: + raise EnvironmentError('"version.txt" indicates version is a ' \ + 'stable build (v%s) - but there is no os.environ["CI_COMMIT_TAG"] ' \ + 'variable defined, which indicates this is **not** ' \ + 'a tagged build. Use ``bdt release`` to create stable releases' % \ + (version,)) + if envtag[1:] != version: + raise EnvironmentError('"version.txt" and the value of ' \ + 'os.environ["CI_COMMIT_TAG"] do **NOT** agree - the former ' \ + 'reports version %s, the latter, %s' % (version, envtag[1:])) + + return version, is_prerelease + + +def git_clean_build(runner, arch): + '''Runs git-clean to clean-up build products + + Args: + + runner: A pointer to the ``run_cmdline()`` function + + ''' + + # runs git clean to clean everything that is not needed. This helps to keep + # the disk usage on CI machines to a minimum. + exclude_from_cleanup = [ + "miniconda.sh", #the installer, cached + "miniconda/pkgs/*.tar.bz2", #downloaded packages, cached + "miniconda/pkgs/urls.txt", #download index, cached + "miniconda/conda-bld/%s/*.tar.bz2" % (arch,), #build artifact -- conda + "dist/*.zip", #build artifact -- pypi package + "sphinx", #build artifact -- documentation + ] + runner(['git', 'clean', '-qffdx'] + \ + ['--exclude=%s' % k for k in exclude_from_cleanup]) + + if __name__ == '__main__': # loads the "adjacent" bootstrap module @@ -338,40 +409,18 @@ if __name__ == '__main__': pyver = os.environ['PYTHON_VERSION'] logger.info('os.environ["%s"] = %s', 'PYTHON_VERSION', pyver) - bootstrap.set_environment('DOCSERVER', bootstrap._SERVER, os.environ, - verbose=True) - bootstrap.set_environment('LANG', 'en_US.UTF-8', os.environ, - verbose=True) - bootstrap.set_environment('LC_ALL', os.environ['LANG'], os.environ, - verbose=True) + bootstrap.set_environment('DOCSERVER', bootstrap._SERVER, verbose=True) + bootstrap.set_environment('LANG', 'en_US.UTF-8', verbose=True) + bootstrap.set_environment('LC_ALL', os.environ['LANG'], verbose=True) # get information about the version of the package being built - version = open("version.txt").read().rstrip() - os.environ['BOB_PACKAGE_VERSION'] = version - logger.info('os.environ["%s"] = %s', 'BOB_PACKAGE_VERSION', version) - - # if we're building a stable release, ensure a tag is set - parsed_version = distutils.version.LooseVersion(version).version - is_prerelease = any([isinstance(k, str) for k in parsed_version]) - if is_prerelease: - if os.environ.get('CI_COMMIT_TAG') is not None: - raise EnvironmentError('"version.txt" indicates version is a ' \ - 'pre-release (v%s) - but os.environ["CI_COMMIT_TAG"]="%s", ' \ - 'which indicates this is a **stable** build. ' \ - 'Have you created the tag using ``bdt release``?', version, - os.environ['CI_COMMIT_TAG']) - else: #it is a stable build - if os.environ.get('CI_COMMIT_TAG') is None: - raise EnvironmentError('"version.txt" indicates version is a ' \ - 'stable build (v%s) - but there is no os.environ["CI_COMMIT_TAG"] ' \ - 'variable defined, which indicates this is **not** ' \ - 'a tagged build. Use ``bdt release`` to create stable releases', - version) + version, is_prerelease = check_version(workdir, + os.environ.get('CI_COMMIT_TAG')) + bootstrap.set_environment('BOB_PACKAGE_VERSION', version, verbose=True) # create the build configuration conda_build_config = os.path.join(mydir, 'data', 'conda_build_config.yaml') recipe_append = os.path.join(mydir, 'data', 'recipe_append.yaml') - logger.info('Merging conda configuration files...') condarc = os.path.join(prefix, 'condarc') logger.info('Loading (this build\'s) CONDARC file from %s...', condarc) @@ -380,13 +429,14 @@ if __name__ == '__main__': # notice this condarc typically will only contain the defaults channel - we # need to boost this up with more channels to get it right. - channels = bootstrap.get_channels( - public=(os.environ['CI_PROJECT_VISIBILITY']=='public'), - stable=(not is_prerelease), server=bootstrap._SERVER, intranet=True) + public = ( os.environ['CI_PROJECT_VISIBILITY']=='public' ) + channels = bootstrap.get_channels(public=public, stable=(not is_prerelease), + server=bootstrap._SERVER, intranet=True) logger.info('Using the following channels during build:\n - %s', '\n - '.join(channels + ['defaults'])) condarc_options['channels'] = channels + ['defaults'] + logger.info('Merging conda configuration files...') conda_config = make_conda_config(conda_build_config, pyver, recipe_append, condarc_options) @@ -401,15 +451,4 @@ if __name__ == '__main__': name, version, pyver.replace('.',''), build_number, arch) conda_build.api.build(os.path.join(workdir, 'conda'), config=conda_config) - # runs git clean to clean everything that is not needed. This helps to keep - # the disk usage on CI machines to a minimum. - exclude_from_cleanup = [ - "miniconda.sh", #the installer, cached - "miniconda/pkgs/*.tar.bz2", #downloaded packages, cached - "miniconda/pkgs/urls.txt", #download index, cached - "miniconda/conda-bld/%s/*.tar.bz2" % (arch,), #build artifact -- conda - "dist/*.zip", #build artifact -- pypi package - "sphinx", #build artifact -- documentation - ] - bootstrap.run_cmdline(['git', 'clean', '-qffdx'] + \ - ['--exclude=%s' % k for k in exclude_from_cleanup]) + git_clean_build(bootstrap.run_cmdline, arch) diff --git a/bob/devtools/scripts/build.py b/bob/devtools/scripts/build.py index 2242ccee4e51fabdb3fa512e9a882653e39dff73..6e7f2038efcdc1f09e740304f9dd769bb0e9d5bc 100644 --- a/bob/devtools/scripts/build.py +++ b/bob/devtools/scripts/build.py @@ -6,9 +6,10 @@ import sys import logging logger = logging.getLogger(__name__) -import pkg_resources -import click import yaml +import click +import pkg_resources +import conda_build.api from . import bdt from ..log import verbosity_option @@ -107,16 +108,16 @@ def build(recipe_dir, python, condarc, config, no_test, append_file, conda_config = make_conda_config(config, python, append_file, condarc_options) - set_environment('LANG', 'en_US.UTF-8', os.environ) - set_environment('LC_ALL', os.environ['LANG'], os.environ) - set_environment('MATPLOTLIBRC', MATPLOTLIB_RCDIR, os.environ) + set_environment('LANG', 'en_US.UTF-8', verbose=True) + set_environment('LC_ALL', os.environ['LANG'], verbose=True) + set_environment('MATPLOTLIBRC', MATPLOTLIB_RCDIR, verbose=True) # setup BOB_DOCUMENTATION_SERVER environment variable (used for bob.extension # and derived documentation building via Sphinx) - set_environment('DOCSERVER', server, os.environ) + set_environment('DOCSERVER', server, verbose=True) doc_urls = get_docserver_setup(public=(not private), stable=stable, server=server, intranet=private) - set_environment('BOB_DOCUMENTATION_SERVER', doc_urls, server=server) + set_environment('BOB_DOCUMENTATION_SERVER', doc_urls, verbose=True) for d in recipe_dir: @@ -126,7 +127,7 @@ def build(recipe_dir, python, condarc, config, no_test, append_file, version_candidate = os.path.join(d, '..', 'version.txt') if os.path.exists(version_candidate): version = open(version_candidate).read().rstrip() - set_environment('BOB_PACKAGE_VERSION', version, os.environ) + set_environment('BOB_PACKAGE_VERSION', version, verbose=True) # pre-renders the recipe - figures out package name and version metadata = get_rendered_metadata(d, conda_config) @@ -146,12 +147,11 @@ def build(recipe_dir, python, condarc, config, no_test, append_file, rendered_recipe['package']['name'], rendered_recipe['package']['version'], python) - set_environment('BOB_BUILD_NUMBER', str(build_number), os.environ) + set_environment('BOB_BUILD_NUMBER', str(build_number), verbose=True) logger.info('Building %s-%s-py%s (build: %d) for %s', rendered_recipe['package']['name'], rendered_recipe['package']['version'], python.replace('.',''), build_number, arch) if not dry_run: - from conda_build.api import build - build(d, config=conda_config, notest=no_test) + conda_build.api.build(d, config=conda_config, notest=no_test) diff --git a/bob/devtools/scripts/ci.py b/bob/devtools/scripts/ci.py index fed7cf008aa279bd6ad66c0967c976997d115bfb..8e10099f2da46d27cc532da38cd4947c5fcd57db 100644 --- a/bob/devtools/scripts/ci.py +++ b/bob/devtools/scripts/ci.py @@ -13,9 +13,15 @@ from click_plugins import with_plugins from . import bdt from ..log import verbosity_option from ..ci import is_stable, is_visible_outside -from ..constants import SERVER, WEBDAV_PATHS, CACERT from ..webdav3 import client as webdav +from ..constants import SERVER, WEBDAV_PATHS, CACERT, CONDA_BUILD_CONFIG, \ + CONDA_RECIPE_APPEND, MATPLOTLIB_RCDIR, BASE_CONDARC +from ..build import next_build_number, conda_arch, should_skip_build, \ + get_rendered_metadata, get_parsed_recipe, make_conda_config, \ + get_docserver_setup, check_version, git_clean_build +from ..bootstrap import set_environment, get_channels, run_cmdline + @with_plugins(pkg_resources.iter_entry_points('bdt.ci.cli')) @click.group(cls=bdt.AliasedGroup) @@ -201,3 +207,90 @@ def pypi(dry_run): from twine.commands.upload import upload upload(settings, zip_files) logger.info('Deployment to PyPI successful') + + +@ci.command(epilog=''' +Examples: + + 1. Builds the current package + + $ bdt ci build -vv + +''') +@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 build(dry_run): + """Builds packages + + This command builds packages in the CI infrastructure. It is **not** meant + to be used outside this context. + """ + + if dry_run: + logger.warn('!!!! DRY RUN MODE !!!!') + logger.warn('Nothing is being built') + + prefix = os.environ['CONDA_ROOT'] + logger.info('os.environ["%s"] = %s', 'CONDA_ROOT', prefix) + + workdir = os.environ['CI_PROJECT_DIR'] + logger.info('os.environ["%s"] = %s', 'CI_PROJECT_DIR', workdir) + + name = os.environ['CI_PROJECT_NAME'] + logger.info('os.environ["%s"] = %s', 'CI_PROJECT_NAME', name) + + pyver = os.environ['PYTHON_VERSION'] + logger.info('os.environ["%s"] = %s', 'PYTHON_VERSION', pyver) + + set_environment('LANG', 'en_US.UTF-8', os.environ, verbose=True) + set_environment('LC_ALL', os.environ['LANG'], os.environ, verbose=True) + set_environment('MATPLOTLIBRC', MATPLOTLIB_RCDIR, verbose=True) + + # setup BOB_DOCUMENTATION_SERVER environment variable (used for bob.extension + # and derived documentation building via Sphinx) + set_environment('DOCSERVER', SERVER, os.environ, verbose=True) + public = ( os.environ['CI_PROJECT_VISIBILITY']=='public' ) + doc_urls = get_docserver_setup(public=public, stable=(not is_prerelease), + server=SERVER, intranet=True) + set_environment('BOB_DOCUMENTATION_SERVER', doc_urls, verbose=True) + + # get information about the version of the package being built + version, is_prerelease = check_version(workdir, + os.environ.get('CI_COMMIT_TAG')) + set_environment('BOB_PACKAGE_VERSION', version, verbose=True) + + condarc = os.path.join(prefix, 'condarc') + logger.info('Loading (this build\'s) CONDARC file from %s...', condarc) + with open(condarc, 'rb') as f: + condarc_options = yaml.load(f) + + # notice this condarc typically will only contain the defaults channel - we + # need to boost this up with more channels to get it right. + channels = bootstrap.get_channels(public=public, stable=(not is_prerelease), + server=SERVER, intranet=True) + logger.info('Using the following channels during build:\n - %s', + '\n - '.join(channels + ['defaults'])) + condarc_options['channels'] = channels + ['defaults'] + + # create the build configuration + logger.info('Merging conda configuration files...') + conda_config = make_conda_config(CONDA_BUILD_CONFIG, pyver, + CONDA_RECIPE_APPEND, condarc_options) + + # retrieve the current build number for this build + build_number, _ = next_build_number(channels[0], name, version, pyver) + set_environment('BOB_BUILD_NUMBER', str(build_number), verbose=True) + + # runs the build using the conda-build API + arch = conda_arch() + logger.info('Building %s-%s-py%s (build: %d) for %s', + name, version, pyver.replace('.',''), build_number, arch) + + if not dry_run: + conda_build.api.build(os.path.join(workdir, 'conda'), config=conda_config) + + git_clean_build(run_cmdline, arch)