blob: a00df7aa05d0229c3c4d19659670e58fa51c2b96 [file] [view]
---
layout: contribute
title: bazel init a.k.a ./configure for Bazel
---
__This design document has been replaced by
[Skylark Remote Repositories](/designs/2015/07/02/skylark-remote-repositories.html)
and is maintained here just for reference__
# bazel init a.k.a ./configure for Bazel
_A configuration mechanism for Bazel_
__Status:__ deprecated, replaced by [Skylark Remote Repositories](/designs/2015/07/02/skylark-remote-repositories.html)
__Author:__ dmarting@google.com
## I. Rationale
Bazel tooling needs special setup to work. For example, C++ crosstool
configuration requires path to GCC or Java configuration requires the
path to the JDK. Autodetecting those paths from Bazel would be broken
because each ruleset requires its own configuration (C++ CROSSTOOL
information is totally different from JDK detection or from go root
detection). Therefore, providing a general mechanism to configure
Bazel tooling seems natural. To have Bazel self-contained, we will
ship this mechanism as an additional command of Bazel. Because this
command deals with non-hermetic parts of Bazel, this command should
also group all non-hermetic steps (i.e. it should fetch the
dependencies from the remote repositories) so a user can run it and
get on a plane with everything needed.
## II. Considered use-cases
We consider the 3 following use-cases:
- __UC1.__ The user wants to not worry about tools configuration and
use the default one for golden languages (Java, C++, Shell) and
wants to also activate an optional language (Go). No configuration
information (aka `tools` package) should be checked into the
version control system.
- __UC2.__ The user wants to tweak Java configuration but not C++. Of
course, the user wants his tweak to be shared with his team (i.e.
`tools/jdk` should be checked into the version control system).
However, the user does not want to have C++ information (i.e.
`tools/cpp`) in the VCS.
- __UC3.__ The user wants his build to be hermetic and he wants to
set up everything in his `tools` directory (Google use-case).
### Notes
This document addresses the special case of the configuration of the
`tools` package, mechanisms presented here could be extended to any
dependency that needs to be configured (e.g., detecting the installed
libncurse) but that is out of the scope of this document.
Anywhere in this document we refer to the `tools` package as the
package that will receive the current `tools` package content, it does
not commit to keep that package name.
## III. Requirements
### `bazel init` should:
- _a1._ Not be available in hermetic version (i.e. Google version of
Bazel, a.k.a Blaze).
- _a2._ Allow per-language configuration. I.e., Java and C++ tooling
configuration should be separated.
- _a3._ Allow Skylark add-ons to specify their configuration, this
should be pluggable so we can actually activate configuration per
rule set.
- _a4._ Support at least 3 modes corresponding to each envisioned
use-cases:
* __UC1.__: __installed__ (default mode): a "hidden" `tools`
package contains the detected tool paths (`gcc`, the JDKs, ...)
as well as their configuration (basically the content of the
current `//tools` package). This package should be constructed
as much as possibly automatically with a way for the user to
overwrite detected settings.
* __UC2.__: __semi-hermetic__: the "hidden" `tools` package is used
only for linking the actual tool paths but the configuration
would be checked-in into the workspace (in a similar way that
what is currently done in Bazel). The "hidden" `tools` package
could contains several versions of the same tools (e.g., jdk-8,
jdk-7, ...) and the workspace link to a specific one.
* __UC3.__: __hermetic__: this is the Google way of thing: the
user check-in everything that he thinks belong to the workspace
and the init command should do nothing.
- _a5._ Support explicit reconfiguration. If the configuration
mechanism changes or the user wants to tune the configuration, it
should support to modify the configuration, i.e., update the
various paths or change the default options.
### `bazel init` could:
- _b1._: Initialize a new workspace: as it would support configuring
a whole tool directory, it might be quite close to actually
initializing a new workspace.
## IV. User interface
To be efficient, when the `tools` directory is missing, `bazel build`
should display an informative error message to actually run `bazel
init`.
Configuration is basically just setting a list of build constants like
the path to the JDK, the list of C++ flags, etc...
When the user type `bazel init`, the configuration process starts with
the default configuration (e.g., configure for gold features such as
C++, Java, sh_, ...). It should try to autodetect as much as possible.
If a language configuration needs something it cannot autodetect, then
it can prompt the user for the missing information and the
configuration can fail if something is really wrong.
On default installation, `bazel init` should not prompt the user at
all. When the process finishes, the command should output a summary of
the configuration. The configuration is then stored in a "hidden"
directory which is similar to our current `tools` package. By default,
the labels in the configuration would direct to that package (always
mapped as a top-level package). The "hidden:" directory would live in
`$(output_base)/init/tools` and be mapped using the package path
mechanism. The `--overwrite` option would be needed to rerun the
automatic detection and overwrite everything including the eventual
user-set options.
For the hermetic mode, the user has to recreate the default tools
package inside the workspace. If the user has a package with the same
name in the workspace, then the "hidden" directory should be ignored
(--package_path).
To set a configuration option, the user would type `bazel init
java:jdk=/path/to/jdk` or to use the autodetection on a specific
option `bazel init java:jdk`. The list of settings group could be
obtained by `bazel init list` and the list of option with their value
for a specific language by `bazel init list group`. `bazel init list
all` could give the full configuration of all activated groups.
_Prospective idea:_ Bazel init should explore the BUILD file to find the
Skylark `load` statements, determine if there is an associated init
script and use it.
## V. Developer interface
This section presents the support for developer that wants to add
autoconfiguration for a ruleset. The developer adding a configuration
would provide with a configuration script for it. This script will be
in charge of creating the package in the tools directory during `bazel
init` (i.e., the script for Java support will construct the
//tools/jdk package in the "hidden" package path).
Because of skylark rules and the fact that the configuration script
should run before having access to the C++ and Java tooling, this
seems unreasonable to use a compiled language (Java or C++) for this
script. We could use the Skylark support to make it a subset of python
or we could use a bash script. Python support would be portable since
provided by Bazel itself and consistent with skylark. It also gives
immediate support for manipulating BUILD files. So keeping a
"skylark-like" syntax, the interface would look like:
```python
configuration(
name, # name of the tools package to configure
autodetect_method, # the auto detection method
generate_method, # the actual package generation
load_method, # A method to load the attributes presented
# to the user from the package
attrs = { # List of attributes this script propose
"jdk_path": String,
"__some_other_path": String, # not user-settable
"jdk_version": Integer,
})
```
Given that interface, an initial run of `bazel init` would do:
1. Find all language configuration scripts
2. Run `load_method` for each script
3. Run `autodetect_method` for each script. Replace non loaded
attribute (attribute still undefined after `load_method`) if and
only if `--rerun` option is provided
4. Run `generate_method` for each script
5. Fetch all non up to date dependencies of remote repository
See Appendix B for examples of such methods.
## VI. Implementation plan
1. Add the hidden tools directory and have it binded with package
path when no tools directory exists. The hidden tools directory
will have a WORKSPACE file and will have an automatic local
repository with the "init" name so that we can actually bind
targets from it into our workspace.
2. Add `bazel init` that support the configuration for native
packages in Java, that is: Java, C++, genrule and test. This
would create the necessary mechanisms for supporting the
developer and the basic user interface. This commands will be
totally in Java for now and should trigger the fetch part of the
remote repository.
3. Design and implement the language extension ala Skylark using the
design for the Java version of point 2.
4. Convert the existing configuration into that language.
5. Integrate the configuration with Skylark (i.e. Skylark rules
writer can add configuration step). We should here decide on
how it should be included (as a separate script? how do we
ship a skylark rule set? can we have load statement loading
a full set of rules?).
6. Create configuration for the existing skylark build rules. If
we support load statement with label, we can then create a
repository for Skylark rules.
## Appendix A. Various comments
1. We should get rid of the requirement for a `tools/defaults/BUILD` file.
2. To works correctly, we need some local caching of the bazel
repository so tools are available. We could have bazelrc specify a
global path to the local cache (with `/etc/bazel.bazelrc` being loaded
first to `~/.bazelrc`). We could use a `~/.bazel` directory to put an
updatable tools cache also. This is needed because user probably want
to initialize a workspace tooling on a plane
3. This proposal would probably add a new top-level package. We
should really take care of the naming convention for default top
packages (i.e., `tools`, `tools/defaults`, `visibility`, `external`,
`condition`...). We are going to make some user unhappy if they cannot
have an `external` directory at the top of their workspace (I would
just not use a build system that goes against my workspace structure).
While it is still time to do it, we should rename them with a nice
naming convention. A good way to do it is to make top-package name
constants, possibly settable in the WORKSPACE file (so we can actually
keep the name we like but user that are bothered by that can change
it).
4. As we will remove the tools directory from the workspace, it
makes sense to add another prelude_bazel file somewhere else. As the
`WORKSPACE` file controls the workspace, it makes sense to have the
`prelude_bazel` logic in it (and the load statement should support
labels so that a user can actually specify remote repository labels).
## Appendix B. Skylark-like code examples of configuration functions
This is just a quick draft, please feel free to propose improvements:
```python
# env is the environment, attrs are the values set either from the command-line
# or from loading the package
def autodetect_method(env, attrs):
if not attrs.java_version: # If not given in the command line nor loaded
attrs.java_version = 8
if not attrs.jdk_path:
if env.has("JDK_HOME"):
attrs.jdk_path = env.get("JDK_HOME")
elif env.os = "darwin":
attrs.jdk_path = system("/usr/libexec/java_home -v 1." + attrs.java_version + "+")
else:
attrs.jdk_path = basename(basename(readlink(env.path.find(java))))
if not attrs.jdk_path:
fail("Could not find JDK home, please set it with `bazel init java:jdk_path=/path/to/jdk`")
attrs.__some_other_path = first(glob(["/usr/bin/java", "/usr/local/bin/java"]))
# attrs is the list of attributes. It basically contains the list of rules
# we should generate in the corresponding package. Please note
# That all labels are replaced by relative ones as it should not be able
# to write out of the package.
def generate_method(attrs):
scratch_file("BUILD.jdk", """
Content of the jdk BUILD file.
""")
# Create binding using local_repository. This should not lie in
# the WORKSPACE file but in a separate WORKSPACE file in the hidden
# directory.
local_repository(name = "jdk", path = attrs.jdk_path, build_file = "BUILD.jdk")
bind("@jdk//jdk", "jdk") # also add a filegroup("jdk", "//external:jdk")
java_toolchain(name = "toolchain", source = attrs.java_version, target = attrs.java_version)
# The magic __BAZEL_*__ variable could be set so we don’t
# redownload the repository if possible. This install_target
# should leverage the work already done on remote repositories.
# This should build and copy the result into the tools directory with
# The corresponding exports_files now.
install_target(__BAZEL_REPOSITORY__, __BAZEL_VERSION__, "//src/java_tools/buildjar:JavaBuilder_deploy.jar")
install_target(__BAZEL_REPOSITORY__, __BAZEL_VERSION__, "//src/java_tools/buildjar:JavaBuilder_deploy.jar")
copy("https://ijar_url", "ijar")
# Load the package attributes.
# - attrs should be written and value will be replaced by the user-provided
# one if any
# - query is a query object restricted to the target package and resolving label
# relatively to the target package. This object should also be able to search
# for repository binding
# Note that the query will resolve in the actual tools directory, not the hidden
# one if it exists whereas the generation only happens in the hidden one.
def load_method(attrs, query):
java_toolchain = query.getOne(kind("java_toolchain", "..."))
if java_toolchain:
attrs.jdk_version = max(java_toolchain.source, java_toolchain.target)
jdk = query.getOne(attr("name", "jdk", kind("local_repository", "...")))
if jdk:
attrs.jdk_path = jdk.path
```