Skip to content

Commit

Permalink
Add bazel config. (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
dougthor42 authored Jan 14, 2024
1 parent f09f12f commit f6a8567
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,25 @@ tags

# Keep it secret. Keep it safe.
secrets.sh

### End of Custom Things ###

# Created by https://www.toptal.com/developers/gitignore/api/bazel
# Edit at https://www.toptal.com/developers/gitignore?templates=bazel

### Bazel ###
# gitignore template for Bazel build system
# website: https://bazel.build/

# Ignore all bazel-* symlinks. There is no full list since this can change
# based on the name of the directory bazel is cloned into.
/bazel-*

# Directories for the Bazel IntelliJ plugin containing the generated
# IntelliJ project files and plugin configuration. Seperate directories are
# for the IntelliJ, Android Studio and CLion versions of the plugin.
/.ijwb/
/.aswb/
/.clwb/

# End of https://www.toptal.com/developers/gitignore/api/bazel
7 changes: 7 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Tell bazel to include some files
filegroup(
name = "mygroup",
srcs = [
"requirements_lock.txt",
],
)
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,60 @@ Then push tags to github. CI will build the source distribution and wheel and
upload them to PyPI.


### Bazel

I'm experimenting with [`bazel`][bazel] for running tests (and perhaps also compiling
a binary in the future).

First, install `bazel`:

```console
$ source setup_bazel.sh
```

Then activate your venv:

```console
$ . .venv/bin/activate
```

To run tests:

```console
$ bazel test //tests:test_main
```

If a test fails, you'll see something like:

```
INFO: Build completed, 1 test FAILED, 2 total actions
//tests:test_main FAILED in 1.7s
/home/dthor/.cache/bazel/_bazel_dthor/7076d176777da645a0c7cf0359126a31/execroot/_main/bazel-out/k8-fastbuild/testlogs/tests/test_main/test.log
Executed 1 out of 1 test: 1 fails locally.
```

To see the logs of that test, open that file in `less` or whatever you prefer:

```console
$ less bazel-out/k8-fastbuild/testlogs/tests/test_main/test.log
```

There are a couple other CLI args that might be useful:

+ `--test_output=streamed`: Run tests serially and show the output of pytest.

To build (though note that this doesn't fully work yet):

```console
$ bazel build //src/bitwarden_to_keepass:cli
```


## Changelog

See [CHANGELOG.md](./CHANGELOG.md).


[bw-cli]: https://bitwarden.com/help/cli/
[bazel]: https://bazel.build/
60 changes: 60 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Name the workspace
workspace(name = "bitwarden-to-keepass")

# Install rules_python, which allows us to define how bazel should work with python files.
# See https://rules-python.readthedocs.io/en/latest/getting-started.html#using-a-workspace-file
# Note: These "##### START ... #####" comments are just my own - they do not have
# any meaning in bazel.
##### START install rules_python snippet (https://github.com/bazelbuild/rules_python/releases/tag/0.28.0) #####
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

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",
)

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

py_repositories()
##### END install rules_python snippet #####


# Use a hermetic python rather than relying on a system-installed interpreter:
# See https://rules-python.readthedocs.io/en/stable/getting-started.html#toolchain-registration
##### START hermetic python snippet #####
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")

py_repositories()

python_register_toolchains(
name = "python3_8",
# Available versions are listed in @rules_python//python:versions.bzl.
# We recommend using the same version your team is already standardized on.
python_version = "3.8.18",
# Support coverage. See https://rules-python.readthedocs.io/en/stable/coverage.html
register_coverage_tool = True,
)

load("@python3_8//:defs.bzl", interpreter = "interpreter")
##### END hermetic python snippet #####


# Install dependencies from PyPI.
# See https://rules-python.readthedocs.io/en/stable/pypi-dependencies.html#using-a-workspace-file
# and https://rules-python.readthedocs.io/en/stable/pip.html
##### START install python dependencies snippet #####
load("@rules_python//python:pip.bzl", "pip_parse")

pip_parse(
name = "pypi",
python_interpreter_target = interpreter, # From hermetic python snippet
# TODO: Can requirements come from pyproject.toml?
requirements_lock = "//:requirements_lock.txt",
)

load("@pypi//:requirements.bzl", "install_deps")

install_deps()
##### END install python dependencies snippet #####
38 changes: 38 additions & 0 deletions requirements_lock.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
appdirs==1.4.3
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
attrs==19.3.0
cffi==1.16.0
cfgv==3.1.0
click==8.1.7
construct==2.10.68
coverage==7.3.2
distlib==0.3.6
exceptiongroup==1.1.3
filelock==3.8.0
future==0.18.3
identify==2.5.6
importlib-metadata==1.5.0
importlib-resources==1.0.2
iniconfig==2.0.0
lxml==4.9.3
more-itertools==8.2.0
nodeenv==1.3.5
packaging==20.1
platformdirs==2.5.2
pluggy==0.13.1
pre-commit==3.4.0
pycparser==2.21
pycryptodomex==3.19.0
pykeepass==4.0.3
pyparsing==2.4.6
pytest==7.4.2
pytest-cov==4.1.0
python-dateutil==2.8.2
PyYAML==6.0.1
six==1.14.0
toml==0.10.0
tomli==2.0.1
virtualenv==20.16.5
wcwidth==0.1.8
zipp==3.0.0
18 changes: 18 additions & 0 deletions setup_bazel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

# Install bazel (https://bazel.build) via bazelisk (https://github.com/bazelbuild/bazelisk).
#
# Bazelisk is a wrapper around bazel and can be invoked the same way as the OG `bazel`.
#
# Puts the bazelisk binary at /usr/local/bin/bazelisk and .../bazel

BAZEL_VERSION="v1.19.0"
URL="https://github.com/bazelbuild/bazelisk/releases/download/$BAZEL_VERSION/bazelisk-linux-amd64"

TMP_PATH="/tmp/bazelisk"
BINARY_PATH="/usr/local/bin/bazelisk"

wget $URL -O $TMP_PATH
sudo cp $TMP_PATH $BINARY_PATH
sudo chmod a+x $BINARY_PATH
sudo ln $BINARY_PATH /usr/local/bin/bazel
27 changes: 27 additions & 0 deletions src/bitwarden_to_keepass/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@rules_python//python:defs.bzl", "py_binary")
load("@pypi//:requirements.bzl", "requirement")

py_library(
name = "__init__",
srcs = ["__init__.py"],
deps = [],
)

py_library(
name = "main",
srcs = ["main.py"],
deps = [
":__init__",
requirement("pykeepass"),
],
visibility = ["//visibility:public"],
)

py_binary(
name = "cli",
srcs = ["cli.py"],
deps = [
":main",
requirement("click"),
],
)
41 changes: 41 additions & 0 deletions tests/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
load("//tools/bazel:defs.bzl", "pytest_test")
load("@rules_python//python:defs.bzl", "py_binary", "py_test")
load("@pypi//:requirements.bzl", "requirement")

filegroup(
name = "test_data",
srcs = glob([
"data/*",
]),
)

py_library(
name = "__init__",
srcs = ["__init__.py"],
deps = [],
)

py_library(
name = "conftest",
srcs = ["conftest.py"],
deps = [
requirement("pytest"),
],
)

pytest_test(
name = "test_main",
srcs = ["test_main.py"],
# imports = ["src/bitwarden_to_keepass"],
deps = [
# To reference something defined in another BUILD file, prefex with
# "//"
"//src/bitwarden_to_keepass:main",
# Reference to current BUILD file
":__init__",
":conftest",
requirement("pykeepass"),
requirement("pytest"),
],
data = [":test_data"],
)
4 changes: 4 additions & 0 deletions tools/bazel/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# We have to tell bazel to make our pytest shim available to everyone.
exports_files([
"pytest_shim.py",
])
26 changes: 26 additions & 0 deletions tools/bazel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Bazel Tools

This dir contains helpers and shims used by bazel.

It's a central location for storing things that can be used by all bazel BUILD
files.

For this project, we use it to define the pytest shim that executes `pytest`
on the project and the `pytest_test` bazel rule.

Other uses may include:

+ A shim for setting environment variables
+ Special build rules
+ Uhh... Other things


## What's In Here?

+ `BUILD`: The bazel build file that exports `pytest_shim.py` so that every
other BUILD file can use it.
+ `defs.bzl`: Kinda like a bazel config file. Defines the `pytest_test` rule
as a wrapper around the `py_test` rule.
+ `pytest_shim.py`: A python module that gets set as the main entry point
when running `pytest_test` rules.
+ `README.md`: This file.
51 changes: 51 additions & 0 deletions tools/bazel/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
load("@rules_python//python:defs.bzl", _py_test = "py_test")
load("@pypi//:requirements.bzl", "requirement")

def pytest_test(name, srcs, deps, **kwargs):
"""
A bazel rule for running python tests via pytest.
Usage:
```bazel
pytest_test(
name = "test_main",
srcs = ["test_main.py"],
deps = [
# The module to test
"//src/my_package:main",
# Still need to include pytest. See TODO below.
requirement("pytest"),
],
)
```
"""
if "main" in kwargs:
fail("Can't set 'main' - we set it in pytest_test rule definition.")

shim = "//tools/bazel:pytest_shim.py"

# TODO: We can automagically update the deps if we want. For now, to make
# sure I understand bazel, I won't be doing that.
# deps =

# Include the shim as a source.
all_srcs = [shim] + srcs

_py_test(
name = name,
srcs = all_srcs,
main = shim,
# TODO: What's this 'location'?
args = ["$(location {})".format(src) for src in srcs],
env = {
# Within bazel, `$HOME` is typically set to $TEST_TMPDIR`, but it looks
# like there's a bug? https://github.com/bazelbuild/bazel/issues/10652
# So we inject it manually.
# TODO: Don't point to root, point to $TEST_TMPDIR instead.
"HOME": "/",
# Force pytest to always color output.
"PYTEST_ADDOPTS": "--color=yes",
},
deps = deps,
**kwargs,
)
28 changes: 28 additions & 0 deletions tools/bazel/pytest_shim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
A Bazel shim for executing pytest.
By default, the `py_test` bazel rule (https://bazel.build/reference/be/python#py_test)
will simply run the test module. In the `unittest` world, typically every module
will have:
```python
if __name__ == "__main__":
unittest.main()
```
so tests actually get run during `bazel test`.
However, this project uses `pytest` as a test runner and test modules don't
have `if __name__ == "__main__":` code in them, so when `bazel test` runs,
nothing actually happens.
This shim gets added to all `pytest_test` rules and set to bazel's `main`, so
it's what gets executed when a pytest file is "run" with `bazel test`.
"""
import sys

import pytest

if __name__ == "__main__":
exit_code = pytest.main(sys.argv[1:])
sys.exit(exit_code)

0 comments on commit f6a8567

Please sign in to comment.