diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ec7e0f754be049fcaffbd52e713f51a4ad4b1d0..89ed30711b73ff8ce160031f6adb7880110b66a8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,12 @@ build_linux_36: <<: *linux_build_job variables: PYTHON_VERSION: "3.6" + + +build_linux_37: + <<: *linux_build_job + variables: + PYTHON_VERSION: "3.7" BUILD_EGG: "true" script: - python3 ./bob/devtools/bootstrap.py -vv build @@ -76,18 +82,27 @@ build_macosx_36: PYTHON_VERSION: "3.6" +build_macosx_37: + <<: *macosx_build_job + variables: + PYTHON_VERSION: "3.7" + + # Deploy targets .deploy_template: &deploy_job stage: deploy script: - - python3 ./bob/devtools/bootstrap.py -vv local bdt + - python3 ./bob/devtools/bootstrap.py -vv local base - source ${CONDA_ROOT}/etc/profile.d/conda.sh - - conda activate bdt + - conda activate base + - bdt ci base-deploy -vv - bdt ci deploy -vv - bdt ci clean -vv dependencies: - build_linux_36 + - build_linux_37 - build_macosx_36 + - build_macosx_37 tags: - docker cache: &build_caches @@ -121,14 +136,16 @@ pypi: except: - branches script: - - python3 ./bob/devtools/bootstrap.py -vv local bdt + - python3 ./bob/devtools/bootstrap.py -vv local base - source ${CONDA_ROOT}/etc/profile.d/conda.sh - - conda activate bdt + - conda activate base - bdt ci pypi -vv dist/*.zip - bdt ci clean -vv dependencies: - build_linux_36 + - build_linux_37 - build_macosx_36 + - build_macosx_37 tags: - docker cache: &build_caches diff --git a/MANIFEST.in b/MANIFEST.in index 64a24146322e9c0e5a7bc873132dc1eb99bbc583..fd350028a9bb270daf9c4fc8f001544a83998fa6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include LICENSE README.rst buildout.cfg version.txt recursive-include doc conf.py *.rst *.sh *.png *.ico recursive-include bob/devtools/data *.md *.yaml *.pem matplotlibrc -recursive-include bob/devtools/templates conf.py *.rst *.png *.ico LICENSE COPYING .gitlab-ci.yml .gitignore *.cfg *.txt *.py +recursive-include bob/devtools/templates conf.py *.rst *.png *.ico LICENSE COPYING MANIFEST.in .gitlab-ci.yml .gitignore *.cfg *.txt *.py diff --git a/bob/devtools/bootstrap.py b/bob/devtools/bootstrap.py index bb338ce0074f97d5913fd4e40bfc90656fb7db80..bcb086b8d29bf453addb81df9dc253ff82c1c31c 100644 --- a/bob/devtools/bootstrap.py +++ b/bob/devtools/bootstrap.py @@ -383,7 +383,7 @@ if __name__ == '__main__': f.write(_BASE_CONDARC) conda_version = '4' - conda_build_version = '3' + conda_build_version = '3.16' conda_verbosity = [] #if args.verbose >= 2: diff --git a/bob/devtools/build.py b/bob/devtools/build.py index ab73d6f87d5ca605cdb00b311724644089b05dae..c33c0d55bc0d0cd2a16ed69581da5cca25ffaaf3 100644 --- a/bob/devtools/build.py +++ b/bob/devtools/build.py @@ -154,6 +154,61 @@ def get_parsed_recipe(metadata): return yaml.load(output) +def exists_on_channel(channel_url, name, version, build_number, + python_version): + """Checks on the given channel if a package with the specs exist + + Args: + + channel_url: The URL where to look for packages clashes (normally a beta + channel) + name: The name of the package + version: The version of the package + build_number: The build number of the package + python_version: The current version of python we're building for + + Returns: A complete package name, version and build string, if the package + already exists in the channel or ``None`` otherwise. + + """ + + from conda.exports import get_index + + # no dot in py_ver + py_ver = python_version.replace('.', '') + + # get the channel index + logger.debug('Downloading channel index from %s', channel_url) + index = get_index(channel_urls=[channel_url], prepend=False) + + logger.info('Checking for %s-%s-%s...', name, version, build_number) + + for dist in index: + + if dist.name == name and dist.version == version and \ + dist.build_string.endswith('_%s' % build_number): + + # two possible options must be checked - (i) the package build_string + # starts with ``py``, which means it is a python specific package so we + # must also check for the matching python version. (ii) the package is + # not a python-specific package and a simple match will do + if dist.build_string.startswith('py'): + match = re.match('py[2-9][0-9]+', dist.build_string) + if match and match.group() == 'py{}'.format(py_ver): + logger.debug('Found matching package (%s-%s-%s)', dist.name, + dist.version, dist.build_string) + return (dist.name, dist.version, dist.build_string) + + else: + logger.debug('Found matching package (%s-%s-%s)', dist.name, + dist.version, dist.build_string) + return (dist.name, dist.version, dist.build_string) + + logger.info('No matches for %s-%s-(py%s_?)%s found among %d packages', + name, version, py_ver, build_number, len(index)) + return + + def remove_pins(deps): return [l.split()[0] for l in deps] @@ -407,6 +462,69 @@ def git_clean_build(runner, verbose): ['--exclude=%s' % k for k in exclude_from_cleanup]) +def base_build(server, intranet, recipe_dir, conda_build_config, + python_version, condarc_options): + '''Builds a non-beat/bob software dependence that does not exist on defaults + + This function will build a software dependence that is required for our + software stack, but does not (yet) exist on the defaults channels. It first + check if the build should run for the current architecture, checks if the + package is not already built on our public channel and, if that is true, then + proceeds with the build of the dependence. + + + Args: + + server: The base address of the server containing our conda channels + intranet: Boolean indicating if we should add "private"/"public" prefixes + on the returned paths + recipe_dir: The directory containing the recipe's ``meta.yaml`` file + conda_build_config: Path to the ``conda_build_config.yaml`` file to use + python_version: String with the python version to build for, in the format + ``x.y`` (should be passed even if not building a python package) + condarc_options: Pre-parsed condarc options loaded from the respective YAML + file + + ''' + + # if you get to this point, tries to build the package + public_channels = bootstrap.get_channels(public=True, stable=True, + server=server, intranet=intranet) + + logger.info('Using the following channels during (potential) build:\n - %s', + '\n - '.join(public_channels + ['defaults'])) + condarc_options['channels'] = public_channels + ['defaults'] + + logger.info('Merging conda configuration files...') + conda_config = make_conda_config(conda_build_config, python_version, + None, condarc_options) + + metadata = get_rendered_metadata(recipe_dir, conda_config) + recipe = get_parsed_recipe(metadata) + + if recipe is None: + logger.info('Skipping build for %s - rendering returned None', recipe_dir) + return + + # no dot in py_ver + py_ver = python_version.replace('.', '') + arch = conda_arch() + + candidate = exists_on_channel(public_channels[0], recipe['package']['name'], + recipe['package']['version'], recipe['build']['number'], + python_version) + if candidate is not None: + logger.info('Skipping build for %s-%s-(py%s_?)%s for %s - exists ' \ + 'on channel', candidate[0], candidate[1], candidate[2], py_ver, arch) + return + + # if you get to this point, just builds the package + logger.info('Building %s-%s-(py%s_?)%s for %s', + recipe['package']['name'], recipe['package']['version'], + recipe['build']['number'], py_ver, arch) + conda_build.api.build(recipe_dir, config=conda_config) + + if __name__ == '__main__': import argparse @@ -476,8 +594,22 @@ if __name__ == '__main__': with open(condarc, 'rb') as f: condarc_options = yaml.load(f) + # dump packages at conda_root + condarc_options['croot'] = os.path.join(args.conda_root, 'conda-bld') + + # builds all dependencies in the 'deps' subdirectory - or at least checks + # these dependencies are already available; these dependencies go directly to + # the public channel once built + for recipe in glob.glob(os.path.join('deps', '*')): + if not os.path.exists(os.path.join(recipe, 'meta.yaml')): + # ignore - not a conda package + continue + base_build(server, not args.internet, recipe, conda_build_config, + args.python_version, condarc_options) + # notice this condarc typically will only contain the defaults channel - we - # need to boost this up with more channels to get it right. + # need to boost this up with more channels to get it right for this package's + # build public = ( args.visibility == 'public' ) channels = bootstrap.get_channels(public=public, stable=(not is_prerelease), server=server, intranet=(not args.internet)) @@ -485,18 +617,15 @@ if __name__ == '__main__': '\n - '.join(channels + ['defaults'])) condarc_options['channels'] = channels + ['defaults'] - # dump packages at conda_root - condarc_options['croot'] = os.path.join(args.conda_root, 'conda-bld') - - logger.info('Merging conda configuration files...') - conda_config = make_conda_config(conda_build_config, args.python_version, - recipe_append, condarc_options) - # retrieve the current build number for this build build_number, _ = next_build_number(channels[0], args.name, version, args.python_version) bootstrap.set_environment('BOB_BUILD_NUMBER', str(build_number)) + logger.info('Merging conda configuration files...') + conda_config = make_conda_config(conda_build_config, args.python_version, + recipe_append, condarc_options) + # runs the build using the conda-build API arch = conda_arch() logger.info('Building %s-%s-py%s (build: %d) for %s', @@ -517,4 +646,4 @@ if __name__ == '__main__': else: logger.info('twine check (a.k.a. readme check) %s: OK', package[0]) - git_clean_build(bootstrap.run_cmdline, verbose=(args.verbose >= 2)) + git_clean_build(bootstrap.run_cmdline, verbose=(args.verbose >= 3)) diff --git a/bob/devtools/data/conda_build_config.yaml b/bob/devtools/data/conda_build_config.yaml index 46fa5c044415da9dda30394f4e0a6cba6865cb69..0302a7dee0226a1dd2a8892df3a2638ccaa60c9f 100644 --- a/bob/devtools/data/conda_build_config.yaml +++ b/bob/devtools/data/conda_build_config.yaml @@ -32,6 +32,7 @@ pin_run_as_build: ## the dependencies that we build against multiple versions python: - 3.6 + - 3.7 zip_keys: - # [win] diff --git a/bob/devtools/data/gitlab-ci/single-package.yaml b/bob/devtools/data/gitlab-ci/single-package.yaml index 5b045a479856f0567d312e8decd6c2cc4d3e5b32..f1cb7a09a2fce60587f134ceb6123468049f8be0 100644 --- a/bob/devtools/data/gitlab-ci/single-package.yaml +++ b/bob/devtools/data/gitlab-ci/single-package.yaml @@ -20,9 +20,9 @@ stages: stage: build script: - curl --silent "${BOOTSTRAP}" --output "bootstrap.py" - - python3 bootstrap.py -vv channel bdt + - python3 bootstrap.py -vv channel base - source ${CONDA_ROOT}/etc/profile.d/conda.sh - - conda activate bdt + - conda activate base - bdt ci build -vv - bdt ci clean -vv cache: &build_caches @@ -66,9 +66,9 @@ build_linux_36: BUILD_EGG: "true" script: - curl --silent "${BOOTSTRAP}" --output "bootstrap.py" - - python3 bootstrap.py -vv channel bdt + - python3 bootstrap.py -vv channel base - source ${CONDA_ROOT}/etc/profile.d/conda.sh - - conda activate bdt + - conda activate base - bdt ci build -vv - bdt ci readme -vv dist/*.zip - bdt ci clean -vv @@ -91,9 +91,9 @@ build_macosx_36: stage: deploy script: - curl --silent "${BOOTSTRAP}" --output "bootstrap.py" - - python3 bootstrap.py channel bdt + - python3 bootstrap.py channel base - source ${CONDA_ROOT}/etc/profile.d/conda.sh - - conda activate bdt + - conda activate base - bdt ci deploy -vv - bdt ci clean -vv dependencies: @@ -133,9 +133,9 @@ pypi: - branches script: - curl --silent "${BOOTSTRAP}" --output "bootstrap.py" - - python3 bootstrap.py -vv channel bdt + - python3 bootstrap.py -vv channel base - source ${CONDA_ROOT}/etc/profile.d/conda.sh - - conda activate bdt + - conda activate base - bdt ci pypi -vv dist/*.zip - bdt ci clean -vv dependencies: diff --git a/bob/devtools/scripts/ci.py b/bob/devtools/scripts/ci.py index f05c4a425591f90621dbee407d7e77f2e6e0efef..ced320084cae86ffdb8fb24996747f842e509707 100644 --- a/bob/devtools/scripts/ci.py +++ b/bob/devtools/scripts/ci.py @@ -30,6 +30,76 @@ def ci(): pass +@ci.command(epilog=''' +Examples: + + 1. Deploys base build artifacts (dependencies) to the appropriate channels: + + $ bdt ci base-deploy -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 base_deploy(dry_run): + """Deploys dependencies not available at the defaults channel + + Deployment happens to our public channel directly, as these are + dependencies are required for proper bob/beat package runtime environments. + """ + + if dry_run: + logger.warn('!!!! DRY RUN MODE !!!!') + logger.warn('Nothing is being deployed to server') + + package = os.environ['CI_PROJECT_PATH'] + + from ..constants import WEBDAV_PATHS + server_info = WEBDAV_PATHS[True][True] #stable=True, visible=True + + logger.info('Deploying dependence packages to %s%s%s...', SERVER, + server_info['root'], server_info['conda']) + + # setup webdav connection + webdav_options = { + 'webdav_hostname': SERVER, + 'webdav_root': server_info['root'], + 'webdav_login': os.environ['DOCUSER'], + 'webdav_password': os.environ['DOCPASS'], + } + from ..webdav3 import client as webdav + davclient = webdav.Client(webdav_options) + assert davclient.valid() + + group, name = package.split('/') + + # uploads conda package artificats + for arch in ('linux-64', 'osx-64', 'noarch'): + # finds conda dependencies and uploads what we can find + package_path = os.path.join(os.environ['CONDA_ROOT'], 'conda-bld', arch, + '*.tar.bz2') + deploy_packages = glob.glob(package_path) + for k in deploy_packages: + basename = os.path.basename(k) + if basename.startswith(name): + logger.debug('Skipping deploying of %s - not a base package', k) + continue + + remote_path = '%s/%s/%s' % (server_info['conda'], arch, basename) + if davclient.check(remote_path): + raise RuntimeError('The file %s/%s already exists on the server ' \ + '- this can be due to more than one build with deployment ' \ + 'running at the same time. Re-running the broken builds ' \ + 'normally fixes it' % (SERVER, remote_path)) + logger.info('[dav] %s -> %s%s%s', k, SERVER, server_info['root'], + remote_path) + if not dry_run: + davclient.upload(local_path=k, remote_path=remote_path) + + @ci.command(epilog=''' Examples: @@ -291,4 +361,4 @@ def clean(ctx): from ..build import git_clean_build from ..bootstrap import run_cmdline - git_clean_build(run_cmdline, verbose=(ctx.meta['verbosity']>=2)) + git_clean_build(run_cmdline, verbose=(ctx.meta['verbosity']>=3)) diff --git a/conda/meta.yaml b/conda/meta.yaml index deb63262b5fc1e768835a2139cd3a9bf9dec0f88..0cc376da3ab5a2eedcc71f43480f49f8bad98279 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -68,6 +68,7 @@ test: - bdt new --help - bdt ci --help - bdt ci build --help + - bdt ci base-deploy --help - bdt ci deploy --help - bdt ci pypi --help - bdt ci readme --help diff --git a/deps/python-gitlab/meta.yaml b/deps/python-gitlab/meta.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8cce6e8296470bc5ea3d891f0ef6436c48ded162 --- /dev/null +++ b/deps/python-gitlab/meta.yaml @@ -0,0 +1,46 @@ +{% set name = "python-gitlab" %} +{% set version = "1.7.0" %} + +package: + name: {{ name|lower }} + version: {{ version }} + +source: + url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz + sha256: 401ef8929db4dcb5b08e0a2263a0a70599fc7e5b27615f956ac26d245802d09e + +build: + number: 0 + script: "{{ PYTHON }} -m pip install . -vv" + entry_points: + - gitlab = gitlab.cli:main + +requirements: + host: + - python + - pip + run: + - python + - requests + - six + +test: + imports: + - gitlab + +about: + home: https://github.com/python-gitlab/python-gitlab + license: LGPL-3.0 + license_family: LGPL + license_file: COPYING + summary: 'Python wrapper for the GitLab API' + description: | + python-gitlab is a Python package providing access to the GitLab + server API. It supports the v4 API of GitLab, and provides a CLI + tool (gitlab). + doc_url: https://python-gitlab.readthedocs.io/ + dev_url: https://github.com/python-gitlab/python-gitlab + +extra: + recipe-maintainers: + - anjos diff --git a/setup.py b/setup.py index 178fd46511dc7f630499511bcee83ddd0b802bd6..01918fb2ffcbeb365f69dbfa00430be17ff57aa5 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ setup( 'bdt.ci.cli': [ 'build = bob.devtools.scripts.ci:build', 'clean = bob.devtools.scripts.ci:clean', + 'base-deploy = bob.devtools.scripts.ci:base_deploy', 'deploy = bob.devtools.scripts.ci:deploy', 'readme = bob.devtools.scripts.ci:readme', 'pypi = bob.devtools.scripts.ci:pypi',