diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..abc8568 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,14 @@ +include *.md +include tox.ini +include .pre-commit-config.yaml + +recursive-include src *.py +recursive-include src *.j2 +recursive-include src *.jinja +recursive-include src *.sh +recursive-include src *.txt +recursive-include src *.yml + +recursive-exclude news * +recursive-exclude config * +recursive-exclude shared-workflows * diff --git a/README.md b/README.md index d3f124d..d62fdae 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,586 @@ -# meta +# plone/meta -This repository's aim is to define and standarize a common set -of configuration files across Plone related repositories. +`plone/meta` defines and standardizes a common set of configuration files across Plone related Python repositories. +It does not cover the following. -By using these files, you can have the same developer experience (DX) +- Volto or any other JavaScript-based project, which has its own ecosystem. +- Monorepo projects with backend and frontend code bases, such as those created by [Cookieplone](https://github.com/plone/cookieplone). + Repositories must have a single Python package at the top level. +- Projects that support multiple versions of Plone in the same branch. + + +## Setup + +Clone `plone/meta` to any machine, then change your current working directory into `meta/config`, create a Python virtual environment, and install `plone/meta`'s requirements. + +```shell +git clone https://github.com/plone/meta.git +cd meta +python3 -m venv venv +venv/bin/pip install -e . +``` + + +## `config-package` usage + +The command `config-package` from `plone/meta` creates or overwrites configuration files for your project. +See a current list of [configuration files](#configuration-files) that it will create or overwrite. + +This command has several [command line options](#cli-arguments) that you can use to override the default options. + +When you run this command, it automatically goes through the following steps. + +1. It creates a new git branch from the current branch in your project. +1. If the file {file}`.meta.toml` is not present in the project, then it creates this and the other new configuration files from `plone/meta`'s Jinja2 templates. + Otherwise, it reads the file {file}`.meta.toml` for regenerating the configuration files. +1. It writes the configuration files. +1. It creates a change log entry. +1. By default, it commits changes. +1. It optionally adds packages, pushes commits, or runs tox from the configuration files. + +> [!TIP] +> If you prefer to name the new git branch instead of letting the command name it using its default naming scheme, then either create a new branch `my-new-branch`, switch to it, and use the `--branch current` option, or do all that in one step with the `--branch my-new-branch` option. + +> [!TIP] +> If you prefer to review changes before committing them, then use the `--no-commit` option. + +For help for `config-package`, use the following command. + +```shell +venv/bin/config-package --help +``` + +You can request more extension points if `plone/meta` does not fulfill your needs in the [issue tracker](https://github.com/plone/meta/issues/new). + + +### Generate configuration files + +Now you can run the command `config-package` to generate configuration files from Jinja2 template files to manage your project. + +```shell +venv/bin/config-package [OPTIONS] PATH/TO/PACKAGE +``` + + +### Manage configuration files + +For each of the configuration files, you should edit its [corresponding stanza](#applying-a-customized-configuration) in the file `.meta.toml`. + +> [!WARNING] +> Do not directly edit the configuration files that `plone/meta` manages. +Anytime someone runs the command `config-package`, any changes made in these files will get clobbered. + +Commit your changes, then run the command `config-package` to regenerate configuration files from your project's `.meta.toml`. + +```shell +venv/bin/config-package [OPTIONS] PATH/TO/PACKAGE +``` + + +### `config-package` command line options + +`config-package` supports the following command line options. + +- `--branch BRANCH_NAME`: Define a specific git branch name to create for the changes. + By default, the script creates one, which includes the name of the configuration type. + > [!TIP] + > Use `current` to update the current branch. +- `--commit-msg MSG`: Use `MSG` as the commit message instead of the default one. +- `--no-commit`: Don't automatically commit changes after the configuration run. +- `-h, --help`: Display help. +- `--push`: Push changes at the end of the configuration run. + By default, the changes are _not_ pushed. +- `--tox`: Whether to run `tox` on the repository. + By default, it does not run. +- `--track`: Whether the package being configured should be added to `defaults/packages.txt`. + By default, they are _not_ added. +- `-t, --type`: define the configuration type. + At this time, `default` is the only option. + This option is only needed one time as their values are stored in `.meta.toml`. + + +### `config-package` configuration files + +`plone.meta` generates configuration files in your local repository. +Currently, the files that `plone.meta` manages are the following. + +- `.meta.toml`: `plone.meta`'s configuration file +- `.editorconfig`: configuration meant to be read by code editors +- `.flake8`: [`flake8`](https://pypi.org/project/flake8) configuration +- `.gitignore`: list of file/folders patterns that `git` should ignore +- `.github/workflows/meta.yml`: GitHub workflows to run the testing and code quality and analysis tools on GitHub, provided the repository is hosted at github.com +- `.gitlab-ci.yml`: GitLab CI configuration, provided the repository is hosted at gitlab.com +- `.pre-commit-config.yaml`: [`pre-commit`](https://pypi.org/project/pre-commit) configuration +- `pyproject.toml`: configuration options for a wide variety of Python tooling +- `tox.ini`: [`tox`](https://pypi.org/project/tox) configuration, _the most important file_ + +You can find the _template_ files for each of these files in the `config/default` folder of this `plone.meta` repository. + +You will notice that they have a `.jinja` extension. +That's because the files are not merely copied over to the target repository, but rather they are enhanced and adapted on the way to their destination. + +The following sections describe how to configure each of the configuration files. + + +#### `.editorconfig` + +Add the `[editorconfig]` TOML table in `.meta.toml`, and set extra configuration for `editorconfig` under the `extra_lines` key. + +```toml +[editorconfig] +extra_lines = """ +_your own configuration lines_ +""" +``` + +If you want to set the indentation to four spaces for frontend related files, add the following to your `.meta.toml`. + +```toml +[editorconfig] +extra_lines = """ +[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss}] +indent_size = 4 +""" +``` + + +#### `.flake8` + +Add the `[flake8]` TOML table in `.meta.toml`, and set the extra configuration for `flake8` under the `extra_lines` key. + +```toml +[flake8] +extra_lines = """ +_your own configuration lines_ +""" +``` + + +#### `.gitignore` + +Add the `[gitignore]` TOML table in `.meta.toml`, and set the extra configuration for `git` under the `extra_lines` key. + +```toml +[gitignore] +extra_lines = """ +_your own configuration lines_ +""" +``` + + +#### `.github/workflows/meta.yml` + +> [!NOTE] +> The variables `TEST_OS_VERSIONS` and `TEST_PYTHON_VERSIONS` variables need to exist, either at the GitHub organization level, or at the repository level. + +See the [GitHub documentation about variables](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables). + +These variables are expected to be lists. + +- `TEST_OS_VERSIONS`: `["ubuntu-latest",]` + - a list of valid operating system names according [to GitHub Actions](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources) +- `TEST_PYTHON_VERSIONS`: `["3.13", "3.12", "3.11", "3.10", ]` + - a list of valid Python versions according to [python-versions action](https://github.com/actions/python-versions/) + +Add the `[github]` TOML table in `.meta.toml`, and set the enabled jobs with the `jobs` key. + +```toml +[github] +jobs = [ + "qa", + "test", + "coverage", + "dependencies", + "release_ready", + "circular", + ] +``` + +It is possible to configure from which branch or tag of `plone/meta` to get the workflow files by setting the value of the `ref` key. +In the following example, all GitHub workflows would come from the `v3` tag, instead of the default `main` branch. + +```toml +[github] +ref = "v3" +``` + +To set environment variables for all jobs, specify the `env` key as follows. + +```toml +[github] +env = """ + debug: 1 + image-name: 'org/image' + image-tag: 'latest' +""" +``` + +To install specific operating system level dependencies, which must be Ubuntu package names, specify the following key. + +```toml +[github] +os_dependencies = "git libxml2" +``` + +To run tests against specific Python versions, specify the `py_versions` key as follows. + +> [!NOTE] +> The GitHub Action expects a string to be parsed as a JSON array. +> Unfortunately, quotes need to be escaped. + +```toml +[github] +py_versions = "[\"3.11\", \"3.10\"]" +``` + +Extend GitHub workflow configuration with additional jobs by setting the values for the `extra_lines` key. + +```toml +[github] +extra_lines = """ + another: + uses: org/repo/.github/workflows/file.yml@main +""" +``` + + +#### `.gitlab-ci.yml` + +Add the `[gitlab]` TOML table in `.meta.toml`, and set the extra configuration for GitLab CI under the `extra_lines` key. + +```toml +[gitlab] +extra_lines = """ +_your own configuration lines_ +""" +``` + +Specify a custom Docker image, if the default does not fit your needs, in the `custom_image` key. + +```toml +[gitlab] +custom_image = "python:3.11-bullseye" +``` + +To install specific test and coverage dependencies, add the `os_dependencies` key as follows. + +```toml +[gitlab] +os_dependencies = """ + - apt-get install libxslt libxml2 +""" +``` + +You can customize the enabled GitLab jobs with the `jobs` key. + +```toml +[gitlab] +jobs = [ + "lint", + "release-ready", + "dependencies", + "circular-dependencies", + "testing", + "coverage", +] +``` + + +#### `.pre-commit-config.yaml` + +Add the `[pre_commit]` TOML table in `.meta.toml`, and set the extra configuration for `pre-commit` under the `extra_lines` key. + +```toml +[pre_commit] +extra_lines = """ +_your own configuration lines_ +""" +``` + +Extend [`zpretty`](https://pypi.org/project/zpretty) configuration by setting the values for the `zpretty_extra_lines` key. + +```toml +[pre_commit] +zpretty_extra_lines = """ +_your own configuration lines_ +""" +``` + +Extend [codespell](https://pypi.org/project/codespell) configuration by setting the values for the `codespell_extra_lines` key. + +```toml +[pre_commit] +codespell_extra_lines = """ +_your own configuration lines_ +""" +``` + +Extend [Flake8](https://pypi.org/project/flake8) configuration by setting the values for the `flake8_extra_lines` key. +For example, to add extra Flake8 plugins, you would specify `additional_dependencies` as shown. + +```toml +[pre_commit] +flake8_extra_lines = """ + additional_dependencies: + - flake8-debugger + - flake8-deprecated +""" +``` + +Extend [i18ndude](https://pypi.org/project/i18ndude) configuration by setting the values for the `i18ndude_extra_lines` key. +For example, to add extra i18ndude plugins, you would specify `additional_dependencies` as shown. + +```toml +[pre_commit] +i18ndude_extra_lines = """ + additional_dependencies: + - toml +""" +``` + +If you want to disable the i18ndude check, add the following pre-commit configuration option to your `.meta.toml` file. + +```toml +[pre_commit] +i18ndude_extra_lines = """ + pass_filenames: false +""" +``` + + +#### `pyproject.toml` + +Add the `[pyproject]` TOML table in `.meta.toml`, and set configuration for any extra tool that you use for the `extra_lines` key. + +```toml +[pyproject] +extra_lines = """ +_your own configuration lines_ +""" +``` + +Extend [codespell](https://pypi.org/project/codespell) configuration by setting the values for the `codespell_ignores` and `codespell_skip` keys. + +```toml +[pyproject] +codespell_ignores = "foo,bar" +codespell_skip = "*.po,*.map,package-lock.json" +``` + +Extend [z3c.dependencychecker](https://pypi.org/project/z3c.dependencychecker) configuration by setting the values for the `dependencies_ignores` and `dependencies_mappings` keys. + +```toml +[pyproject] +dependencies_ignores = "['zestreleaser.towncrier']" +dependencies_mappings = [ + "gitpython = ['git']", + "pygithub = ['github']", +] +``` + +Extend [check-manifest](https://pypi.org/project/check-manifest) configuration by setting the values for the `check_manifest_ignores` key. + +```toml +[pyproject] +check_manifest_ignores = """ + "*.map.js", + "*.pyc", +""" +``` + +Extend [Black](https://pypi.org/project/black) configuration by setting the values for the `black_extra_lines` key. + +```toml +[pyproject] +black_extra_lines = """ +_custom configuration_ +""" +``` + +Extend [isort](https://pypi.org/project/isort) configuration by setting the values for the `isort_extra_lines` key. + +```toml +[pyproject] +isort_extra_lines = """ +_custom configuration_ +""" +``` + + +##### `towncrier` configuration + +If your project contains a `news/` folder, `plone.meta` will add the configuration for `towncrier`. + +If your `CHANGES` file has the extension `.md`, a `changelog_template.jinja` template will be generated inside the `news/` folder. + +Configure [`towncrier`](https://pypi.org/project/towncrier) [`issue_format`](https://towncrier.readthedocs.io/en/stable/configuration.html) by setting the new format in the `towncrier_issue_format` key. + +```toml +[pyproject] +towncrier_issue_format = "[#{issue}](https://github.com/plonegovbr/plonegovbr.portal/issues/{issue})" +``` + +Extend [`towncrier`](https://pypi.org/project/towncrier) configuration by setting the values for the `towncrier_extra_lines` key. + +```toml +[pyproject] +towncrier_extra_lines = """ +_custom configuration_ +""" +``` + + +#### `tox.ini` + +Depending on the test runner that you want to use, `plone.meta` will adapt `tox.ini` to it. + +In the `[tox]` TOML table in `.meta.toml`, set the value for the key `test_runner` to `pytest` if you want to use [`pytest`](https://pypi.org/project/pytest). +By default, it falls back to use [zope.testrunner](https://pypi.org/project/zope.testrunner). + +Likewise, the root path where the tests are to be found can be specified under the key `test_path`. +By default, it is set to nothing, that is, the repository's top level is already importable and thus the tests can be found directly. + +If either a `tests` or `src` folder exists, then they are used as safe fallbacks. + +Add the `[tox]` TOML table in `.meta.toml`, and set the extra configuration for `tox` under the `extra_lines` key. + +```toml +[tox] +extra_lines = """ +_your own configuration lines_ +""" +``` + +Extend the list of default `tox` environments in the `envlist_lines` key. +Add extra top level configuration for `tox` in the `config_lines` key. + +```toml +[tox] +envlist_lines = """ + my_other_environment +""" +config_lines = """ +my_extra_top_level_tox_configuration_lines +""" +``` + +Customize the default values for all `tox` environments in the `testenv_options` key. + +```toml +[tox] +testenv_options = """ +basepython = /usr/bin/python3.8 +""" +``` + +Extend the list of `extras` that need to be installed for running the test suite and generate the coverage report by setting them on the `test_extras` key. + +```toml +[tox] +test_extras = """ + tests + widgets +""" +``` + +If your package uses [mxdev](https://pypi.org/project/mxdev/) to handle source checkouts for dependencies, you can set the `use_mxdev` key to ensure `tox` will first run mxdev. +You also need to manually set the installation of additional packages that `mxdev` pulls in with the `test_deps_additional` key. + +```toml +[tox] +use_mxdev = true +test_deps_additional = """ + -esources/plonegovbr.portal_base[test] +""" +``` + +When using `plone.meta` outside of plone core packages, there might be extra version pins, or overrides over the official versions. +To specify a custom constraints file, use the `constraints_file` key. + +Generating a custom `constraints.txt` is out of scope for `plone.meta` itself. +There are plenty of tools that can do that though. + +```toml +[tox] +constraints_file = "https://my-server.com/constraints.txt" +``` + +Extend the list of custom environment variables that the test and coverage environments can get in the `test_environment_variables` key. + +```toml +[tox] +test_environment_variables = """ + PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +""" +``` + +For packages that have `plone.app.robotframework` based tests, it automatically detects it and primes [Playwright](https://playwright.dev/) to install the needed browsers. + + + +### Manage multiple repositories with `multi-call` + +The `config-package` command runs only on a single repository. +To update multiple repositories at once, you can use the command `multi-call`. +It runs on all repositories listed in a `packages.txt` file. + +To run `multi-call` on all packages listed in a `packages.txt` file use the following command with positional arguments as shown. + +- The file path the Python script to be called. +- The file path to `packages.txt`, which lists repositories of packages on which the Python script will be called. +- The file path to the directory where the clones of the repositories are stored. +- Arguments to pass into the Python script. + +```shell +bin/multi-call +``` + +The script performs the following steps for each line in the given `packages.txt` that does not start with a hash mark (`#`). + +1. Check if there is a repository in `` with the name of the repository. + If it does not exist, then clone it. + If it exists, then clean the clone from changes, switch to the `master` branch, and pull from the origin. +2. Call the given script with the package name and arguments for the script. + +> [!CAUTION] +> Running this script stashes any uncommitted changes in the repositories. +> Run `git stash pop` to recover them. + + + +### Re-enable GitHub Actions with `re-enable-actions` + +After a certain period of time without commits to a repository, GitHub automatically disables Actions. +You can re-enable them manually per repository. +`re-enable-actions` can do this for all repositories. +It does no harm if Actions are already enabled for a repository. + + +#### Setup GitHub CLI + +- Install [GitHub's CLI application](https://github.com/cli/cli). +- Authorize using the application: + - `gh auth login` + - It is probably enough to do it once. + + +#### `re-enable-actions` usage + +Use the following command. + +```shell +venv/bin/re-enable-actions +``` + + +## Explanation + +This section provides explanation of design decisions and capabilities of `plone/meta`. + + +### Project management + +By using `plone/meta`, you can have the same developer experience (DX) across all Plone related packages. The idea is to make it mandatory for repositories under the [GitHub Plone organization](https://github.com/plone), @@ -13,14 +590,14 @@ and even your own private packages for your customers. With this configuration in place, any developer has the answer to the following questions at their fingertips: -- Do the tests of this package pass? -- What's the coverage of the test suite? -- Is the package ready to be released? -- Are all dependencies clearly defined? -- What does the dependency graph look like? -- Are there any circular dependency problems? -- Is the code formatted to some agreed upon standards? -- Do all agreed upon code quality checks pass? +- Do the tests of this package pass? +- What's the coverage of the test suite? +- Is the package ready to be released? +- Are all dependencies clearly defined? +- What does the dependency graph look like? +- Are there any circular dependency problems? +- Is the code formatted to some agreed upon standards? +- Do all agreed upon code quality checks pass? To find the answers to these questions, you can run the following commands. @@ -43,17 +620,46 @@ tox -e format tox -e lint ``` -## Tox - As seen above, [`tox`](https://pypi.org/project/tox) provides the answers. Tooling is like fashion, it keeps evolving and changing. -The great power behind `plone/meta` is that when we implement a better solution or tool, +The great power behind `plone.meta` is that when we implement a better solution or tool, we can swiftly move all packages to the new approach, making it as painless as possible! -## Configure a package -To get the above answers to any package, use the `config-package.py` script found in this repository's `config` folder. +### Configuration philosophy + +It is one thing to standardize, yet another to be flexible enough to adapt to each repository's particular needs. + +Fortunately `plone.meta` tries its best to accomplish both: + +- it provides sane defaults +- it allows extension of the defaults with custom configuration + +The configuration files have comments all over them +with instructions on how to extend, modify, and influence +what `plone.meta` ends up adding on those files. + +Those options are to be stored, +as it is mentioned on the comments themselves, +in `.meta.toml`. + +This way, when the configuration files get regenerated, +`plone.meta` reads the configuration in `.meta.toml` +and reapplies the specific configuration on the other files. + +See the specific configuration files sections below on how to extend and modify each configuration file. + +The idea behind the configuration system +in `plone.meta` controlled configuration files is to make it as simple as possible. + +Rather than adding plenty of configuration options, +almost all configuration files have an `extra_lines` section +that allows you to paste as much configuration as you want there. + +In this way, it provides a simple, yet powerful, extension mechanism. + +There are a few, and growing, other configuration options in a few files, +where the simple approach described above is not enough. -See the [README](config/README.md) documentation for the details. diff --git a/config/README.md b/config/README.md deleted file mode 100644 index 2fa03a8..0000000 --- a/config/README.md +++ /dev/null @@ -1,639 +0,0 @@ -# Configure a package - -The `config-package.py` is the script that configures a given repository -to follow the standards agreed upon by the Plone community. - -## Quick start - -To configure a repository run the following commands: - -```shell -cd config -python3 -m venv venv -. venv/bin/activate -pip install -r requirements.txt -python config-package.py PATH/TO/PACKAGE -``` - -That's it. :-) - -Now, you can use the `tox` commands -to adapt the repository just configured! - -## Configuration files - -In a nutshell `plone/meta`, puts some configuration files on the repository. - -Currently the files managed by `plone/meta` are the following. - -- `.meta.toml`: `plone/meta`'s configuration file -- `.editorconfig`: configuration meant to be read by code editors -- `.flake8`: [`flake8`](https://pypi.org/project/flake8) configuration -- `.gitignore`: list of file/folders patterns that `git` should ignore -- `.github/workflows/meta.yml`: GitHub Actions to run the testing and QA tools on GitHub (if the repository is hosted in GitHub.com) -- `.gitlab-ci.yml`: GitLab CI configuration (if the repository is hosted in GitLab.com) -- `.pre-commit-config.yaml`: [`pre-commit`](https://pypi.org/project/pre-commit) configuration -- `pyproject.toml`: configuration options for a wide variety of Python tooling -- `tox.ini`: [`tox`](https://pypi.org/project/tox) configuration, __the most important file__ - -You can find the _template_ files for each of these files -in the `config/default` folder of this `plone/meta` repository. - -You will notice that they have a `.jinja` extension. -That's because the files are not merely copied over to the target repository, -but rather they are enhanced and adapted on the way there. - -See the next section about how to extend and modify these configuration files. - -## Configuration options - -It is one thing to standardize, yet another to be flexible enough to adapt to each repository's particular needs. - -Fortunately `plone/meta` tries its best to accomplish both: - -- it provides sane defaults -- it allows extension of the defaults with custom configuration - -The files mentioned above have comments all over them -with instructions on how to extend, modify, and influence -what `plone/meta` ends up adding on those files. - -Those options are to be stored, -as it is mentioned on the comments themselves, -in `.meta.toml`. - -This way, when the configuration files get regenerated, -`plone/meta` reads the configuration in `.meta.toml` -and reapplies the specific configuration on the other files. - -See the specific configuration files sections below on how to extend and modify each configuration file. - -### Configuration philosophy - -The idea behind the configuration system -in `plone/meta` controlled configuration files is to make it as simple as possible. - -Rather than adding plenty of configuration options, -almost all configuration files have an `extra_lines` section -that allows you to paste as much configuration as you want there. - -In this way, it provides a simple, yet powerful, extension mechanism. - -There are a few, and growing, other configuration options in a few files, -where the simple approach described above is not enough. - -### More configuration options - -Please use the [issue tracker](https://github.com/plone/meta/issues/new) -to ask for more extension points whenever `plone/meta` -does not fulfill all your needs. - -### Applying a customized configuration - -To apply a customized configuration from `.meta.toml` just re-run the configuration script on a clean repository like so: - -``` -python config-package.py PATH/TO/PACKAGE -``` - -Make sure you have commited your changes before running the configuration script. - -### `.editorconfig` - -Add the `[editorconfig]` TOML table in `.meta.toml`, -and set extra configuration for `editorconfig` under the `extra_lines` key. - -```toml -[editorconfig] -extra_lines = """ -_your own configuration lines_ -""" -``` - -If you want to set the indentation for frontend related files to 4 spaces add the following to your `.meta.toml`: - -```toml -[editorconfig] -extra_lines = """ -[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss}] -indent_size = 4 -""" -``` - -### `.flake8` - -Add the `[flake8]` TOML table in `.meta.toml`, -and set the extra configuration for `flake8` under the `extra_lines` key. - -```toml -[flake8] -extra_lines = """ -_your own configuration lines_ -""" -``` - -### `.gitignore` - -Add the `[gitignore]` TOML table in `.meta.toml`, -and set the extra configuration for `git` under the `extra_lines` key. - -```toml -[gitignore] -extra_lines = """ -_your own configuration lines_ -""" -``` - -### `.github/workflows/meta.yml` - -Note: `TEST_OS_VERSIONS` and `TEST_PYTHON_VERSIONS` variables need to exist, -either at the GitHub organization level, or at the repository level. - -See the [GitHub documentation about variables](https://docs.github.com/en/actions/learn-github-actions/variables). - -These variables are expected to be **lists**: - -- `TEST_OS_VERSIONS`: `["ubuntu-latest",]` - - a list of valid Operating System names according [to GitHub Actions](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources) -- `TEST_PYTHON_VERSIONS`: `["3.11", "3.10", ]` - - a list of valid Python versions according to [python-versions action](https://github.com/actions/python-versions/) - -Add the `[github]` TOML table in `.meta.toml`, -and set the enabled jobs with the `jobs` key. - -```toml -[github] -jobs = [ - "qa", - "test", - "coverage", - "dependencies", - "release_ready", - "circular", - ] -``` - -It is possible to configure from which branch/tag of `plone/meta` -to get the workflow files by setting the value of the `ref` key: - -```toml -[github] -ref = "v3" -``` - -In the previous example, all GitHub workflows would come from -the `v3` tag, instead of the default `main` branch. - -To set environment variables to be used by all jobs, -specify the following key: - -```toml -[github] -env = """ - debug: 1 - image-name: 'org/image' - image-tag: 'latest' -""" -``` - -To install specific OS level dependencies, -note that they have to be Ubuntu package names, specify the following key: - -```toml -[github] -os_dependencies = "git libxml2" -``` - -To run tests against specific python versions, -specify the following key: - -Note: the GHA expects a string to be parsed as a JSON array. - -Note 2: unfortunately, quotes need to be escaped :-/ - -```toml -[github] -py_versions = "[\"3.11\", \"3.10\"]" -``` - -Extend github workflow configuration with additional jobs -by setting the values for the `extra_lines` key. - -```toml -[github] -extra_lines = """ - another: - uses: org/repo/.github/workflows/file.yml@main -""" -``` - -### `.gitlab-ci.yml` - -Add the `[gitlab]` TOML table in `.meta.toml`, -and set the extra configuration for GitLab CI under the `extra_lines` key. - -```toml -[gitlab] -extra_lines = """ -_your own configuration lines_ -""" -``` - -Specify a custom docker image if the default does not fit your needs on the `custom_image` key. - -```toml -[gitlab] -custom_image = "python:3.11-bullseye" -``` - -To install test/coverage specific dependencies, add the following: - -```toml -[gitlab] -os_dependencies = """ - - apt-get install libxslt libxml2 -""" -``` - -You can customize the enabled gitlab jobs with the `jobs` key: - -```toml -[gitlab] -jobs = [ - "lint", - "release-ready", - "dependencies", - "circular-dependencies", - "testing", - "coverage", -] -``` - -### `.pre-commit-config.yaml` - -Add the `[pre_commit]` TOML table in `.meta.toml`, -and set the extra configuration for `pre-commit` under the `extra_lines` key. - -```toml -[pre_commit] -extra_lines = """ -_your own configuration lines_ -""" -``` - -Extend [`zpretty`](https://pypi.org/project/zpretty) configuration -by setting the values on the `zpretty_extra_lines` key. - -```toml -[pre_commit] -zpretty_extra_lines = """ -_your own configuration lines_ -""" -``` - -Extend [`codespell`](https://pypi.org/project/codespell) configuration -by setting the values on the `codespell_extra_lines` key. - -```toml -[pre_commit] -codespell_extra_lines = """ -_your own configuration lines_ -""" -``` - -Extend [`flake8`](https://pypi.org/project/flake8) configuration -by setting the values on the `flake8_extra_lines` key. - -Like to add extra plugins: - -```toml -[pre_commit] -flake8_extra_lines = """ - additional_dependencies: - - flake8-debugger - - flake8-deprecated -""" -``` - -Extend [`i18ndude`](https://pypi.org/project/i18ndude) configuration -by setting the values on the `i18ndude_extra_lines` key. - -Like to add extra plugins: - -```toml -[pre_commit] -i18ndude_extra_lines = """ - additional_dependencies: - - toml -""" -``` - -If you want to disable the i18ndude check, add the following pre-commit config option to your `.meta.toml` file: - -```toml -[pre_commit] -i18ndude_extra_lines = """ - pass_filenames: false -""" -``` - -### `pyproject.toml` - -It's automatic, you don't need to do anything! - -Add the `[pyproject]` TOML table in `.meta.toml`, -and set extra configuration for any extra tool that you use -for the `extra_lines` key. - -```toml -[pyproject] -extra_lines = """ -_your own configuration lines_ -""" -``` - -Extend [`codespell`](https://pypi.org/project/codespell) configuration -by setting the values for the `codespell_ignores` and `codespell_skip` keys. - -```toml -[pyproject] -codespell_ignores = "foo,bar" -codespell_skip = "*.po,*.map,package-lock.json" -``` - -Extend [`z3c.dependencychecker`](https://pypi.org/project/z3c.dependencychecker) configuration -by setting the values for the `dependencies_ignores` and `dependencies_mappings` keys. - -```toml -[pyproject] -dependencies_ignores = "['zestreleaser.towncrier']" -dependencies_mappings = [ - "gitpython = ['git']", - "pygithub = ['github']", -] -``` - -Extend [`check-manifest`](https://pypi.org/project/check-manifest) configuration -by setting the values for the `check_manifest_ignores` key. - -```toml -[pyproject] -check_manifest_ignores = """ - "*.map.js", - "*.pyc", -""" -``` - -Extend [`black`](https://pypi.org/project/black) configuration -by setting the values for the `black_extra_lines` key. - -```toml -[pyproject] -black_extra_lines = """ -_custom configuration_ -""" -``` - -Extend [`isort`](https://pypi.org/project/isort) configuration -by setting the values for the `isort_extra_lines` key. - -```toml -[pyproject] -isort_extra_lines = """ -_custom configuration_ -""" -``` - -#### `towncrier` configuration: - -If your project contains a `news/` folder, `plone/meta` will add -the configuration for `towncrier`. - -If your `CHANGES` file has the extension `.md`, a `changelog_template.jinja` -template will be generated inside the `news/` folder. - -Configure [`towncrier`](https://pypi.org/project/towncrier) [`issue_format`](https://towncrier.readthedocs.io/en/stable/configuration.html) by setting the new format in the `towncrier_issue_format` key. - -```toml -[pyproject] -towncrier_issue_format = "[#{issue}](https://github.com/plonegovbr/plonegovbr.portal/issues/{issue})" -``` - -Extend [`towncrier`](https://pypi.org/project/towncrier) configuration -by setting the values for the `towncrier_extra_lines` key. - -```toml -[pyproject] -towncrier_extra_lines = """ -_custom configuration_ -""" -``` - -### `tox.ini` - -Depending on the test runner that you want to use, -`plone/meta` will adapt `tox.ini` to it. - -In the `[tox]` TOML table in `.meta.toml`, set the value for the key `test_runner` to `pytest` if you want to use [`pytest`](https://pypi.org/project/pytest). -By default, it falls back to use [`zope.testrunner`]((https://pypi.org/project/zope.testrunner)). - -Likewise, the root path where the tests are to be found can be specified -under the key `test_path`. By default, it is set to nothing, -that is, the repository's top level is already importable -and thus the tests can be found directly. - -If either a `tests` or `src` folder exists, then they are used as a safe fallbacks. - -Add the `[tox]` TOML table in `.meta.toml`, -and set the extra configuration for `tox` under the `extra_lines` key. - -```toml -[tox] -extra_lines = """ -_your own configuration lines_ -""" -``` - -Extend the list of default `tox` environments by using the `envlist_lines` key. - -Add extra top level configuration for `tox` by using the `config_lines` key. - -```toml -[tox] -envlist_lines = """ - my_other_environment -""" -config_lines = """ -my_extra_top_level_tox_configuration_lines -""" -``` - -Customize the default values for all `tox` environments by using the `testenv_options` key. - -```toml -[tox] -testenv_options = """ -basepython = /usr/bin/python3.8 -""" -``` - -Extend the list of `extras` that need to be installed to run the test suite -and generate the coverage report by setting them on the `test_extras` key. - -```toml -[tox] -test_extras = """ - tests - widgets -""" -``` - -If your package uses [`mxdev`](https://pypi.org/project/mxdev/) to handle source checkouts for dependencies, you can set the `use_mxdev` key to ensure `tox` will first run mxdev. - -You also will need to manually set the installation of additional packages being pulled by `mxdev` in the `test_deps_additional` key. - -```toml -[tox] -use_mxdev = true -test_deps_additional = """ - -esources/plonegovbr.portal_base[test] -""" -``` - -When using `plone/meta` outside of plone core packages -there might be extra version pins, or overrides over the official versions. -To specify a custom constraints file, use the `constraints_file` key. - -Generating a custom constraints.txt is out of scope for `plone/meta` itself, -there are plenty of tools that can do that though. - -```toml -[tox] -constraints_file = "https://my-server.com/constraints.txt" -``` - -Extend the list of custom environment variables -that the test and coverage environments can get by using the `test_environment_variables` key. - -```toml -[tox] -test_environment_variables = """ - PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ -""" -``` - -For packages that have `plone.app.robotframework` based tests, -it automatically detects it and primes `playwright` to install the needed browsers. - -## Detailed script usage - -As said above, the `config-package.py` script is the tool to apply the configuration. - -See its `--help` for the up-to-date possible options. - -### CLI arguments - -The following arguments are supported. - -`--commit-msg=MSG`: use `MSG` as commit message instead of the default one - -`--no-commit`: don't automatically commit changes after the configuration run - -`--push`: push changes at the end of the configuration run. -By default, the changes are __not__ pushed - -`--branch`: define a specific git branch name to be created for the changes. -By default, the script creates one which includes the name of the configuration type. -__Tip:__ Use `current` to update the current branch. - -`--tox`: whether to run `tox` on the repository. By default it is not run. - -`--track`: whether the package being configured should be added on `defaults/packages.txt`. By default, they are _not_ added. - -The following options are only needed one time -as their values are stored in `.meta.toml.`. - -`--type`: define the configuration type. At this time, `default` is the only option. - -## Other scripts - -### Calling a script on multiple repositories - -The `config-package.py` script only runs on a single repository. -To update multiple repositories at once, you can use `multi-call.py`. -It runs a given script on all repositories listed in a `packages.txt` file. - -#### Usage - -To run a script on all packages listed in a `packages.txt` file, call -`multi-call.py` as follows. - -```shell -bin/python multi-call.py -``` - -See `--help` for details. - -The script does the following steps for each line in the given `packages.txt` -which does not start with `#`: - -1. Check if there is a repository in `` with the name of the - repository. If it does not exist: clone it. If it exists: clean the clone - from changes, switch to `master` branch and pull from origin. -2. Call the given script with the package name and arguments for the script. - -__CAUTION:__ - -Running this script stashes any uncommitted changes in the repositories. -Run `git stash pop` to recover them. - -### Re-enabling GitHub Actions - -After a certain period of time (currently 60 days) without commits, GitHub -automatically disables Actions. They can be re-enabled manually per repository. -There is a script to do this for all repositories. It does no harm if Actions -is already enabled for a repository. - -#### Preparation - -- Install GitHub's CLI application, see https://github.com/cli/cli. -- Authorize using the application: - - `gh auth login` - - It is probably enough to do it once. - -#### Usage - -To run the script just call it: - -```shell -bin/python re-enable-actions.py -``` - -### Dropping support for legacy Python versions - -To drop support for Python 2.7 up to 3.6, several steps have to be done as -documented at https://zope.dev/developer/python2.html#how-to-drop-support. -There is a script to ease this process. - -#### Preparation - -- The package from which to remove legacy Python support has to have a `.meta.toml` - file, in other words, it must be under control of the `config-package.py` script. - -#### Usage - -To run the script call: - -```shell -bin/python drop-legacy-python.py -``` - -Additional optional parameters, see above at `config-package.py` for a -descriptions of them: - -* `--branch` - -You can call the script interactively by passing the argument -`--interactive`, this will let the various scripts prompt for information and -prevent automatic commits and pushes. That way all changes can be viewed before -committing them. diff --git a/config/drop-legacy-python.py b/config/drop-legacy-python.py deleted file mode 100644 index 25d1e5b..0000000 --- a/config/drop-legacy-python.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env', 'python3 -from shared.call import call -from shared.call import wait_for_accept -from shared.git import get_branch_name -from shared.git import git_branch -from shared.path import change_dir -import argparse -import collections -import os -import pathlib -import shutil -import sys -import tomlkit - - -parser = argparse.ArgumentParser( - description='Drop support of Python 2.7 up to 3.7 from a package.', -) -parser.add_argument( - 'path', - type=pathlib.Path, - help='path to the repository to be configured', -) -parser.add_argument( - '--branch', - dest='branch_name', - default=None, - help='Define a git branch name to be used for the changes. If not given' - ' it is constructed automatically and includes the configuration' - ' type', -) -parser.add_argument( - '--interactive', - dest='interactive', - action='store_true', - default=False, - help='Run interactively: Scripts will prompt for input and changes will ' - 'not be committed and pushed automatically.', -) - - -args = parser.parse_args() -path = args.path.absolute() - -if not (path / '.git').exists(): - raise ValueError('`path` does not point to a git clone of a repository!') -if not (path / '.meta.toml').exists(): - raise ValueError('The repository `path` points to has no .meta.toml!') - -with change_dir(path) as cwd_str: - cwd = pathlib.Path(cwd_str) - bin_dir = cwd / 'bin' - with open('.meta.toml', 'rb') as meta_f: - meta_cfg = collections.defaultdict(dict, **tomlkit.load(meta_f)) - config_type = meta_cfg['meta']['template'] - branch_name = get_branch_name(args.branch_name, config_type) - updating = git_branch(branch_name) - - if not args.interactive: - call(bin_dir / 'bumpversion', '--breaking', '--no-input') - call( - bin_dir / 'addchangelogentry', - 'Drop support for Python 2.7, 3.5, 3.6., 3.7.', - '--no-input', - ) - else: - call(bin_dir / 'bumpversion', '--breaking') - call( - bin_dir / 'addchangelogentry', - 'Drop support for Python 2.7, 3.5, 3.6, 3.7.', - ) - call(bin_dir / 'check-python-versions', - '--drop=2.7,3.5,3.6,3.7', '--only=setup.py') - print('Remove legacy Python specific settings from .meta.toml') - call(os.environ['EDITOR'], '.meta.toml') - - config_package_args = [ - sys.executable, - 'config-package.py', - path, - f'--branch={branch_name}', - '--no-push', - ] - if args.interactive: - config_package_args.append('--no-commit') - call(*config_package_args, cwd=cwd_str) - print('Remove `six` from the list of dependencies and other Py 2 things.') - call(os.environ['EDITOR'], 'setup.py') - src = path.resolve() / 'src' - call('find', src, '-name', '*.py', '-exec', - bin_dir / 'pyupgrade', '--py3-plus', '--py38-plus', '{}', ';') - call(bin_dir / 'pyupgrade', '--py3-plus', '--py38-plus', 'setup.py', - allowed_return_codes=(0, 1)) - - excludes = ('--exclude-dir', '__pycache__', '--exclude-dir', '*.egg-info', - '--exclude', '*.pyc', '--exclude', '*.so') - print( - 'Replace all remaining `six` mentions or continue if none are listed.') - call('grep', '-rn', 'six', src, *excludes, allowed_return_codes=(0, 1)) - wait_for_accept() - print('Replace any remaining code that may support legacy Python 2:') - call('egrep', '-rn', - '2.7|3.5|3.6|3.7|sys.version|PY2|PY3|Py2|Py3|Python 2|Python 3' - '|__unicode__|ImportError', src, *excludes, - allowed_return_codes=(0, 1)) - wait_for_accept() - tox_path = shutil.which('tox') or (cwd / 'bin' / 'tox') - call(tox_path, '-p', 'auto') - if not args.interactive: - print('Adding, committing and pushing all changes ...') - call('git', 'add', '.') - call('git', 'commit', '-m', 'Drop support for Python 2.7 up to 3.7.') - call('git', 'push', '--set-upstream', 'origin', branch_name) - if updating: - print('Updated the previously created PR.') - else: - print('If everything went fine up to here:') - print('Create a PR, using the URL shown above.') - else: - print('Applied all changes. Please check and commit manually.') diff --git a/config/multi-call.py b/config/multi-call.py deleted file mode 100644 index 0253135..0000000 --- a/config/multi-call.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -from shared.call import call -from shared.packages import list_packages -from shared.path import change_dir -from shared.path import path_factory -import argparse -import sys - - -parser = argparse.ArgumentParser( - description='Call a script on all repositories listed in a packages.txt.', - epilog='Additional optional arguments are passed directly to the script.') -parser.add_argument( - 'script', type=path_factory('script', has_extension='.py'), - help='path to the Python script to be called') -parser.add_argument( - 'packages_txt', type=path_factory('packages.txt', has_extension='.txt'), - help='path to the packages.txt; script is called on each repository listed' - ' inside', metavar='packages.txt') -parser.add_argument( - 'clones', type=path_factory('clones', is_dir=True), - help='path to the directory where the clones of the repositories are' - ' stored') - -# idea from https://stackoverflow.com/a/37367814/8531312 -args, sub_args = parser.parse_known_args() -packages = list_packages(args.packages_txt) - -for package in packages: - print(f'*** Running {args.script.name} on {package} ***') - if (args.clones / package).exists(): - with change_dir(args.clones / package): - print('Updating existing checkout …') - call('git', 'stash') - call('git', 'checkout', 'master') - call('git', 'pull') - else: - with change_dir(args.clones): - print('Cloning repository …') - call('git', 'clone', - f'https://github.com/zopefoundation/{package}') - - call_args = [ - sys.executable, - args.script, - args.clones / package - ] - call_args.extend(sub_args) - call(*call_args) diff --git a/config/re-enable-actions.py b/config/re-enable-actions.py deleted file mode 100644 index f43e192..0000000 --- a/config/re-enable-actions.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/env python3 -from shared.call import call -from shared.packages import list_packages -import argparse -import itertools -import pathlib - - -org = 'zopefoundation' -base_url = f'https://github.com/{org}' -base_path = pathlib.Path(__file__).parent -types = ['buildout-recipe', 'c-code', 'pure-python', 'zope-product'] - - -parser = argparse.ArgumentParser( - description='Re-enable GitHub Actions for all repos in a packages.txt' - ' files.') -parser.add_argument( - '--force-run', - help='Run workflow even it is already enabled.', - action='store_true') - -args = parser.parse_args() -repos = itertools.chain( - *[list_packages(base_path / type / 'packages.txt') - for type in types]) - - -def run_workflow(base_url, org, repo): - """Manually start the tests.yml workflow of a repository.""" - result = call('gh', 'workflow', 'run', 'tests.yml', '-R', f'{org}/{repo}') - if result.returncode != 0: - print('To enable manually starting workflows clone the repository' - ' and run meta/config/config-package.py on it.') - print('Command to clone:') - print(f'git clone {base_url}/{repo}.git') - return False - return True - - -for repo in repos: - print(repo) - wfs = call( - 'gh', 'workflow', 'list', '--all', '-R', f'{org}/{repo}', - capture_output=True).stdout - test_line = [x for x in wfs.splitlines() if x.startswith('test')][0] - if 'disabled_inactivity' not in test_line: - print(' ☑️ already enabled') - if args.force_run: - run_workflow(base_url, org, repo) - continue - test_id = test_line.split()[-1] - call('gh', 'workflow', 'enable', test_id, '-R', f'{org}/{repo}') - if run_workflow(base_url, org, repo): - print(' ✅ enabled') diff --git a/config/requirements.txt b/config/requirements.txt deleted file mode 100644 index a825656..0000000 --- a/config/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -check-python-versions==0.22.0 -Jinja2==3.1.4 -pyupgrade==3.15.2 -tomlkit==0.13.2 -tox==4.15.0 -zest.releaser==9.1.3 -towncrier==23.11.0 -validate-pyproject[all]==0.17 -pyyaml==6.0.1 -editorconfig==0.12.4 diff --git a/news/185.feature b/news/185.feature index 69160ee..203bd77 100644 --- a/news/185.feature +++ b/news/185.feature @@ -1 +1 @@ -`tox`: allow to configure what gets in `testenv` envrionment @gforcada +`tox`: allow to configure what gets in `testenv` environment @gforcada diff --git a/news/239.feature b/news/239.feature new file mode 100644 index 0000000..5219c8a --- /dev/null +++ b/news/239.feature @@ -0,0 +1 @@ +Turn this repository into a proper Python distribution. @gforcada diff --git a/pyproject.toml b/pyproject.toml index b268a3e..200bcc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools < 74"] +build-backend = "setuptools.build_meta" + [tool.towncrier] directory = "news/" filename = "CHANGES.md" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c42ba6c --- /dev/null +++ b/setup.py @@ -0,0 +1,61 @@ +"""Setup for plone.meta package +""" + +from pathlib import Path +from setuptools import find_packages +from setuptools import setup + + +setup( + name="plone.meta", + version="1.0.dev0", + author="Plone Foundation", + author_email="releaseteam@plone.org", + description="Helper functions for package management", + long_description=f"{Path('README.md').read_text()}\n\n{Path('CHANGES.md').read_text()}", + long_description_content_type="text/markdown", + keywords="plone packaging", + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Natural Language :: English", + "Operating System :: OS Independent", + ], + license="GPL version 2", + url="https://github.com/plone/meta", + project_urls={ + "Issue Tracker": "https://github.com/plone/meta/issues", + "Sources": "https://github.com/plone/meta", + }, + packages=find_packages("src"), + package_dir={"": "src"}, + namespace_packages=["plone"], + install_requires=[ + "setuptools", + "Jinja2", + "editorconfig", + "pyyaml", + "tomlkit", + "tox", + "validate-pyproject[all]", + ], + python_requires=">=3.9", + include_package_data=True, + zip_safe=False, + entry_points={ + "console_scripts": [ + "config-package=plone.meta.config_package:main", + "multi-call=plone.meta.multi_call:main", + "re-enable-actions=plone.meta.re_enable_actions:main", + ], + }, +) diff --git a/shared-workflows/tests.yml b/shared-workflows/tests.yml index bb2bb43..7055c1f 100644 --- a/shared-workflows/tests.yml +++ b/shared-workflows/tests.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: 'Deprecation warning' run: | - echo "This GHA is deprecated, please re-run plone/meta on your repository" + echo "This GHA is deprecated, please re-run plone.meta on your repository" echo "If you are still relying on this jobs, " echo "please create an issue in https://github.com/plone/meta/issues/new" exit 1 diff --git a/src/plone/__init__.py b/src/plone/__init__.py new file mode 100644 index 0000000..5284146 --- /dev/null +++ b/src/plone/__init__.py @@ -0,0 +1 @@ +__import__("pkg_resources").declare_namespace(__name__) diff --git a/config/shared/__init__.py b/src/plone/meta/__init__.py similarity index 100% rename from config/shared/__init__.py rename to src/plone/meta/__init__.py diff --git a/config/config-package.py b/src/plone/meta/config_package.py similarity index 97% rename from config/config-package.py rename to src/plone/meta/config_package.py index 1cdb32b..5b2447d 100755 --- a/config/config-package.py +++ b/src/plone/meta/config_package.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python3 from functools import cached_property -from shared.call import call -from shared.git import get_branch_name -from shared.git import get_commit_id -from shared.git import git_branch -from shared.git import git_server_url -from shared.path import change_dir +from .shared.call import call +from .shared.git import get_branch_name +from .shared.git import get_commit_id +from .shared.git import git_branch +from .shared.git import git_server_url +from .shared.path import change_dir import argparse import collections @@ -22,12 +21,12 @@ META_HINT = """\ # Generated from: -# https://github.com/plone/meta/tree/main/config/{config_type} +# https://github.com/plone/meta/tree/main/src/plone/meta/{config_type} # See the inline comments on how to expand/tweak this configuration file""" META_HINT_MARKDOWN = """\ """ DEFAULT = object() @@ -541,7 +540,7 @@ def validate_files(self, files_changed): def _validate_toml(self, file_obj): """Validate files that are in TOML format""" with change_dir(self.path): - + with open(file_obj, 'rb') as meta_f: data = tomlkit.load(meta_f) @@ -568,7 +567,7 @@ def _validate_editorconfig(self, file_obj): @property def _commit_msg(self): - return self.args.commit_msg or 'Configuring with plone/meta' + return self.args.commit_msg or 'Configuring with plone.meta' def commit_and_push(self, filenames): if not self.args.commit: @@ -633,6 +632,3 @@ def main(): package = PackageConfiguration(args) package.configure() - - -main() diff --git a/config/default/changelog_template.jinja b/src/plone/meta/default/changelog_template.jinja similarity index 100% rename from config/default/changelog_template.jinja rename to src/plone/meta/default/changelog_template.jinja diff --git a/config/default/dependabot.yml b/src/plone/meta/default/dependabot.yml similarity index 100% rename from config/default/dependabot.yml rename to src/plone/meta/default/dependabot.yml diff --git a/config/default/editorconfig.j2 b/src/plone/meta/default/editorconfig.j2 similarity index 100% rename from config/default/editorconfig.j2 rename to src/plone/meta/default/editorconfig.j2 diff --git a/config/default/flake8.j2 b/src/plone/meta/default/flake8.j2 similarity index 100% rename from config/default/flake8.j2 rename to src/plone/meta/default/flake8.j2 diff --git a/config/default/gitignore.j2 b/src/plone/meta/default/gitignore.j2 similarity index 100% rename from config/default/gitignore.j2 rename to src/plone/meta/default/gitignore.j2 diff --git a/config/default/gitlab-ci.yml.j2 b/src/plone/meta/default/gitlab-ci.yml.j2 similarity index 100% rename from config/default/gitlab-ci.yml.j2 rename to src/plone/meta/default/gitlab-ci.yml.j2 diff --git a/config/default/meta.yml.j2 b/src/plone/meta/default/meta.yml.j2 similarity index 100% rename from config/default/meta.yml.j2 rename to src/plone/meta/default/meta.yml.j2 diff --git a/config/default/packages.txt b/src/plone/meta/default/packages.txt similarity index 96% rename from config/default/packages.txt rename to src/plone/meta/default/packages.txt index 048e79c..767f530 100644 --- a/config/default/packages.txt +++ b/src/plone/meta/default/packages.txt @@ -1,6 +1,7 @@ # Do not edit the file by hand but use the script config-package.py as # described in README.rst # Packages configured for Plone are listed here. +# Only packages in the `plone` GitHub organization should be listed here. borg.localrole five.intid plone.alterego @@ -20,7 +21,6 @@ plone.app.intid plone.app.iterate plone.app.layout plone.app.linkintegrity -plone.app.locales plone.app.lockingbehavior plone.app.mosaic plone.app.multilingual diff --git a/config/default/pre-commit-config.yaml.j2 b/src/plone/meta/default/pre-commit-config.yaml.j2 similarity index 96% rename from config/default/pre-commit-config.yaml.j2 rename to src/plone/meta/default/pre-commit-config.yaml.j2 index 362d504..3b0cd4d 100644 --- a/config/default/pre-commit-config.yaml.j2 +++ b/src/plone/meta/default/pre-commit-config.yaml.j2 @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.19.1 hooks: - id: pyupgrade args: [--py38-plus] @@ -13,7 +13,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black - repo: https://github.com/collective/zpretty @@ -55,7 +55,7 @@ repos: # """ ## - repo: https://github.com/mgedmin/check-manifest - rev: "0.49" + rev: "0.50" hooks: - id: check-manifest - repo: https://github.com/regebro/pyroma @@ -63,7 +63,7 @@ repos: hooks: - id: pyroma - repo: https://github.com/mgedmin/check-python-versions - rev: "0.22.0" + rev: "0.22.1" hooks: - id: check-python-versions args: ['--only', 'setup.py,pyproject.toml'] diff --git a/config/default/pyproject.toml.j2 b/src/plone/meta/default/pyproject.toml.j2 similarity index 100% rename from config/default/pyproject.toml.j2 rename to src/plone/meta/default/pyproject.toml.j2 diff --git a/config/default/tox.ini.j2 b/src/plone/meta/default/tox.ini.j2 similarity index 100% rename from config/default/tox.ini.j2 rename to src/plone/meta/default/tox.ini.j2 diff --git a/src/plone/meta/multi_call.py b/src/plone/meta/multi_call.py new file mode 100644 index 0000000..21c2955 --- /dev/null +++ b/src/plone/meta/multi_call.py @@ -0,0 +1,55 @@ +from .shared.call import call +from .shared.packages import list_packages +from .shared.path import change_dir +from .shared.path import path_factory +import argparse +import sys + + +def main(): + parser = argparse.ArgumentParser( + description='Call a script on all repositories listed' + ' in a packages.txt.', + epilog='Additional optional arguments are passed' + ' directly to the script.') + parser.add_argument( + 'script', type=path_factory('script', has_extension='.py'), + help='path to the Python script to be called') + parser.add_argument( + 'packages_txt', + type=path_factory( + 'packages.txt', + has_extension='.txt'), + help='path to packages.txt; script is called on each repository listed' + ' inside', + metavar='packages.txt') + parser.add_argument( + 'clones', type=path_factory('clones', is_dir=True), + help='path to the directory where the clones of the repositories are' + ' stored') + + # idea from https://stackoverflow.com/a/37367814/8531312 + args, sub_args = parser.parse_known_args() + packages = list_packages(args.packages_txt) + + for package in packages: + print(f'*** Running {args.script.name} on {package} ***') + if (args.clones / package).exists(): + with change_dir(args.clones / package): + print('Updating existing checkout …') + call('git', 'stash') + call('git', 'checkout', 'master') + call('git', 'pull') + else: + with change_dir(args.clones): + print('Cloning repository …') + call('git', 'clone', + f'https://github.com/zopefoundation/{package}') + + call_args = [ + sys.executable, + args.script, + args.clones / package + ] + call_args.extend(sub_args) + call(*call_args) diff --git a/src/plone/meta/re_enable_actions.py b/src/plone/meta/re_enable_actions.py new file mode 100644 index 0000000..9a4c496 --- /dev/null +++ b/src/plone/meta/re_enable_actions.py @@ -0,0 +1,72 @@ +from .shared.call import call +from .shared.packages import list_packages +import argparse +import itertools +import pathlib +import sys + + +org = 'plone' +base_url = f'https://github.com/{org}' +base_path = pathlib.Path(__file__).parent +types = ['default'] +config_package_command = sys.argv[0].replace( + "re-enable-actions", + "config-package", +) + + +def run_workflow(base_url, org, repo): + """Manually start the tests.yml workflow of a repository.""" + result = call('gh', 'workflow', 'run', 'tests.yml', '-R', f'{org}/{repo}') + if result.returncode != 0: + print( + 'To enable starting workflows manually, clone the repository' + f' and run {config_package_command} on it.' + ) + print('Command to clone:') + print(f'git clone {base_url}/{repo}.git') + return False + return True + + +def main(): + parser = argparse.ArgumentParser( + description='Re-enable GitHub Actions for all repos in a packages.txt' + ' file.') + parser.add_argument( + '--force-run', + help='Run workflow even it is already enabled.', + action='store_true') + + args = parser.parse_args() + + repos = itertools.chain( + *[list_packages(base_path / type / 'packages.txt') + for type in types]) + + + for repo in repos: + print(repo) + wfs = call( + 'gh', 'workflow', 'list', '--all', '-R', f'{org}/{repo}', + capture_output=True).stdout + test_lines = [x for x in wfs.splitlines() if x.startswith('Meta')] + if not test_lines: + print( + 'Meta is not in the workflows. Clone the repository' + f' and run {config_package_command} on it.' + ) + print('Command to clone:') + print(f'git clone {base_url}/{repo}.git') + continue + test_line = test_lines[0] + if 'disabled_inactivity' not in test_line: + print(' ☑️ already enabled') + if args.force_run: + run_workflow(base_url, org, repo) + continue + test_id = test_line.split()[-1] + call('gh', 'workflow', 'enable', test_id, '-R', f'{org}/{repo}') + if run_workflow(base_url, org, repo): + print(' ✅ enabled') diff --git a/src/plone/meta/shared/__init__.py b/src/plone/meta/shared/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/shared/call.py b/src/plone/meta/shared/call.py similarity index 81% rename from config/shared/call.py rename to src/plone/meta/shared/call.py index 0b18ae7..26458da 100644 --- a/config/shared/call.py +++ b/src/plone/meta/shared/call.py @@ -24,5 +24,10 @@ def call(*args, capture_output=False, cwd=None, allowed_return_codes=(0, )): result = subprocess.run( args, capture_output=capture_output, text=True, cwd=cwd) if result.returncode not in allowed_return_codes: + print(f"ERROR: exit code {result.returncode}.") + print("output:") + print(result.stdout) + print("ERROR:") + print(result.stderr) abort(result.returncode) return result diff --git a/config/shared/git.py b/src/plone/meta/shared/git.py similarity index 100% rename from config/shared/git.py rename to src/plone/meta/shared/git.py diff --git a/config/shared/packages.py b/src/plone/meta/shared/packages.py similarity index 100% rename from config/shared/packages.py rename to src/plone/meta/shared/packages.py diff --git a/config/shared/path.py b/src/plone/meta/shared/path.py similarity index 100% rename from config/shared/path.py rename to src/plone/meta/shared/path.py diff --git a/config/shared/utils.py b/src/plone/meta/shared/utils.py similarity index 100% rename from config/shared/utils.py rename to src/plone/meta/shared/utils.py