相约 2023 年 BazelCon 将于 10 月 24 日至 25 日在 Google 慕尼黑举办!了解详情

在 Windows 上编写规则

报告问题 查看源代码

本页将重点介绍如何编写与 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]。连接很容易创建,并且可以指向路径较长的目录(同一台计算机上)。如果构建操作创建路径较短但目标为较长的连接,则具有路径限制较短的工具可以访问位于该连接目录中的文件。

    .bat 文件或 cmd.exe 中,您可以按如下方式创建连接:

    mklink /J c:\path\to\junction c:\path\to\very\long\target\path
    

    [1]:严格来说,联结不是符号链接,但为了执行构建操作,您可以将 Junctions 视为目录符号链接。

  • 将 actions / envvars 路径中的 / 替换为 ``。

    为操作创建命令行或环境变量时,请将路径设为 Windows 样式。例如:

    def as_path(p, is_windows):
        if is_windows:
            return p.replace("/", "\\")
        else:
            return p
    

环境变量

问题:

  • 区分大小写:Windows 环境变量名称不区分大小写。

    例如,在 Java 中,System.getenv("SystemRoot")System.getenv("SYSTEMROOT") 会生成相同的结果。(这也适用于其他语言。)

  • 封闭性:操作应使用尽可能少的自定义环境变量。

    环境变量是操作的缓存键的一部分。如果某项操作使用的环境变量经常发生更改或针对用户自定义,则会导致该规则的缓存性降低。

解决方案:

  • 仅使用大写环境变量名称。

    这适用于 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.runexecutable。此外,也没有文件可以拥有的 +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。

    构建规则示例:

    • copy_file()源代码文档):将文件复制到其他位置,可以选择将其设为可执行文件

    • write_file()来源文档):写入具有所需行尾(autounixwindows)的文本文件,并视需要使其可执行(如果是脚本)

    • run_binary()源代码文档):以构建操作(这是 ctx.actions.run 的构建规则封装容器)运行具有指定输入和预期输出的二进制文件(或 *_binary 规则)

    • native_binary()来源文档):将原生二进制文件封装在 *_binary 规则中,您可以bazel run或在 run_binary()tool 属性或 native.genrule()tools 属性中使用该二进制文件

    测试规则示例:

    • diff_test()源代码文档):用于比较两个文件的内容的测试

    • native_test()来源文档):将原生二进制文件封装在 *_test 规则中,您可以bazel test

  • 在 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:。原则上,请尝试尽快关闭句柄。