本页重点介绍如何编写与 Windows 兼容的规则、编写可移植规则时遇到的常见问题以及一些解决方案。
路径
问题:
长度限制:最长路径长度为 259 个字符。
虽然 Windows 也支持更长的路径(最多 32767 个字符),但许多程序都是按照较低的限制构建的。
请注意,在操作中运行的程序存在此问题。
工作目录:同样不得超过 259 个字符。
进程无法
cd
到长度超过 259 个字符的目录中。区分大小写:Windows 路径不区分大小写,Unix 路径区分大小写。
在为操作创建命令行时,请注意这一点。
路径分隔符:是反斜杠 (
\`), not forward slash (
/)。Bazel 以 Unix 风格存储路径,使用
/
分隔符。虽然某些 Windows 程序支持 Unix 样式的路径,但其他程序不支持。cmd.exe 中的某些内置命令支持它们,而有些则不支持。在为操作创建命令行和环境变量时,最好始终使用
\` separators on Windows: replace
/with
。绝对路径:不以斜杠 (
/
) 开头。Windows 上的绝对路径以盘符开头,例如
C:\foo\bar.txt
。没有单一的文件系统根。如果您的规则会检查路径是否为绝对路径,请注意这一点。应避免使用绝对路径,因为它们通常不可移植。
解决方案:
路径应尽量简短。
避免使用较长的目录名称、深度嵌套的目录结构、较长的文件名、较长的工作区名称、较长的目标名称。
所有这些都可能成为操作的输入文件的路径组成部分,并可能超出路径长度限制。
使用较短的输出根目录。
使用
--output_user_root=<path>
标志为 Bazel 输出指定短路径。最好有一个专门用于存储 Bazel 输出(例如D:\`), and adding this line to your
.bazelrc` 文件)的驱动器(或虚拟驱动器):build --output_user_root=D:/
或
build --output_user_root=C:/_bzl
使用连接点。
从广义上讲,联接是目录符号链接[1]。联接易于创建,并且可以指向具有长路径的目录(在同一台计算机上)。如果 build 操作创建的联接路径较短但目标路径较长,则具有短路径限制的工具可以访问联接目录中的文件。
在
.bat
文件或 cmd.exe 中,您可以创建如下所示的连接点:mklink /J c:\path\to\junction c:\path\to\very\long\target\path
[1]:严格来说,联接不是符号链接,但为了构建操作,您可以将联接视为目录符号链接。
将操作 / 环境变量中的路径中的
/
替换为 ``。为操作创建命令行或环境变量时,请使用 Windows 风格的路径。示例:
def as_path(p, is_windows): if is_windows: return p.replace("/", "\\") else: return p
环境变量
问题:
大小写区分:Windows 环境变量名称不区分大小写。
例如,在 Java 中,
System.getenv("SystemRoot")
和System.getenv("SYSTEMROOT")
会产生相同的结果。(这也适用于其他语言。)Hermeticity:操作应尽可能少地使用自定义环境变量。
环境变量是操作的缓存键的一部分。如果某项操作使用的环境变量经常变化或因用户而异,则该规则的可缓存性会降低。
解决方案:
仅使用大写环境变量名称。
此方法适用于 Windows、macOS 和 Linux。
尽量减少操作环境。
使用
ctx.actions.run
时,请将环境设置为ctx.configuration.default_shell_env
。如果操作需要更多环境变量,请将它们全部放入字典中,然后将该字典传递给操作。 示例: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})
操作
问题:
可执行输出:每个可执行文件都必须具有可执行扩展名。
最常见的扩展名是
.exe
(二进制文件)和.bat
(批处理脚本)。请注意,Shell 脚本 (
.sh
) 无法在 Windows 上执行;您无法将其指定为ctx.actions.run
的executable
。也没有文件可以拥有的+x
权限,因此您无法像在 Linux 上那样执行任意文件。Bash 命令:为了提高可移植性,请避免在操作中直接运行 Bash 命令。
Bash 在类 Unix 系统上广泛使用,但在 Windows 上通常不可用。Bazel 本身对 Bash (MSYS2) 的依赖程度越来越低,因此未来用户不太可能在安装 Bazel 的同时安装 MSYS2。为了便于在 Windows 上使用规则,请避免在操作中运行 Bash 命令。
行尾:Windows 使用 CRLF (
\r\n
),类 Unix 系统使用 LF (\n
)。在比较文本文件时,请注意这一点。请注意您的 Git 设置,尤其是在签出或提交时要注意行尾。(请参阅 Git 的
core.autocrlf
设置。)
解决方案:
使用无 Bash 的专用规则。
native.genrule()
是 Bash 命令的封装容器,通常用于解决简单问题,例如复制文件或写入文本文件。您可以避免依赖 Bash(并重新发明轮子):看看 bazel-skylib 是否有专门针对您需求的规则。在 Windows 上构建/测试时,它们都不依赖于 Bash。build 规则示例:
write_file()
(来源、文档):写入具有所需行尾(auto
、unix
或windows
)的文本文件,可以选择使其可执行(如果它是脚本)run_binary()
(来源、文档):以给定的输入和预期输出运行二进制文件(或*_binary
规则)作为 build 操作(这是ctx.actions.run
的 build 规则封装容器)native_binary()
(来源、文档):将原生二进制文件封装在*_binary
规则中,您可以在bazel run
中使用该规则,也可以在run_binary()
的tool
属性或native.genrule()
的tools
属性中使用该规则
测试规则示例:
在 Windows 上,请考虑使用
.bat
脚本来处理琐碎的事情。您可以使用
.bat
脚本解决琐碎的任务,而无需使用.sh
脚本。例如,如果您需要一个什么都不做、打印消息或以固定错误代码退出的脚本,那么简单的
.bat
文件就足够了。如果您的规则返回DefaultInfo()
提供程序,则executable
字段可能引用 Windows 上的该.bat
文件。由于文件扩展名在 macOS 和 Linux 上并不重要,因此您始终可以使用
.bat
作为扩展名,即使是对于 shell 脚本也是如此。请注意,无法执行空的
.bat
文件。如果您需要空白脚本,请在其中写入一个空格。以有原则的方式使用 Bash。
在 Starlark 构建和测试规则中,使用
ctx.actions.run_shell
以将 Bash 脚本和 Bash 命令作为操作运行。在 Starlark 宏中,将 Bash 脚本和命令封装在
native.sh_binary()
或native.genrule()
中。Bazel 将检查 Bash 是否可用,并通过 Bash 运行脚本或命令。在 Starlark 代码库规则中,尽量完全避免使用 Bash。Bazel 目前无法在代码库规则中以规范的方式运行 Bash 命令。
删除文件
问题:
文件处于打开状态时无法删除。
默认情况下,无法删除已打开的文件,尝试删除会导致“拒绝访问”错误。如果您无法删除某个文件,可能是因为某个正在运行的进程仍处于打开该文件的状态。
无法删除正在运行的进程的工作目录。
进程对其工作目录具有打开的句柄,并且在进程终止之前无法删除该目录。
解决方案:
在代码中,尝试尽早关闭文件。
在 Java 中使用
try-with-resources
。在 Python 中,使用with open(...) as f:
。原则上,应尽快尝试关闭句柄。