CCF/scripts/extract-release-notes.py

169 строки
6.2 KiB
Python

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Apache 2.0 License.
import argparse
import re
import sys
import subprocess
MICROSOFT_ARTIFACT_REGISTRY_NAME = "mcr.microsoft.com"
MICROSOFT_ARTIFACT_REGISTRY_PREFIX = "product"
CCF_APP_IMAGE_PREFIX = "ccf/app"
CCF_MCR_IMAGES = {
"App Development": f"{CCF_APP_IMAGE_PREFIX}/dev",
"C++ Runtime": f"{CCF_APP_IMAGE_PREFIX}/run",
"TypeScript/JavaScript Runtime": f"{CCF_APP_IMAGE_PREFIX}/run-js",
}
def main():
parser = argparse.ArgumentParser(
description="Parses a CHANGELOG file and checks it meets some formatting expectations. "
"Will also extract release notes for targeted versions."
)
parser.add_argument(
"--target-version",
help="Add a version string which must be present in the CHANGELOG",
action="append",
default=[],
)
parser.add_argument(
"--target-git-version",
help="Derive target version from current git version",
action="store_true",
)
parser.add_argument(
"--changelog", help="Path to CHANGELOG file to parse", default="CHANGELOG.md"
)
parser.add_argument(
"-f",
"--fix",
help="Fix any automatically correctable errors",
action="store_true",
)
parser.add_argument(
"--append-mcr-images",
help="If true, automatically append MCR images URLs to release notes",
action="store_true",
default=False,
)
parser.add_argument(
"--describe-path-changes",
help="If true, add a note whenever the given path has changes between releases.",
action="append",
default=[],
)
args = parser.parse_args()
if args.target_git_version:
git_version = subprocess.run(
["git", "describe", "--tags"], capture_output=True, universal_newlines=True
).stdout.strip()
git_version = git_version.replace("ccf-", "")
args.target_version.append(git_version)
version_header = re.compile(r"## \[(.+)\]")
link_definition = re.compile(r"\[(.+)\]:")
release_notes = {}
links_found = []
# Parse file, bucketing lines into each version's release notes
current_release_notes = None
with open(args.changelog) as f:
while line := f.readline():
if match := version_header.match(line):
log_version = match.group(1)
current_release_notes = []
release_notes[log_version] = current_release_notes
elif match := link_definition.match(line):
link_version = match.group(1)
links_found.append(link_version)
else:
if current_release_notes != None:
current_release_notes.append(line.strip())
documented_versions = set(release_notes.keys())
# Check that each version has a link
versions_without_links = documented_versions - set(links_found)
if len(versions_without_links) > 0:
print("Missing links for following versions:")
for version in versions_without_links:
print(f" {version}")
# Check that each target version is present
missing_target_versions = set(args.target_version) - documented_versions
if len(missing_target_versions) > 0:
print("Missing required versions:")
for version in missing_target_versions:
print(f" {version}")
# If there were any problems, try to fix them and exit
if len(versions_without_links) > 0 or len(missing_target_versions) > 0:
if args.fix:
with open(args.changelog, "a") as f:
for version in versions_without_links:
# Append presumed link
f.write(
f"[{version}]: https://github.com/microsoft/CCF/releases/tag/ccf-{version}\n"
)
sys.exit(1)
else:
# File is valid.
if len(args.target_version) > 0:
# Print release notes for each target version.
# If multiple versions are requested, delimit and prefix each.
multiple = len(args.target_version) > 1
for i, version in enumerate(args.target_version):
if multiple:
if i > 0:
print("\n" + "-" * 80 + "\n")
print(f"# {version}")
print("\n".join(release_notes[version]).strip())
for path in args.describe_path_changes:
git_version = f"ccf-{version}"
prev_version = subprocess.run(
["git", "describe", "--tags", f"{git_version}^", "--abbrev=0"],
capture_output=True,
universal_newlines=True,
).stdout.strip()
diff = subprocess.run(
[
"git",
"diff",
"--exit-code",
prev_version,
git_version,
"--",
path,
],
capture_output=True,
universal_newlines=True,
)
if diff.returncode == 1:
# Insert a hyperlink to a GitHub compare page.
# This shows all changes between tags, not localised to the path, but seems to be the best we can do automatically.
print(
f"\n- **Note**: This release include changes to `{path}`, which may be viewed [here](https://github.com/Microsoft/CCF/compare/{prev_version}...{git_version}#files_bucket)"
)
if args.append_mcr_images:
print("\n**MCR Docker Images:** ", end="")
print(
", ".join(
[
f"[{desc}](https://{MICROSOFT_ARTIFACT_REGISTRY_NAME}/{MICROSOFT_ARTIFACT_REGISTRY_PREFIX}/{name}/tags)"
for desc, name in CCF_MCR_IMAGES.items()
]
)
)
else:
print("CHANGELOG is valid!")
if __name__ == "__main__":
main()