Bazel Can Overwrite __init__.py With Empty File In Source Tree
Introduction
Bazel is a popular build tool used for building and testing software. However, like any other tool, it can have its own set of issues and bugs. In this article, we will discuss a specific bug that can occur when using Bazel with Python rules. The bug involves Bazel overwriting the existing __init__.py
file with an empty file when the BUILD structure is not properly set up.
Description of the Bug
The bug occurs when the srcs
attribute is not properly set in the BUILD file. Specifically, if the srcs
attribute is not set to include __init__.py
, Bazel can overwrite the existing __init__.py
file with an empty file. This can lead to unexpected behavior and errors in the code.
Reproducing the Bug
To reproduce the bug, we need to create a minimal example. Let's create a directory structure as follows:
.
├── MODULE.bazel
└── some_package
├── BUILD.bazel
├── __init__.py
├── _some_library.py
└── test.py
In the MODULE.bazel
file, we define a module with the following content:
module(name = "test_module", version = "0.0.1", compatibility_level = 1)
bazel_dep(name = "rules_python", version = "1.3.0")
In the some_package/_some_library.py
file, we define a simple function:
def method():
pass
In the some_package/test.py
file, we import the some_package
module and call the method
function:
import some_package
some_package.method()
In the some_package/__init__.py
file, we import the _some_library
module and define a variable method
:
import some_package._some_library
method=_some_library.method
In the some_package/BUILD.bazel
file, we define two Python libraries and two test targets:
load("@rules_python//python:defs.bzl", "py_library", "py_test")
py_library(
name = "_some_library",
srcs = ["_some_library.py"],
)
py_library(
name = "some_package",
srcs = ["__init__.py"],
deps = [":_some_library"],
)
py_test(
name = "correct_test",
srcs = ["test.py"],
main = "test.py",
deps = [":some_package"],
)
py_test(
name = "bad_dep_test",
srcs = ["test.py"],
main = "test.py",
deps = [":_some_library"],
)
We then run the bazel test
command to execute the test targets:
$ bazel test //some_package:correct_test
[... ] Executed 1 out of 1 test: 1 test passes. [...]
The __init__.py
file is not overwritten in this case.
However, if we run the bazel test
command with the bad_dep_test
target, we get the following output:
$ bazel test //_package:bad_dep_test
[...] Executed 1 out of 1 test: 1 fails locally.
And the __init__.py
file is overwritten with an empty file:
$ tree -h some_package/
[4.0K] some_package/
├── [ 444] BUILD.bazel
├── [ 0] __init__.py
├── [ 21] _some_library.py
└── [ 43] test.py
Fixing the Bug
The bug can be fixed by setting the common --incompatible_default_to_explicit_init_py
flag in the .bazelrc
file. This flag tells Bazel to always include the __init__.py
file in the srcs
attribute, even if it is not explicitly specified.
Conclusion
Q: What is the bug that Bazel can overwrite init.py with an empty file?
A: The bug occurs when the srcs
attribute is not properly set in the BUILD file. Specifically, if the srcs
attribute is not set to include __init__.py
, Bazel can overwrite the existing __init__.py
file with an empty file.
Q: How can I reproduce the bug?
A: To reproduce the bug, you need to create a minimal example. Let's create a directory structure as follows:
.
├── MODULE.bazel
└── some_package
├── BUILD.bazel
├── __init__.py
├── _some_library.py
└── test.py
In the MODULE.bazel
file, you define a module with the following content:
module(name = "test_module", version = "0.0.1", compatibility_level = 1)
bazel_dep(name = "rules_python", version = "1.3.0")
In the some_package/_some_library.py
file, you define a simple function:
def method():
pass
In the some_package/test.py
file, you import the some_package
module and call the method
function:
import some_package
some_package.method()
In the some_package/__init__.py
file, you import the _some_library
module and define a variable method
:
import some_package._some_library
method=_some_library.method
In the some_package/BUILD.bazel
file, you define two Python libraries and two test targets:
load("@rules_python//python:defs.bzl", "py_library", "py_test")
py_library(
name = "_some_library",
srcs = ["_some_library.py"],
)
py_library(
name = "some_package",
srcs = ["__init__.py"],
deps = [":_some_library"],
)
py_test(
name = "correct_test",
srcs = ["test.py"],
main = "test.py",
deps = [":some_package"],
)
py_test(
name = "bad_dep_test",
srcs = ["test.py"],
main = "test.py",
deps = [":_some_library"],
)
You then run the bazel test
command to execute the test targets:
$ bazel test //some_package:correct_test
[... ] Executed 1 out of 1 test: 1 test passes. [...]
The __init__.py
file is not overwritten in this case.
However, if you run the bazel test
command with the bad_dep_test
target, you get the following output:
$ bazel test //_package:bad_dep_test
[...] Executed 1 out of 1 test: 1 fails locally.
And the __init__.py
file is overwritten with an empty file:
$ tree -h some_package/
[4.0K] some_package/
├── [ 444] BUILD.bazel
├── [ 0] __init__.py
├─�� [ 21] _some_library.py
└── [ 43] test.py
Q: How can I fix the bug?
A: The bug can be fixed by setting the common --incompatible_default_to_explicit_init_py
flag in the .bazelrc
file. This flag tells Bazel to always include the __init__.py
file in the srcs
attribute, even if it is not explicitly specified.
Q: What is the impact of this bug?
A: The impact of this bug is that the __init__.py
file is overwritten with an empty file, which can lead to unexpected behavior and errors in the code.
Q: How can I prevent this bug from occurring?
A: To prevent this bug from occurring, you can set the common --incompatible_default_to_explicit_init_py
flag in the .bazelrc
file. This flag tells Bazel to always include the __init__.py
file in the srcs
attribute, even if it is not explicitly specified.
Q: Is this bug specific to Bazel?
A: No, this bug is not specific to Bazel. It can occur with any build tool that uses the srcs
attribute to specify the files to be built.
Q: How can I report this bug?
A: If you encounter this bug, you can report it to the Bazel team using the Bazel issue tracker.