Skip to content

Commit

Permalink
Start Configuration of Python Gazelle (#26)
Browse files Browse the repository at this point in the history
Changes:
- Establish a mechanism for defining external python requirements via a
`requirements.in`, generating updates to a lock file with `bazel run
//:requirements.update`
- Configure gazelle python  and protobufs
- Configures via gazelle directives to use a file based approach, stick
with the BUILD configuration, disable go, default visibility to private
and resolve py imports
- Update dev_setup to install openjdk (java), tree, ranger, ag

New Commands:
```
bazel run //:requirements.update
bazel run //:gazelle_python_manifest.update
bazel run //:gazelle
```

New Tests:
```
//:requirements_test
//::gazelle_python_manifest.test
```

Notes:
- One of the recurring issues I have every time I deal with updating
WORKSPACE is slight incompatibilities with various repos that result in
incomprehensible output errors.
- stackb/rules_proto is one of the only things that seems to support
what I'm looking for, and while it is getting steady contributions
hasn't pubilshed a release in about a year.
https://github.com/rules-proto-grpc/rules_proto_grpc. Additionally, it
could be handy to look into what it would take to make my own "shitty"
version of these rules to learn more about what's going on in gazelle's
/ bazel's internals
- bzlmod migration didn't go well for me, most tutorials out there still
use WORKSPACE, might be good to look into in the future / figure out how
to migrate piecemeal, but bypassed this and 7.0.1 bazel upgrade for now

Related Issues:
- Noting the issue with requiring a gazelle directive for proto
libraries: bazelbuild/rules_python#1703
- bazelbuild/rules_python#1664 fixes the reason
I had to "disable" my py_binary, wait for that to release and then
update. I could probably patch this in, but don't quite know how 🤷
- Sounds like there's some talk around gazelle c++
bazel-contrib/bazel-gazelle#910
- I'm helping ;) bazelbuild/rules_python#1712
(doc update)

References:
- https://github.com/bazelbuild/bazel-gazelle
- https://github.com/stackb/rules_proto
- https://github.com/bazelbuild/rules_python/tree/main/gazelle

Future Things to Look Into:
- Setup a gazelle test that checks output of `//:gazelle -- -mode diff`
is zero
- Configure rust with gazelle: https://github.com/Calsign/gazelle_rust
- A really cool / general parser / generator (what github uses for
"semantic" tool): https://tree-sitter.github.io/tree-sitter/ Could use
to make small code generator / modification? Or maybe more general
utilities?
- General compile command completion:
https://github.com/hedronvision/bazel-compile-commands-extractor
- Maybe play with https://github.com/rules-proto-grpc/rules_proto_grpc
as an alternative to stackb
- If I can't use gazelle for some things, mess around with
https://github.com/bazelbuild/buildtools/tree/master/buildozer
  • Loading branch information
michael-christen authored Jan 22, 2024
1 parent 272da4d commit d21e33d
Show file tree
Hide file tree
Showing 17 changed files with 494 additions and 84 deletions.
105 changes: 105 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Load various rules so that we can have bazel download
# various rulesets and dependencies.
# The `load` statement imports the symbol for the rule, in the defined
# ruleset. When the symbol is loaded you can use the rule.
load("@bazel_gazelle//:def.bzl", "gazelle", "gazelle_binary")
load("@pip//:requirements.bzl", "all_whl_requirements")
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
load("@rules_python_gazelle_plugin//manifest:defs.bzl", "gazelle_python_manifest")
load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping")

compile_pip_requirements(
name = "requirements",
requirements_in = "requirements.in",
requirements_txt = "requirements_lock.txt",
)

# This repository rule fetches the metadata for python packages we
# depend on. That data is required for the gazelle_python_manifest
# rule to update our manifest file.
# To see what this rule does, try `bazel run @modules_map//:print`
modules_mapping(
name = "modules_map",
exclude_patterns = [
"^_|(\\._)+", # This is the default.
"(\\.tests)+", # Add a custom one to get rid of the psutil tests.
],
wheels = all_whl_requirements,
)

# Gazelle python extension needs a manifest file mapping from
# an import to the installed package that provides it.
# This macro produces two targets:
# - //:gazelle_python_manifest.update can be used with `bazel run`
# to recalculate the manifest
# - //:gazelle_python_manifest.test is a test target ensuring that
# the manifest doesn't need to be updated
gazelle_python_manifest(
name = "gazelle_python_manifest",
modules_mapping = ":modules_map",
pip_repository_name = "pip",
# NOTE: We can pass a list just like in `bzlmod_build_file_generation` example
# but we keep a single target here for regression testing.
requirements = "//:requirements_lock.txt",
)

gazelle_binary(
name = "gazelle_bin",
languages = [
"@bazel_gazelle//language/bazel/visibility", # bazel visibility rules
"@bazel_gazelle//language/go", # Built-in rule from gazelle for Golang
"@bazel_gazelle//language/proto", # Built-in rule from gazelle for Protos
# Any languages that depend on the proto plugin must come after it
"@rules_python_gazelle_plugin//python:python", # Use gazelle from rules_python
"@build_stack_rules_proto//language/protobuf", # Protobuf language generation
# TODO: Add buf suppport
# "@rules_buf//gazelle/buf:buf", # Generates buf lint and buf breaking detection rules
],
)

# Our gazelle target points to the python gazelle binary.
# This is the simple case where we only need one language supported.
# If you also had proto, go, or other gazelle-supported languages,
# you would also need a gazelle_binary rule.
# See https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.rst#example
gazelle(
name = "gazelle",
args = [
"-proto_configs=gazelle_proto_config.yaml",
],
gazelle = ":gazelle_bin",
)

# https://github.com/bazelbuild/bazel-gazelle#directives
# https://github.com/bazelbuild/rules_python/blob/main/gazelle/README.md#directives

# Make a py_library per python file
# gazelle:python_generation_mode file

# Disable BUILD.bazel files
# gazelle:build_file_name BUILD

# Exclude these folders from gazelle generation
# gazelle:exclude venv

# Don't use go
# gazelle:go_generate_proto false

# Generate 1 proto rule per file
# gazelle:proto file

# Set default BUILD rule visibility
# gazelle:default_visibility //visibility:private

# Configure aspect_rules_py to be used for py_test, py_binary, py_library
# gazelle:map_kind py_binary py_binary @aspect_rules_py//py:defs.bzl
# gazelle:map_kind py_library py_library @aspect_rules_py//py:defs.bzl
# gazelle:map_kind py_test py_test @aspect_rules_py//py:defs.bzl

# Tell gazelle where to find imports
# gazelle:resolve py google.protobuf.message @com_google_protobuf//:protobuf_python

# TODO: Figure out a way to not need these
# gazelle:resolve py examples.basic.hello_pb2 //examples/basic:hello_py_library

package(default_visibility = ["//visibility:private"])
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ CARGO_BAZEL_REPIN=1 bazel sync --only=crate_index
#### Notes
- bzlmod is where things will be moving to better share dependency information and use a central regirstry: https://bazel.build/external/migration


## Language FAQs

Generally, each language needs a way to answer these questions:
- How to check & enforce style and typing? (linter & formatter)
- How to bulid?
- How to test?
- How to run?
- How to add dependencies?
- How to package?
- How to release / distribute?

### Rust

#### Command Quick Reference
Expand All @@ -85,13 +97,12 @@ Copied from "Bazel"
CARGO_BAZEL_REPIN=1 bazel sync --only=crate_index
```

## Language FAQs
### Python

Generally, each language needs a way to answer these questions:
- How to check & enforce style and typing? (linter & formatter)
- How to bulid?
- How to test?
- How to run?
- How to add dependencies?
- How to package?
- How to release / distribute?
#### Adding / Modifying External Dependencies

1. Update `requirements.in`
2. `bazel run //:requirements.update` to modify `requirements_lock.txt`
3. `bazel run //:gazelle_python_manifest.update` to modify `gazelle_python.yaml`

NOTE: Once `requirements.in` is modified, tests will ensure the above commands have been run (or CI will fail)
184 changes: 157 additions & 27 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
# Set the name of the bazel workspace.
workspace(name = "mchristen")

# Load the http_archive rule so that we can have bazel download
# various rulesets and dependencies.
# The `load` statement imports the symbol for http_archive from the http.bzl
# file. When the symbol is loaded you can use the rule.
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# TODO: Install the newest version once #1664 gets released to fix py_binary issues
http_archive(
name = "rules_python",
sha256 = "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8",
strip_prefix = "rules_python-0.28.0",
url = "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz",
)

http_archive(
name = "aspect_rules_py",
sha256 = "50b4b43491cdfc13238c29cb159b7ccacf0a1e54bd27b65ff2d5fac69af4d46f",
strip_prefix = "rules_py-0.4.0",
url = "https://github.com/aspect-build/rules_py/releases/download/v0.4.0/rules_py-v0.4.0.tar.gz",
)

# Fetches the rules_py dependencies.
# (must come before aspect's gcc toolchain dependencies)
load("@aspect_rules_py//py:repositories.bzl", "rules_py_dependencies")
rules_py_dependencies()

load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")

# We install the rules_python dependencies using the function below.
py_repositories()
python_register_toolchains(
name = "python39",
python_version = "3.9",
)
rules_py_dependencies()

http_archive(
name = "aspect_gcc_toolchain",
Expand All @@ -28,30 +36,18 @@ http_archive(
"https://github.com/aspect-build/gcc-toolchain/archive/refs/tags/0.4.2.tar.gz",
],
)

load("@aspect_gcc_toolchain//toolchain:repositories.bzl", "gcc_toolchain_dependencies")

gcc_toolchain_dependencies()

load("@aspect_gcc_toolchain//toolchain:defs.bzl", "gcc_register_toolchain", "ARCHS")
load("@aspect_gcc_toolchain//toolchain:defs.bzl", "ARCHS", "gcc_register_toolchain")

gcc_register_toolchain(
name = "gcc_toolchain_x86_64",
target_arch = ARCHS.x86_64,
)


# http_archive(
# name = "rules_python_gazelle_plugin",
# sha256 = "9d04041ac92a0985e344235f5d946f71ac543f1b1565f2cdbc9a2aaee8adf55b",
# strip_prefix = "rules_python-0.26.0/gazelle",
# url = "https://github.com/bazelbuild/rules_python/releases/download/0.26.0/rules_python-0.26.0.tar.gz",
# )
#
# # To compile the rules_python gazelle extension from source,
# # we must fetch some third-party go dependencies that it uses.
#
# load("@rules_python_gazelle_plugin//:deps.bzl", _py_gazelle_deps = "gazelle_deps")
#
# _py_gazelle_deps()

http_archive(
name = "rules_rust",
sha256 = "6357de5982dd32526e02278221bb8d6aa45717ba9bbacf43686b130aa2c72e1e",
Expand All @@ -63,7 +59,7 @@ load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_regi
rules_rust_dependencies()

rust_register_toolchains(
edition = "2021",
edition = "2021",
)

load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies")
Expand All @@ -86,7 +82,6 @@ load("@crate_index//:defs.bzl", "crate_repositories")

crate_repositories()


http_archive(
name = "com_google_protobuf",
sha256 = "616bb3536ac1fff3fb1a141450fa28b875e985712170ea7f1bfe5e5fc41e2cd8",
Expand All @@ -97,6 +92,7 @@ http_archive(
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")

protobuf_deps()
# TODO: Should add buf too

http_archive(
name = "rules_proto",
Expand All @@ -110,3 +106,137 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_
rules_proto_dependencies()

rules_proto_toolchains()

######################################################################
# We need rules_go and bazel_gazelle, to build the gazelle plugin from source.
# Setup instructions for this section are at
# https://github.com/bazelbuild/bazel-gazelle#running-gazelle-with-bazel
# You may need to update the version of the rule, which is listed in the above
# documentation.
######################################################################

# Define an http_archive rule that will download the below ruleset,
# test the sha, and extract the ruleset to you local bazel cache.

http_archive(
name = "io_bazel_rules_go",
integrity = "sha256-fHbWI2so/2laoozzX5XeMXqUcv0fsUrHl8m/aE8Js3w=",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.44.2/rules_go-v0.44.2.zip",
"https://github.com/bazelbuild/rules_go/releases/download/v0.44.2/rules_go-v0.44.2.zip",
],
)

http_archive(
name = "bazel_gazelle",
sha256 = "d3fa66a39028e97d76f9e2db8f1b0c11c099e8e01bf363a923074784e451f809",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.33.0/bazel-gazelle-v0.33.0.tar.gz",
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.33.0/bazel-gazelle-v0.33.0.tar.gz",
],
)

# Load rules_go ruleset and expose the toolchain and dep rules.
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")

############################################################
# Define your own dependencies here using go_repository.
# Else, dependencies declared by rules_go/gazelle will be used.
# The first declaration of an external repository "wins".
############################################################

# go_rules_dependencies is a function that registers external dependencies
# needed by the Go rules.
# See: https://github.com/bazelbuild/rules_go/blob/master/go/dependencies.rst#go_rules_dependencies
go_rules_dependencies()

# go_rules_dependencies is a function that registers external dependencies
# needed by the Go rules.
# See: https://github.com/bazelbuild/rules_go/blob/master/go/dependencies.rst#go_rules_dependencies
go_register_toolchains(version = "1.20.5")

# The following call configured the gazelle dependencies, Go environment and Go SDK.
gazelle_dependencies()

# Remaining setup is for rules_python.

http_archive(
name = "rules_python_gazelle_plugin",
sha256 = "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8",
strip_prefix = "rules_python-0.28.0/gazelle",
url = "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz",
)

# Next we load the setup and toolchain from rules_python.
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")

# Perform general setup
py_repositories()

# We now register a hermetic Python interpreter rather than relying on a system-installed interpreter.
# This toolchain will allow bazel to download a specific python version, and use that version
# for compilation.
python_register_toolchains(
name = "python39",
python_version = "3.9",
)

load("@python39//:defs.bzl", "interpreter")

load("@rules_python//python:pip.bzl", "pip_parse")

# This macro wraps the `pip_repository` rule that invokes `pip`, with `incremental` set.
# Accepts a locked/compiled requirements file and installs the dependencies listed within.
# Those dependencies become available in a generated `requirements.bzl` file.
# You can instead check this `requirements.bzl` file into your repo.
pip_parse(
name = "pip",
# Set the location of the lock file.
requirements_lock = "//:requirements_lock.txt",
# (Optional) You can provide a python_interpreter (path) or a python_interpreter_target (a Bazel target, that
# acts as an executable). The latter can be anything that could be used as Python interpreter. E.g.:
# 1. Python interpreter that you compile in the build file.
# 2. Pre-compiled python interpreter included with http_archive.
# 3. Wrapper script, like in the autodetecting python toolchain.
# Here, we use the interpreter constant that resolves to the host interpreter from the default Python toolchain.
python_interpreter_target = interpreter,
)

# Load the install_deps macro.
load("@pip//:requirements.bzl", "install_deps")

# Initialize repositories for all packages in requirements_lock.txt.
install_deps()

# The rules_python gazelle extension has some third-party go dependencies
# which we need to fetch in order to compile it.
load("@rules_python_gazelle_plugin//:deps.bzl", _py_gazelle_deps = "gazelle_deps")

# See: https://github.com/bazelbuild/rules_python/blob/main/gazelle/README.md
# This rule loads and compiles various go dependencies that running gazelle
# for python requirements.
_py_gazelle_deps()

# Load stackb/rules_proto
http_archive(
name = "build_stack_rules_proto",
sha256 = "ac7e2966a78660e83e1ba84a06db6eda9a7659a841b6a7fd93028cd8757afbfb",
strip_prefix = "rules_proto-2.0.1",
urls = ["https://github.com/stackb/rules_proto/archive/v2.0.1.tar.gz"],
)

register_toolchains("@build_stack_rules_proto//toolchain:standard")

# Bring in @io_bazel_rules_go, @bazel_gazelle, @rules_proto if not already present
load("@build_stack_rules_proto//deps:core_deps.bzl", "core_deps")

core_deps()

load("@build_stack_rules_proto//:go_deps.bzl", "gazelle_protobuf_extension_go_deps")

gazelle_protobuf_extension_go_deps()

load("@build_stack_rules_proto//deps:protobuf_core_deps.bzl", "protobuf_core_deps")

protobuf_core_deps()
Loading

0 comments on commit d21e33d

Please sign in to comment.