How to contribute
We’d love to accept your patches and contributions to this project. There arejust a few small guidelines you need to follow.
Contributor License Agreement
First, the most important step: signing the Contributor License Agreement. Wecannot look at any of your code unless one is signed.
Contributions to this project must be accompanied by a Contributor LicenseAgreement. You (or your employer) retain the copyright to your contribution,this simply gives us permission to use and redistribute your contributions aspart of the project. Head over tohttps://cla.developers.google.com/ to seeyour current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you’ve already submitted one(even if it was for a different project), you probably don’t need to do itagain.
Getting started
Before we can work on the code, we need to get a copy of it and setup somelocal environment and tools.
First, fork the code to your user and clone your fork. This gives you a privateplayground where you can do any edits you’d like. For this guide, we’ll usetheGitHubgh tool(Linux install).(More advanced users may prefer the GitHub UI and rawgit commands).
ghrepoforkbazel-contrib/rules_python--clone--remote
Next, make sure you have a new enough version of Python installed that supports thevarious code formatters and other devtools. For a quick start,install pyenv andat least Python 3.9.15:
curlhttps://pyenv.run|bashpyenvinstall3.9.15pyenvshell3.9.15
Development workflow
It’s suggested that you create what is called a “feature/topic branch” in yourfork when you begin working on code you want to eventually send or code review.
gitcheckoutmain# Start our branch from the latest codegitcheckout-bmy-feature# Create and switch to our feature branchgitpushoriginmy-feature# Cause the branch to be created in your fork.
From here, you then edit code and commit to your local branch. If you want tosave your work to github, you usegitpush to do so:
gitpushoriginmy-feature
Once the code is in your github repo, you can then turn it into a Pull Requestto the actual rules_python project and begin the code review process.
Developer guide
For more more details, guidance, and tips for working with the code base,seedocs/devguide.md
Formatting
Starlark files should be formatted bybuildifier.Otherwise the Buildkite CI will fail with formatting/linting violations.We suggest using a pre-commit hook to automate this.Firstinstall pre-commit,then run
pre-commitinstall
Running buildifer manually
You can also run buildifier manually. To do this,install buildifier,and run the following command:
$buildifier--lint=fix--warnings=native-py-warnings=allWORKSPACE
Replace the argument “WORKSPACE” with the file that you are linting.
Code reviews
All submissions, including submissions by project members, require review. Weuse GitHub pull requests for this purpose. ConsultGitHub Help for moreinformation on using pull requests.
Commit messages
Commit messages (upon merging) and PR messages should follow theConventionalCommits style:
type(scope)!: <summary><body>BREAKING CHANGE: <summary>
Where(scope) is optional, and! is only required if there is a breaking change.If a breaking change is introduced, thenBREAKINGCHANGE: is required; seetheBreaking Changes section for how to introduce breakingchanges.
User visible changes, such as features, fixes, or notable refactors, shouldbe documneted in CHANGELOG.md and their respective API doc. See [Documentingchanges] for how to do so.
Commontypes:
build:means it affects the building or development workflow.docs:means only documentation is being added, updated, or fixed.feat:means a user-visible feature is being added. See [Documenting versionchanges] for how to documenAdd{versionadded}to appropriate docs.fix:means a user-visible behavior is being fixed. If the fix is changingbehavior of a function, add{versionchanged}to appropriate docs, as necessary.refactor:means some sort of code cleanup that doesn’t change user-visiblebehavior. Add{versionchanged}to appropriate docs, as necessary.revert:means a prior change is being reverted in some way.test:means only tests are being added.
For the full details of types, seeConventional Commits.
Documenting changes
Changes are documented in two places: CHANGELOG.md and API docs.
CHANGELOG.md contains a brief, human friendly, description. This text isintended for easy skimming so that, when people upgrade, they can quickly get asense of what’s relevant to them.
API documentation are the doc strings for functions, fields, attributes, etc.When user-visible or notable behavior is added, changed, or removed, the{versionadded},{versionchanged} or{versionremoved} directives should beused to note the change. When specifying the version, use the valuesVERSION_NEXT_FEATURE orVERSION_NEXT_PATCH to indicate what sort ofversion increase the change requires.
These directives use Sphinx MyST syntax, e.g.
:::{versionadded} VERSION_NEXT_FEATUREThe `allow_new_thing` arg was added.::::::{versionchanged} VERSION_NEXT_PATCHLarge numbers no longer consume exponential memory.::::::{versionremoved} VERSION_NEXT_FEATUREThe `legacy_foo` arg was removed:::Style and idioms
For the most part, we just accept whatever the code formatters do, so thereisn’t much style to enforce.
Some miscellanous style, idioms, and conventions we have are:
Markdown/Sphinx Style
Use colons for prose sections of text, e.g.
:::{note}, not backticks.Use backticks for code blocks.
Max line length: 100.
BUILD/bzl Style
When a macro generates public targets, use a dot (
.) to separate theuser-provided name from the generted name. e.g.foo(name="x")generatesx.test. The.is our convention to communicate that it’s a generatedtarget, and thus one should look forname="x"when searching for thedefinition.The different build phases shouldn’t load code that defines objects thataren’t valid for their phase. e.g.
The bzlmod phase shouldn’t load code defining regular rules or providers.
The repository phase shouldn’t load code defining module extensions, regularrules, or providers.
The loading phase shouldn’t load code defining module extensions orrepository rules.
Loading utility libraries or generic code is OK, but should strive to loadcode that is usable for its phase. e.g. loading-phase code shouldn’tload utility code that is predominately only usable to the bzlmod phase.
Providers should be in their own files. This allows implementing a custom rulethat implements the provider without loading a specific implementation.
One rule per file is preferred, but not required. The goal is that defining ane.g. library shouldn’t incur loading all the code for binaries, tests,packaging, etc; things that may be niche or uncommonly used.
Separate files should be used to expose public APIs. This ensures our publicAPI is well defined and prevents accidentally exposing a package-privatesymbol as a public symbol.
Note
The public API file’s docstring becomes part of the user-facing docs. Thatfile’s docstring must be used for module-level API documentation.
Repository rules should have name ending in
_repo. This helps distinguishthem from regular rules.Each bzlmod extension, the “X” of
use_repo("//foo:foo.bzl","X")should bein its own file. The path given in theuse_repo()expression is the identityBazel uses and cannot be changed.
Generated files
Some checked-in files are generated and need to be updated when a new PR ismerged:
requirements lock files: These are usually generated by a
compile_pip_requirementsupdate target, which is usually in the same directory.e.g.bazelrun//docs:requirements.update
Binary artifacts
Checking in binary artifacts is not allowed. This is because they are extremelyproblematic to verify and ensure they’re safe. This is true even intest contexts.
Examples include, but aren’t limited to: prebuilt binaries, shared libraries,zip files, or wheels.
See the dev guide for utilities to help with testing.
Breaking Changes
Breaking changes are generally permitted, but we follow a 3-step process forintroducing them. The intent behind this process is to balance the difficulty ofversion upgrades for users, maintaining multiple code paths, and being able tointroduce modern functionality.
The general process is:
In version
N, introduce the new behavior, but it must be disabled bydefault. Users can opt into the new functionality when they upgrade toversionN, which lets them try it and verify functionality.In version
N+1, the new behavior can be enabled by default. Users canopt out if necessary, but doing so causes a warning to be issued.In version
N+2, the new behavior is always enabled and cannot be opted outof. The API for the control mechanism can be removed in this release.
Note that the+1 and+2 releases are just examples; the steps are notrequired to happen in immediately subsequent releases.
Once The first major version is released, the process will be:
In
N.M.0we introduce the new behaviour, but it is disabled by a feature flag.In
N.M+1.0we may choose the behaviour to become the default if it is not toodisruptive.In
N+1.0.0we get rid of the old behaviour.
How to control breaking changes
The details of the control mechanism will depend on the situation. Below isa summary of some different options.
Environment variables are best for repository rule behavior. Environmentvariables can be propagated to rules and macros using the generated
@rules_python_internal//:config.bzlfile.Attributes are applicable to macros and regular rules, especially when thebehavior is likely to vary on a per-target basis.
User defined build settings(aka custom build flags) are applicable for rules when the behavior changegenerally wouldn’t vary on a per-target basis. They also have the benefit thatan entire code base can have them easily enabled by a bazel command line flag.
Allowlists allow a project to centrally control if something isenabled/disabled. Under the hood, they are basically a specialized custombuild flag.
Note that attributes and flags can seamlessly interoperate by having the defaultcontrolled by a flag, and an attribute can override the flag setting. Thisallows a project to enable the new behavior by default while they work to fixproblematic cases to prepare for the next upgrade.
What is considered a breaking change?
Precisely defining what constitutes a breaking change is hard because it’seasy forsomeone, somewhere to depend onsome observable behavior, despiteour best efforts to thoroughly document what is or isn’t supported and hidingany internal details.
In general, something is considered a breaking change when it changes thedirect behavior of a supported public API. Simply being able to observe abehavior change doesn’t necessarily mean it’s a breaking change.
Long standing undocumented behavior is a large grey area and really depends onhow load-bearing it has become and what sort of reasonable expectation ofbehavior there is.
Here’s some examples of what would or wouldn’t be considered a breaking change.
Breaking changes:
Renaming an function argument for public functions.
Enforcing stricter validation than was previously required when there’s asensible reason users would run afoul of it.
Changing the name of a public rule.
Not breaking changes:
Upgrading dependencies
Changing internal details, such as renaming an internal file.
Changing a rule to a macro.
AI-assisted Contributions
Contributions assisted by AI tools are allowed. However, the human authorsubmitting the pull request is responsible for the contributed code as if theyhad written it entirely themselves. This means:
Understanding the code: You must be able to explain what the code doesand why it’s implemented that way. This includes discussing itsimplications, and any trade-offs made during its development, just as if youhad written it entirely yourself.
Vetting the correctness and functionality: You are responsible forthoroughly testing and verifying that the code is correct, functional, andmeets all project requirements and standards.
If the human PR author cannot fulfill these responsibilities, therules_pythonmaintainers will not spend time reviewing or merging the PR. The goal is toensure that all contributions, regardless of their origin, maintain the qualityand integrity of the project and do not place an undue burden on maintainers.
FAQ
Installation errors when duringgitcommit
If you didpre-commitinstall, various tools are run when you dogitcommit.This might show as an error such as:
[INFO]Installingenvironmentforhttps://github.com/psf/black.[INFO]Onceinstalledthisenvironmentwillbereused.[INFO]Thismaytakeafewminutes...Anunexpectederrorhasoccurred:CalledProcessError:command:...
To fix, you’ll need to figure out what command is failing and why. Because theseare tools that run locally, its likely you’ll need to fix something with yourenvironment or the installation of the tools. For Python tools (e.g. black orisort), you can try using a different Python version in your shell by usingtools such aspyenv.