diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5b049b38..59ec4ccb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,10 @@ jobs: riscv32-unknown-elf-gcc --version verible-verilog-lint --version + - name: Commit metadata + run: ./ci/lint-commits.sh "$GITHUB_BASE_REF" + if: ${{ github.event_name == 'pull_request' }} + # Verible format is experimental so only run on default config for now, # will eventually become part of the per-config CI - name: Format all source code with Verible format (experimental) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 000000000..cf6f0fb78 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,51 @@ +name: "CLA Assistant" +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +# Explicitly configure permissions for this action on the repository. +permissions: + actions: write + contents: read # This is 'read' because the signatures live in a remote repository. + pull-requests: write + statuses: write + +env: + CLA_URL: 'https://github.com/lowRISC/ibex/blob/master/CLA.md' + CLA_SIGNATURE: 'I have read the CLA Document. By submitting this pull request comment, I am hereby confirming my acceptance of the terms of the CLA Document and my agreement to be legally bound by its terms.' + +jobs: + CLAAssistant: + runs-on: ubuntu-latest + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == env.CLA_SIGNATURE) || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # The below token should have repo scope and must be manually added by you in the repository's secret and is needed because the signatures live in a remote repository. + PERSONAL_ACCESS_TOKEN: ${{ secrets.SIGNATURE_REPO_SECRET }} + with: + path-to-signatures: 'signatures/version1/cla.json' + path-to-document: '${{ env.CLA_URL }}' # e.g. a CLA or a DCO document + # This branch on the signature repository should not be protected: + branch: 'main' + + # The followings are optional inputs. + remote-organization-name: 'lowRISC' # Enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository). + remote-repository-name: 'cla-signatures' # Enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository). + custom-notsigned-prcomment: | + Thank you for your submission, we really appreciate it. + + Like many open-source projects, we ask that you sign the [Contributor License Agreement](${{ env.CLA_URL }}) before we can accept your contribution. + + The CLA ensures that all users of the project are granted rights to use the submission. + + Before signing the CLA, please ensure that you have the authority from your organisation to grant these rights. + + You will be asked to sign the CLA when you first contribute to each lowRISC repository, and will be asked to re-sign if the CLA changes. + + Each individual who has committed in this Pull Request should sign the CLA by posting a Pull Request Comment containing the text below. + custom-pr-sign-comment: ${{ env.CLA_SIGNATURE }} diff --git a/CLA.md b/CLA.md new file mode 100644 index 000000000..6fcd5636b --- /dev/null +++ b/CLA.md @@ -0,0 +1,29 @@ +# lowRISC CIC - Contributor License Agreement ("Agreement") v1.1 + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to the Project and recipients of software distributed by the Project, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + +"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with the Project. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project for inclusion in, or documentation of, any of the products owned or managed by the Project (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +"Project" means the open source project identified by name in the associated README.md file and comprising the collection of hardware, software, utility and documentation files saved in this repository. + +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to the Project and to recipients of software distributed by the Project a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to the Project and to recipients of the Work distributed by the Project a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to the Project, or that your employer has executed a separate Corporate CLA with the Project. + +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, You may submit it to the Project separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify the Project of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. + +9. Tool and AI-Generated Contributions. If any portion of Your Contribution is generated, assisted, or created by an automated tool, including but not limited to artificial intelligence (AI) systems, such Contribution must be intentionally submitted by You. By submitting such a Contribution, You represent that You have reasonably reviewed and understand the generated material, and You take full responsibility for the submission. If you have not reasonably reviewed and understood the generated material, you must not submit the Contribution. Furthermore, You agree to prominently disclose the use of such tools in Your submission (such as within a commit message, pull request, or as otherwise directed by the Project). You explicitly agree that the tool-generated material constitutes Your Contribution and is bound by all terms, licenses, and representations of this Agreement. + +10. Export Controls and Trade Sanctions. You represent that your Contribution is not subject to any export control, economic sanctions, or cryptography regulations of the United Kingdom, Switzerland, the European Union, the United States of America, or any other applicable jurisdiction, that would restrict or require a license for its free, open, and public distribution. You agree that you will not submit any Contribution that is subject to the US International Traffic in Arms Regulations (ITAR), requires an export license under the US Export Administration Regulations (EAR), or requires any other government approval for public release. If you are unsure whether your Contribution is subject to such restrictions, you must not submit the Contribution. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f873d73f..60fccc366 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## Git Considerations -- Create your branch to commit your changes and then create a Pull Request. +- Create your own fork and branch to commit your changes and then create a Pull Request. - Separate subject from body with a blank line. - Limit the subject line to 50 characters. - Capitalize the subject line. @@ -9,6 +9,7 @@ - Use the present tense ("Add feature" not "Added feature"). - Wrap the body at 72 characters. - Use the body to explain what and why vs. how. +- Add a sign-off message, which you can automaticaly do using: `git commit --signoff` For a detailed why and how please refer to one of the multiple [resources](https://chris.beams.io/posts/git-commit/) regarding git commit messages. diff --git a/README.md b/README.md index 06c5603e0..052e45fdd 100644 --- a/README.md +++ b/README.md @@ -73,13 +73,15 @@ The Ibex Demo System is maintained by lowRISC but is not an official part of Ibe We highly appreciate community contributions. To ease our work of reviewing your contributions, please: -* Create your own branch to commit your changes and then open a Pull Request. +* Create your own fork to commit your changes and then open a Pull Request. * Split large contributions into smaller commits addressing individual changes or bug fixes. Do not mix unrelated changes into the same commit! * Write meaningful commit messages. For more information, please check out the [contribution guide](https://github.com/lowrisc/ibex/blob/master/CONTRIBUTING.md). * If asked to modify your changes, do fixup your commits and rebase your branch to maintain a clean history. +* Sign our [contributors license agreement + (CLA)](https://github.com/lowrisc/ibex/blob/master/CLA.md). When contributing SystemVerilog source code, please try to be consistent and adhere to [our Verilog coding style guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md). diff --git a/ci/lint-commits.sh b/ci/lint-commits.sh new file mode 100755 index 000000000..8fbeed276 --- /dev/null +++ b/ci/lint-commits.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env -S bash -eu +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# A wrapper around lint_commits.py, used for CI. +# +# Expects a single argument, which is the pull request's target branch +# (usually "master"). + +if [ $# != 1 ]; then + echo >&2 "Usage: lint-commits.sh " + exit 1 +fi +tgt_branch="$1" + +merge_base="$(git merge-base origin/$tgt_branch HEAD)" || { + echo >&2 "Failed to find fork point for origin/$tgt_branch." + exit 1 +} +echo "Checking commit messages since $merge_base" + +# Notes: +# * Merge commits are not checked. We always use rebases instead of +# merges to keep a linear history, which makes merge commits disappear +# ultimately, making them only a CI artifact which should not be +# checked. +util/lint_commits.py \ + --no-merges \ + --error-msg-prefix="::error::" \ + --warning-msg-prefix="::warning::" \ + "$merge_base"..HEAD diff --git a/python-requirements.txt b/python-requirements.txt index 00bb2bd3c..7718f818f 100644 --- a/python-requirements.txt +++ b/python-requirements.txt @@ -27,3 +27,7 @@ premailer < 3.9.0 # Recurse to get any requirements from riscv-dv -r vendor/google_riscv-dv/requirements.txt + +# Needed by commit lint +argparse +GitPython diff --git a/util/lint_commits.py b/util/lint_commits.py new file mode 100755 index 000000000..63e3237fb --- /dev/null +++ b/util/lint_commits.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import re +import sys + +from git import Repo + +error_msg_prefix = 'ERROR: ' +warning_msg_prefix = 'WARNING: ' + +# Maximum length of the summary line in the commit message (the first line) +# There is no hard limit, but a typical convention is to keep this line at or +# below 50 characters, with occasional outliers. +COMMIT_MSG_MAX_SUMMARY_LEN = 100 + + +def error(msg, commit=None): + full_msg = msg + if commit: + full_msg = "Commit %s: %s" % (commit.hexsha, msg) + print(error_msg_prefix + full_msg, file=sys.stderr) + + +def warning(msg, commit=None): + full_msg = msg + if commit: + full_msg = "Commit %s: %s" % (commit.hexsha, msg) + print(warning_msg_prefix + full_msg, file=sys.stderr) + + +def lint_commit_author(commit): + success = True + if commit.author.email.endswith('users.noreply.github.com'): + error( + f'Commit author has no valid email address set: ' # noqa: F541 + '{commit.author.email!r}. ' + 'Use "git config user.email user@example.com" to ' + 'set a valid email address, then update the commit ' + 'with "git rebase -i" and/or ' + '"git commit --amend --signoff --reset-author". ' + 'Also check your GitHub settings at ' + 'https://github.com/settings/emails: your email address ' + 'must be verified, and the option "Keep my email address ' + 'private" must be disabled. ' + 'This command will also sign off your commit indicating agreement ' + 'to the Contributor License Agreement. See CONTRIBUTING.md for ' + 'more details.', commit) + success = False + + if ' ' not in commit.author.name: + warning( + f'The commit author name {commit.author.name!r} contains no space. ' + 'Use "git config user.name \'Johnny English\'" to ' + 'set your real name, and update the commit with "git rebase -i " ' + 'and/or "git commit --amend --signoff --reset-author". ' + 'This command will also sign off your commit indicating agreement ' + 'to the Contributor License Agreement. See CONTRIBUTING.md for ' + 'more details.', commit) + # A warning doesn't fail lint. + + return success + + +def lint_commit_message(commit): + success = True + lines = commit.message.splitlines() + + # Check length of summary line. + summary_line_len = len(lines[0]) + if summary_line_len > COMMIT_MSG_MAX_SUMMARY_LEN: + error( + "The summary line in the commit message is %d characters long; " + "only %d characters are allowed." % + (summary_line_len, COMMIT_MSG_MAX_SUMMARY_LEN), commit) + success = False + + # Check for an empty line separating the summary line from the long + # description. + if len(lines) > 1 and lines[1] != "": + error( + "The second line of a commit message must be empty, as it " + "separates the summary from the long description.", commit) + success = False + + # Check that the commit message contains at least one Signed-off-by line + # that matches the author name and email. There might be other signoffs (if + # there are multiple authors). We don't have any requirements about those + # at the moment and just pass them through. + signoff_lines = [] + signoff_pfx = 'Signed-off-by: ' + for idx, line in enumerate(lines): + if not line.startswith(signoff_pfx): + continue + + signoff_body = line[len(signoff_pfx):] + match = re.match(r'[^<]+ <[^>]*>$', signoff_body) + if match is None: + error('Commit has Signed-off-by line {!r}, but the second part ' + 'is not of the required form. It should be of the form ' + '"Signed-off-by: NAME ".' + .format(line)) + success = False + + signoff_lines.append(line) + + expected_signoff_line = ("Signed-off-by: {} <{}>" + .format(commit.author.name, + commit.author.email)) + signoff_req_msg = ('The commit message must contain a Signed-off-by line ' + 'that matches the commit author name and email, ' + 'indicating agreement to the Contributor License ' + 'Agreement. See CONTRIBUTING.md for more details. ' + 'You can use "git commit --signoff" to ask git to add ' + 'this line for you.') + + if not signoff_lines: + error('Commit has no Signed-off-by line. ' + signoff_req_msg) + success = False + elif expected_signoff_line not in signoff_lines: + error(('Commit has one or more Signed-off-by lines, but not the one ' + 'we expect. We expected to find "{}". ' + .format(expected_signoff_line)) + + signoff_req_msg) + success = False + + return success + + +def lint_commit(commit): + success = True + if not lint_commit_author(commit): + success = False + if not lint_commit_message(commit): + success = False + return success + + +def main(): + global error_msg_prefix + global warning_msg_prefix + + parser = argparse.ArgumentParser( + description='Check commit metadata for common mistakes') + parser.add_argument('--error-msg-prefix', + default=error_msg_prefix, + required=False, + help='string to prepend to all error messages') + parser.add_argument('--warning-msg-prefix', + default=warning_msg_prefix, + required=False, + help='string to prepend to all warning messages') + parser.add_argument('--no-merges', + required=False, + action="store_true", + help='do not check commits with more than one parent') + parser.add_argument('commit_range', + metavar='commit-range', + help=('commit range to check ' + '(must be understood by git log)')) + args = parser.parse_args() + + error_msg_prefix = args.error_msg_prefix + warning_msg_prefix = args.warning_msg_prefix + + lint_successful = True + + repo = Repo() + commits = repo.iter_commits(args.commit_range) + for commit in commits: + print("Checking commit %s" % commit.hexsha) + is_merge = len(commit.parents) > 1 + if is_merge and args.no_merges: + print("Skipping merge commit.") + continue + + if not lint_commit(commit): + lint_successful = False + + if not lint_successful: + error('Commit lint failed.') + sys.exit(1) + + +if __name__ == '__main__': + main()