diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index fefd5dc5b..000000000 --- a/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -core/CHANGELOG.md merge=union -python/CHANGELOG.md merge=union -legacy/firmware/CHANGELOG.md merge=union -legacy/bootloader/CHANGELOG.md merge=union diff --git a/Makefile b/Makefile index 3c6312c49..faff63bde 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ C_FILES = $(shell find . -type f -name '*.[ch]' | grep -f ./tools/style.c.inclu style_check: pystyle_check cstyle_check changelog_check yaml_check editor_check ## run all style checks (C+Py) -style: pystyle cstyle changelog ## apply all code styles (C+Py) +style: pystyle cstyle ## apply all code styles (C+Py) pystyle_check: ## run code style check on application sources and tests flake8 --version @@ -40,7 +40,10 @@ pystyle: ## apply code style on application sources and tests make -C python style changelog_check: ## check changelog format - ./tools/linkify-changelogs.py --check + ./tools/generate-changelog.py --check core + ./tools/generate-changelog.py --check python + ./tools/generate-changelog.py --check legacy/firmware + ./tools/generate-changelog.py --check legacy/bootloader yaml_check: ## check yaml formatting yamllint . @@ -48,9 +51,6 @@ yaml_check: ## check yaml formatting editor_check: ## check editorconfig formatting editorconfig-checker -exclude '.*\.(so|dat|toif|der)' -changelog: ## fill out issue links in changelog - ./tools/linkify-changelogs.py - cstyle_check: ## run code style check on low-level C code clang-format --version @echo [CLANG-FORMAT] diff --git a/ci/check_changelog.sh b/ci/check_changelog.sh new file mode 100755 index 000000000..d89d4a4bc --- /dev/null +++ b/ci/check_changelog.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +base_branch=master +fail=0 +subdirs="core python legacy/firmware legacy/bootloader" + +git fetch origin "$base_branch" + +check_feature_branch () { + for subdir in $subdirs + do + echo "Checking $subdir" + files=$(git diff --name-only "origin/$base_branch..." -- "$subdir") + + if echo "$files" | grep . | grep -Fq -v .changelog.d; then + if ! echo "$files" | grep -Fq .changelog.d; then + fail=1 + echo "FAILURE! No changelog entry for changes in $subdir." + fi + fi + done +} + +check_release_branch () { + if git diff --name-only "origin/$base_branch..." | grep -Fq .changelog.d; then + fail=1 + echo "FAILURE! Changelog fragments not allowed in release branch:" + git diff --name-only "origin/$base_branch..." | grep -F .changelog.d + fi +} + +if echo "$CI_COMMIT_BRANCH" | grep -q "^release/"; then + check_release_branch +else + check_feature_branch +fi + +if [[ "$fail" -ne 0 ]]; then + echo "Please see https://docs.trezor.io/trezor-firmware/misc/changelog.html for instructions." +fi + +exit $fail diff --git a/ci/prebuild.yml b/ci/prebuild.yml index aa80f223f..bc1d3c159 100644 --- a/ci/prebuild.yml +++ b/ci/prebuild.yml @@ -41,3 +41,12 @@ release commit messages prebuild: - $CI_PROJECT_PATH_SLUG == 'satoshilabs-trezor-trezor-firmware' script: - nix-shell --run "ci/check_release_commit_messages.sh" + +changelog prebuild: + stage: prebuild + before_script: [] # nothing needed + variables: + GIT_SUBMODULE_STRATEGY: "none" + GIT_STRATEGY: clone + script: + - nix-shell --run "ci/check_changelog.sh" diff --git a/core/.changelog.d/.gitignore b/core/.changelog.d/.gitignore new file mode 100644 index 000000000..f935021a8 --- /dev/null +++ b/core/.changelog.d/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/core/.changelog.d/1167.changed b/core/.changelog.d/1167.changed new file mode 100644 index 000000000..12c445cfd --- /dev/null +++ b/core/.changelog.d/1167.changed @@ -0,0 +1 @@ +Support PIN of unlimited length. diff --git a/core/.changelog.d/1249.added b/core/.changelog.d/1249.added new file mode 100644 index 000000000..f566a063d --- /dev/null +++ b/core/.changelog.d/1249.added @@ -0,0 +1 @@ +Decred staking. diff --git a/core/.changelog.d/1404.added b/core/.changelog.d/1404.added new file mode 100644 index 000000000..0f0632650 --- /dev/null +++ b/core/.changelog.d/1404.added @@ -0,0 +1 @@ +Locking the device by holding finger on the homescreen for 2.5 seconds. diff --git a/core/.changelog.d/1491.changed b/core/.changelog.d/1491.changed new file mode 100644 index 000000000..caa2c13a6 --- /dev/null +++ b/core/.changelog.d/1491.changed @@ -0,0 +1 @@ +Allow decreasing the output value in RBF transactions. diff --git a/core/.changelog.d/1502.changed b/core/.changelog.d/1502.changed new file mode 100644 index 000000000..035c8cd3a --- /dev/null +++ b/core/.changelog.d/1502.changed @@ -0,0 +1 @@ +Cardano: Allow stake pool registrations with zero margin. diff --git a/core/.changelog.d/1510.changed b/core/.changelog.d/1510.changed new file mode 100644 index 000000000..86b5e4484 --- /dev/null +++ b/core/.changelog.d/1510.changed @@ -0,0 +1 @@ +Cardano: Assets are now shown as CIP-0014. diff --git a/core/.changelog.d/1518.added b/core/.changelog.d/1518.added new file mode 100644 index 000000000..65957fdfc --- /dev/null +++ b/core/.changelog.d/1518.added @@ -0,0 +1 @@ +Public key to ECDHSessionKey. diff --git a/core/.towncrier.template.md b/core/.towncrier.template.md new file mode 120000 index 000000000..ee2d21f96 --- /dev/null +++ b/core/.towncrier.template.md @@ -0,0 +1 @@ +../tools/towncrier.template.md \ No newline at end of file diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index a146c7fd5..7b02fcfb7 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -4,27 +4,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## 2.4.0 [unreleased] - -### Added -- Locking the device by holding finger on the homescreen for 2.5 seconds. [#1404] -- Public key to ECDHSessionKey. [#1518] -- Decred staking. [#1249] - -### Changed -- Allow decreasing the output value in RBF transactions. [#1491] -- Cardano: Allow stake pool registrations with zero margin. [#1502] -- Cardano: Assets are now shown as CIP-0014. [#1510] -- Support PIN of unlimited length. [#1167] - -### Deprecated - -### Removed - -### Fixed - -### Security - ## 2.3.6 [15th February 2021] ### Added @@ -359,7 +338,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#1139]: https://github.com/trezor/trezor-firmware/issues/1139 [#1159]: https://github.com/trezor/trezor-firmware/issues/1159 [#1165]: https://github.com/trezor/trezor-firmware/pull/1165 -[#1167]: https://github.com/trezor/trezor-firmware/issues/1167 [#1173]: https://github.com/trezor/trezor-firmware/pull/1173 [#1184]: https://github.com/trezor/trezor-firmware/issues/1184 [#1188]: https://github.com/trezor/trezor-firmware/issues/1188 @@ -367,7 +345,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#1193]: https://github.com/trezor/trezor-firmware/issues/1193 [#1206]: https://github.com/trezor/trezor-firmware/issues/1206 [#1246]: https://github.com/trezor/trezor-firmware/issues/1246 -[#1249]: https://github.com/trezor/trezor-firmware/issues/1249 [#1271]: https://github.com/trezor/trezor-firmware/issues/1271 [#1292]: https://github.com/trezor/trezor-firmware/issues/1292 [#1322]: https://github.com/trezor/trezor-firmware/issues/1322 @@ -378,10 +355,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). [#1384]: https://github.com/trezor/trezor-firmware/issues/1384 [#1399]: https://github.com/trezor/trezor-firmware/issues/1399 [#1402]: https://github.com/trezor/trezor-firmware/pull/1402 -[#1404]: https://github.com/trezor/trezor-firmware/issues/1404 [#1415]: https://github.com/trezor/trezor-firmware/pull/1415 [#1467]: https://github.com/trezor/trezor-firmware/issues/1467 -[#1491]: https://github.com/trezor/trezor-firmware/issues/1491 -[#1502]: https://github.com/trezor/trezor-firmware/issues/1502 -[#1510]: https://github.com/trezor/trezor-firmware/issues/1510 -[#1518]: https://github.com/trezor/trezor-firmware/pull/1518 diff --git a/core/CHANGELOG.unreleased b/core/CHANGELOG.unreleased new file mode 120000 index 000000000..7d8b3f49a --- /dev/null +++ b/core/CHANGELOG.unreleased @@ -0,0 +1 @@ +../tools/generate-changelog-unreleased.sh \ No newline at end of file diff --git a/core/towncrier.toml b/core/towncrier.toml new file mode 120000 index 000000000..489bc47ac --- /dev/null +++ b/core/towncrier.toml @@ -0,0 +1 @@ +../tools/towncrier.toml \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c1b05a062..eb9a60b68 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -44,4 +44,5 @@ - [Monorepo Notes](misc/monorepo.md) - [Purpose48 derivation scheme](misc/purpose48.md) - [Review Process](misc/review.md) + - [Changelog](misc/changelog.md) - [TOIF Image Format](misc/toif.md) diff --git a/docs/misc/changelog.md b/docs/misc/changelog.md new file mode 100644 index 000000000..0ca50bcf8 --- /dev/null +++ b/docs/misc/changelog.md @@ -0,0 +1,59 @@ +# Changelog + +Our releases are accompanied by changelogs based on the +[Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format. We are using +the [towncrier](https://github.com/twisted/towncrier) utility to generate them +at the time a new version is released. There are currently four such changelogs +for different components of the repository: + +* **[`core/CHANGELOG.md`](https://github.com/trezor/trezor-firmware/blob/master/core/CHANGELOG.md)** for Trezor T firmware +* **[`legacy/firmware/CHANGELOG.md`](https://github.com/trezor/trezor-firmware/blob/master/legacy/firmware/CHANGELOG.md)** for Trezor 1 firmware +* **[`legacy/bootloader/CHANGELOG.md`](https://github.com/trezor/trezor-firmware/blob/master/legacy/bootloader/CHANGELOG.md)** for Trezor 1 bootloader +* **[`python/CHANGELOG.md`](https://github.com/trezor/trezor-firmware/blob/master/python/CHANGELOG.md)** for Python client library + +## Adding changelog entry + +[`towncrier`](https://github.com/twisted/towncrier) aims to create changelogs +that are convenient to read, at the expense of being somewhat inconvenient to +create. Furthermore every changelog entry has to be linked to a GitHub issue or +pull request number. If you don't want to create an issue just to satisfy this +rule you can use self-reference to your change's pull request number by first +creating the PR and then adding the entry. + +There are a few types of changelog entries, as described by the [Keep a +Changelog](https://keepachangelog.com/en/1.0.0/) format: + +* `added` +* `changed` +* `deprecated` +* `removed` +* `fixed` +* `security` +* `incompatible` (for backwards incompatible changes) + +Entries are added by creating files in the `.changelog.d` directory where the +file name is `.` and contains single line describing the change. +As an example, an entry describing bug fix for issue 1234 in Trezor T firmware +is added by creating file `core/.changelog.d/1234.fixed`. The file can be +formatted with markdown. If more entries are desired for single issue number and +type you can add numeral suffix, e.g. `1234.fixed.1`, `1234.fixed.2`, etc. + +You can also add this entry using your `$VISUAL` editor by running `towncrier +create --edit 1234.fixed` in the `core` directory. + +## Generating changelog at the time of release + +When it's time to release new version of a repository component the formatted +changelog needs to be generated using the `tools/generate-changelog.py` script. +It accepts repo subdirectory and the version number as arguments and you can +specify the release date if it's different from today's date: + +``` +tools/generate-changelog.py --date "20th April 2021" legacy/firmware 1.10.0 +``` + +## Cherry-picking changes to release branch + +Branches named `release/YY.MM` already have their corresponding `CHANGELOG.md` +section generated. When cherry-picking bug fix to such branch you need to +bypass towncrier and edit `CHANGELOG.md` directly. diff --git a/docs/misc/contributing.md b/docs/misc/contributing.md index 755b60de1..77a52b7ce 100644 --- a/docs/misc/contributing.md +++ b/docs/misc/contributing.md @@ -12,5 +12,6 @@ Your Pull Request should follow these criteria: - The generated files are up-to-date. Use `make gen` in repository root to make it happen. - Commits must have concise commit messages, we endorse [Conventional Commits](https://www.conventionalcommits.org). +- A [changelog entry](changelog.md) must be part of the pull request. ### Please read and follow our [review procedure](review.md). diff --git a/legacy/bootloader/CHANGELOG.unreleased b/legacy/bootloader/CHANGELOG.unreleased new file mode 120000 index 000000000..075dc6055 --- /dev/null +++ b/legacy/bootloader/CHANGELOG.unreleased @@ -0,0 +1 @@ +../../tools/generate-changelog-unreleased.sh \ No newline at end of file diff --git a/legacy/firmware/CHANGELOG.unreleased b/legacy/firmware/CHANGELOG.unreleased new file mode 120000 index 000000000..075dc6055 --- /dev/null +++ b/legacy/firmware/CHANGELOG.unreleased @@ -0,0 +1 @@ +../../tools/generate-changelog-unreleased.sh \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index bd0a650c2..d6262e4b7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -97,6 +97,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "click-default-group" +version = "1.2.2" +description = "Extends click.Group to invoke a command without explicit subcommand name" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +click = "*" + [[package]] name = "colorama" version = "0.4.4" @@ -344,6 +355,17 @@ zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] +[[package]] +name = "incremental" +version = "21.3.0" +description = "A small library that versions your Python projects." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] + [[package]] name = "iniconfig" version = "1.1.1" @@ -377,6 +399,20 @@ pyproject = ["toml"] requirements = ["pipreqs", "pip-api"] xdg_home = ["appdirs (>=1.4.0)"] +[[package]] +name = "jinja2" +version = "2.11.3" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + [[package]] name = "libusb1" version = "1.9.1" @@ -773,6 +809,24 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "towncrier" +version = "21.3.0" +description = "Building newsfiles for your project." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +click = "*" +click-default-group = "*" +incremental = "*" +jinja2 = "*" +toml = "*" + +[package.extras] +dev = ["packaging"] + [[package]] name = "tox" version = "3.21.4" @@ -910,7 +964,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "1182233da1c699fa3f6fc47c397c064f05f14c02582a71e00983f2a05c8fdc92" +content-hash = "f731bd62e9c130760c8b619cf8ef9eb102ac26dcff99834d26e0e356475ab6be" [metadata.files] appdirs = [ @@ -982,6 +1036,9 @@ click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] +click-default-group = [ + {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, +] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, @@ -1102,6 +1159,10 @@ importlib-resources = [ {file = "importlib_resources-5.1.0-py3-none-any.whl", hash = "sha256:885b8eae589179f661c909d699a546cf10d83692553e34dca1bf5eb06f7f6217"}, {file = "importlib_resources-5.1.0.tar.gz", hash = "sha256:bfdad047bce441405a49cf8eb48ddce5e56c696e185f59147a8b79e75e9e6380"}, ] +incremental = [ + {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, + {file = "incremental-21.3.0.tar.gz", hash = "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57"}, +] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -1114,6 +1175,10 @@ isort = [ {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, ] +jinja2 = [ + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, +] libusb1 = [ {file = "libusb1-1.9.1-py2-none-any.whl", hash = "sha256:4a024fffe195c49f3e7eadd2266087b4be065982f0cb41ef4b7e2c5053e7e65c"}, {file = "libusb1-1.9.1-py2-none-win32.whl", hash = "sha256:16203d77a1f623b6f8f4e6c9d6bac79c1293b8d3e11de5f2f3c30d91380ae478"}, @@ -1145,39 +1210,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ @@ -1466,6 +1512,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +towncrier = [ + {file = "towncrier-21.3.0-py2.py3-none-any.whl", hash = "sha256:e6ccec65418bbcb8de5c908003e130e37fe0e9d6396cb77c1338241071edc082"}, + {file = "towncrier-21.3.0.tar.gz", hash = "sha256:6eed0bc924d72c98c000cb8a64de3bd566e5cb0d11032b73fcccf8a8f956ddfe"}, +] tox = [ {file = "tox-3.21.4-py2.py3-none-any.whl", hash = "sha256:65d0e90ceb816638a50d64f4b47b11da767b284c0addda2294cb3cd69bd72425"}, {file = "tox-3.21.4.tar.gz", hash = "sha256:cf7fef81a3a2434df4d7af2a6d1bf606d2970220addfbe7dea2615bd4bb2c252"}, diff --git a/pyproject.toml b/pyproject.toml index 220067543..210903ed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ yamllint = "^1.25.0" [tool.poetry.dev-dependencies] scan-build = "*" +towncrier = "^21.3.0" [build-system] requires = ["poetry>=1;<1.1", "pip>=20"] diff --git a/python/CHANGELOG.unreleased b/python/CHANGELOG.unreleased new file mode 120000 index 000000000..7d8b3f49a --- /dev/null +++ b/python/CHANGELOG.unreleased @@ -0,0 +1 @@ +../tools/generate-changelog-unreleased.sh \ No newline at end of file diff --git a/tools/generate-changelog-unreleased.sh b/tools/generate-changelog-unreleased.sh new file mode 100755 index 000000000..9f5fcd935 --- /dev/null +++ b/tools/generate-changelog-unreleased.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd $(dirname "$0") || exit +towncrier build --draft --version unreleased diff --git a/tools/generate-changelog.py b/tools/generate-changelog.py new file mode 100755 index 000000000..1e501ce44 --- /dev/null +++ b/tools/generate-changelog.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +import datetime +from pathlib import Path +import re +import subprocess + +import click + +LINK_RE = re.compile(r"\[#(\d+)\]") +ISSUE_URL = "https://github.com/trezor/trezor-firmware/issues/{issue}" + +VERSION_HEADER_RE = re.compile(r"## \[([.0-9]+)\]") +DIFF_LINK = "[{new}]: https://github.com/trezor/trezor-firmware/compare/{tag_prefix}{old}...{tag_prefix}{new}\n" + + +def linkify_changelog(changelog_file, only_check=False): + links = {} + orig_links = {} + result_lines = [] + + with open(changelog_file, "r+") as changelog: + for line in changelog: + m = LINK_RE.match(line) + if m: # line *starts with* issue identifier + # keep existing links as-is + orig_links[int(m[1])] = line.replace(m[0] + ": ", "").strip() + else: + for issue in LINK_RE.findall(line): + links[int(issue)] = ISSUE_URL.format(issue=issue) + result_lines.append(line) + + if only_check: + missing_links = set(links.keys()) - set(orig_links.keys()) + if missing_links: + click.echo(f"missing links: {missing_links}") + return False + else: + return True + + links.update(orig_links) + + changelog.seek(0) + changelog.truncate(0) + for line in result_lines: + changelog.write(line) + for marker, url in sorted(links.items()): + changelog.write(f"[#{marker}]: {url}\n") + + return True + + +def linkify_gh_diff(changelog_file, tag_prefix): + linkified = False + versions = [] + result_lines = [] + + with open(changelog_file, "r+") as changelog: + for line in changelog: + m = VERSION_HEADER_RE.match(line) + if m: + versions.append(m[1]) + result_lines.append(line) + + changelog.seek(0) + changelog.truncate(0) + for line in result_lines: + changelog.write(line) + if not linkified and VERSION_HEADER_RE.match(line): + changelog.write( + DIFF_LINK.format( + tag_prefix=tag_prefix, new=versions[0], old=versions[1] + ) + ) + linkified = True + + +def current_date(project): + parts = project.parts + today = datetime.datetime.now() + + if parts[-2:] == ("legacy", "bootloader"): + return today.strftime("%B %Y") + elif parts[-1] == "python": + return today.strftime("%Y-%m-%d") + else: + daysuffix = {1: "st", 2: "nd", 3: "rd"}.get(today.day % 10, "th") + return today.strftime(f"%d{daysuffix} %B %Y") + + +@click.command() +@click.argument( + "project", + type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True), +) +@click.argument( + "version", + type=str, + required=False, +) +@click.option("--date", help="Specify release date (default: today).") +@click.option( + "--check", is_flag=True, help="Dry run, do not actually create changelog." +) +def cli(project, version, date, check): + """Generate changelog for given project (core, python, legacy/firmware, + legacy/bootloader). + + - Run towncrier to assemble changelog from fragments in .changelog.d/. + + - Find all occurences of "[#123]" in text, and add a Markdown link to the + referenced issue. + + - Tell git to stage changed files. + """ + project = Path(project) + changelog = project / "CHANGELOG.md" + + if not changelog.exists(): + raise click.ClickException("{} not found".format(changelog)) + + if version is None: + if not check: + raise click.ClickException("Version argument is required.") + version = "unreleased" + + if date is None: + date = current_date(project) + + args = ["towncrier", "build", "--yes", "--version", version, "--date", date] + if check: + args.append("--draft") + subprocess.run(args, cwd=project, check=True) + + if not check: + linkify_changelog(changelog) + + # python changelog has links to github diffs + if project.parts[-1] == "python": + linkify_gh_diff(changelog, tag_prefix="python/v") + + # towncrier calls git add before we do linkification, stage the changes too + subprocess.run(["git", "add", changelog], check=True) + + +if __name__ == "__main__": + cli() diff --git a/tools/linkify-changelogs.py b/tools/linkify-changelogs.py deleted file mode 100755 index ae9d617f0..000000000 --- a/tools/linkify-changelogs.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import Path -import re - -import click - -LINK_RE = re.compile(r"\[#(\d+)\]") -ISSUE_URL = "https://github.com/trezor/trezor-firmware/issues/{issue}" - -ROOT = Path(__file__).parent.resolve().parent - -DEFAULT_CHANGELOGS = ( # TODO replace with a wildcard? - ROOT / "core" / "CHANGELOG.md", - ROOT / "legacy" / "firmware" / "CHANGELOG.md", - ROOT / "legacy" / "bootloader" / "CHANGELOG.md", - ROOT / "python" / "CHANGELOG.md", -) - - -def process_changelog(changelog_file, only_check=False): - links = {} - orig_links = {} - result_lines = [] - - with open(changelog_file, "r+") as changelog: - for line in changelog: - m = LINK_RE.match(line) - if m: # line *starts with* issue identifier - # keep existing links as-is - orig_links[int(m[1])] = line.replace(m[0] + ": ", "").strip() - else: - for issue in LINK_RE.findall(line): - links[int(issue)] = ISSUE_URL.format(issue=issue) - result_lines.append(line) - - if only_check: - missing_links = set(links.keys()) - set(orig_links.keys()) - if missing_links: - click.echo(f"missing links: {missing_links}") - return False - else: - return True - - links.update(orig_links) - - changelog.seek(0) - changelog.truncate(0) - for line in result_lines: - changelog.write(line) - for marker, url in sorted(links.items()): - changelog.write(f"[#{marker}]: {url}\n") - - return True - - -@click.command() -@click.argument( - "changelogs", - nargs=-1, - type=click.Path(exists=True, dir_okay=False, writable=True), -) -@click.option("--check", is_flag=True, help="Check for missing links, do not modify.") -def cli(changelogs, check): - """Linkify changelog. - - Find all occurences of "[#123]" in text, and add a Markdown link to the referenced - issue. - - If no arguments are provided, runs on all known changelogs. - """ - if not changelogs: - changelogs = DEFAULT_CHANGELOGS - - all_ok = True - for changelog in changelogs: - click.echo(changelog) - if not process_changelog(changelog, check): - all_ok = False - - if not all_ok: - raise click.ClickException("Some links are missing. Run `make style` to fix.") - - -if __name__ == "__main__": - cli() diff --git a/tools/towncrier.template.md b/tools/towncrier.template.md new file mode 100644 index 000000000..5b92dda20 --- /dev/null +++ b/tools/towncrier.template.md @@ -0,0 +1,16 @@ +{% for section, _ in sections.items() %} +{% if section %}{{section}}{% endif -%} + +{% if sections[section] %} + +{% for category, val in definitions.items() if category in sections[section] %} + +### {{ definitions[category]['name'] }} +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +- {{ text }} {{ values|join(', ') }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} diff --git a/tools/towncrier.toml b/tools/towncrier.toml new file mode 100644 index 000000000..f7172a51b --- /dev/null +++ b/tools/towncrier.toml @@ -0,0 +1,43 @@ +[tool.towncrier] +directory = ".changelog.d" +filename = "CHANGELOG.md" +template = ".towncrier.template.md" +start_string = "The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)." +title_format = "\n## {version} [{project_date}]" +issue_format = "[#{issue}]" +underlines = ["", ""] + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "security" +name = "Security" +showcontent = true + +[[tool.towncrier.type]] +directory = "incompatible" +name = "Incompatible changes" +showcontent = true