| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*-" |
| # |
| # Copyright 2020 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Parse ChangeLog from CPCon and generate commit message. |
| |
| Usage: |
| 1. Copy ChangeLog from CPCon (everything except the MENU bar) |
| 2. xsel -b | ./gen_uprev_msg.py [-b BOARD] [--extra-repo-file FILE] |
| 3. Commit message will be printed to stdout |
| 4. Be aware of the warning messages in stderr |
| """ |
| from __future__ import print_function |
| |
| import argparse |
| import collections |
| import logging |
| import re |
| import sys |
| |
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger() |
| |
| MAX_LENGTH = 72 |
| CHANGES_PATTERN = r"Changes between ([0-9\.]+) and ([0-9\.]+)" |
| |
| IGNORED_PATTERNS = ( |
| "Marking set of ebuilds as stable", |
| r"Incremented to version: \d+\.\d+\.\d+", |
| r"Increment to version R\d+-\d+\.\d+\.\d+.*", |
| ) |
| |
| DEFAULT_REPOS = ( |
| "src/platform/bmpblk", |
| "src/platform/depthcharge", |
| "src/platform/ec", |
| "src/platform/firmware", |
| "src/platform/vboot_reference", |
| "src/third_party/arm-trusted-firmware", |
| "src/third_party/coreboot", |
| "src/third_party/coreboot/3rdparty/blobs", |
| ) |
| |
| |
| CL = collections.namedtuple("CL", ["commit", "cl", "bug", "title"]) |
| |
| |
| def read_extra_repos(filename): |
| """Read extra repos from |filename|.""" |
| repos = set() |
| with open(filename) as f: |
| for line in f: |
| repo = line.strip() |
| if repo: |
| repos.add(repo) |
| return repos |
| |
| |
| def parse_cl(line): |
| """Parse CL.""" |
| tokens = line.split("\t") |
| if len(tokens) != 6: |
| return None |
| commit, cl, bug, _date, _author, title = tokens |
| return CL(commit, int(cl) if cl else None, int(bug) if bug else None, title) |
| |
| |
| def wrap_line(s, max_length): |
| """Wrap a line.""" |
| # Assume words are separated by one space |
| words = s.split() |
| lines = [[]] |
| length = 0 |
| for word in words: |
| if length + 1 + len(word) > max_length: |
| lines.append([]) |
| length = 0 |
| lines[-1].append(word) |
| length += 1 + len(word) |
| return [" ".join(line) for line in lines] |
| |
| |
| def main(args): |
| """Parse ChangeLog and print commit message.""" |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("-b", "--board", required=True) |
| parser.add_argument( |
| "--extra-repo-file", help="File containing extra repo names" |
| ) |
| args = parser.parse_args(args) |
| board = args.board |
| included_repos = set(DEFAULT_REPOS) |
| if args.extra_repo_file: |
| included_repos |= read_extra_repos(args.extra_repo_file) |
| |
| changes = False |
| repos = [] |
| repo = None |
| ignored_repos = [] |
| ignored_commits = [] |
| skipped_lines = [] |
| for line in sys.stdin: |
| line = line.strip() |
| |
| # Parse "Changes between 12573.80.0 and 12573.88.0" |
| if not changes: |
| m = re.match(CHANGES_PATTERN, line) |
| if m: |
| groups = m.groups() |
| if len(groups) == 2: |
| changes = groups |
| continue |
| |
| # Parse repo |
| tokens = line.split() |
| if len(tokens) == 1 and "/" in tokens[0]: |
| repo = tokens[0] |
| if repo in included_repos: |
| cl_list = [] |
| repos.append((repo, cl_list)) |
| else: |
| ignored_repos.append(repo) |
| repo = None |
| continue |
| |
| # Parse CL |
| if not repo: |
| continue |
| |
| cl = parse_cl(line) |
| if not cl: |
| skipped_lines.append(line) |
| continue |
| |
| ignored = False |
| for pattern in IGNORED_PATTERNS: |
| if re.fullmatch(pattern, cl.title): |
| ignored = True |
| continue |
| if ignored: |
| ignored_commits.append((cl.commit, repo)) |
| else: |
| cl_list.append(cl) |
| |
| if not repos: |
| logger.error("No repo found from ChangeLog") |
| return 1 |
| |
| # Output |
| if changes: |
| title = ( |
| f"chromeos-firmware-{board}: " |
| f"Uprev firmware to {changes[1]} for {board}" |
| ) |
| print(title) |
| print() |
| |
| print(f"Changes between {changes[0]} and {changes[1]}:") |
| |
| for repo, cl_list in repos: |
| if not cl_list: |
| continue |
| print() |
| print(repo) |
| private = "private" in repo |
| for cl in cl_list: |
| if cl.cl: |
| cl_str = f"CL:*{cl.cl}" if private else f"CL:{cl.cl}" |
| else: |
| ignored = False |
| for pattern in IGNORED_PATTERNS: |
| if re.fullmatch(pattern, cl.title): |
| ignored = True |
| continue |
| if ignored: |
| continue |
| cl_str = "CL:" + "?" * 7 |
| print(">>>>>>> PLEASE FILL THE CL NUMBER MANUALLY") |
| title = cl.title |
| indentation = 1 + len(cl_str) + 4 |
| for i, title_line in enumerate( |
| wrap_line(cl.title, MAX_LENGTH - indentation) |
| ): |
| if i == 0: |
| line = f" {cl_str} {title_line}" |
| else: |
| line = " " * indentation + f"{title_line}" |
| print(line) |
| |
| print() |
| print("BUG=<INSERT BUGS HERE>") |
| print(f"TEST=emerge-{board} chromeos-firmware-{board}") |
| |
| # Warnings |
| for repo in ignored_repos: |
| logger.warning("Ignore repo %s", repo) |
| for commit, repo in ignored_commits: |
| logger.warning("Ignore commit %s in %s", commit, repo) |
| for line in skipped_lines: |
| logger.warning("Skipping line: %s", line) |
| |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |