-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Windows, docs: tips and tricks to write rules
See #3889 Change-Id: Id768d8bd0cb996af566cdda518772ea329e5c6b2 Closes #8420. Change-Id: I59c7952582decee585bbb23f7e8dc6d88e21aa73 PiperOrigin-RevId: 249431252
- Loading branch information
1 parent
207c24f
commit 108bc5a
Showing
2 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
--- | ||
layout: documentation | ||
title: Writing rules on Windows | ||
--- | ||
|
||
# Writing rules on Windows | ||
|
||
Common problems of writing portable rules, and some solutions. | ||
|
||
This document focuses on writing Windows-compatible rules. | ||
|
||
## Paths | ||
|
||
Problems: | ||
|
||
- **Length limit**: maximum path length is 259 characters. | ||
|
||
Though Windows also supports longer paths (up to 32767 characters), many programs are built with | ||
the lower limit. | ||
|
||
Be aware of this about programs you run in the actions. | ||
|
||
- **Working directory**: is also limited to 259 characters. | ||
|
||
Processes cannot `cd` into a directory longer than 259 characters. | ||
|
||
- **Case-sensitivity**: Windows paths are case-insensitive, Unix paths are case-sensitive. | ||
|
||
Be aware of this when creating command lines for actions. | ||
|
||
- **Path separators**: are backslash (`\`), not forward slash (`/`). | ||
|
||
Bazel stores paths Unix-style, i.e. with `/` separators. Though some Windows programs support | ||
Unix-style paths, others don't. Some built-in commands in cmd.exe support them, some don't. | ||
|
||
It's best to always use `\` separators on Windows: replace `/` with `\` when you create command | ||
lines and environment variables for actions. | ||
|
||
- **Absolute paths**: don't start with slash (`/`). | ||
|
||
Absolute paths on Windows start with a drive letter, e.g. `C:\foo\bar.txt`. There's no single | ||
filesystem root. | ||
|
||
Be aware of this if your rule checks if a path is absolute. (Absolute paths should be avoided | ||
though, they are often non-portable.) | ||
|
||
Solutions: | ||
|
||
- **Keep paths short.** | ||
|
||
Avoid long directory names, deeply nested directory structures, long file names, long workspace | ||
names, long target names. | ||
|
||
All of these may become path components of actions' input files, and may exhaust the path length | ||
limit. | ||
|
||
- **Use a short output root.** | ||
|
||
Use the `--output_user_root=<path>` flag to specify a short path for Bazel outputs. A good idea | ||
is to have a drive (or virtual drive) just for Bazel outputs (e.g. `D:\`), and adding this line | ||
to your `.bazelrc` file: | ||
|
||
``` | ||
build --output_user_root=D:/ | ||
``` | ||
|
||
or | ||
|
||
``` | ||
build --output_user_root=C:/_bzl | ||
``` | ||
|
||
- **Use junctions.** | ||
|
||
Junctions are, loosely speaking<sup>[1]</sup>, directory symlinks. Junctions are easy to create | ||
and can point to directories (on the same computer) with long paths. If a build action creates a | ||
junction whose path is short but whose target is long, then tools with short path limit can access | ||
the files in the junction'ed directory. | ||
|
||
In `.bat` files or in cmd.exe you can create junctions like so: | ||
|
||
``` | ||
mklink /J c:\path\to\junction c:\path\to\very\long\target\path | ||
``` | ||
|
||
<sup>[1]</sup>: Strictly speaking | ||
[Junctions are not Symbolic Links](https://superuser.com/a/343079), but for sake of build actions | ||
we may regard Junctions as Directory Symlinks. | ||
|
||
- **Replace `/` with `\` in paths in actions / envvars.** | ||
|
||
When you create the command line or environment variables for an action, make the paths | ||
Windows-style. Example: | ||
|
||
```python | ||
def as_path(p, is_windows): | ||
if is_windows: | ||
return p.replace("/", "\\") | ||
else: | ||
return p | ||
``` | ||
|
||
## Environment Variables | ||
|
||
Problems: | ||
|
||
- **Case-sensitivity**: Windows environment variable names are case-insensitive. | ||
|
||
For example, in Java `System.getenv("SystemRoot")` and `System.getenv("SYSTEMROOT")` yields the | ||
same result. (This applies to other languages too.) | ||
|
||
- **Hermeticity**: actions should use as few custom environment variables as possible. | ||
|
||
Environment variables are part of the action's cache key. If an action uses environment variables | ||
that change often, or are custom to users, that makes the rule less cache-able. | ||
|
||
Solutions: | ||
|
||
- **Only use upper-case environment variable names.** | ||
|
||
This works on Windows, macOS, and Linux. | ||
|
||
- **Minimize action environments.** | ||
|
||
When using `ctx.actions.run`, set the environment to `ctx.configuration.default_shell_env`. If the | ||
action needs more environment variables, put them all in a dictionary and pass that to the action. | ||
Example: | ||
|
||
```python | ||
load("@bazel_skylib//lib:dicts.bzl", "dicts") | ||
|
||
def _make_env(ctx, output_file, is_windows): | ||
out_path = output_file.path | ||
if is_windows: | ||
out_path = out_path.replace("/", "\\") | ||
return dicts.add(ctx.configuration.default_shell_env, {"MY_OUTPUT": out_path}) | ||
``` | ||
|
||
## Actions | ||
|
||
Problems: | ||
|
||
- **Executable outputs**: Every executable file must have an executable extension. | ||
|
||
The most common extensions are `.exe` (binary files) and `.bat` (Batch scripts). | ||
|
||
Be aware that shell scripts (`.sh`) are NOT executable on Windows, i.e. you cannot specify them as | ||
`ctx.actions.run`'s `executable`. There's also no `+x` permission that files can have, so you | ||
can't execute arbitrary files like on Linux. | ||
|
||
- **Bash commands**: For sake of portability, avoid running Bash commands directly in actions. | ||
|
||
Bash is widespread on Unix-like systems, but it's often unavailable on Windows. Bazel itself is | ||
relying less and less on Bash (MSYS2), so in the future users would be less likely to have MSYS2 | ||
installed along with Bazel. To make rules easier to use on Windows, avoid running Bash commands in | ||
actions. | ||
|
||
- **Line endings**: Windows uses CRLF (`\r\n`), Unix-like systems uses LF (`\n`). | ||
Be aware of this when comparing text files. Be mindful of your Git settings, especially of line | ||
endings when checking out or committing. (See Git's `core.autocrlf` setting.) | ||
|
||
Solutions: | ||
|
||
- **Use a Bash-less purpose-made rule.** | ||
|
||
`native.genrule()` is a wrapper for Bash commands, and it's often used to solve simple problems | ||
like copying a file or writing a text file. You can avoid relying on Bash (and reinventing the | ||
wheel): see if bazel-skylib has a purpose-made rule for your needs. None of them depends on Bash | ||
when built/tested on Windows. | ||
|
||
Build rule examples: | ||
|
||
- `copy_file()` | ||
([source](https://github.com/bazelbuild/bazel-skylib/blob/master/rules/copy_file.bzl), | ||
[documentation](https://github.com/bazelbuild/bazel-skylib/blob/master/docs/copy_file_doc.md)): | ||
copies a file somewhere else, optionally making it executable | ||
|
||
- `write_file()` | ||
([source](https://github.com/bazelbuild/bazel-skylib/blob/master/rules/write_file.bzl), | ||
[documentation](https://github.com/bazelbuild/bazel-skylib/blob/master/docs/write_file_doc.md)): | ||
writes a text file, with the desired line endings (`auto`, `unix`, or `windows`), optionally | ||
making it executable (if it's a script) | ||
|
||
- `run_binary()` | ||
([source](https://github.com/bazelbuild/bazel-skylib/blob/master/rules/run_binary.bzl), | ||
[documentation](https://github.com/bazelbuild/bazel-skylib/blob/master/docs/run_binary_doc.md)): | ||
runs a binary (or `*_binary` rule) with given inputs and expected outputs as a build action | ||
(this is a build rule wrapper for `ctx.actions.run`) | ||
|
||
- `native_binary()` | ||
([source](https://github.com/bazelbuild/bazel-skylib/blob/master/rules/native_binary.bzl), | ||
[documentation](https://github.com/bazelbuild/bazel-skylib/blob/master/docs/native_binary_doc.md#native_binary)): | ||
wraps a native binary in a `*_binary` rule, which you can `bazel run` or use in `run_binary()`'s | ||
`tool` attribute or `native.genrule()`'s `tools` attribute | ||
|
||
Test rule examples: | ||
|
||
- `diff_test()` | ||
([source](https://github.com/bazelbuild/bazel-skylib/blob/master/rules/diff_test.bzl), | ||
[documentation](https://github.com/bazelbuild/bazel-skylib/blob/master/docs/diff_test_doc.md)): | ||
test that compares contents of two files | ||
|
||
- `native_test()` | ||
([source](https://github.com/bazelbuild/bazel-skylib/blob/master/rules/native_binary.bzl), | ||
[documentation](https://github.com/bazelbuild/bazel-skylib/blob/master/docs/native_binary_doc.md#native_test)): | ||
wraps a native binary in a `*_test` rule, which you can `bazel test` | ||
|
||
- **On Windows, consider using `.bat` scripts for trivial things.** | ||
|
||
Instead of `.sh` scripts, you can solve trivial tasks with `.bat` scripts. | ||
|
||
For example, if you need a script that does nothing, or prints a message, or exits with a fixed | ||
error code, then a simple `.bat` file will suffice. If your rule returns a `DefaultInfo()` | ||
provider, the `executable` field may refer to that `.bat` file on Windows. | ||
|
||
And since file extensions don't matter on macOS and Linux, you can always use `.bat` as the | ||
extension, even for shell scripts. | ||
|
||
Be aware that empty `.bat` files cannot be executed. If you need an empty script, write one space | ||
in it. | ||
|
||
- **Use Bash in a principled way.** | ||
|
||
In Starlark build and test rules, use `ctx.actions.run_shell` to run Bash scripts and Bash | ||
commands as actions. | ||
|
||
In Starlark macros, wrap Bash scripts and commands in a `native.sh_binary()` or | ||
`native.genrule()`. Bazel will check if Bash is available and run the script or command through | ||
Bash. | ||
|
||
In Starlark repository rules, try avoiding Bash altogether. Bazel currently offers no way to run | ||
Bash commands in a principled way in repository rules. | ||
|
||
## Deleting files | ||
|
||
Problems: | ||
|
||
- **Files cannot be deleted while open.** | ||
|
||
Open files cannot be deleted (by default), attempts result in "Access Denied" errors. If you | ||
cannot delete a file, maybe a running process still holds it open. | ||
|
||
- **Working directory of a running process cannot be deleted.** | ||
|
||
Processes have an open handle to their working directory, and the directory cannot be deleted | ||
until the process terminates. | ||
|
||
Solutions: | ||
|
||
- **In your code, try to close files eagerly.** | ||
|
||
In Java, use `try-with-resources`. In Python, use `with open(...) as f:`. In principle, try | ||
closing handles as soon as possible. | ||
|
||
<!-- | ||
TODO: | ||
- runfiles, runfiles libraries, -nolegacy_external_runfiles | ||
- runfiles envvars, runfiles manifest structure | ||
- avoid using runfiles for things that could be inputs | ||
- whether to use runfiles manifest on non-windows | ||
- how to patch tools that expect to read from the filesystem to do a lookup through the manifest file instead (including helpers in many languages) | ||
- how this applies in tests as well that rely on $TEST_SRCDIR | ||
- unzip is slow | ||
- cmd.exe has 8k command length limit | ||
- put paths in envvars instead of args | ||
- put cmd.exe commands in .bat files | ||
- use ctx.resolve_tools instead of ctx.resolve_command (Bash dep) | ||
- how to run cmd.exe actions (maybe I should write a genrule-like rule for these) | ||
|
||
--> |