diff --git a/.github/workflows/pr_rtl_change_check.yml b/.github/workflows/pr_rtl_change_check.yml new file mode 100644 index 00000000000000..071b49c187e75d --- /dev/null +++ b/.github/workflows/pr_rtl_change_check.yml @@ -0,0 +1,37 @@ +name: 'Testing GH Actions' + +# **What it does**: Renders the content of every page and check all internal links. +# **Why we have it**: To make sure all links connect correctly. +# **Who does it impact**: Docs content. + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + +permissions: + contents: read + # Needed for the 'trilom/file-changes-action' action + pull-requests: read + +jobs: + check-links: + runs-on: "ubuntu-latest" + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Creates file "$/files.json", among others + - name: Gather files changed + uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b + with: + fileOutput: 'json' + + # For verification + - name: Show files changed + run: cat $HOME/files.json + + - name: Check for blocked changs + run: ./ci/scripts/check-pr-changes-allowed.py $HOME/files.json diff --git a/BLOCKFILE b/BLOCKFILE new file mode 100644 index 00000000000000..54e2f4407ccba4 --- /dev/null +++ b/BLOCKFILE @@ -0,0 +1,8 @@ +# A test block file + +hw/ip/*/rtl/* +hw/ip_templates/*/rtl/* +hw/top_earlgrey/rtl/* +hw/top_earlgrey/ip/*/rtl/* +hw/top_earlgrey/ip_autogen/*/rtl/* + diff --git a/ci/scripts/check-pr-changes-allowed.py b/ci/scripts/check-pr-changes-allowed.py new file mode 100755 index 00000000000000..46538b399a9aff --- /dev/null +++ b/ci/scripts/check-pr-changes-allowed.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +from fnmatch import fnmatch +import json +import argparse +import sys + +def load_blockfile(blockfile_name): + blocklist = [] + with open(blockfile_name, 'r') as blockfile: + for line in blockfile.readlines(): + # Remove everything following a '#' for comments + line = line.partition('#')[0] + # Remove leading and trailing whitespace + line = line.strip() + + # If anything remains after the above add it to the list + if line != '': + blocklist.append(line) + + return blocklist + +def load_and_validate_changes(changes_json_file): + with open(changes_json_file, 'r') as changes_json: + changes = json.load(changes_json) + + if type(changes) != list: + raise ValueError('Changes JSON must be a list at top-level') + + for change in changes: + if type(change) != str: + raise ValueError(f'Saw value {change} in changes JSON which is not ' + 'a string. Changes JSON must be a single list of ' + 'strings') + + return changes + +def check_changes_against_blocklist(changes, blocklist): + blocked_changes = {} + + for change in changes: + # Create list of patterns from blocklist that match change + blocked = list(filter(lambda x: x is not None, + map(lambda b: b if fnmatch(change, b) else None, blocklist))) + + # If any match patterns exist add them to the blocked_changes dictionary + if blocked: + blocked_changes[change] = blocked + + return blocked_changes + +def main(): + arg_parser = argparse.ArgumentParser( + prog='check-pr-changes-allowed', + description='''Checks a list of changed files supplied as a json + list against blocked file patterns. If any match + exits with code 1 and the PR should be blocked.''') + + arg_parser.add_argument('changes_json_file', metavar='changes-json-file', + help='''JSON file containing a list of changed files''') + + arg_parser.add_argument('--block-file', default='BLOCKFILE', + help='''Plain text file containing path patterns that when matched + indicate a blocked file. One pattern per line''') + + arg_parser.add_argument('--gh-token', + help='''A github access token used to read PR comments for override + commands''') + + args = arg_parser.parse_args() + + blocklist = load_blockfile(args.block_file) + print(blocklist) + changes = load_and_validate_changes(args.changes_json_file) + print(changes) + blocked_changes = check_changes_against_blocklist(changes, blocklist) + print(blocked_changes) + + if blocked_changes: + for change, block_patterns in blocked_changes.items(): + patterns_str = ' '.join(block_patterns) + print(f'{change} blocked by pattern(s): {patterns_str}') + + print('UNAUTHORIZED CHANGES PRESENT, PR cannot be merged!') + return 1 + + print('No unauthorized changes, clear to merge') + return 0 + +if __name__ == '__main__': + sys.exit(main()) +