mirror of https://github.com/mamba-org/mamba.git
Automate releases (`CHANGELOG.md` updating) (#3179)
* Add script to automate updating CHANGELOG.md for releasing * Add PRs label check workflow
This commit is contained in:
parent
a2a2d26445
commit
67fd10a830
|
@ -0,0 +1,43 @@
|
|||
name: Check release label
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- synchronize
|
||||
- opened
|
||||
- reopened
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
jobs:
|
||||
label_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check labels
|
||||
run: |
|
||||
NUMBER_OF_LABELS=$(jq '.pull_request.labels | length' "$GITHUB_EVENT_PATH")
|
||||
if [ $NUMBER_OF_LABELS -eq 0 ]; then
|
||||
echo "PR has no labels. Please add at least one label of release type."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_LABELS=("release::enhancements" "release::bug_fixes" "release::ci_docs")
|
||||
PR_LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
|
||||
NB_RELEASE_LABELS=0
|
||||
|
||||
for LABEL in $PR_LABELS; do
|
||||
if [[ " ${RELEASE_LABELS[@]} " =~ " ${LABEL} " ]]; then
|
||||
NB_RELEASE_LABELS=$((NB_RELEASE_LABELS+1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $NB_RELEASE_LABELS -eq 0 ]; then
|
||||
echo "PR has no release labels. Please add a label of release type."
|
||||
exit 1
|
||||
elif [ $NB_RELEASE_LABELS -gt 1 ]; then
|
||||
echo "PR has multiple release labels. Please remove all but one label."
|
||||
exit 1
|
||||
fi
|
|
@ -1,4 +1,6 @@
|
|||
# script to release any of the mamba packages
|
||||
# Script to release any of the mamba packages
|
||||
# Please refer to `update_changelog.py` for more info about the release process
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import re
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
# Script to update `CHANGELOG.md` in order to release any of the mamba packages
|
||||
|
||||
# Steps:
|
||||
|
||||
# 1. Run this script to update the root `CHANGELOG.md` file by giving the date of
|
||||
# the last release as input (cf. last date shown at the top of the file for reference)
|
||||
# or any other starting date that may be relevant for the release,
|
||||
# and the release version to be made.
|
||||
|
||||
# 2. If you are happy with the changes, run `releaser.py` to update the versions and
|
||||
# corresponding nested `CHANGELOG.md` files.
|
||||
|
||||
# N.B If the release is to be a pre-release (alpha,...), the versions should not be updated in `.py` and `.h` files.
|
||||
# Only the `CHANGELOG.md` files should be modified.
|
||||
# If otherwise, please revert the corresponding files if modified by the script.
|
||||
|
||||
# 3. Follow the steps described in the `releaser.py` output.
|
||||
|
||||
from datetime import date
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
def validate_date(date_str):
|
||||
try:
|
||||
date.fromisoformat(date_str)
|
||||
except ValueError:
|
||||
raise ValueError("Incorrect date format, should be YYYY-MM-DD")
|
||||
|
||||
|
||||
def subprocess_run(*args: str, **kwargs) -> str:
|
||||
"""Execute a command in a subprocess while properly capturing stderr in exceptions."""
|
||||
try:
|
||||
p = subprocess.run(
|
||||
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, **kwargs
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Command {args} failed with stderr: {e.stderr.decode()}")
|
||||
print(f"Command {args} failed with stdout: {e.stdout.decode()}")
|
||||
raise e
|
||||
return p.stdout
|
||||
|
||||
|
||||
def append_to_file(ctgr_name, prs, out_file):
|
||||
out_file.write("\n{}:\n\n".format(ctgr_name))
|
||||
for pr in prs:
|
||||
# Author
|
||||
pr_author_cmd = "gh pr view {} --json author".format(pr)
|
||||
author_login = dict(json.loads(subprocess_run(*pr_author_cmd.split()).decode("utf-8")))[
|
||||
"author"
|
||||
]["login"]
|
||||
# Title
|
||||
pr_title_cmd = "gh pr view {} --json title".format(pr)
|
||||
title = dict(json.loads(subprocess_run(*pr_title_cmd.split()).decode("utf-8")))["title"]
|
||||
# URL
|
||||
pr_url_cmd = "gh pr view {} --json url".format(pr)
|
||||
url = dict(json.loads(subprocess_run(*pr_url_cmd.split()).decode("utf-8")))["url"]
|
||||
# Files
|
||||
pr_files_cmd = "gh pr view {} --json files".format(pr)
|
||||
files = dict(json.loads(subprocess_run(*pr_files_cmd.split()).decode("utf-8")))["files"]
|
||||
ref_mamba_pkgs = ["libmamba/", "libmambapy/", "micromamba/"]
|
||||
concerned_pkgs = set()
|
||||
for f in files:
|
||||
for ref_pkg in ref_mamba_pkgs:
|
||||
if f["path"].startswith(ref_pkg):
|
||||
concerned_pkgs.add(ref_pkg)
|
||||
|
||||
if (sorted(ref_mamba_pkgs) == sorted(concerned_pkgs)) or (len(concerned_pkgs) == 0):
|
||||
concerned_pkgs = ["all/"]
|
||||
# Write in file
|
||||
out_file.write(
|
||||
"- [{}] {} by @{} in {}\n".format(
|
||||
(", ".join([pkg[:-1] for pkg in concerned_pkgs])), title, author_login, url
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
commits_starting_date = input(
|
||||
"Enter the starting date of commits to be included in the release in the format YYYY-MM-DD: "
|
||||
)
|
||||
validate_date(commits_starting_date)
|
||||
release_version = input("Enter the version to be released: ")
|
||||
|
||||
# Get commits to include in the release
|
||||
log_cmd = "git log --since=" + commits_starting_date
|
||||
commits = subprocess_run(*log_cmd.split()).decode("utf-8")
|
||||
|
||||
# Create the regular expression pattern
|
||||
opening_char = "(#"
|
||||
closing_char = ")"
|
||||
pattern = re.compile(re.escape(opening_char) + "(.*?)" + re.escape(closing_char))
|
||||
|
||||
# Get the PRs numbers
|
||||
prs_nbrs = re.findall(pattern, commits)
|
||||
|
||||
# Make three lists to categorize PRs: "Enhancements", "Bug fixes" and "CI fixes and doc"
|
||||
enhancements_prs = [] # release::enhancements
|
||||
bug_fixes_prs = [] # release::bug_fixes
|
||||
ci_docs_prs = [] # release::ci_docs
|
||||
|
||||
for pr in prs_nbrs:
|
||||
# Get labels
|
||||
pr_labels_cmd = "gh pr view {} --json labels".format(pr)
|
||||
labels = dict(json.loads(subprocess_run(*pr_labels_cmd.split()).decode("utf-8")))["labels"]
|
||||
nb_rls_lbls_types = 0
|
||||
label = ""
|
||||
for lab in labels:
|
||||
if lab["name"].startswith("release::"):
|
||||
nb_rls_lbls_types = nb_rls_lbls_types + 1
|
||||
label = lab["name"]
|
||||
|
||||
# Only one release label should be set
|
||||
if nb_rls_lbls_types == 0:
|
||||
raise ValueError("No release label is set for PR #{}".format(pr))
|
||||
elif nb_rls_lbls_types > 1:
|
||||
raise ValueError(
|
||||
"Only one release label should be set. PR #{} has {} labels.".format(
|
||||
pr, nb_rls_lbls_types
|
||||
)
|
||||
)
|
||||
|
||||
# Dispatch PRs with their corresponding label
|
||||
if label == "release::enhancements":
|
||||
enhancements_prs.append(pr)
|
||||
elif label == "release::bug_fixes":
|
||||
bug_fixes_prs.append(pr)
|
||||
elif label == "release::ci_docs":
|
||||
ci_docs_prs.append(pr)
|
||||
else:
|
||||
raise ValueError("Unknown release label {} for PR #{}".format(label, pr))
|
||||
|
||||
with open("CHANGELOG.md", "r+") as changelog_file:
|
||||
# Make sure we're appending at the beginning of the file
|
||||
content_to_restore = changelog_file.read()
|
||||
changelog_file.seek(0)
|
||||
|
||||
# Append new info
|
||||
# Release date and version
|
||||
changelog_file.write("{}\n".format(date.today().strftime("%Y.%m.%d")))
|
||||
changelog_file.write("==========\n")
|
||||
changelog_file.write(
|
||||
"\nReleases: libmamba {0}, libmambapy {0}, micromamba {0}\n".format(release_version)
|
||||
)
|
||||
# PRs info
|
||||
append_to_file("Enhancements", enhancements_prs, changelog_file)
|
||||
append_to_file("Bug fixes", bug_fixes_prs, changelog_file)
|
||||
append_to_file("CI fixes and doc", ci_docs_prs, changelog_file)
|
||||
|
||||
# Write back old content of CHANGELOG file
|
||||
changelog_file.write("\n" + content_to_restore)
|
||||
|
||||
print(
|
||||
"'CHANGELOG.md' was successfully updated.\nPlease run 'releaser.py' if you agree with the changes applied."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue