From d0f789a038b1ae74716a96c2d1838dc0aaab8364 Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.dos.anjos@gmail.com> Date: Sun, 6 Jan 2019 09:22:06 +0100 Subject: [PATCH] Major commit to refactor this package as a hub for more functionality - Start moving bob.admin scripts here - Add baseline documentation system - Add logging support - Rename it as bob.devtools - Prepare for conda-packaging - Name main tool `bdt` --- MANIFEST.in | 4 +- README.rst | 18 +- bob/__init__.py | 3 + {bob_tools => bob/devtools}/__init__.py | 0 bob/devtools/changelog.py | 296 +++++++++ {bob_tools/utils => bob/devtools}/conda.py | 0 .../data/changelog_since_last_release.md | 587 ++++++++++++++++++ bob/devtools/release.py | 464 ++++++++++++++ .../devtools}/scripts/__init__.py | 0 bob/devtools/scripts/bdt.py | 119 ++++ bob/devtools/scripts/cb_output.py | 48 ++ bob/devtools/scripts/changelog.py | 148 +++++ bob/devtools/scripts/lasttag.py | 48 ++ bob/devtools/scripts/release.py | 184 ++++++ bob_tools/scripts/cb_output.py | 41 -- bob_tools/scripts/main.py | 13 - bob_tools/utils/__init__.py | 0 bob_tools/utils/click.py | 20 - buildout.cfg | 3 +- doc/api.rst | 21 + doc/conf.py | 253 ++++++++ doc/img/favicon.ico | Bin 0 -> 4286 bytes doc/img/logo.png | Bin 0 -> 6266 bytes doc/index.rst | 29 + doc/links.rst | 5 + doc/release.rst | 141 +++++ env.yml | 22 + setup.py | 28 +- 28 files changed, 2401 insertions(+), 94 deletions(-) create mode 100644 bob/__init__.py rename {bob_tools => bob/devtools}/__init__.py (100%) create mode 100644 bob/devtools/changelog.py rename {bob_tools/utils => bob/devtools}/conda.py (100%) create mode 100644 bob/devtools/data/changelog_since_last_release.md create mode 100644 bob/devtools/release.py rename {bob_tools => bob/devtools}/scripts/__init__.py (100%) create mode 100644 bob/devtools/scripts/bdt.py create mode 100644 bob/devtools/scripts/cb_output.py create mode 100644 bob/devtools/scripts/changelog.py create mode 100644 bob/devtools/scripts/lasttag.py create mode 100644 bob/devtools/scripts/release.py delete mode 100644 bob_tools/scripts/cb_output.py delete mode 100644 bob_tools/scripts/main.py delete mode 100644 bob_tools/utils/__init__.py delete mode 100644 bob_tools/utils/click.py create mode 100644 doc/api.rst create mode 100644 doc/conf.py create mode 100644 doc/img/favicon.ico create mode 100644 doc/img/logo.png create mode 100644 doc/index.rst create mode 100644 doc/links.rst create mode 100644 doc/release.rst create mode 100644 env.yml diff --git a/MANIFEST.in b/MANIFEST.in index 6386c90a..09fc130a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,3 @@ include LICENSE README.rst buildout.cfg version.txt recursive-include doc conf.py *.rst -recursive-include bob *.cpp *.h -recursive-include bob/extension/examples * -recursive-include bob/extension/data * +recursive-include bob/devtools/data *.md diff --git a/README.rst b/README.rst index 06af8fbc..27c36ba3 100644 --- a/README.rst +++ b/README.rst @@ -5,18 +5,24 @@ =============================================== This package is part of the signal-processing and machine learning toolbox -Bob_. It provides some tools to help maintain Bob_. +Bob_. It provides tools to help maintain Bob_. + Installation ------------ -This package needs to be installed in your base conda environment. To install +This package needs to be installed in a conda environment. To install this package, run:: - $ conda activate base - # the dependency list below matches the ones in setup.py - $ conda install pip click click-plugins conda-build - $ pip install -e . + $ conda env create -f env.yml + $ conda activate bdt + (bdt) $ buildout + (bdt) $ ./bin/bdt --help + ... + +To build the documentation, just do:: + + (bdt) $ ./bin/sphinx-build doc sphinx Contact diff --git a/bob/__init__.py b/bob/__init__.py new file mode 100644 index 00000000..2ab1e28b --- /dev/null +++ b/bob/__init__.py @@ -0,0 +1,3 @@ +# see https://docs.python.org/3/library/pkgutil.html +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/bob_tools/__init__.py b/bob/devtools/__init__.py similarity index 100% rename from bob_tools/__init__.py rename to bob/devtools/__init__.py diff --git a/bob/devtools/changelog.py b/bob/devtools/changelog.py new file mode 100644 index 00000000..812f41df --- /dev/null +++ b/bob/devtools/changelog.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +import io +import datetime +import logging +logger = logging.getLogger(__name__) + +import pytz +import dateutil.parser + +from .release import ensure_correct_package + + +def parse_date(d): + '''Parses any date supported by :py:func:`dateutil.parser.parse`''' + + return dateutil.parser.parse(d, ignoretz=True).replace( + tzinfo=pytz.timezone("Europe/Zurich")) + + +def _sort_commits(commits, reverse): + '''Sorts gitlab commit objects using their ``committed_date`` attribute''' + + return sorted(commits, + key=lambda x: parse_date(x.committed_date), + reverse=reverse, + ) + + +def _sort_tags(tags, reverse): + '''Sorts gitlab tag objects using their ``committed_date`` attribute''' + + return sorted(tags, + key=lambda x: parse_date(x.commit['committed_date']), + reverse=reverse, + ) + + +def get_file_from_gitlab(gitpkg, path, ref='master'): + '''Retrieves a file from a Gitlab repository, returns a (StringIO) file''' + + return io.StringIO(gitpkg.files.get(file_path=path, ref=branch).decode()) + + +def get_last_tag(package): + '''Returns the last (gitlab object) tag for the given package + + Args: + + package: The gitlab project object from where to fetch the last release + date information + + + Returns: a tag object + ''' + + # according to the Gitlab API documentation, tags are sorted from the last + # updated to the first, by default - no need to do further sorting! + tag_list = package.tags.list() + + if tag_list: + # there are tags, use these + return tag_list[0] + + +def get_last_tag_date(package): + '''Returns the last release date for the given package + + Falls back to the first commit date if the package has not yet been tagged + + + Args: + + package: The gitlab project object from where to fetch the last release + date information + + + Returns: a datetime object that refers to the last date the package was + released. If the package was never released, then returns the + date just before the first commit. + ''' + + # according to the Gitlab API documentation, tags are sorted from the last + # updated to the first, by default - no need to do further sorting! + tag_list = package.tags.list() + + if tag_list: + # there are tags, use these + last = tag_list[0] + logger.debug('Last tag for package %s (id=%d) is %s', package.name, + package.id, last.name) + return parse_date(last.commit['committed_date']) + \ + datetime.timedelta(milliseconds=500) + + else: + commit_list = package.commits.list(all=True) + + if commit_list: + # there are commits, use these + first = _sort_commits(commit_list, reverse=False)[0] + logger.debug('First commit for package %s (id=%d) is from %s', + package.name, package.id, first.committed_date) + return parse_date(first.committed_date) - \ + datetime.timedelta(milliseconds=500) + + else: + # there are no commits nor tags - abort + raise RuntimeError('package %s (id=%d) does not have commits ' \ + 'or tags so I cannot devise a good starting date' % \ + (package.name, package.id)) + + +def _get_tag_changelog(tag): + + try: + return tag.release['description'] + except Exception: + return '' + + +def _write_one_tag(f, pkg_name, tag): + '''Prints commit information for a single tag of a given package + + Args: + + f: A :py:class:`File` ready to be written at + pkg_name: The name of the package we are writing tags of + tag: The tag value + + ''' + + git_date = parse_date(tag.commit['committed_date']) + f.write(' * %s (%s)\n' % (tag.name, git_date.strftime('%b %d, %Y %H:%M'))) + + for line in _get_tag_changelog(tag).replace('\r\n', '\n').split('\n'): + + line = line.strip() + if line.startswith('* ') or line.startswith('- '): + line = line[2:] + + line = line.replace('!', pkg_name + '!').replace(pkg_name + \ + pkg_name, pkg_name) + line = line.replace('#', pkg_name + '#') + if not line: + continue + f.write('%s* %s' % (5*' ', line)) + + +def _write_commits_range(f, pkg_name, commits): + '''Writes all commits of a given package within a range, to the output file + + Args: + + f: A :py:class:`File` ready to be written at + pkg_name: The name of the package we are writing tags of + commits: List of commits to be written + + ''' + + + for commit in commits: + commit_title = commit.title + + # skip commits that do not carry much useful information + if '[skip ci]' in commit_title or \ + 'Merge branch' in commit_title or \ + 'Increased stable' in commit_title: + continue + + commit_title = commit_title.strip() + commit_title = commit_title.replace('!', pkg_name + '!').replace(pkg_name + pkg_name, pkg_name) + commit_title = commit_title.replace('#', pkg_name + '#') + f.write('%s- %s' % (' ' * 5, commit_title)) + + +def _write_mergerequests_range(f, pkg_name, mrs): + '''Writes all merge-requests of a given package, with a range, to the + output file + + Args: + + f: A :py:class:`File` ready to be written at + pkg_name: The name of the package we are writing tags of + mrs: The list of merge requests to write + + ''' + + for mr in mrs: + title = mr.title.strip().replace('\r','').replace('\n', ' ') + title = title.replace(' !', ' ' + pkg_name + '!') + title = title.replace(' #', ' ' + pkg_name + '#') + description = mr.description.strip().replace('\r','').replace('\n', ' ') + description = description.replace(' !', ' ' + pkg_name + '!') + description = description.replace(' #', ' ' + pkg_name + '#') + space = ': ' if description else '' + log = ''' - {pkg}!{iid} {title}{space}{description}''' + f.write(log.format(pkg=pkg_name, iid=mr.iid, title=title, space=space, description=description)) + f.write('\n') + + +def write_tags_with_commits(f, gitpkg, since, mode): + '''Writes all tags and commits of a given package to the output file + + Args: + + f: A :py:class:`File` ready to be written at + gitpkg: A pointer to the gitlab package object + since: Starting date (as a datetime object) + mode: One of mrs (merge-requests), commits or tags indicating how to + list entries in the changelog for this package + + ''' + + # get tags since release and sort them + tags = gitpkg.tags.list() + + # sort tags by date + tags = [k for k in tags if parse_date(k.commit['committed_date']) >= since] + tags = _sort_tags(tags, reverse=False) + + # get commits since release date and sort them too + commits = gitpkg.commits.list(since=since, all=True) + + # sort commits by date + commits = _sort_commits(commits, reverse=False) + + # get merge requests since the release data + mrs = list(reversed(gitpkg.mergerequests.list(state='merged', updated_after=since, order_by='updated_at', all=True))) + f.write('* %s\n' % (gitpkg.name,)) + + # go through tags and writes each with its message and corresponding + # commits + start_date = since + for tag in tags: + + # write tag name and its text + _write_one_tag(f, gitpkg.name, tag) + end_date = parse_date(tag.commit['committed_date']) + + if mode == 'commits': + # write commits from the previous tag up to this one + commits4tag = [k for k in commits \ + if (start_date < parse_date(k.committed_date) <= end_date)] + _write_commits_range(f, gitpkg.name, commits4tag) + + elif mode == 'mrs': + # write merge requests from the previous tag up to this one + # the attribute 'merged_at' is not available in GitLab API as of 27 + # June 2018 + mrs4tag = [k for k in mrs \ + if (start_date < parse_date(k.updated_at) <= end_date)] + _write_mergerequests_range(f, gitpkg.name, mrs4tag) + + start_date = end_date + + if mode != 'tags': + + # write the tentative patch version bump for the future tag + f.write(' * patch\n') + + if mode == 'mrs': + # write leftover merge requests + # the attribute 'merged_at' is not available in GitLab API as of 27 + # June 2018 + leftover_mrs = [k for k in mrs \ + if parse_date(k.updated_at) > start_date] + _write_mergerequests_range(f, gitpkg.name, leftover_mrs) + + else: + # write leftover commits that were not tagged yet + leftover_commits = [k for k in commits \ + if parse_date(k.committed_date) > start_date] + _write_commits_range(f, gitpkg.name, leftover_commits) + + +def write_tags(f, gitpkg, since): + '''Writes all tags of a given package to the output file + + Args: + + f: A :py:class:`File` ready to be written at + gitpkg: A pointer to the gitlab package object + since: Starting date as a datetime object + + ''' + + tags = gitpkg.tags.list() + # sort tags by date + tags = [k for k in tags if parse_date(k.commit['committed_date']) >= since] + tags = _sort_tags(tags, reverse=False) + f.write('* %s\n') + + for tag in tags: + _write_one_tag(gitpkg.name, tag) diff --git a/bob_tools/utils/conda.py b/bob/devtools/conda.py similarity index 100% rename from bob_tools/utils/conda.py rename to bob/devtools/conda.py diff --git a/bob/devtools/data/changelog_since_last_release.md b/bob/devtools/data/changelog_since_last_release.md new file mode 100644 index 00000000..aa913355 --- /dev/null +++ b/bob/devtools/data/changelog_since_last_release.md @@ -0,0 +1,587 @@ +* bob.buildout + * patch + - bob.buildout!30 Update guide.rst for automated environment creation +* bob.extension + * v3.0.0 (Jun 27, 2018 13:53) + - Breaking and significant changes: + - Removed bob_new_version script bob.extension!76 + - Implemented `bob` click command bob.extension!64 bob.extension!73 + bob.extension!77 bob.extension!74 bob.extension!75 + - Detailed changes: + - bob.extension!75 Fix a bug when commands where invoked multiple times + - bob.extension!72 Added the function bob.extension.download_and_unzip in + the core functionalities: Closes bob.extension#50 + - bob.extension!77 Fix list option + - bob.extension!64 Implementation of the bob script using click: Fixes + bob.extension#44 + - bob.extension!79 added support for pytorch doc + - bob.extension!80 Add scikit-learn intersphinx mapping + - bob.extension!81 Add prefix aliasing for bob commands + - bob.extension!83 Fixed issue with bz2 files: For some reason, it is not + possible to open some `bz2` files for reading ('r:bz2') using the + `tarfile` module. For instance, this is failing with the `dlib` + landmarks model. If I use the `bz2` module it works. This patch uses + the `bz2` module for `bz2` compressed files. + - bob.extension!82 Resolve "The `-DBOOST_VERSION` flag has unnecessary and + unwanted quotes": Closes bob.extension#58 + - bob.extension!76 Resolve "Documentation should be improved": [doc] added + the reference to NumPy style docstrings, added note on new package + instructions, added corresponding links Closes bob.extension#55 + * minor + - Improved the help option on all Bob click commands !84 + - Improved the click bool option !85 + - Added functionality to dump config file when calling a ConfigCommand !86 + - Add an ignore variable to log_parameters function (click helper) !88 + - bob.extension!87 Add a common_attribute argument to config.load: Fixes + bob.extension#64 +* bob.blitz + * patch + - bob.blitz!11 [sphinx] Fixed doctest: Close bob.blitz#12 +* bob.core + * patch + - bob.core!17 Adapted the documentation to the new behavior of version + exporting: When merging bob.extension!82, this MR will update the + according documentation. + - bob.core!16 Use log modules from bob.extension +* bob.io.base + * patch + - bob.io.base!25 Resolve "HDF5_VERSION is computed but never used": Closes + bob.io.base#19 +* bob.math + * patch + - bob.math!18 Fixing SVD test: Just fixing the sign of the last + eigenvector. More info check it out bob.math#10 +* bob.measure + * major + - Breaking and significant changes: + - Removed the old plotting scripts (``compute_perf.py``, etc.). + - Added a new 2-column score format specific to bob.measure. + - Implemented generic metrics and plotting scripts. Do ``bob measure + --help`` to see the new scripts and refer to the documentation. + - Matplotlib 2.2 and above is required now. + - Some biometric-related-only functionality is moved to bob.bio.base. + - Detailed changes: + - bob.measure!52 generic plotting script for bob measure: From + bob.measure#37, provide generic plotting scripts: * bob measure + evaluate * bob measure hist * bob measure hter * ... + - bob.measure!55 Change option name criter to criterion + - bob.measure!56 Fix error in context name + - bob.measure!57 Extend bins number option + - bob.measure!58 Change variable name form criter to criterion + - bob.measure!59 Bug fix: incorrect input file reading + - bob.measure!60 Bugfix + - bob.measure!54 Refactors the score loading and scripts functionality: + This merge request: * Move the biometric related functionality of + bob.measure to bob.bio.base. * Add confidence interval calculations * + Provide a score format for bob.measure with load functionalities * + Provide a generic plotting script bob measure in bob.measure using + bob.measure input file format: * `bob measure metrics`: to compute + thresholds and evaluate performances * `bob measure roc`: to plot + ROC * `bob measure det`: to plot DET * `bob measure hist`: + to plot histograms * `bob measure epc` : to plot EPC * `bob + measure evaluate`: applies all the above commands at once * `bob + measure gen`: to generate fake scores for `bob.measure` Each command + accepts one or several (dev,eval) score(s) file(s) for each system. + - bob.measure!61 Title and histograms subplots: Account for this + bob.bio.base!146#note_28760. + * Titles can be remove using an empty string, i.e. `-t ' '` * + Histograms support subplot display, see options `--subplots` * Add + `--legends-ncol` option + - bob.measure!62 Improve legends in histograms + - bob.measure!48 recompute far values in roc_for_far: Fixes bob.measure#27 + - bob.measure!44 Compute roc using roc_for_far internally: Fixes + bob.measure#26 + - bob.measure!43 Resolve "FAR and FRR thresholds are computed even when + there is no data support": Closes bob.measure#27 Also changes + behavior of far_threshold and frr_threshold where the returned threshold + guarantees the at most the requested far/frr value. + - bob.measure!63 Enable semilogx option in roc curves: * Fixes + bob.measure#40 * Remove 0 points on x-axis in semilogx plots. + Matplotlib 2 was doing this automatically before but matplotlib 2.2 + doesn't * Code clean-up + - bob.measure!64 Fix semilog plots for non numpy arrays: Fixes + bob.bio.base#117 + - bob.measure!65 Modification of criterion_option: Correct display of + available criteria + - bob.measure!66 Add prefix aliasing: Prefix aliasing using AliasedGroup + for bob.extension + - bob.measure!68 Change --eval option default and Various fixes: Change + defaults, clean unused options for histograms. Fix bob.bio.base#112 + - bob.measure!69 Explain that the thresholds might not satisfy the + requested criteria: * Add a helper eer function for convenience. + - bob.measure!50 Generic loading input file: Add a generic input file + loading function for bob measure and its corresponding test. From issue + bob.measure#39, comments bob.measure#39#note_26366. + - bob.measure!71 Fix issues with matplotlib.hist.: For some reason + `mpl.hist` hangs when `n_bins` is set to `auto`. This is related with + https://github.com/numpy/numpy#8203 This MR set the default value to + `doane`. Furthermore, allows you to pick one of the options here + https://docs.scipy.org/doc/numpy/reference/generated/numpy.histogram.html#numpy.histogram + This will fix one of the points here bob.pad.base!43 + - bob.measure!72 Various improvements: Fix bob.measure#41 and + bob.measure#42 + - bob.measure!70 Improvements to new metrics codes + - bob.measure!73 Histogram legends: Fix bob.measure#44 + - bob.measure!74 Bins histograms: Option only depends on the number of + data plotter per histograms, not the number of system. For example, pad + hist requires 2 nbins and vuln hist requires 3 independently on the + number of systems to be plotted. Fix bob.measure#45. + - bob.measure!75 Fix issue with histo legends: Fix bob.measure#47 Also + change default for number of legend columns. Add comments and doc in + the code for Hist. + - bob.measure!76 Histo fix: Fix typo and bob.pad.base!43#note_31884 + - bob.measure!77 dd lines for dev histograms: See + bob.pad.base!43#note_31903 + - bob.measure!78 Metrics: All stuff related to bob.measure#46. + - bob.measure!79 Add a command for multi protocol (N-fold cross + validation) analysis + - bob.measure!80 Consider non 1 values negatives: This will make + bob.measure scripts work with FOFRA scores + - bob.measure!81 Compute HTER using FMR and FNMR: As discussed in the + meeting. Fixes bob.measure#48 + - bob.measure!83 Enable grid on all histograms + - bob.measure!84 Improve the constrained layout option + - bob.measure!82 Various fixes: * Change measure metrics * Document and + change HTER * add decimal precision option for metric * Use acronyms + instead of full names in figures * Remove filenames form figures and + add log output instead + - bob.measure!86 Fix decimal number control for metrics: Fixes + bob.measure#52 + - bob.measure!88 Fix tests: Should fix bob.nightlies#40 + - bob.measure!67 Titles: Allow list of titles and remove `(development)` + `(evaluation)` when default titles are modified + - bob.measure!45 Condapackage + - bob.measure!85 Fix broken commands cref bob.extension!86 + - bob.measure!53 Change the way the scores arguments are passed to the + compute() function: it now: Change the way the scores arguments are + passed to the compute() function: it now does not rely on dev,eval pairs + anymore and can take any number of different files (e.g. train) + - bob.measure!87 Update documentation and commands: FAR->FPR, FRR->FNR: + Fix bob.measure#54 +* bob.io.image + * patch + - bob.io.image!41 Resolve "versions of image libraries are evaluated by + hand and given as parameters on the compiler command line": Closes + bob.io.image#32 +* bob.db.base + * patch + - bob.db.base!42 Handle errors when loading db interfaces: Fixes + bob.db.base#24 +* bob.io.video + * v2.1.1 (Apr 17, 2018 11:06) + - Sligthly increased noise test parameters on mp4 files with mpeg2video codecs (closed #11) +* bob.io.matlab + * patch +* bob.io.audio + * patch + - bob.io.audio!7 Resolve "version relies on compiler command line + parameter": Closes bob.io.audio#6 +* bob.sp + * patch +* bob.ap + * patch + - [sphinx] Fixed doc tests +* bob.ip.base + * patch + - bob.ip.base!16 Add a block_generator function. Fixes bob.ip.base#11 + - [sphinx] Fixed doc tests +* bob.ip.color + * patch +* bob.ip.draw + * patch +* bob.ip.gabor + * patch +* bob.learn.activation + * patch +* bob.learn.libsvm + * patch + - bob.learn.libsvm!9 Resolve "LIBSVM_VERSION is passed as a string to the + compiler, but evaluated as uint64_t in the code": Closes + bob.learn.libsvm#10 +* bob.learn.linear + * patch + - [sphinx] Fixed doc tests +* bob.learn.mlp + * patch + - [sphinx] Fixed doc tests +* bob.learn.boosting + * patch +* bob.db.iris + * patch +* bob.learn.em + * patch + - [sphinx] Fixed doc tests +* bob.db.wine + * patch +* bob.db.mnist + * patch +* bob.db.atnt + * patch +* bob.ip.facedetect + * patch + - [sphinx] Fixed doc tests +* bob.ip.optflow.hornschunck + * patch +* bob.ip.optflow.liu + * patch +* bob.ip.flandmark + * patch +* gridtk + * patch + - gridtk!21 remove the submitted command line column when not long: Fixes + gridtk#27 + - gridtk!20 Resolve "jman fails when jobs are deleted before they are + finished": Closes gridtk#26 + - gridtk!22 Memory argument sets gpumem parameter for gpu queues: Fixes + gridtk#24 + - gridtk!23 Accept a plus sign for specifying the job ranges +* bob.ip.qualitymeasure + * patch +* bob.ip.skincolorfilter + * patch +* bob.ip.facelandmarks + * patch +* bob.ip.dlib + * patch + - bob.ip.dlib!13 Removed bob_to_dlib_image_convertion functions and + replaced them to bob.io.image.to_matplotlib: CLoses bob.ip.dlib#6 + - bob.ip.dlib!11 Fix in the download mechanism +* bob.ip.mtcnn + * v0.0.1 (May 23, 2018 17:56) + * First release + - bob.ip.mtcnn!4 Added conda recipe: Closes bob.ip.mtcnn#2 We don't + have a caffe for Mac OSX in the defaults channel. This can be an issue + for the bob.pad.face builds. + - bob.ip.mtcnn!5 Deleted the fuctions bob_to_dlib_image_convertion and…: + Deleted the fuctions bob_to_dlib_image_convertion and + dlib_to_bob_image_convertion and replaced them by the + bob.io.color.to_matplotlib Closes bob.ip.mtcnn#3 [sphinx] Fixed + plots + - bob.ip.mtcnn!6 [conda] Fixed conda recipe for the nightlies build issue + bob.ip.mtcnn#5: Closes bob.ip.mtcnn#5 + * v1.0.0 (May 23, 2018 20:01) + * First release + * patch +* bob.db.arface + * patch +* bob.db.asvspoof + * patch +* bob.db.asvspoof2017 + * patch + - bob.db.asvspoof2017!5 Update link to avspoof 2017 +* bob.db.atvskeystroke + * patch +* bob.db.avspoof + * patch +* bob.db.banca + * patch +* bob.db.biosecure + * patch +* bob.db.biosecurid.face + * patch +* bob.db.casme2 + * patch +* bob.db.caspeal + * patch +* bob.db.cohface + * patch +* bob.db.frgc + * patch +* bob.db.gbu + * patch +* bob.db.hci_tagging + * patch +* bob.db.kboc16 + * patch +* bob.db.lfw + * patch +* bob.db.livdet2013 + * patch +* bob.db.mobio + * patch +* bob.db.msu_mfsd_mod + * patch +* bob.db.multipie + * patch +* bob.db.nist_sre12 + * patch +* bob.db.putvein + * patch +* bob.db.replay + * patch +* bob.db.replaymobile + * patch + - bob.db.replaymobile!10 Ignore all exceptions when closing the session +* bob.db.scface + * patch +* bob.db.utfvp + * patch +* bob.db.verafinger + * patch +* bob.db.fv3d + * patch +* bob.db.hkpu + * patch + - bob.db.hkpu!2 Antecipated issue with conda-build. Check bob.db.ijbc!1 +* bob.db.thufvdt + * patch +* bob.db.mmcbnu6k + * patch + - bob.db.mmcbnu6k!2 Antecipated issue with conda-build. Check + bob.db.ijbc!1 +* bob.db.hmtvein + * patch + - bob.db.hmtvein!2 Antecipated issue with conda-build. Check bob.db.ijbc!1 +* bob.db.voicepa + * patch +* bob.db.xm2vts + * patch +* bob.db.youtube + * patch +* bob.db.pericrosseye + * patch +* bob.bio.base + * major + - Breaking and significant changes + - Removed the old ``evaluate.py`` script. + - Functionality to load biometric scores are now in ``bob.bio.base.score`` + - Added new scripts for plotting and evaluations. Refer to docs. + - Added a new baselines concept. Refer to docs. + - Detailed changes + - bob.bio.base!147 Update installation instructions since conda's usage + has changed. + - bob.bio.base!148 Archive CSU: closes bob.bio.base#109 + - bob.bio.base!146 Add 4-5-col files related functionalities and add + click commands: In this merge: * Add loading functionalities from + `bob.measure` * Add the following click commands (as substitutes for + old script evaluate.py) using 4- or 5 - scores input files: * `bob + bio metrics` * `bob bio roc` * `bob bio det` * `bob bio + epc` * `bob bio hist` * `bob bio evaluate` : calls all the + above commands at once * `bob bio cmc` * `bob bio dic` * + `bob bio gen` Plots follow ISO standards. The underlying + implementation of the mentioned commands uses `bob.measure` base + classes. Fixes bob.bio.base#108 + - bob.bio.base!149 Set io-big flag for the demanding grid config: Closes + bob.bio.base#110 Anyone cares to review this one? It's harmless. + - bob.bio.base!143 Set of click commands for bio base: From + bob.bio.base#65 Provide commands in bio base: - bob bio metrics + - bob bio roc - bob bio evaluate (Very similar to evalute.py) + - bob.bio.base!152 Removed unused import imp and solving bob.bio.base#83: + Closes bob.bio.base#83 + - bob.bio.base!153 Added the protocol argument issue bob.bio.base#111: + Closes bob.bio.base#111 + - bob.bio.base!154 Fixes in ROC and DET labels + - bob.bio.base!157 Fixed bob bio dir x_labels and y_labels: The labels of + the DIR plot were incorrect. + - bob.bio.base!155 Write parameters in a temporary config file to enable + chain loading: Fixes bob.bio.base#116 + - bob.bio.base!150 Exposing the method groups in our FileDatabase API + - bob.bio.base!158 Add prefix aliasing for Click commands + - bob.bio.base!160 Titltes: Allows a list of titles Fixes + bob.bio.base#121. Requires bob.measure!67 + - bob.bio.base!159 Resolve "Documentation does not include a link to the + recordings of the IJCB tutorial": Closes bob.bio.base#122 + - bob.bio.base!161 Change --eval option default and Various fixes: fixes + bob.bio.base#112. Add and clean histo options. See + bob.measure!67#note_30951 Requires bob.measure!68 + - bob.bio.base!163 Reduce repition between commands: Depends on + bob.measure!70 + - bob.bio.base!162 Removed traces of evaluate.py in the documentation + - bob.bio.base!164 Fix test according to changes in nbins option + - bob.bio.base!165 Set names for different bio metrics: Bio specific names + for metrics when using bob.measure Metrics + - bob.bio.base!166 Add a command for multi protocol (N-fold cross + validation) analysis: Similar to bob.measure!79 + - bob.bio.base!167 Various fixes: Requires bob.measure!82 Similar to + bob.measure!82 for bio commands + - bob.bio.base!168 Documentation changes in bob bio annotate: Depends on + bob.extension!86 + - bob.bio.base!156 Using the proper verify script depending on system: + Closes bob.bio.base#119 + - bob.bio.base!151 Created the Baselines Concept + - bob.bio.base!169 Change assert to assert_click_runner_result +* bob.bio.gmm + * patch + - bob.bio.gmm!19 Fix bob.measure->bob.bio.base related issues: Fixes + bob.bio.gmm#25 + - bob.bio.gmm!20 argument allow_missing_files wrongly passed to qsub + - bob.bio.gmm!21 IVector - Fix LDA rank: With this MR we make sure that + the dimension of the LDA matrix is not higher than its rank. +* bob.bio.face + * major + - Breaking changes: + - Dropped support for CSU baselines + - Detailed changes: + - bob.bio.face!47 Accept an annotator in FaceCrop: related to + bob.bio.face#26 + - bob.bio.face!48 Dropped support to CSU baselines issue bob.bio.face#29 + - bob.bio.face!51 Removing baselines: Related to this MR bob.bio.face!49 + - bob.bio.face!50 Ijbc highlevel + - bob.bio.face!49 Refactoring baselines: Now we can all the facerec + baselines can be reached via ``bob bio baselines --help``. Refer to + docs. +* bob.bio.spear + * patch + - bob.bio.spear!40 Handle mute audio correctly: Fixes bob.bio.spear#31 +* bob.bio.video + * patch + - bob.bio.video!35 Add video wrappers using chain loading: Fixes + bob.bio.video#12 + - bob.bio.video!36 Load videos frame by frame in annotators: Related to + bob.bio.video#13 +* bob.bio.vein + * patch + - bob.bio.vein!42 Fix bob.measure->bob.bio.base related issues + - bob.bio.vein!43 Using the new bob.bio API + - bob.bio.vein!44 Fixing comparison: Closes bob.bio.vein#18 +* bob.db.voxforge + * patch + - bob.db.voxforge!20 Handling the --help command in download_and_untar + script: Fixes bob.db.voxforge#12 + - bob.db.voxforge!18 Fixed the index.rst to match joint docs requirements +* bob.rppg.base + * v1.1.0 (Jun 22, 2018 08:58) + - Initial conda release for rPPG algorithms + - Bug correction in the CHROM algorithm + - Python 2 and 3 compatibility + - Improved documentation (mostly Python API) + * v2.0.0 (Jun 27, 2018 15:52) + - getting rid of bob.db.* dependencies + - usage of configuration files + * patch + - bob.rppg.base!8 Resolve "Potential bug when computing average color from + mask": Closes bob.rppg.base#15 + - bob.rppg.base!7 Fixed recipe to test all modules +* bob.pad.base + * minor + - Significant changes: + - Added new plotting and evaluation scripts. Refer to docs. + - Detailed Changes: + - bob.pad.base!52 Change assert to assert_click_runner_result + - bob.pad.base!50 Add new classification algorithms: As mentioned here + bob.pad.base!33 here is the new branch + - bob.pad.base!51 Fix broken commands cref bob.extension!86 + - bob.pad.base!48 Various fix: Requires bob.measure!82 Similar to + bob.measure!82 for PAD + - bob.pad.base!45 Allow PAD filelist database to be used in vulnerability + experiments + - bob.pad.base!47 Remove bob vuln metrics and evaluate commands: since + they were not well defined and we do not know what should be in there. + rename --hlines-at to --fnmr in bob vuln roc,det commands small + nit-pick fixes overall + - bob.pad.base!43 Finalization of plots: Fixes bob.pad.base#22. Requires + merge bob.measure!65. Requires bob.measure!67 + - bob.pad.base!46 Add a command for multi protocol (N-fold cross + validation) analysis: Depends on bob.measure!79 + - bob.pad.base!44 Remove the grid_search.py entrypoint: Fixes + bob.pad.base#23 + - bob.pad.base!41 Set of click commands for pad: Provide commands: bob + pad metrics bob pad epc bob pad epsc bob pad gen + - bob.pad.base!42 Set of click commands for pad: Provide commands: bob + pad metrics bob pad epc bob pad epsc bob pad gen + - added MLP and LDA classifiers for PAD +* bob.pad.face + * minor + - bob.pad.face!68 Improve load_utils.py: * Plus some minor fixes to the + frame-diff method + - bob.pad.face!67 HLDI for the CelebA database and quality estimation + script: This MR contains two contributions: 1. The High Level DB + interface for the CelebA database. 2. The quality assessment script + + config file allowing to estimate the quality of preprocessed CelebA + images. + - bob.pad.face!66 Added an option to exclude specific types of attacks + from train set in BATL DB: This allows to exclude specific PAIs from the + training set of the BATL DB. PAIs currently handled: makeup. + - bob.pad.face!65 Updated the HLDI of BATL DB, added FunnyEyes fix, and + protocol joining test and dev sets + - bob.pad.face!64 Add support for external annotations in replaymobile + - bob.pad.face!60 Change the API of yield_faces + - bob.pad.face!62 Add a script for analyzing database face sizes + - added preprocessors and extractors for pulse-based PAD. +* bob.pad.voice + * v1.0.5 (Jun 27, 2018 16:00) + - Fix imports and add an alias for PadVoiceFile + - experimental audio eval: support for 2, 3 layers lstms + - Fix error related to bob.measure->bob.bio.base movings Fixes + bob.pad.voicebob.pad.voice#4 + - bob.pad.voice!14 Fix error related to bob.measure->bob.bio.base movings: + Fixes bob.pad.voice#4 + - bob.pad.voice!15 Fix imports and add an alias for PadVoiceFile + * patch + - Fix avspoof db interface (added annotations method) !16 +* bob.pad.vein + * patch + - Fix database tests +* bob.fusion.base + * patch + - bob.fusion.base!7 Major refactoring: Fixes bob.fusion.base#3 Fixes + biometric/software#7 Fixes biometric/software#8 + - bob.fusion.base!8 improve behaviour and tests + - bob.fusion.base!9 Fix the boundary script due to changes in + bob.extension: Fixes bob.fusion.base#7 +* bob.db.oulunpu + * patch + - bob.db.oulunpu!3 Training depends on protocol + - bob.db.oulunpu!4 Add protocol 1_2 +* bob.db.uvad + * patch + - bob.db.uvad!2 Convert annotations to properties +* bob.db.swan + * patch + - bob.db.swan!2 DRY + - bob.db.swan!3 Recreate the PAD protocols and add a global face and voice + pad protocol +* bob.db.cuhk_cufs + * v2.2.1 (May 23, 2018 16:55) + * Removed deprecated entrypoints + - bob.db.cuhk_cufs!8 Removing deprecated entry-points: Removing deprecated + entry-points fixing bob.db.cuhk_cufs!7 + * patch +* bob.db.cbsr_nir_vis_2 + * v2.0.2 (May 23, 2018 17:35) + * Removed deprecated entrypoints + * Ported to the new CI + * patch +* bob.db.nivl + * v0.0.1 (May 23, 2018 18:15) + * First release + * v1.0.0 (May 24, 2018 11:37) + * First release + * patch +* bob.db.pola_thermal + * v0.0.1 (May 23, 2018 18:57) + * First release + * v1.0.0 (May 24, 2018 11:54) + * First release + * patch +* bob.db.cuhk_cufsf + * v0.0.1 (May 23, 2018 19:10) + * First release + * v1.0.0 (May 24, 2018 12:09) + * First release + * patch +* bob.db.ijba + * patch + - bob.db.ijba!13 Fix bob.measure->bob.bio.base related issues +* bob.ip.tensorflow_extractor + * v0.0.2 (Apr 27, 2018 15:16) + - added DR-GAN extractor + * patch + - Replaced the download_model method to the new one implemented in + bob.extension !7 + - Fixed conda-build issue !8 +* bob.ip.caffe_extractor + * v2.0.0 (Jun 30, 2018 12:22) + * Added Light CNN bob.ip.caffe_extractor!6 bob.ip.caffe_extractor!7 + * Created conda package of it bob.ip.caffe_extractor!9 + * Replaced the download_model method to the new one implemented in + bob.extension bob.ip.caffe_extractor!10 + - bob.ip.caffe_extractor!10 Replaced the download_model method to the new + one implemented in bob.extension: Closes bob.ip.caffe_extractor#6 I + already updated the URL in this one + * patch +* bob.bio.caffe_face + * patch +* bob.db.maskattack + * patch + - first release diff --git a/bob/devtools/release.py b/bob/devtools/release.py new file mode 100644 index 00000000..d0b780f1 --- /dev/null +++ b/bob/devtools/release.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import re +import time +import gitlab + +import logging +logger = logging.getLogger(__name__) + +from distutils.version import StrictVersion + + +def get_gitlab_instance(): + '''Returns an instance of the gitlab object for remote operations''' + + # tries to figure if we can authenticate using a global configuration + cfgs = ['~/.python-gitlab.cfg', '/etc/python-gitlab.cfg'] + cfgs = [os.path.expanduser(k) for k in cfgs] + if any([os.path.exists(k) for k in cfgs]): + gl = gitlab.Gitlab.from_config('idiap', cfgs) + else: #ask the user for a token + server = "https://gitlab.idiap.ch" + token = input("%s token: " % server) + gl = gitlab.Gitlab(server, private_token=token, api_version=4) + + return gl + + +def ensure_correct_package(candidates, group_name, pkg_name): + + for pkg in candidates: + # make sure the name and the group name match exactly + if pkg.name == pkg_name and pkg.namespace['name'] == group_name: + return pkg + + raise ValueError('Package "{0}" was not found inside group "{1}"'.format(pkg_name, group_name)) + + +def _update_readme(readme, version): + """ + Inside text of the readme, replaces parts of the links to the provided + version. If version is not provided, replace to `stable` or `master`. + + Args: + + readme: Text of the README.rst file from a bob package + version: Format of the version string is '#.#.#' + + Returns: New text of readme with all replaces done + """ + + # replace the badge in the readme's text with the given version + DOC_IMAGE = re.compile(r'\-(stable|(v\d+\.\d+\.\d+([abc]\d+)?))\-') + BRANCH_RE = re.compile(r'/(stable|master|(v\d+\.\d+\.\d+([abc]\d+)?))') + + new_readme = [] + for line in readme.splitlines(): + if BRANCH_RE.search(line) is not None: + if "gitlab" in line: # gitlab links + replacement = "/v%s" % version if version is not None \ + else "/master" + line = BRANCH_RE.sub(replacement, line) + if ("software/bob" in line) or \ + ("software/beat" in line): # our doc server + if 'master' not in line: # don't replace 'latest' pointer + replacement = "/v%s" % version if version is not None \ + else "/stable" + line = BRANCH_RE.sub(replacement, line) + if DOC_IMAGE.search(line) is not None: + replacement = '-v%s-' % version if version is not None \ + else '-stable-' + line = DOC_IMAGE.sub(replacement, line) + new_readme.append(line) + return '\n'.join(new_readme) + '\n' + + +def get_latest_tag_name(gitpkg): + """Find the name of the latest tag for a given package in the format '#.#.#' + + Args: + gitpkg: gitlab package object + + Returns: The name of the latest tag in format '#.#.#'. None if no tags for + the package were found. + """ + + # get 50 latest tags as a list + latest_tags = gitpkg.tags.list(all=True) + if not latest_tags: + return None + # create list of tags' names but ignore the first 'v' character in each name + # also filter out non version tags + tag_names = [tag.name[1:] for tag in latest_tags \ + if StrictVersion.version_re.match(tag.name[1:])] + # sort them correctly according to each subversion number + tag_names.sort(key=StrictVersion) + # take the last one, as it is the latest tag in the sorted tags + latest_tag_name = tag_names[-1] + return latest_tag_name + + +def get_parsed_tag(gitpkg, tag): + """ + An older tag is formatted as 'v2.1.3 (Sep 22, 2017 10:37)', from which we + need only v2.1.3 + + The latest tag is either patch, minor, major, or none + """ + + m = re.search(r"(v\d+.\d+.\d+)", tag) + if m: + return m.group(0) + # tag = Version(tag) + + # if we bump the version, we need to find the latest released version for + # this package + if 'patch' == tag or 'minor' == tag or 'major' == tag: + + # find the correct latest tag of this package (without 'v' in front), + # None if there are no tags yet + latest_tag_name = get_latest_tag_name(gitpkg) + + # if there were no tags yet, assume the very first version + if not latest_tag_name: return 'v0.0.1' + + # check that it has expected format #.#.# + # latest_tag_name = Version(latest_tag_name) + m = re.match(r"(\d.\d.\d)", latest_tag_name) + if not m: + raise ValueError('The latest tag name {0} in package {1} has ' \ + 'unknown format'.format('v' + latest_tag_name, gitpkg.name)) + + # increase the version accordingly + major, minor, patch = latest_tag_name.split('.') + + if 'major' == tag: + # increment the first number in 'v#.#.#' but make minor and patch + # to be 0 + return 'v' + str(int(major) + 1) + '.0.0' + + if 'minor' == tag: + # increment the second number in 'v#.#.#' but make patch to be 0 + return 'v' + major + '.' + str(int(minor) + 1) + '.0' + + if 'patch' == tag: + # increment the last number in 'v#.#.#' + return 'v' + major + '.' + minor + '.' + str(int(patch) + 1) + + if 'none' == tag: + # we do nothing in this case + return tag + + raise ValueError('Cannot parse changelog tag {0} of the ' \ + 'package {1}'.format(tag, gitpkg.name)) + + +def update_tag_comments(gitpkg, tag_name, tag_comments_list, dry_run=False): + """Write annotations inside the provided tag of a given package. + + Args: + + gitpkg: gitlab package object + tag_name: The name of the tag to update + tag_comments_list: New annotations for this tag in a form of list + dry_run: If True, nothing will be committed or pushed to GitLab + + Returns: The gitlab object for the tag that was updated + """ + + # get tag and update its description + logger.info(tag_name) + tag = gitpkg.tags.get(tag_name) + tag_comments = '\n'.join(tag_comments_list) + logger.info('Found tag %s, updating its comments with:\n%s', tag.name, + tag_comments) + if not dry_run: tag.set_release_description(tag_comments) + return tag + + +def commit_files(gitpkg, files_dict, message='Updated files', dry_run=False): + """Commit files of a given GitLab package. + + Args: + + gitpkg: gitlab package object + files_dict: Dictionary of file names and their contents (as text) + message: Commit message + dry_run: If True, nothing will be committed or pushed to GitLab + + """ + + data = { + 'branch': 'master', # v4 + 'commit_message': message, + 'actions': [] + } + + # add files to update + for filename in files_dict.keys(): + update_action = dict(action='update', file_path=filename) + update_action['content'] = files_dict[filename] + data['actions'].append(update_action) + + logger.info("Committing changes in files: %s", str(files_dict.keys())) + if not dry_run: + gitpkg.commits.create(data) + + +def get_last_pipeline(gitpkg): + """Returns the last pipeline of the project + + Args: + + gitpkg: gitlab package object + + Returns: The gtilab object of the pipeline + """ + + # wait for 10 seconds to ensure that if a pipeline was just submitted, + # we can retrieve it + time.sleep(10) + + # get the last pipeline + return gitpkg.pipelines.list(per_page=1, page=1)[0] + + +def just_build_package(gitpkg, dry_run=False): + """Creates the pipeline with the latest tag and starts it + + Args: + + gitpkg: gitlab package object + dry_run: If True, the pipeline will not be created on GitLab + + Returns: + + """ + + # get the latest tag + latest_tag_name = 'v' + get_latest_tag_name(gitpkg) + + # create the pipeline with this tag and start it + logger.info("Creating and starting pipeline for tag %s", latest_tag_name) + + if not dry_run: + new_pipeline = gitpkg.pipelines.create({'ref': latest_tag_name}) + return new_pipeline.id + + return None + + +def wait_for_pipeline_to_finish(gitpkg, pipeline_id, dry_run=False): + """Using sleep function, wait for the latest pipeline to finish building. + + This function pauses the script until pipeline completes either + successfully or with error. + + Args: + + gitpkg: gitlab package object + pipeline_id: id of the pipeline for which we are waiting to finish + dry_run: If True, outputs log message and exit. There wil be no + waiting. + + """ + + sleep_step = 30 + max_sleep = 120 * 60 # two hours + # pipeline = get_last_pipeline(gitpkg, before_last=before_last) + + logger.info('Waiting for the pipeline %s of package %s to finish. ' \ + 'Do not interrupt.', pipeline_id, gitpkg.name) + + if dry_run: return + + # retrieve the pipeline we are waiting for + pipeline = gitpkg.pipelines.get(pipeline_id) + + # probe and wait for the pipeline to finish + slept_so_far = 0 + + while pipeline.status == 'running' or pipeline.status == 'pending': + + time.sleep(sleep_step) + slept_so_far += sleep_step + if slept_so_far > max_sleep: + raise ValueError('I cannot wait longer than {0} seconds for ' + 'pipeline {1} to finish running!'.format(max_sleep, pipeline_id)) + # probe gitlab to update the status of the pipeline + pipeline = gitpkg.pipelines.get(pipeline_id) + + # finished running, now check if it succeeded + if pipeline.status != 'success': + raise ValueError('Pipeline {0} of project {1} exited with ' \ + 'undesired status "{2}". Release is not possible.' \ + .format(pipeline_id, gitpkg.name, pipeline.status)) + + logger.info('Pipeline %s of package %s SUCCEEDED. Continue processing.', + pipeline_id, gitpkg.name) + + +def cancel_last_pipeline(gitpkg): + """ Cancel the last started pipeline of a package + + Args: + + gitpkg: gitlab package object + + """ + + pipeline = get_last_pipeline(gitpkg) + logger.info('Cancelling the last pipeline %s of project %s', pipeline.id, + gitpkg.name) + pipeline.cancel() + + +def release_package(gitpkg, tag_name, tag_comments_list, dry_run=False): + """Release package + + The provided tag will be annotated with a given list of comments. + README.rst and version.txt files will also be updated according to the + release procedures. + + Args: + + gitpkg: gitlab package object + tag_name: The name of the release tag + tag_comments_list: New annotations for this tag in a form of list + dry_run: If True, nothing will be committed or pushed to GitLab + + """ + + # if there is nothing to release, just rebuild the package + latest_tag = get_latest_tag_name(gitpkg) + + if tag_name == 'none' or (latest_tag and ('v' + latest_tag) == tag_name): + logger.warn("Since the tag is 'none' or already exists, we just " \ + "re-build the last pipeline") + return just_build_package(gitpkg, dry_run) + + # 1. Replace branch tag in Readme to new tag, change version file to new + # version tag. Add and commit to gitlab + version_number = tag_name[1:] # remove 'v' in front + readme_file = gitpkg.files.get(file_path='README.rst', ref='master') + readme_content = readme_file.decode().decode() + readme_content = _update_readme(readme_content, version_number) + # commit and push changes + commit_files(gitpkg, + { + 'README.rst': readme_content, + 'version.txt': version_number + }, + 'Increased stable version to %s' % version_number, dry_run) + + if not dry_run: + # cancel running the pipeline triggered by the last commit + cancel_last_pipeline(gitpkg) + + # 2. Tag package with new tag and push + logger.info("Creating tag %s", tag_name) + tag_comments = '\n'.join(tag_comments_list) + logger.info("Updating tag comments with:\n%s", tag_comments) + if not dry_run: + tag = gitpkg.tags.create({'tag_name': tag_name, 'ref': 'master'}) + # update tag with comments + tag.set_release_description(tag_comments) + + # get the pipeline that is actually running with no skips + running_pipeline = get_last_pipeline(gitpkg) + + # 3. Replace branch tag in Readme to master, change version file to beta + # version tag. Git add, commit, and push. + readme_content = _update_readme(readme_content, None) + major, minor, patch = version_number.split('.') + version_number = '{}.{}.{}b0'.format(major, minor, int(patch)+1) + # commit and push changes + commit_files(gitpkg, { + 'README.rst': readme_content, + 'version.txt': version_number, + }, + 'Increased latest version to %s [skip ci]' % version_number, dry_run) + + return running_pipeline.id + + +def parse_and_process_package_changelog(gl, bob_group, pkg_name, + package_changelog, dry_run): + """Process the changelog of a single package + + Parse the log following specific format. Update annotations of the + provided older tags and release the package by following the last tag + description. + + Args: + + gl: Gitlab API object + bob_group: gitlab object for the group + pkg_name: name of the package + package_changelog: the changelog corresponding to the provided package + dry_run: If True, nothing will be committed or pushed to GitLab + + Returns: gitlab handle for the package, name of the latest tag, and tag's + comments + + """ + + cur_tag = None + cur_tag_comments = [] + + grpkg = ensure_correct_package(bob_group.projects.list(search=pkg_name), + bob_group.name, pkg_name) + + # so, we need to retrieve the full info from GitLab using correct project id + gitpkg = gl.projects.get(id=grpkg.id) + + # we assume that changelog is formatted as structured text + # first line is the name of the package + for line in package_changelog: + if ' *' == line[:3]: # a tag level + # write the comments collected for the previous tag + if cur_tag: + update_tag_comments(gitpkg, cur_tag, cur_tag_comments, dry_run) + cur_tag_comments = [] # reset comments + + # parse the current tag name + cur_tag = get_parsed_tag(gitpkg, line[3:].strip()) + + else: # all other lines are assumed to be comments + cur_tag_comments.append(line.strip()) + + # return the last tag and comments for release + return gitpkg, cur_tag, cur_tag_comments + + +def release_bob(changelog_file): + """Process the changelog and releases the ``bob`` metapackage""" + + logger.info('Read the section "Releasing the Bob meta package" ' \ + 'on the documentation') + + # get the list of bob's dependencies. + # Get their latest tags (since bob's last release) and the tag's changelog + saw_a_new_package = True + latest_tag = None + latest_pkg = None + for line in changelog_file: + # if saw_a_new_package: + if line.startswith('*'): + pkg = line[2:].strip() + saw_a_new_package = True + logger.info('%s == %s', latest_pkg, latest_tag) + latest_pkg = pkg + latest_tag = None + continue + if line.startswith(' *'): + latest_tag = line.split()[1][1:] + saw_a_new_package = False + logger.info('%s == %s', latest_pkg, latest_tag) + readme = open('../../bob/README.rst').read() + readme = _update_readme(readme, bob_version) + open('../../bob/README.rst', 'wt').write(readme) + open('../../bob/version.txt', 'wt').write(bob_version) diff --git a/bob_tools/scripts/__init__.py b/bob/devtools/scripts/__init__.py similarity index 100% rename from bob_tools/scripts/__init__.py rename to bob/devtools/scripts/__init__.py diff --git a/bob/devtools/scripts/bdt.py b/bob/devtools/scripts/bdt.py new file mode 100644 index 00000000..4af5720c --- /dev/null +++ b/bob/devtools/scripts/bdt.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Main entry point for bdt +""" + +import pkg_resources + +import click +from click_plugins import with_plugins + +import logging +logger = logging.getLogger('bdt') + + +def set_verbosity_level(logger, level): + """Sets the log level for the given logger. + + Parameters + ---------- + logger : :py:class:`logging.Logger` or str + The logger to generate logs for, or the name of the module to generate + logs for. + level : int + Possible log levels are: 0: Error; 1: Warning; 2: Info; 3: Debug. + Raises + ------ + ValueError + If the level is not in range(0, 4). + """ + if level not in range(0, 4): + raise ValueError( + "The verbosity level %d does not exist. Please reduce the number " + "of '--verbose' parameters in your command line" % level + ) + # set up the verbosity level of the logging system + log_level = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG + }[level] + + # set this log level to the logger with the specified name + if isinstance(logger, str): + logger = logging.getLogger(logger) + logger.setLevel(log_level) + + +def verbosity_option(**kwargs): + """Adds a -v/--verbose option to a click command. + + Parameters + ---------- + **kwargs + All kwargs are passed to click.option. + + Returns + ------- + callable + A decorator to be used for adding this option. + """ + def custom_verbosity_option(f): + def callback(ctx, param, value): + ctx.meta['verbosity'] = value + set_verbosity_level(logger, value) + logger.debug("`bdt' logging level set to %d", value) + return value + return click.option( + '-v', '--verbose', count=True, + expose_value=False, default=2, + help="Increase the verbosity level from 0 (only error messages) " + "to 1 (warnings), 2 (info messages), 3 (debug information) by " + "adding the --verbose option as often as desired " + "(e.g. '-vvv' for debug).", + callback=callback, **kwargs)(f) + return custom_verbosity_option + + +class AliasedGroup(click.Group): + ''' Class that handles prefix aliasing for commands ''' + def get_command(self, ctx, cmd_name): + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + matches = [x for x in self.list_commands(ctx) + if x.startswith(cmd_name)] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + + +def raise_on_error(view_func): + """Raise a click exception if returned value is not zero. + + Click exits successfully if anything is returned, in order to exit properly + when something went wrong an exception must be raised. + """ + + from functools import wraps + + def _decorator(*args, **kwargs): + value = view_func(*args, **kwargs) + if value not in [None, 0]: + exception = click.ClickException("Error occured") + exception.exit_code = value + raise exception + return value + return wraps(view_func)(_decorator) + + +@with_plugins(pkg_resources.iter_entry_points('bdt.cli')) +@click.group(cls=AliasedGroup, + context_settings=dict(help_option_names=['-?', '-h', '--help'])) +@verbosity_option() +def main(): + """Bob Development Tools - see available commands below""" diff --git a/bob/devtools/scripts/cb_output.py b/bob/devtools/scripts/cb_output.py new file mode 100644 index 00000000..35c98c0d --- /dev/null +++ b/bob/devtools/scripts/cb_output.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import click +from click.testing import CliRunner +import conda_build.api as cb + +from . import bdt + +from ..conda import should_skip_build + + +@click.command(context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + ), + epilog='''\b +Examples: +$ bdt cb-output conda_recipe_dir +$ bdt cb-output ../bob.conda/conda/kaldi -m ../bob.admin/gitlab/conda_build_config.yaml --python 3.6 +''' +) +@click.argument('recipe_path') +@click.option('-m', '--variant-config-files', help='see conda build --help') +@click.option('--python', help='see conda build --help') +@bdt.raise_on_error +def cb_output(recipe_path, variant_config_files, python): + """Outputs name(s) of package(s) that would be generated by conda build. + + This command accepts extra unknown arguments so you can give it the same + arguments that you would give to conda build. + + As of now, it only parses -m/--variant_config_files and --python and other + arguments are ignored. + """ + clirunner = CliRunner() + with clirunner.isolation(): + # render + config = cb.get_or_merge_config( + None, variant_config_files=variant_config_files, python=python) + metadata_tuples = cb.render(recipe_path, config=config) + + # check if build(s) should be skipped + if should_skip_build(metadata_tuples): + return 0 + + paths = cb.get_output_file_paths(metadata_tuples, config=config) + click.echo('\n'.join(sorted(paths))) diff --git a/bob/devtools/scripts/changelog.py b/bob/devtools/scripts/changelog.py new file mode 100644 index 00000000..7765d960 --- /dev/null +++ b/bob/devtools/scripts/changelog.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python + +import os +import sys +import datetime + +import click + +from . import bdt +from ..changelog import get_last_tag_date, write_tags_with_commits +from ..changelog import parse_date +from ..release import get_gitlab_instance + + +@click.command(context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + ), + epilog=''' +Examples: + + 1. Generates the changelog for a single package using merge requests: + + $ bdt -vvv changelog group/package.xyz changelog.md + + + 2. The same as above, but dumps the changelog to stdout instead of a file + + $ bdt -vvv changelog group/package.xyz - + + + 3. Generates the changelog for a single package looking at commits + (not merge requests): + + $ bdt -vvv changelog --mode=commits group/package.xyz changelog.md + + + 4. Generates the changelog for a single package looking at merge requests starting from a given date of January 1, 2016: + +\b + $ bdt -vvv changelog --mode=mrs --since=2016-01-01 group/package.xyz changelog.md + + + 5. Generates a complete list of changelogs for a list of packages (one per line: + +\b + $ curl -o order.txt https://gitlab.idiap.ch/bob/bob.nightlies/raw/master/order.txt + $ bdt lasttag bob/bob + # copy and paste date to next command + $ bdt -vvv changelog --since="2018-07-17 10:23:40" order.txt changelog.md +''') +@click.argument('target') +@click.argument('changelog', type=click.Path(exists=False, dir_okay=False, + file_okay=True, writable=True)) +@click.option('-g', '--group', default='bob', show_default=True, + help='Gitlab default group name where packages are located (if not ' \ + 'specified using a "/" on the package name - e.g. ' \ + '"bob/bob.extension")') +@click.option('-m', '--mode', type=click.Choice(['mrs', 'tags', 'commits']), + default='mrs', show_default=True, + help='Changes the way we produce the changelog. By default, uses the ' \ + 'text in every merge request (mode "mrs"). To use tag annotations, ' \ + 'use mode "tags". If you use "commits" as mode, we use the text ' \ + 'in commits to produce the changelog') +@click.option('-s', '--since', + help='A starting date in any format accepted by dateutil.parser.parse() ' \ + '(see https://dateutil.readthedocs.io/en/stable/parser.html) from ' \ + 'which you want to generate the changelog. If not set, the package\'s' \ + 'last release date will be used') +@bdt.raise_on_error +def changelog(target, changelog, group, mode, since): + """Generates changelog file for package(s) from the Gitlab server. + + This script generates changelogs for either a single package or multiple + packages, depending on the value of TARGET. The changelog (in markdown + format) is written to the output file CHANGELOG. + + There are two modes of operation: you may provide the package name in the + format ``<gitlab-group>/<package-name>`` (or simply ``<package-name>``, in + which case the value of ``--group`` will be used). Or, optionally, provide + an existing file containing a list of packages that will be iterated on. + + For each package, we will contact the Gitlab server and create a changelog + using merge-requests (default), tags or commits since a given date. If a + starting date is not passed, we'll use the date of the last tagged value or + the date of the first commit, if no tags are available in the package. + """ + + gl = get_gitlab_instance() + + # reads package list or considers name to be a package name + if os.path.exists(target) and os.path.isfile(target): + bdt.logger.info('Reading package names from file %s...', target) + with open(target, 'rb') as f: + packages = [k.strip() for k in f.readlines() if k and not \ + k.strip().startswith('#')] + else: + bdt.logger.info('Assuming %s is a package name (file does not ' \ + 'exist)...', target) + packages = [target] + + # if the user passed a date, convert it + if since: since = parse_date(since) + + # iterates over the packages and dumps required information + for package in packages: + + if '/' not in package: + package = '/'.join(group, package) + + # retrieves the gitlab package object + use_package = gl.projects.get(package) + bdt.logger.info('Found gitlab project %s (id=%d)', + use_package.attributes['path_with_namespace'], use_package.id) + + last_release_date = since or get_last_tag_date(use_package) + bdt.logger.info('Retrieving data (mode=%s) since %s', mode, + last_release_date.strftime('%b %d, %Y %H:%M')) + + # add 1s to avoid us retrieving previous release data + last_release_date += datetime.timedelta(seconds=1) + + if mode == 'tags': + visibility = ('public',) + else: + visibility = ('public', 'private', 'internal') + + if use_package.attributes['namespace'] == use_package.name: + # skip system meta-package + bdt.logger.warn('Skipping meta package %s...', + use_package.attributes['path_with_namespace']) + continue + + if use_package.attributes['visibility'] not in visibility: + bdt.logger.warn('Skipping package %s (visibility not in ' \ + '"%s")...', use_package.attributes['path_with_namespace'], + '|'.join(visibility)) + continue + + if changelog == '-': + changelog_file = sys.stdout + else: + changelog_file = open(changelog, 'at') + + # write_tags(f, use_package, last_release_date) + write_tags_with_commits(changelog_file, use_package, last_release_date, + mode) + changelog_file.flush() diff --git a/bob/devtools/scripts/lasttag.py b/bob/devtools/scripts/lasttag.py new file mode 100644 index 00000000..2826ad18 --- /dev/null +++ b/bob/devtools/scripts/lasttag.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +import os + +import click + +from . import bdt +from ..changelog import get_last_tag, parse_date +from ..release import get_gitlab_instance + + +@click.command(context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + ), + epilog=''' +Examples: + + 1. Get the last tag information of the bob/bob package + + $ bdt lasttag bob/bob + + + 2. Get the last tag information of the beat/beat.core package + + $ bdt lasttag beat/beat.core + +''') +@click.argument('package') +@bdt.raise_on_error +def lasttag(package): + """Returns the last tag information on a given PACKAGE + """ + + if '/' not in package: + raise RuntimeError('PACKAGE should be specified as "group/name"') + + gl = get_gitlab_instance() + + # we lookup the gitlab group once + use_package = gl.projects.get(package) + bdt.logger.info('Found gitlab project %s (id=%d)', + use_package.attributes['path_with_namespace'], use_package.id) + + tag = get_last_tag(use_package) + date = parse_date(tag.commit['committed_date']) + click.echo('Lastest tag for %s is %s (%s)' % \ + (package, tag.name, date.strftime('%Y-%m-%d %H:%M:%S'))) diff --git a/bob/devtools/scripts/release.py b/bob/devtools/scripts/release.py new file mode 100644 index 00000000..63b8374f --- /dev/null +++ b/bob/devtools/scripts/release.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +import os + +import click + +from . import bdt +from ..release import release_bob, parse_and_process_package_changelog +from ..release import release_package, wait_for_pipeline_to_finish +from ..release import get_gitlab_instance + +@click.command(context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + ), + epilog=''' +Examples: + + 1. Releases a single package: + + $ bdt -vvv release --package=bob.package.xyz changelog.md + + + 2. If there is a single package in the ``changelog.md`` file, the flag + ``--package`` is not required: + + $ bdt -vvv release changelog.md + + + 2. Releases the whole of bob using `changelog_since_last_release.md`: + + $ bdt -vvv release bob/devtools/data/changelog_since_last_release.md + + + 3. In case of errors, resume the release of the whole of Bob: + + $ bdt -vvv release --resume bob/devtools/data/changelog_since_last_release.md + + + 4. The option `-dry-run` can be used to let the script print what it would do instead of actually doing it: + + $ bdt -vvv release --dry-run changelog_since_last_release.md +''' +) +@click.argument('changelog', type=click.File('rb', lazy=False)) +@click.option('-g', '--group', default='bob', show_default=True, + help='Group name where all packages are located') +@click.option('-p', '--package', + help='If the name of a package is provided, then this package will be ' \ + 'found in the changelog file and the release will resume from it ' \ + '(if option ``--resume`` is set) or only this package will be ' \ + 'released. If there is only a single package in the changelog, ' \ + 'then you do NOT need to set this flag') +@click.option('-r', '--resume/--no-resume', default=False, + help='The overall release will resume from the provided package name') +@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') +@bdt.raise_on_error +def release(changelog, group, package, resume, dry_run): + """\b + Tags packages on gitlab from an input CHANGELOG in markdown formatting + + By using a CHANGELOG file as an input (that can be generated with the ``bdt + changelog`` command), this script goes through all packages in CHANGELOG + file (in order listed), tags them correctly as per the file, and pushes + this tag to gitlab them one by one. Tagged releases are treated specially + by the CI and are auto-deployed to our stable conda channels and PyPI. + + This script uses the provided CHANGELOG file to release one or more + package. The CHANGELOG is expected to have the following structure: + + \b + * package name + * tag1 name (date of the tag). + * tag description. Each line of the tag description starts with `*` + character. + - commits (from earliest to latest). Each line of the commit starts + with `-` character. + * tag2 name (date of the tag). + * tag description. Each line of the tag description starts with `*` + character. + - commits (from earliest to latest). Each line of the commit starts + with `-` character. + * patch + - leftover not-tagged commits (from earliest to latest) + + This script can also be used to release a single package. + + IMPORTANT: There are some considerations that needs to be taken into + account **before** you release a new version of a package: + + \b + * In the changelog file: + - write the name of this package and write (at least) the next tag value. + For the next tag value, you can either indicate one of the special + values: ``patch``, ``minor`` or ``major``, and the package will be then + released with either patch, minor, or major version **bump**. + - Alternatively, you can specify the tag value directly (using + a ``vX.Y.Z`` format), but be careful that it is higher than the last + release tag of this package. Make sure that the version that you are + trying to release is not already released. You must follow semantic + versioning: http://semver.org. + - Then, under the desired new tag version of the package, please write + down the changes that are applied to the package between the last + released version and this version. This changes are written to + release tags of packages in the Gitlab interface. For an example + look at: https://gitlab.idiap.ch/bob/bob.extension/tags + * Make sure all the tests for the package are passing. + * Make sure the documentation is building with the following command: + ``sphinx-build -aEWn doc sphinx`` + * Ensure all changes are committed to the git repository and pushed. + * Ensure the documentation badges in README.rst are pointing to: + https://www.idiap.ch/software/bob/docs/bob/... + * For database packages, ensure that the '.sql3' file or other metadata + files have been generated (if any). + * Ensure the nightlies build is green after the changes are submitted if + the package is a part of the nightlies. + * If your package depends on an unreleased version of another package, + you need to release that package first. + """ + + gl = get_gitlab_instance() + + use_group = gl.groups.list(search='"%s"' % group)[0] + + # if we are releasing 'bob' metapackage, it's a simple thing, no GitLab + # API + if package == 'bob': + release_bob(changelog) + return + + # traverse all packages in the changelog, edit older tags with updated + # comments, tag them with a suggested version, then try to release, and + # wait until done to proceed to the next package + changelogs = changelog.readlines() + + # find the starts of each package's description in the changelog + pkgs = [i for i, line in enumerate(changelogs) if line[0] == '*'] + pkgs.append(len(changelogs)) #the end + start_idx = 0 + + if package: + # get the index where the package first appears in the list + start_idx = [i for i, line in enumerate(changelogs) \ + if line[1:].strip() == package] + + if not start_idx: + bdt.logger.error('Package %s was not found in the changelog', + package) + return + + start_idx = pkgs.index(start_idx[0]) + + # if we are in a dry-run mode, let's let it be known + if dry_run: + bdt.logger.warn('!!!! DRY RUN MODE !!!!') + bdt.logger.warn('Nothing is being committed to Gitlab') + + # go through the list of packages and release them starting from the + # start_idx + for i in range(start_idx, len(pkgs) - 1): + cur_package_name = changelogs[pkgs[i]][1:].strip() + bdt.logger.info('Processing package %s', changelogs[pkgs[i]]) + gitpkg, tag, tag_comments = parse_and_process_package_changelog(gl, + use_group, cur_package_name, + changelogs[pkgs[i] + 1: pkgs[i + 1]], dry_run) + + # release the package with the found tag and its comments + if gitpkg: + pipeline_id = release_package(gitpkg, tag, tag_comments, dry_run) + # now, wait for the pipeline to finish, before we can release the + # next package + wait_for_pipeline_to_finish(gitpkg, pipeline_id, dry_run) + + # if package name is provided and resume is not set, process only + # this package + if package == cur_package_name and not resume: + break + + bdt.logger.info('Finished processing %s', changelog) diff --git a/bob_tools/scripts/cb_output.py b/bob_tools/scripts/cb_output.py deleted file mode 100644 index 6545ce9a..00000000 --- a/bob_tools/scripts/cb_output.py +++ /dev/null @@ -1,41 +0,0 @@ -from ..utils.click import raise_on_error -from ..utils.conda import should_skip_build -from click.testing import CliRunner -import click -import conda_build.api as cb - - -@click.command(context_settings=dict( - ignore_unknown_options=True, allow_extra_args=True), - epilog='''\b -Examples: -$ bob-tools cb-output conda_recipe_dir -$ bob-tools cb-output ../bob.conda/conda/kaldi -m ../bob.admin/gitlab/conda_build_config.yaml --python 3.6 -''' -) -@click.argument('recipe_path') -@click.option('-m', '--variant-config-files', help='see conda build --help') -@click.option('--python', help='see conda build --help') -@raise_on_error -def cb_output(recipe_path, variant_config_files, python): - """Outputs name(s) of package(s) that would be generated by conda build. - - This command accepts extra unknown arguments so you can give it the same - arguments that you would give to conda build. - - As of now, it only parses -m/--variant_config_files and --python and other - arguments are ignored. - """ - clirunner = CliRunner() - with clirunner.isolation(): - # render - config = cb.get_or_merge_config( - None, variant_config_files=variant_config_files, python=python) - metadata_tuples = cb.render(recipe_path, config=config) - - # check if build(s) should be skipped - if should_skip_build(metadata_tuples): - return 0 - - paths = cb.get_output_file_paths(metadata_tuples, config=config) - click.echo('\n'.join(sorted(paths))) diff --git a/bob_tools/scripts/main.py b/bob_tools/scripts/main.py deleted file mode 100644 index a5d7a6ca..00000000 --- a/bob_tools/scripts/main.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This is the main entry to bob_tools's scripts. -""" -import pkg_resources -import click -from click_plugins import with_plugins - - -@with_plugins(pkg_resources.iter_entry_points('bob_tools.cli')) -@click.group(context_settings=dict(help_option_names=['-?', '-h', '--help'])) -def main(): - """The main command line interface for bob tools. Look below for available - commands.""" - pass diff --git a/bob_tools/utils/__init__.py b/bob_tools/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bob_tools/utils/click.py b/bob_tools/utils/click.py deleted file mode 100644 index e698e594..00000000 --- a/bob_tools/utils/click.py +++ /dev/null @@ -1,20 +0,0 @@ -from functools import wraps - -import click - - -def raise_on_error(view_func): - """Raise a click exception if returned value is not zero. - - Click exits successfully if anything is returned, in order to exit properly - when something went wrong an exception must be raised. - """ - - def _decorator(*args, **kwargs): - value = view_func(*args, **kwargs) - if value not in [None, 0]: - exception = click.ClickException("Error occurred") - exception.exit_code = value - raise exception - return value - return wraps(view_func)(_decorator) diff --git a/buildout.cfg b/buildout.cfg index 6e6c0804..81fcf838 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -4,8 +4,7 @@ [buildout] parts = scripts develop = . -eggs = bob_tools - +eggs = bob.devtools newest = false [scripts] diff --git a/doc/api.rst b/doc/api.rst new file mode 100644 index 00000000..6ca5b6c2 --- /dev/null +++ b/doc/api.rst @@ -0,0 +1,21 @@ +.. vim: set fileencoding=utf-8 : + + +============ + Python API +============ + +.. autosummary:: + bob.devtools.conda + bob.devtools.release + bob.devtools.changelog + + +Detailed Information +-------------------- + +.. automodule:: bob.devtools.conda + +.. automodule:: bob.devtools.release + +.. automodule:: bob.devtools.changelog diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000..10c340bc --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : + +import os +import sys +import glob +import pkg_resources + + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.3' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.ifconfig', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.graphviz', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx.ext.mathjax', + ] + +# Be picky about warnings +nitpicky = False + +# Ignores stuff we can't easily resolve on other project's sphinx manuals +nitpick_ignore = [] + +# Allows the user to override warnings from a separate file +if os.path.exists('nitpick-exceptions.txt'): + for line in open('nitpick-exceptions.txt'): + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + try: # python 2.x + target = unicode(target) + except NameError: + pass + nitpick_ignore.append((dtype, target)) + +# Always includes todos +todo_include_todos = True + +# Generates auto-summary automatically +autosummary_generate = True + +# Create numbers on figures with captions +numfig = True + +# If we are on OSX, the 'dvipng' path maybe different +dvipng_osx = '/opt/local/libexec/texlive/binaries/dvipng' +if os.path.exists(dvipng_osx): pngmath_dvipng = dvipng_osx + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'bob.devtools' +import time +copyright = u'%s, Idiap Research Institute' % time.strftime('%Y') + +# Grab the setup entry +distribution = pkg_resources.require(project)[0] + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = distribution.version +# The full version, including alpha/beta/rc tags. +release = distribution.version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['links.rst'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# Some variables which are useful for generated material +project_variable = project.replace('.', '_') +short_description = u'Tools for development and CI integration of Bob packages' +owner = [u'Idiap Research Institute'] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +import sphinx_rtd_theme +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = project_variable + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = 'img/logo.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = 'img/favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = project_variable + u'_doc' + + +# -- Post configuration -------------------------------------------------------- + +# Included after all input documents +rst_epilog = """ +.. |project| replace:: Bob +.. |version| replace:: %s +.. |current-year| date:: %%Y +""" % (version,) + +# Default processing flags for sphinx +autoclass_content = 'class' +autodoc_member_order = 'bysource' +autodoc_default_flags = [ + 'members', + 'undoc-members', + 'show-inheritance', + ] + +# Adds simplejson, pyzmq links +#intersphinx_mapping['http://simplejson.readthedocs.io/en/stable/'] = None +#intersphinx_mapping['http://pyzmq.readthedocs.io/en/stable/'] = None + +# We want to remove all private (i.e. _. or __.__) members +# that are not in the list of accepted functions +accepted_private_functions = ['__array__'] + +def member_function_test(app, what, name, obj, skip, options): + # test if we have a private function + if len(name) > 1 and name[0] == '_': + # test if this private function should be allowed + if name not in accepted_private_functions: + # omit privat functions that are not in the list of accepted private functions + return skip + else: + # test if the method is documented + if not hasattr(obj, '__doc__') or not obj.__doc__: + return skip + return False + +def setup(app): + app.connect('autodoc-skip-member', member_function_test) diff --git a/doc/img/favicon.ico b/doc/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4cc3264302627d40868261add69eb755856611b6 GIT binary patch literal 4286 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 literal 0 HcmV?d00001 diff --git a/doc/img/logo.png b/doc/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b60858a7068bf45c1ed8e3da12fe244ccdcfe85d GIT binary patch literal 6266 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 literal 0 HcmV?d00001 diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 00000000..9a5f8ba7 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,29 @@ +.. vim: set fileencoding=utf-8 : + +.. _bob.devtools: + +======================= + Bob Development Tools +======================= + +This package provides tools to help maintain Bob_. + + +Documentation +------------- + +.. toctree:: + :maxdepth: 2 + + release + api + + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. include:: links.rst diff --git a/doc/links.rst b/doc/links.rst new file mode 100644 index 00000000..021350c1 --- /dev/null +++ b/doc/links.rst @@ -0,0 +1,5 @@ +.. vim: set fileencoding=utf-8 : + +.. Place here references to all citations in lower case + +.. _bob: https://www.idiap.ch/software/bob diff --git a/doc/release.rst b/doc/release.rst new file mode 100644 index 00000000..7e1c3936 --- /dev/null +++ b/doc/release.rst @@ -0,0 +1,141 @@ +.. vim: set fileencoding=utf-8 : + +.. _bob.devtools.release: + + +Release Management +------------------ + +This package offers tools to release (tag) packages that follow Bob's +development guidelines. It automatically updates the ``README.rst`` file of +the package to setup the correct pointers for the release build and +documentation badges. The tools are setup to ensure a changelog is provided +with each release. The changelog can be autogenerated from merge-requests or +commits in the target package. + + +Setup +===== + +These programs require access to your gitlab private token which you can pass +at every iteration or setup at your ``~/.python-gitlab.cfg``. If you don't set +it up, it will request for your API token on-the-fly, what can be cumbersome +and repeatitive. Your ``~/.python-gitlab.cfg`` should roughly look like this +(there must be an "idiap" section on it, at least): + +.. code-block:: ini + + [global] + default = idiap + ssl_verify = true + timeout = 15 + + [idiap] + url = https://gitlab.idiap.ch + private_token = <obtain token at your settings page in gitlab> + api_version = 4 + +We recommend you set ``chmod 600`` to this file to avoid prying us to read out +your personal token. Once you have your token set up, communication should work +transparently between these gitlab clients and the server. + + +Usage +===== + +Using these scripts is a 2-step process: + +1. Generate a changelog using ``bdt changelog`` +2. Tag a release using ``bdt release`` + +Use the ``--help`` flag in each command to learn more about each command. + + +Manually update changelog +========================= + +.. todo:: These instructions may be outdated!! + +The changelog since the last release can be found in the file +``bob/devtools/data/changelog_since_last_release.md``. The structure is +documented as part of the help message of ``bdt release``. Read it. + +To manually update the changelog, follow these guidelines: + + 1. For each tag, summarize the commits into several descriptive lines. + These summaries become tag descriptions and they should extend/update + the existing tag description. Therefore, each line of the summary should + also start with ``*`` character like the tag descriptions. + 2. The last tag name is called ``patch``. This indicates that a patch + version of this package will be automatically updated during the next + tag/release. You can change ``patch`` to ``minor`` or ``major``, and the + package will be then tagged/released with either minor or major version + bump. + 3. Once all commits were changed to corresponding tag descriptions (no more + lines start with ``-`` characters), this package is ready for release + and you can continue to another package in the changelog. + + +Releasing the Bob meta package +============================== + +.. todo:: These instructions may be outdated!! + +Here are the instructions to release Bob meta package: + +* Run ./check_private.sh bob.buildout bob.extension ... + with the list of packages from `bob/bob.nightlies' "order.txt" + <https://gitlab.idiap.ch/bob/bob.nightlies/blob/master/order.txt>`_ +* Put the list of public packages in ../../bob/requirements.txt +* Run ``bdt changelog`` first: + + .. code-block:: sh + + $ bdt changelog -l ../../bob/requirements.txt -R -- TOKEN | tee bob_changelog.md + +* Put the beta of version of the intended release version in + ``../../bob/version.txt`` + + * For example do ``$ echo 5.0.0b0 > version.txt`` for bob 5.0.0 release. + * Commit only this change to master: ``$ git commit -m "prepare for bob 5 release" version.txt`` + +* Get the pinnings (``--bob-version`` needs to be changed): + + .. code-block:: sh + + $ bdt release -p bob -c bob_changelog.md --bob-version 5.0.0 -- TOKEN + +* Put the pinnings below in requirements.txt and meta.yaml (like ``bob.buildout + == 2.1.6``) and meta.yaml (like ``bob.buildout 2.1.6``) + + * Make sure you add `` # [linux]`` to Linux only packages. + +* Test the conda recipe: + + .. code-block:: sh + + $ cd ../../bob + $ conda render -m ../bob.admin/gitlab/conda_build_config.yaml -c https://www.idiap.ch/software/bob/conda conda + +* Update the badges and version.txt to point to this version of Bob. +* Commit, push and tag a new version manually: + + .. code-block:: sh + + $ git commit -am "Increased stable version to 4.0.0" + $ git tag v4.0.0 + $ git push + $ git push --tags + +* Put ``bob_changelog.md`` inside bob's tag description. +* Cancel the pipeline for master and make sure that tag pipeline passes before + continuing. +* Remove pinnings from bob's requirement.txt and meta.yaml and revert changes + that went in ``README.rst`` back to master version. +* Commit and push the following (not verbatim): + + .. code-block:: sh + + $ echo 4.0.1b0 > version.txt + $ git commit -am "Increased latest version to 4.0.1b0 [skip ci]" + $ git push diff --git a/env.yml b/env.yml new file mode 100644 index 00000000..6788cba4 --- /dev/null +++ b/env.yml @@ -0,0 +1,22 @@ +name: bdt +channels: +- https://www.idiap.ch/software/bob/conda +- defaults +dependencies: +- python=3.6 +- bob.buildout +- click +- click-plugins +- conda-build +- ipdb +- sphinx +- sphinx_rtd_theme +- pip +- pytz +- python-dateutil +- chardet +- idna +- requests +- urllib3 +- pip: + - python-gitlab diff --git a/setup.py b/setup.py index 477e8faa..a4160852 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,24 @@ #!/usr/bin/env python -"""A package that contains tools to maintain Bob -""" from setuptools import setup, find_packages # Define package version version = open("version.txt").read().rstrip() +requires = [ + 'setuptools', + 'click', + 'click-plugins', + 'conda-build', + 'python-gitlab', + 'requests', + ] + setup( - name="bob_tools", + name="bob.devtools", version=version, - description="Tools to maintain Bob packages", - url='http://gitlab.idiap.ch/bob/bob_tools', + description="Tools for development and CI integration of Bob packages", + url='http://gitlab.idiap.ch/bob/bob.devtools', license="BSD", author='Bob Developers', author_email='bob-devel@googlegroups.com', @@ -22,14 +29,17 @@ setup( zip_safe=False, # when updating these dependencies, update the README too - install_requires=['setuptools', 'click', 'click-plugins', 'conda_build'], + install_requires=requires, entry_points={ 'console_scripts': [ - 'bob-tools = bob_tools.scripts.main:main', + 'bdt = bob.devtools.scripts.bdt:main', ], - 'bob_tools.cli': [ - 'cb-output = bob_tools.scripts.cb_output:cb_output', + 'bdt.cli': [ + 'cb-output = bob.devtools.scripts.cb_output:cb_output', + 'release = bob.devtools.scripts.release:release', + 'changelog = bob.devtools.scripts.changelog:changelog', + 'lasttag = bob.devtools.scripts.lasttag:lasttag', ], }, classifiers=[ -- GitLab