diff --git a/bob/devtools/changelog.py b/bob/devtools/changelog.py index ddba8b29fb077415c633b0ab5a892d4d301861a8..355ba2b84a27674bc05b3289b6f26c0b903f05ce 100644 --- a/bob/devtools/changelog.py +++ b/bob/devtools/changelog.py @@ -43,7 +43,7 @@ def _sort_tags(tags, 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()) + return io.StringIO(gitpkg.files.get(file_path=path, ref=ref).decode()) def get_last_tag(package): @@ -229,18 +229,21 @@ def _write_mergerequests_range(f, pkg_name, mrs): 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. +def get_changes_since(gitpkg, since): + """Gets the list of MRs, tags, and commits since the provided date - Args: + Parameters + ---------- + gitpkg : object + A gitlab pakcage object + since : object + A parsed date - 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 + Returns + ------- + tuple + mrs, tags, commits """ - # get tags since release and sort them tags = gitpkg.tags.list() @@ -265,6 +268,21 @@ def write_tags_with_commits(f, gitpkg, since, mode): ) ) ) + return mrs, tags, commits + +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 + """ + mrs, tags, commits = get_changes_since(gitpkg, since) + f.write("* %s\n" % (gitpkg.attributes["path_with_namespace"],)) # go through tags and writes each with its message and corresponding diff --git a/bob/devtools/scripts/changelog.py b/bob/devtools/scripts/changelog.py index 186b4e35eb0a2886b48d4c6d9b68ea82ed7124d3..ae1cd84ee1909f6b928581ca0786a64e50e6394d 100644 --- a/bob/devtools/scripts/changelog.py +++ b/bob/devtools/scripts/changelog.py @@ -1,16 +1,8 @@ #!/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 - from ..log import verbosity_option, get_logger logger = get_logger(__name__) @@ -22,41 +14,39 @@ Examples: 1. Generates the changelog for a single package using merge requests: - $ bdt gitlab changelog group/package.xyz changelog.md + $ bdt gitlab changelog -vvv group/package.xyz changelog.md 2. The same as above, but dumps the changelog to stdout instead of a file: - $ bdt gitlab changelog group/package.xyz + $ bdt gitlab changelog -vvv group/package.xyz 3. Generates the changelog for a single package looking at commits (not merge requests): - $ bdt gitlab changelog --mode=commits group/package.xyz changelog.md + $ bdt gitlab changelog -vvv --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 gitlab changelog --mode=mrs --since=2016-01-01 group/package.xyz changelog.md + $ bdt gitlab changelog -vvv --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: + 5. Generates a complete list of changelogs for a list of packages (one per line): \b $ bdt gitlab getpath bob/bob.nightlies order.txt $ bdt gitlab lasttag bob/bob # copy and paste date to next command - $ bdt gitlab changelog --since="2018-07-17 10:23:40" order.txt changelog.md + $ bdt gitlab changelog -vvv --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 - ), + type=click.Path(exists=False, dir_okay=False, file_okay=True, writable=True), required=False, ) @click.option( @@ -102,10 +92,22 @@ def changelog(target, changelog, group, mode, since): 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. + 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. """ + import os + import sys + import datetime + + from ..changelog import ( + get_last_tag_date, + write_tags_with_commits, + parse_date, + get_changes_since, + get_last_tag, + ) + from ..release import get_gitlab_instance gl = get_gitlab_instance() @@ -119,15 +121,26 @@ def changelog(target, changelog, group, mode, since): if k.strip() and not k.strip().startswith("#") ] else: - logger.info( - "Assuming %s is a package name (file does not exist)...", target - ) + 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) + # Since tagging packages requires bob.devtools to be tagged first. Add that to the + # list as well if bob.devtools has changed. Note that bob.devtools can release + # itself. + def bdt_has_changes(): + gitpkg = gl.projects.get("bob/bob.devtools") + tag = get_last_tag(gitpkg) + last_tag_date = parse_date(tag.commit["committed_date"]) + _, _, commits = get_changes_since(gitpkg, last_tag_date) + return len(commits) + + if bdt_has_changes(): + packages.insert(0, "bob/bob.devtools") + # iterates over the packages and dumps required information for package in packages: @@ -179,7 +192,5 @@ def changelog(target, changelog, group, mode, since): 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 - ) + write_tags_with_commits(changelog_file, use_package, last_release_date, mode) changelog_file.flush() diff --git a/bob/devtools/scripts/common_options.py b/bob/devtools/scripts/common_options.py new file mode 100644 index 0000000000000000000000000000000000000000..df211a09622cfbedb3f50a38091125748c0e68f2 --- /dev/null +++ b/bob/devtools/scripts/common_options.py @@ -0,0 +1,18 @@ +import click + + +def ref_option(**kwargs): + """An option for getting branch name.""" + + def custom_ref_option(func): + + return click.option( + "-r", + "--ref", + default="master", + show_default=True, + help="Download path from the provided git reference (may be a branch, tag or commit hash)", + **kwargs + )(func) + + return custom_ref_option diff --git a/bob/devtools/scripts/getpath.py b/bob/devtools/scripts/getpath.py index 04cba7715af3049bebe0451dacd2d00c5a79f821..a7aede79bc5f547a5ede77be8faf8a6067c6ef60 100644 --- a/bob/devtools/scripts/getpath.py +++ b/bob/devtools/scripts/getpath.py @@ -6,7 +6,7 @@ import click from . import bdt from ..release import get_gitlab_instance, download_path - +from .common_options import ref_option from ..log import verbosity_option, get_logger logger = get_logger(__name__) @@ -34,13 +34,7 @@ Examples: @click.argument("package") @click.argument("path") @click.argument("output", type=click.Path(exists=False), required=False) -@click.option( - "-r", - "--ref", - default="master", - show_default=True, - help="Download path from the provided git reference (may be a branch, tag or commit hash)", -) +@ref_option() @verbosity_option() @bdt.raise_on_error def getpath(package, path, output, ref): diff --git a/bob/devtools/scripts/gitlab.py b/bob/devtools/scripts/gitlab.py index 90afda5183a82fd33a0c10c8866e8c12ebaeed05..7e66d36e2317788d7be9606f44c2b7beb6c8097c 100644 --- a/bob/devtools/scripts/gitlab.py +++ b/bob/devtools/scripts/gitlab.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -import os - import pkg_resources import click diff --git a/bob/devtools/scripts/release.py b/bob/devtools/scripts/release.py index f666bc57f636ef07dc7d35eeb90fb95d44cbc8bc..11b932c34626dae0578db109a2398f90e503d2f0 100644 --- a/bob/devtools/scripts/release.py +++ b/bob/devtools/scripts/release.py @@ -22,28 +22,28 @@ Examples: 1. Releases a single package: - $ bdt gitlab release --package=bob.package.xyz changelog.md + $ bdt gitlab release -vvv --package=bob/bob.package.xyz changelog.md 2. If there is a single package in the ``changelog.md`` file, the flag ``--package`` is not required: - $ bdt gitlab release changelog.md + $ bdt gitlab release -vvv changelog.md - 2. Releases the whole of bob using `changelog_since_last_release.md`: + 2. Releases the whole of bob using `changelog.md`: - $ bdt gitlab release bob/devtools/data/changelog_since_last_release.md + $ bdt gitlab release -vvv changelog.md 3. In case of errors, resume the release of the whole of Bob: - $ bdt gitlab release --resume bob/devtools/data/changelog_since_last_release.md + $ bdt gitlab release -vvv --resume --package=bob/bob.package.xyz changelog.md 4. The option `-dry-run` can be used to let the script print what it would do instead of actually doing it: - $ bdt gitlab release --dry-run changelog_since_last_release.md + $ bdt gitlab release -vvv --dry-run changelog.md """ ) @click.argument("changelog", type=click.File("rt", lazy=False)) @@ -143,12 +143,6 @@ def release(changelog, group, package, resume, dry_run): gl = get_gitlab_instance() - # 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 @@ -162,9 +156,7 @@ def release(changelog, group, package, resume, dry_run): 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 + i for i, line in enumerate(changelogs) if line[1:].strip() == package ] if not start_idx: @@ -201,9 +193,7 @@ def release(changelog, group, package, resume, dry_run): # release the package with the found tag and its comments if use_package: - pipeline_id = release_package( - use_package, tag, tag_comments, dry_run - ) + pipeline_id = release_package(use_package, tag, tag_comments, dry_run) # now, wait for the pipeline to finish, before we can release the # next package wait_for_pipeline_to_finish(use_package, pipeline_id, dry_run) diff --git a/bob/devtools/scripts/update_bob.py b/bob/devtools/scripts/update_bob.py new file mode 100644 index 0000000000000000000000000000000000000000..20f9a072e57bb69b89cd2530065c121d911cf01c --- /dev/null +++ b/bob/devtools/scripts/update_bob.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + + +import click + +from . import bdt +from .common_options import ref_option + +from ..log import verbosity_option, get_logger + +logger = get_logger(__name__) + + +@click.command( + epilog="""\b +Examples: + bdt gitlab update-bob -vv + bdt gitlab update-bob -vv --stable +""" +) +@click.option( + "--stable/--beta", help="To use the stable versions in the list and pin packages." +) +@verbosity_option() +@bdt.raise_on_error +def update_bob(stable): + """Updates the Bob meta package with new packages. + """ + import tempfile + from ..ci import read_packages + from ..release import get_gitlab_instance, download_path, get_latest_tag_name + + gl = get_gitlab_instance() + + # download order.txt form bob.nightlies and get the list of packages + nightlies = gl.projects.get("bob/bob.nightlies") + + with tempfile.NamedTemporaryFile() as f: + download_path(nightlies, "order.txt", f.name, ref="master") + packages = read_packages(f.name) + + # find the list of public packages + public_packages, private_packages = [], [] + for n, (package, branch) in enumerate(packages): + + if package == "bob/bob": + continue + + # determine package visibility + use_package = gl.projects.get(package) + is_public = use_package.attributes["visibility"] == "public" + + if is_public: + public_packages.append(package.replace("bob/", "")) + else: + private_packages.append(package.replace("bob/", "")) + + logger.debug("%s is %s", package, "public" if is_public else "not public") + + logger.info("Found %d public packages", len(public_packages)) + logger.info( + "The following packages were not public:\n%s", "\n".join(private_packages) + ) + + # if requires stable versions, add latest tag versions to the names + if stable: + logger.info("Getting latest tag names for the public packages") + tags = [ + get_latest_tag_name(gl.projects.get(f"bob/{pkg}")) + for pkg in public_packages + ] + public_packages = [f"{pkg} =={tag}" for pkg, tag in zip(public_packages, tags)] + + + # modify conda/meta.yaml and requirements.txt in bob/bob + logger.info("Updating conda/meta.yaml") + start_tag = "# LIST OF BOB PACKAGES - START" + end_tag = "# LIST OF BOB PACKAGES - END" + + with open("conda/meta.yaml") as f: + lines = f.read() + i1 = lines.find(start_tag) + len(start_tag) + i2 = lines.find(end_tag) + + lines = ( + lines[:i1] + "\n - ".join([""] + public_packages) + "\n " + lines[i2:] + ) + + with open("conda/meta.yaml", "w") as f: + f.write(lines) + + logger.info("Updating requirements.txt") + with open("requirements.txt", "w") as f: + f.write("\n".join(public_packages) + "\n") + + click.echo( + "You may need to add the ` # [linux]` tag in front of linux only " + "packages in conda/meta.yaml" + ) diff --git a/doc/release.rst b/doc/release.rst index bcb5a77afdd14303febea17f0cadcc1543a15e65..f627fe639c1e593993fc363fd0b91b4d46e876c4 100644 --- a/doc/release.rst +++ b/doc/release.rst @@ -69,67 +69,77 @@ To manually update the changelog, follow these guidelines: Releasing the Bob meta package ============================== -.. todo:: These instructions may be outdated!! - Here are the instructions to release Bob meta package: * Run: .. code-block:: sh - $ bdt gitlab getpath bob/bob.nightlies order.txt - $ bdt gitlab visibility order.txt + $ cd bob + $ bdt gitlab update-bob -vvv --stable -* Put the list of public packages in ../../bob/requirements.txt -* Run ``bdt gitlab changelog`` first: +* The script above cannot identify linux only packages. After running the script, + **you need to manually tag linux only packages** in both ``conda/meta.yaml`` and + ``requirements.txt``. For example, in ``conda/meta.yaml``:: - .. code-block:: sh + .. code-block:: yaml - $ bdt gitlab changelog ../../bob/requirements.txt bob_changelog.md + - bob.ip.binseg ==1.1.0 # [linux] -* Put the beta of version of the intended release version in - ``../../bob/version.txt`` + and, in ``requirements.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`` + bob.ip.binseg ==1.1.0 ; sys_platform == 'linux' -* Get the pinnings (``--bob-version`` needs to be changed): +* Test the conda recipe of bob. You may want to cancel the + command below once it reaches the nosetests.: .. code-block:: sh - $ bdt gitlab release -p bob -c bob_changelog.md --bob-version 5.0.0 -- TOKEN + $ bdt build -vv --stable + +* Commit the changes and push: -* 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``) + .. code-block:: sh + + $ git commit -m "Pinning packages for the next release. [skip ci]" conda/meta.yaml requirements.txt + $ git push - * Make sure you add `` # [linux]`` to Linux only packages. -* Test the conda recipe: +* Tag the package using the same changelog mechanism that you used to tag other + packages. Assuming the changelog has a ``* bob/bob`` entry: .. code-block:: sh - $ cd ../../bob - $ conda render -m ../bob.admin/gitlab/conda_build_config.yaml -c https://www.idiap.ch/software/bob/conda conda + $ bdt gitlab release -vvv CHANGELOG --package bob/bob -* Update the badges and version.txt to point to this version of Bob. -* Commit, push and tag a new version manually: +* When the script says ``Waiting for the pipeline *** of "bob/bob" to finish``, you may + cancel it and check the progress online. + +* To revert the pins while in beta run:: .. code-block:: sh - $ git commit -am "Increased stable version to 4.0.0" - $ git tag v4.0.0 - $ git push - $ git push --tags + $ git pull --rebase + $ bdt gitlab update-bob -vvv --beta -* 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): +* Like before, **tag the linux only packages manually**. + +* Commit and push the changes: .. code-block:: sh - $ echo 4.0.1b0 > version.txt - $ git commit -am "Increased latest version to 4.0.1b0 [skip ci]" + $ git commit -m "Remove package pins while in beta. [skip ci]" conda/meta.yaml requirements.txt $ git push + +You can see that if we could identify linux only packages automatically, the whole +release process would have been only to run +``bdt gitlab release -vvv CHANGELOG --package bob/bob``. +Do you want to help fix that? + + +Releasing the docs meta package +=============================== + +Don't forget to release ``bob/docs`` after the bob release has successfully finished. +To do so, go to https://gitlab.idiap.ch/bob/docs/-/tags and click on ``New tag``. +Use the same version number you used for bob. diff --git a/setup.py b/setup.py index f9ac0ca9b2d85691ec5c03520cae6c51d41e0db8..449ba6d330f33c97c335a99c42987d819bc7cce5 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,8 @@ setup( 'getpath = bob.devtools.scripts.getpath:getpath', 'process-pipelines = bob.devtools.scripts.pipelines:process_pipelines', 'get-pipelines- = bob.devtools.scripts.pipelines:get_pipelines', - 'graph = bob.devtools.scripts.graph:graph' + 'graph = bob.devtools.scripts.graph:graph', + 'update-bob = bob.devtools.scripts.update_bob:update_bob', ], 'bdt.ci.cli': [