mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
Add Windows MSVC Bazel targets for native voice libraries (#43144)
## What changed - Add explicit x64 and ARM64 targets for native builds, runtime preparation, and linking using the existing voice recipes. Each target requires native Windows execution of the matching architecture. - Declare compiler, SDK, Python, and CMake inputs; require an explicitly supplied Cygwin/pkgconf tool tree and a fixed `SystemRoot`. Validate installed tool selections against the manifest and declared files. - Pair Windows DLLs with SDK import libraries, preserve plugin and receipt runfiles, and omit Unix runtime-search flags. Use Python for portable payload copying. - Correct the MSVC ARM64 tool directory casing to `HostArm64` and document provisioning and build commands. The generic Rust-consumer aliases remain separate; native Windows Bazel execution and consumer validation are still needed to establish complete Windows voice support. ## Testing Add eight unit tests covering tool selection, path anchoring, invalid inputs, and DLL/import-library copying, plus x64 and ARM64 link smoke targets that reference `gst_version`. GitOrigin-RevId: fb2b4998e9ffb5b64ca830ebf4bb4633fc959eb9
This commit is contained in:
committed by
copyberry
parent
008bbd5884
commit
1c40ffe427
@@ -174,7 +174,7 @@ use_repo(voice_python, "python_3_12")
|
||||
register_toolchains("@rules_foreign_cc//toolchains:built_make_toolchain")
|
||||
|
||||
foreign_cc_tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
|
||||
use_repo(foreign_cc_tools, "pkgconfig_src")
|
||||
use_repo(foreign_cc_tools, "cmake-3.31.8-windows-x86_64", "pkgconfig_src")
|
||||
|
||||
single_version_override(
|
||||
module_name = "rules_rs",
|
||||
|
||||
4
MODULE.bazel.lock
generated
4
MODULE.bazel.lock
generated
@@ -582,7 +582,7 @@
|
||||
},
|
||||
"@@windows_support+//windows:extensions.bzl%msvc_runtime": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "IARx/OSheg9jZOotMp87h4ZSH1sYXy2d0otLXaCQdL0=",
|
||||
"bzlTransitiveDigest": "hSBf9aCBJn12Lq4IKLqoUFuySmukaarmyjhYNGlHMX0=",
|
||||
"usagesDigest": "XZ1DGFYwC06tBdgHxuZLhTfFlPFVKjmYmiBfiY2xe3c=",
|
||||
"recordedInputs": [
|
||||
"REPO_MAPPING:windows_support+,bazel_tools bazel_tools"
|
||||
@@ -613,7 +613,7 @@
|
||||
},
|
||||
"@@windows_support+//windows:extensions.bzl%windows_sdk": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "IARx/OSheg9jZOotMp87h4ZSH1sYXy2d0otLXaCQdL0=",
|
||||
"bzlTransitiveDigest": "hSBf9aCBJn12Lq4IKLqoUFuySmukaarmyjhYNGlHMX0=",
|
||||
"usagesDigest": "M5xde76Im8TfiBsVtKQIavNOkqfp1QsjwJD4fVrrtFs=",
|
||||
"recordedInputs": [
|
||||
"REPO_MAPPING:windows_support+,bazel_tools bazel_tools"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/windows/private/extensions/msvc_runtime.bzl b/windows/private/extensions/msvc_runtime.bzl
|
||||
--- a/windows/private/extensions/msvc_runtime.bzl
|
||||
+++ b/windows/private/extensions/msvc_runtime.bzl
|
||||
@@ -204,13 +204,19 @@
|
||||
@@ -204,13 +204,20 @@
|
||||
_keep_only_children(
|
||||
repository_ctx,
|
||||
repository_ctx.path("{}/Contents/VC/Tools/MSVC/{}".format(sysroot_dir, msvc_version)),
|
||||
@@ -16,9 +16,10 @@ diff --git a/windows/private/extensions/msvc_runtime.bzl b/windows/private/exten
|
||||
+
|
||||
+ # Keep native-host tools and their support files separate from runtime inputs.
|
||||
+ bin_dir = "{}/Contents/VC/Tools/MSVC/{}/bin".format(sysroot_dir, msvc_version)
|
||||
+ _keep_only_children(repository_ctx, repository_ctx.path(bin_dir), ["Host" + arch for arch in architectures])
|
||||
+ _keep_only_children(repository_ctx, repository_ctx.path(bin_dir), ["HostArm64" if arch == "arm64" else "Host" + arch for arch in architectures])
|
||||
+ for arch in architectures:
|
||||
+ _keep_only_children(repository_ctx, repository_ctx.path(bin_dir + "/Host" + arch), [arch])
|
||||
+ host = "HostArm64" if arch == "arm64" else "Host" + arch
|
||||
+ _keep_only_children(repository_ctx, repository_ctx.path(bin_dir + "/" + host), [arch])
|
||||
|
||||
def _msvc_runtime_repository_impl(repository_ctx):
|
||||
_check_msvc_license_requirements(repository_ctx)
|
||||
@@ -69,7 +70,7 @@ diff --git a/windows/private/extensions/msvc_runtime.BUILD.bazel b/windows/priva
|
||||
+directory(
|
||||
+ name = "msvc_tools_arm64_files",
|
||||
+ srcs = glob(
|
||||
+ ["__MSVC_RUNTIME_DIR__/Contents/VC/Tools/MSVC/__MSVC_VERSION__/bin/Hostarm64/arm64/**"],
|
||||
+ ["__MSVC_RUNTIME_DIR__/Contents/VC/Tools/MSVC/__MSVC_VERSION__/bin/HostArm64/arm64/**"],
|
||||
+ allow_empty = True,
|
||||
+ ),
|
||||
+)
|
||||
@@ -77,7 +78,7 @@ diff --git a/windows/private/extensions/msvc_runtime.BUILD.bazel b/windows/priva
|
||||
+subdirectory(
|
||||
+ name = "msvc_tools_arm64",
|
||||
+ parent = ":msvc_tools_arm64_files",
|
||||
+ path = "__MSVC_RUNTIME_DIR__/Contents/VC/Tools/MSVC/__MSVC_VERSION__/bin/Hostarm64/arm64",
|
||||
+ path = "__MSVC_RUNTIME_DIR__/Contents/VC/Tools/MSVC/__MSVC_VERSION__/bin/HostArm64/arm64",
|
||||
+)
|
||||
diff --git a/windows/private/extensions/windows_sdk.bzl b/windows/private/extensions/windows_sdk.bzl
|
||||
--- a/windows/private/extensions/windows_sdk.bzl
|
||||
|
||||
119
third_party/voice/BUILD.bazel
vendored
119
third_party/voice/BUILD.bazel
vendored
@@ -1,8 +1,10 @@
|
||||
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
|
||||
load("@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl", "native_tool_toolchain")
|
||||
load(":native.bzl", "native_prefix")
|
||||
load(":native_link.bzl", "native_link")
|
||||
load(":pkg_config.bzl", "pkg_config")
|
||||
load(":runtime.bzl", "native_runtime")
|
||||
load(":windows_native.bzl", "windows_build_tools", "windows_native_prefix")
|
||||
|
||||
# All native build consumers must receive the same pinned source manifest.
|
||||
exports_files(["opus-toolchain.cmake"])
|
||||
@@ -243,3 +245,120 @@ alias(
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Explicit opt-in inputs: no private downloader is loaded by the public module.
|
||||
# CMake and Cygwin use x64 emulation on native ARM64; Python/MSVC/pkgconf do not.
|
||||
filegroup(
|
||||
name = "windows_tools_unavailable",
|
||||
srcs = [],
|
||||
)
|
||||
|
||||
label_flag(
|
||||
name = "windows_installed_tools",
|
||||
build_setting_default = ":windows_tools_unavailable",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
[
|
||||
config_setting(
|
||||
name = "windows_" + cpu + "_msvc",
|
||||
constraint_values = [
|
||||
"@platforms//os:windows",
|
||||
"@platforms//cpu:" + cpu,
|
||||
"@llvm//constraints/windows/abi:msvc",
|
||||
],
|
||||
)
|
||||
for cpu in ("x86_64", "aarch64")
|
||||
]
|
||||
|
||||
[
|
||||
windows_build_tools(
|
||||
name = "windows_tools_" + cpu,
|
||||
includes = ["@msvc_runtime//:msvc_include"] + ["@windows_sdk//:winsdk_" + part + "_include" for part in ("ucrt", "shared", "um", "winrt")],
|
||||
installed_tools = ":windows_installed_tools",
|
||||
libraries = ["@msvc_runtime//:msvc_lib_" + arch] + ["@windows_sdk//:winsdk_" + part + "_lib_" + arch for part in ("ucrt", "um")],
|
||||
msvc = "@msvc_runtime//:msvc_tools_" + arch,
|
||||
sdk = "@windows_sdk//:winsdk_tools_" + arch,
|
||||
tags = ["manual"],
|
||||
target = cpu + "-pc-windows-msvc",
|
||||
target_compatible_with = [
|
||||
"@platforms//os:windows",
|
||||
"@platforms//cpu:" + cpu,
|
||||
],
|
||||
)
|
||||
for cpu, arch in (
|
||||
("x86_64", "x64"),
|
||||
("aarch64", "arm64"),
|
||||
)
|
||||
]
|
||||
|
||||
[
|
||||
windows_native_prefix(
|
||||
name = "native_prefix_windows_" + cpu,
|
||||
archives = [":archives"],
|
||||
build_tools = ":windows_tools_" + cpu,
|
||||
exec_compatible_with = [
|
||||
"@platforms//os:windows",
|
||||
"@platforms//cpu:" + cpu,
|
||||
],
|
||||
tags = ["manual"],
|
||||
target_compatible_with = select({
|
||||
":windows_" + cpu + "_msvc": [],
|
||||
"//conditions:default": ["@platforms//:incompatible"],
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
for cpu in ("x86_64", "aarch64")
|
||||
]
|
||||
|
||||
[
|
||||
native_runtime(
|
||||
name = "native_runtime_windows_" + cpu,
|
||||
exec_compatible_with = [
|
||||
"@platforms//os:windows",
|
||||
"@platforms//cpu:" + cpu,
|
||||
],
|
||||
prefix = ":native_prefix_windows_" + cpu,
|
||||
tags = ["manual"],
|
||||
target = cpu + "-pc-windows-msvc",
|
||||
target_compatible_with = select({
|
||||
":windows_" + cpu + "_msvc": [],
|
||||
"//conditions:default": ["@platforms//:incompatible"],
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
windows_tools = ":windows_tools_" + cpu,
|
||||
)
|
||||
for cpu in ("x86_64", "aarch64")
|
||||
]
|
||||
|
||||
[
|
||||
native_link(
|
||||
name = "native_link_windows_" + cpu,
|
||||
runtime = ":native_runtime_windows_" + cpu,
|
||||
tags = ["manual"],
|
||||
target = cpu + "-pc-windows-msvc",
|
||||
target_compatible_with = select({
|
||||
":windows_" + cpu + "_msvc": [],
|
||||
"//conditions:default": ["@platforms//:incompatible"],
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
for cpu in ("x86_64", "aarch64")
|
||||
]
|
||||
|
||||
# Link a concrete symbol so CI checks the Windows CcInfo import-library path,
|
||||
# not only the native runtime and SDK artifact actions.
|
||||
[
|
||||
cc_binary(
|
||||
name = "windows_link_smoke_" + cpu,
|
||||
srcs = ["windows_link_smoke.cc"],
|
||||
tags = ["manual"],
|
||||
target_compatible_with = [
|
||||
"@platforms//os:windows",
|
||||
"@platforms//cpu:" + cpu,
|
||||
"@llvm//constraints/windows/abi:msvc",
|
||||
],
|
||||
deps = [":native_link_windows_" + cpu],
|
||||
)
|
||||
for cpu in ("x86_64", "aarch64")
|
||||
]
|
||||
|
||||
45
third_party/voice/README.md
vendored
45
third_party/voice/README.md
vendored
@@ -240,3 +240,48 @@ Microsoft Visual C++ runtime (`VCRUNTIME140.dll`) already installed. The latter
|
||||
is not a guaranteed OS component. Release redistribution/licensing, Authenticode
|
||||
policy and actual helper loading remain separate requirements; this script does
|
||||
not install or redistribute Microsoft runtime files or enable voice.
|
||||
# Windows Bazel inputs and actions
|
||||
|
||||
The named `native_prefix_windows_{x86_64,aarch64}`,
|
||||
`native_runtime_windows_{x86_64,aarch64}` and
|
||||
`native_link_windows_{x86_64,aarch64}` targets use the existing native recipe,
|
||||
runtime inspection and SDK export. These targets are MSVC-only. They require
|
||||
native Windows execution of the matching architecture; they are not cross builds.
|
||||
The generic Rust-consumer aliases are connected separately, after these inputs.
|
||||
|
||||
Provide the complete installed Cygwin/pkgconf repository explicitly. The default
|
||||
`windows_installed_tools` label setting is empty and fails if a Windows action
|
||||
needs it. This keeps ordinary public dependency queries independent of private
|
||||
provisioning; it does not silently omit tools from a requested Windows build.
|
||||
The module does not download that installed tree or accept compiler licenses.
|
||||
|
||||
After provisioning, a native PowerShell invocation is:
|
||||
|
||||
```powershell
|
||||
bazel build //third_party/voice:native_link_windows_x86_64 `
|
||||
--platforms=//:local_windows_msvc `
|
||||
--inject_repository="voice_windows_tools=$env:VOICE_WINDOWS_BAZEL_REPOSITORY" `
|
||||
--//third_party/voice:windows_installed_tools=@voice_windows_tools//:tools `
|
||||
--action_env="SystemRoot=$env:SystemRoot" --host_action_env="SystemRoot=$env:SystemRoot"
|
||||
```
|
||||
|
||||
Use `native_link_windows_aarch64` on native ARM64 Windows. Existing MSVC license
|
||||
acceptance requirements still apply. `SystemRoot` must be a fixed action value,
|
||||
not the inherited form `--action_env=SystemRoot`; Bazel analysis cannot inspect
|
||||
that inherited value. The action validates the Windows command directory and
|
||||
constructs its tool search path from declared inputs, not developer PATH entries.
|
||||
|
||||
Compiler, SDK, Python and CMake inputs use the existing pinned repositories.
|
||||
CMake and Cygwin execute as x64 under emulation on ARM64; compiler, SDK tools,
|
||||
Python and pkgconf match the native architecture. Complete support, include and
|
||||
library files are declared, and installed entrypoints must match the supplied
|
||||
target/architecture manifest and belong to that declared tree. This does not
|
||||
turn caller-provided files into authenticated inputs: provisioning retains that
|
||||
responsibility. These build tools must never enter shipped Codex packages.
|
||||
|
||||
Windows link inputs pair SDK import libraries with the corresponding prepared
|
||||
DLLs. DLLs, plugins and the receipt remain under the normal native-link runfiles
|
||||
layout; Windows receives no ELF or Mach-O runtime-search flags. This capability
|
||||
still needs real native x64/ARM64 Bazel execution and consumer validation before
|
||||
it establishes complete Windows voice support. The existing direct native recipe
|
||||
passing on Windows does not prove this new Bazel input and execution path.
|
||||
|
||||
19
third_party/voice/bazel_copy.py
vendored
Normal file
19
third_party/voice/bazel_copy.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Copy declared library payloads while keeping a real native-search directory."""
|
||||
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
def copy_payloads(locator, pairs):
|
||||
if len(pairs) % 2:
|
||||
raise ValueError("library copies require source/destination pairs")
|
||||
Path(locator).mkdir(parents=True, exist_ok=True)
|
||||
for source, destination in zip(pairs[::2], pairs[1::2], strict=True):
|
||||
output = Path(destination)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
copy_payloads(sys.argv[1], sys.argv[2:])
|
||||
171
third_party/voice/bazel_windows.py
vendored
Normal file
171
third_party/voice/bazel_windows.py
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
"""Adapt declared Windows tool paths to the existing native build/runtime recipes."""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from build_native import NativeBuild
|
||||
from windows_build_inputs import build_environment
|
||||
|
||||
|
||||
def selected_inputs(config, root):
|
||||
inputs = dict(config["inputs"])
|
||||
tools = {name: (root / path).absolute() for name, path in inputs["tools"].items()}
|
||||
manifest = (root / config["manifest"]).absolute()
|
||||
declared = {(root / path).absolute() for path in config["installed_files"]}
|
||||
if manifest.stat().st_size > 65536:
|
||||
raise ValueError("installed Windows tool metadata exceeds limits")
|
||||
installed = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
expected = {"shell", "make", "cygpath", "automake", "pkg_config"}
|
||||
if (
|
||||
installed.get("schemaVersion") != 1
|
||||
or installed.get("target") != inputs["target"]
|
||||
or installed.get("cygwinArchitecture") != "x86_64"
|
||||
or set(installed.get("tools", {})) != expected
|
||||
):
|
||||
raise ValueError("installed Windows tools do not match the selected target")
|
||||
for name in expected:
|
||||
relative = Path(installed["tools"][name])
|
||||
source = (manifest.parent / relative).absolute()
|
||||
if (
|
||||
relative.is_absolute()
|
||||
or ".." in relative.parts
|
||||
or source not in declared
|
||||
or (name == "pkg_config" and tools.get(name) != source)
|
||||
or not source.resolve(strict=True).is_relative_to(
|
||||
manifest.parent.resolve(strict=True)
|
||||
)
|
||||
):
|
||||
raise ValueError(f"installed Windows tool selection differs: {name}")
|
||||
tools[name] = source
|
||||
inputs["tools"] = {name: str(path) for name, path in tools.items()}
|
||||
for name in ("INCLUDE", "LIB"):
|
||||
inputs[name] = [str((root / path).absolute()) for path in inputs[name]]
|
||||
return inputs
|
||||
|
||||
|
||||
def main():
|
||||
operation, config_path = sys.argv[1:]
|
||||
config = json.loads(Path(config_path).read_text(encoding="utf-8"))
|
||||
root = Path.cwd()
|
||||
with tempfile.TemporaryDirectory(prefix="voice-windows-") as temporary:
|
||||
temporary = Path(temporary)
|
||||
document = temporary / "windows-inputs.json"
|
||||
inputs = selected_inputs(config, root)
|
||||
document.write_text(json.dumps(inputs), encoding="utf-8")
|
||||
environment, _ = build_environment(
|
||||
document, inputs["target"], {"python": Path(sys.executable)}
|
||||
)
|
||||
# Keep executor scratch directories, never its developer tool search path.
|
||||
environment.update(
|
||||
{name: os.environ[name] for name in ("TMP", "TEMP") if name in os.environ}
|
||||
)
|
||||
home = temporary / "home"
|
||||
home.mkdir()
|
||||
environment.update({"HOME": str(home), "USERPROFILE": str(home)})
|
||||
os.environ.clear()
|
||||
os.environ.update(environment)
|
||||
if operation == "prepare":
|
||||
from prepare_built_runtime import prepare_archive
|
||||
|
||||
output = root / config["output"]
|
||||
sdk = root / config["sdk"]
|
||||
for path in (output, sdk):
|
||||
if path.exists():
|
||||
path.rmdir()
|
||||
try:
|
||||
prepare_archive(
|
||||
root / config["prefix"],
|
||||
root / config["receipt"],
|
||||
root / config["status"],
|
||||
inputs["target"],
|
||||
output,
|
||||
sdk_output=sdk,
|
||||
)
|
||||
except ValueError as exc:
|
||||
if str(exc) != "native build receipt is incomplete or mismatched":
|
||||
raise
|
||||
receipt = json.loads((root / config["receipt"]).read_text())
|
||||
commits = [
|
||||
line
|
||||
for line in (root / config["status"]).read_text().splitlines()
|
||||
if line.startswith("STABLE_GIT_COMMIT ")
|
||||
]
|
||||
steps = receipt.get("steps", [])
|
||||
checks = {
|
||||
"commit_count": len(commits) == 1,
|
||||
"commit_format": len(commits) == 1
|
||||
and bool(
|
||||
re.fullmatch(r"STABLE_GIT_COMMIT [0-9a-f]{40}", commits[0])
|
||||
),
|
||||
"target": receipt.get("target") == inputs["target"],
|
||||
"manifest": receipt.get("manifest_sha256")
|
||||
== hashlib.sha256(
|
||||
Path(__file__).with_name("sources.json").read_bytes()
|
||||
).hexdigest(),
|
||||
"step_count": 1 <= len(steps) <= 128,
|
||||
"step_exit": all(step.get("exit_code") == 0 for step in steps),
|
||||
"last_step": bool(steps)
|
||||
and steps[-1].get("name") == "gst-plugins-good-install",
|
||||
}
|
||||
print(
|
||||
"Receipt checks failed: "
|
||||
+ ", ".join(name for name, valid in checks.items() if not valid),
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
return
|
||||
if operation != "build":
|
||||
raise ValueError("unknown Windows native action")
|
||||
archives = temporary / "archives"
|
||||
archives.mkdir()
|
||||
for path in config["archives"]:
|
||||
source = root / path
|
||||
shutil.copyfile(source, archives / source.name)
|
||||
tools = inputs["tools"]
|
||||
builder = NativeBuild(
|
||||
SimpleNamespace(
|
||||
target=inputs["target"],
|
||||
deployment_target=None,
|
||||
jobs=8,
|
||||
output=temporary / "build",
|
||||
archives=archives,
|
||||
windows_build_inputs=document,
|
||||
**{
|
||||
name: Path(tools[name])
|
||||
for name in (
|
||||
"cc",
|
||||
"cxx",
|
||||
"cmake",
|
||||
"make",
|
||||
"pkg_config",
|
||||
"shell",
|
||||
"bootstrap_make",
|
||||
)
|
||||
},
|
||||
),
|
||||
environment,
|
||||
)
|
||||
try:
|
||||
builder.build()
|
||||
except Exception:
|
||||
if builder.record["steps"]:
|
||||
log = builder.output / (builder.record["steps"][-1]["name"] + ".log")
|
||||
if log.is_file():
|
||||
print(log.read_text(errors="replace"), file=sys.stderr)
|
||||
raise
|
||||
with tarfile.open(root / config["prefix"], "w") as archive:
|
||||
archive.add(builder.prefix, arcname=".")
|
||||
shutil.copyfile(builder.output / "built.json", root / config["receipt"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
33
third_party/voice/native_link.bzl
vendored
33
third_party/voice/native_link.bzl
vendored
@@ -3,6 +3,7 @@
|
||||
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
|
||||
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
|
||||
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
|
||||
load("@rules_python//python:py_runtime_info.bzl", "PyRuntimeInfo")
|
||||
|
||||
# ABI filenames from the pinned native sources. Missing outputs fail the build.
|
||||
_ABI_VERSIONS = {
|
||||
@@ -30,8 +31,10 @@ def _native_link_impl(ctx):
|
||||
runtime = ctx.file.runtime
|
||||
versions = dict(_ABI_VERSIONS)
|
||||
macos = ctx.attr.target.endswith("apple-darwin")
|
||||
if not macos:
|
||||
windows = ctx.attr.target.endswith("windows-msvc")
|
||||
if ctx.attr.target.endswith("unknown-linux-gnu"):
|
||||
versions["gstallocators-1.0"] = "0"
|
||||
sdk = ctx.attr.runtime[OutputGroupInfo].sdk.to_list()[0] if windows else None
|
||||
locator = ctx.actions.declare_directory(ctx.label.name + "/lib/search-path")
|
||||
originals, aliases, libraries = [], [], []
|
||||
arguments = [locator.path]
|
||||
@@ -45,39 +48,47 @@ def _native_link_impl(ctx):
|
||||
for name, version in versions.items():
|
||||
filename = "lib" + name + ("." + version + ".dylib" if macos else ".so." + version)
|
||||
alias = "lib" + name + (".dylib" if macos else ".so")
|
||||
library = ctx.actions.declare_file(ctx.label.name + "/lib/" + filename)
|
||||
directory = "lib"
|
||||
if windows:
|
||||
# Windows names come from the same pinned recipe's SDK and runtime.
|
||||
filename = {"ffi": "libffi-8.dll", "intl": "intl-8.dll", "opus": "opus.dll", "pcre2-8": "pcre2-8.dll", "z": "z.dll"}.get(name, name + "-0.dll")
|
||||
alias = ("libffi" if name == "ffi" else name) + ".lib"
|
||||
directory = "bin"
|
||||
library = ctx.actions.declare_file(ctx.label.name + "/" + directory + "/" + filename)
|
||||
development = ctx.actions.declare_file(ctx.label.name + "/lib/" + alias)
|
||||
originals.append(library)
|
||||
aliases.append(development)
|
||||
arguments.extend([runtime.path + "/lib/" + filename, library.path])
|
||||
arguments.extend([runtime.path + "/lib/" + filename, development.path])
|
||||
arguments.extend([runtime.path + "/" + directory + "/" + filename, library.path])
|
||||
arguments.extend([sdk.path + "/lib/" + alias if windows else runtime.path + "/lib/" + filename, development.path])
|
||||
libraries.append(cc_common.create_library_to_link(
|
||||
actions = ctx.actions,
|
||||
feature_configuration = features,
|
||||
cc_toolchain = cc,
|
||||
dynamic_library = library,
|
||||
interface_library = development if windows else None,
|
||||
# @loader_path/$ORIGIN dependencies must stay beside one another.
|
||||
dynamic_library_symlink_path = "voice/" + ctx.label.name + "/" + filename,
|
||||
))
|
||||
payloads = []
|
||||
paths = ["runtime.json"] + [
|
||||
("plugins/libgst" + plugin + ".dylib" if macos else "lib/gstreamer-1.0/libgst" + plugin + ".so")
|
||||
("bin/gst" + plugin + ".dll" if windows else "plugins/libgst" + plugin + ".dylib" if macos else "lib/gstreamer-1.0/libgst" + plugin + ".so")
|
||||
for plugin in "app audioconvert audioresample coreelements opus rtp rtpmanager".split(" ")
|
||||
]
|
||||
for path in paths:
|
||||
payload = ctx.actions.declare_file(ctx.label.name + "/" + path)
|
||||
payloads.append(payload)
|
||||
arguments.extend([runtime.path + "/" + path, payload.path])
|
||||
ctx.actions.run_shell(
|
||||
inputs = [runtime],
|
||||
python = ctx.attr._python[PyRuntimeInfo]
|
||||
ctx.actions.run(
|
||||
executable = python.interpreter,
|
||||
inputs = depset([runtime, ctx.file._copy, python.interpreter] + ([sdk] if windows else []), transitive = [python.files]),
|
||||
outputs = [locator] + originals + aliases + payloads,
|
||||
arguments = arguments,
|
||||
command = 'set -eu; /bin/mkdir -p "$1"; shift; while [ "$#" -gt 0 ]; do /bin/mkdir -p "${2%/*}"; /bin/cp "$1" "$2"; shift 2; done',
|
||||
arguments = [ctx.file._copy.path] + arguments,
|
||||
mnemonic = "VoiceNativeLinkInputs",
|
||||
)
|
||||
linker = cc_common.create_linker_input(
|
||||
owner = ctx.label,
|
||||
user_link_flags = depset([
|
||||
user_link_flags = depset([] if windows else [
|
||||
"-Wl,-rpath," + ("@loader_path/../lib" if macos else "$ORIGIN/../lib"),
|
||||
]),
|
||||
libraries = depset(libraries),
|
||||
@@ -98,6 +109,8 @@ native_link = rule(
|
||||
attrs = {
|
||||
"runtime": attr.label(mandatory = True, allow_single_file = True),
|
||||
"target": attr.string(mandatory = True),
|
||||
"_copy": attr.label(default = "//third_party/voice:bazel_copy.py", allow_single_file = True),
|
||||
"_python": attr.label(default = "@python_3_12//:py3_runtime", cfg = "exec"),
|
||||
},
|
||||
fragments = ["cpp"],
|
||||
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
|
||||
|
||||
28
third_party/voice/runtime.bzl
vendored
28
third_party/voice/runtime.bzl
vendored
@@ -1,6 +1,7 @@
|
||||
"""Prepare build-produced native libraries with the existing platform policy."""
|
||||
|
||||
load("@rules_python//python:py_runtime_info.bzl", "PyRuntimeInfo")
|
||||
load(":windows_native.bzl", "WindowsBuildToolsInfo")
|
||||
|
||||
def _native_runtime_impl(ctx):
|
||||
python = ctx.attr._python[PyRuntimeInfo]
|
||||
@@ -8,6 +9,31 @@ def _native_runtime_impl(ctx):
|
||||
receipt = ctx.attr.prefix[OutputGroupInfo].receipt.to_list()[0]
|
||||
output = ctx.actions.declare_directory(ctx.label.name)
|
||||
sdk = ctx.actions.declare_directory(ctx.label.name + "_sdk")
|
||||
if ctx.attr.windows_tools:
|
||||
tools = ctx.attr.windows_tools[WindowsBuildToolsInfo]
|
||||
if tools.inputs["target"] != ctx.attr.target:
|
||||
fail("Windows runtime target must match its declared native tools")
|
||||
config = ctx.actions.declare_file(ctx.label.name + ".json")
|
||||
ctx.actions.write(config, json.encode({
|
||||
"inputs": tools.inputs,
|
||||
"manifest": tools.manifest.path,
|
||||
"installed_files": [file.path for file in tools.installed_files],
|
||||
"prefix": prefix.path,
|
||||
"receipt": receipt.path,
|
||||
"status": ctx.info_file.path,
|
||||
"output": output.path,
|
||||
"sdk": sdk.path,
|
||||
}))
|
||||
ctx.actions.run(
|
||||
executable = tools.python.interpreter,
|
||||
arguments = [ctx.file._windows_driver.path, "prepare", config.path],
|
||||
inputs = depset([config, prefix, receipt, ctx.info_file, ctx.file._driver, ctx.file._windows_driver] + ctx.files._preparers, transitive = [tools.files]),
|
||||
outputs = [output, sdk],
|
||||
env = tools.environment,
|
||||
execution_requirements = {"no-remote-exec": "1"},
|
||||
mnemonic = "VoiceWindowsRuntime",
|
||||
)
|
||||
return [DefaultInfo(files = depset([output])), OutputGroupInfo(sdk = depset([sdk]))]
|
||||
ctx.actions.run(
|
||||
executable = python.interpreter,
|
||||
arguments = [
|
||||
@@ -45,6 +71,8 @@ native_runtime = rule(
|
||||
attrs = {
|
||||
"prefix": attr.label(mandatory = True, allow_single_file = True),
|
||||
"target": attr.string(mandatory = True),
|
||||
"windows_tools": attr.label(cfg = "exec", providers = [WindowsBuildToolsInfo]),
|
||||
"_windows_driver": attr.label(default = "//third_party/voice:bazel_windows.py", allow_single_file = True),
|
||||
"_driver": attr.label(default = "//third_party/voice:prepare_built_runtime.py", allow_single_file = True),
|
||||
"_preparers": attr.label(default = "//third_party/voice:build_inputs"),
|
||||
"_python": attr.label(default = "@python_3_12//:py3_runtime", cfg = "exec"),
|
||||
|
||||
170
third_party/voice/test_bazel_windows.py
vendored
Normal file
170
third_party/voice/test_bazel_windows.py
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
"""Exercise declared tool selection and the platform-independent payload copy action."""
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from bazel_copy import copy_payloads
|
||||
from bazel_windows import selected_inputs
|
||||
|
||||
|
||||
class WindowsInputsTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(temporary.cleanup)
|
||||
self.root = Path(temporary.name)
|
||||
self.repository = self.root / "external/installed tools"
|
||||
self.names = {
|
||||
"shell": "cygwin/bin/bash.exe",
|
||||
"make": "cygwin/bin/make.exe",
|
||||
"cygpath": "cygwin/bin/cygpath.exe",
|
||||
"automake": "cygwin/bin/automake-1.18",
|
||||
"pkg_config": "pkgconf-image/PFiles64/pkgconf 3.0.6/pkgconf.exe",
|
||||
}
|
||||
for name in self.names.values():
|
||||
path = self.repository / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(b"synthetic declared executable")
|
||||
self.manifest = self.repository / "voice-tools.json"
|
||||
self.metadata = {
|
||||
"schemaVersion": 1,
|
||||
"target": "aarch64-pc-windows-msvc",
|
||||
"cygwinArchitecture": "x86_64",
|
||||
"tools": self.names,
|
||||
}
|
||||
self.manifest.write_text(json.dumps(self.metadata))
|
||||
self.config = {
|
||||
"inputs": {
|
||||
"schemaVersion": 1,
|
||||
"target": "aarch64-pc-windows-msvc",
|
||||
"systemRoot": "C:/Windows",
|
||||
"tools": {
|
||||
"cc": "external/msvc/cl.exe",
|
||||
"pkg_config": str(
|
||||
(self.repository / self.names["pkg_config"]).relative_to(
|
||||
self.root
|
||||
)
|
||||
),
|
||||
},
|
||||
"INCLUDE": ["external/sdk/include"],
|
||||
"LIB": ["external/sdk/lib"],
|
||||
},
|
||||
"manifest": str(self.manifest.relative_to(self.root)),
|
||||
"installed_files": [
|
||||
str((self.repository / name).relative_to(self.root))
|
||||
for name in self.names.values()
|
||||
],
|
||||
}
|
||||
|
||||
def test_paths_are_anchored_before_recipe_changes_directory(self):
|
||||
before = copy.deepcopy(self.config)
|
||||
expected = {
|
||||
**self.config["inputs"],
|
||||
"tools": {
|
||||
"cc": str(self.root / "external/msvc/cl.exe"),
|
||||
**{
|
||||
name: str(self.repository / path)
|
||||
for name, path in self.names.items()
|
||||
},
|
||||
},
|
||||
"INCLUDE": [str(self.root / "external/sdk/include")],
|
||||
"LIB": [str(self.root / "external/sdk/lib")],
|
||||
}
|
||||
self.assertEqual(selected_inputs(self.config, self.root), expected)
|
||||
self.assertEqual(self.config, before)
|
||||
|
||||
def test_manifest_cannot_select_a_file_omitted_from_action_inputs(self):
|
||||
self.config["installed_files"].pop()
|
||||
with self.assertRaisesRegex(ValueError, "selection differs: pkg_config"):
|
||||
selected_inputs(self.config, self.root)
|
||||
|
||||
def test_manifest_cannot_escape_installed_tree(self):
|
||||
outside = self.repository.parent / "outside.exe"
|
||||
outside.write_bytes(b"not part of the installed support tree")
|
||||
inside = self.repository / "cygwin/bin/other-bash.exe"
|
||||
try:
|
||||
inside.symlink_to(outside)
|
||||
except OSError as error:
|
||||
if error.errno not in (errno.EPERM, errno.EACCES):
|
||||
raise
|
||||
self.skipTest("creating symlinks requires OS permission")
|
||||
self.config["installed_files"].append(str(inside.relative_to(self.root)))
|
||||
self.metadata["tools"]["shell"] = str(inside.relative_to(self.repository))
|
||||
self.manifest.write_text(json.dumps(self.metadata))
|
||||
with self.assertRaisesRegex(ValueError, "selection differs: shell"):
|
||||
selected_inputs(self.config, self.root)
|
||||
|
||||
def test_manifest_cannot_replace_the_build_script_executable(self):
|
||||
alternative = self.repository / "pkgconf-image/another/pkgconf.exe"
|
||||
alternative.parent.mkdir(parents=True)
|
||||
alternative.write_bytes(b"different declared executable")
|
||||
self.config["installed_files"].append(str(alternative.relative_to(self.root)))
|
||||
self.metadata["tools"]["pkg_config"] = str(
|
||||
alternative.relative_to(self.repository)
|
||||
)
|
||||
self.manifest.write_text(json.dumps(self.metadata))
|
||||
with self.assertRaisesRegex(ValueError, "selection differs: pkg_config"):
|
||||
selected_inputs(self.config, self.root)
|
||||
|
||||
def test_wrong_target_or_emulation_contract_is_rejected(self):
|
||||
for key, value in (
|
||||
("target", "x86_64-pc-windows-msvc"),
|
||||
("cygwinArchitecture", "aarch64"),
|
||||
):
|
||||
with self.subTest(key=key):
|
||||
self.manifest.write_text(json.dumps({**self.metadata, key: value}))
|
||||
with self.assertRaisesRegex(ValueError, "selected target"):
|
||||
selected_inputs(self.config, self.root)
|
||||
|
||||
def test_missing_declared_executable_does_not_fall_back_to_path(self):
|
||||
(self.repository / self.names["shell"]).unlink()
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
selected_inputs(self.config, self.root)
|
||||
|
||||
|
||||
class LibraryCopiesTests(unittest.TestCase):
|
||||
def test_import_library_and_dll_bytes_remain_distinct(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
dll = root / "source.dll"
|
||||
library = root / "source.lib"
|
||||
dll.write_bytes(b"DLL payload")
|
||||
library.write_bytes(b"import library payload")
|
||||
locator = root / "package/lib/search-path"
|
||||
copy_payloads(
|
||||
locator,
|
||||
[
|
||||
dll,
|
||||
root / "package/bin/audio.dll",
|
||||
library,
|
||||
root / "package/lib/audio.lib",
|
||||
],
|
||||
)
|
||||
self.assertTrue(locator.is_dir())
|
||||
self.assertEqual(
|
||||
{
|
||||
path.relative_to(root / "package").as_posix(): path.read_bytes()
|
||||
for path in (root / "package").rglob("*")
|
||||
if path.is_file()
|
||||
},
|
||||
{
|
||||
"bin/audio.dll": b"DLL payload",
|
||||
"lib/audio.lib": b"import library payload",
|
||||
},
|
||||
)
|
||||
|
||||
def test_missing_payload_fails_the_action(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
copy_payloads(
|
||||
root / "lib/search-path",
|
||||
[root / "missing.dll", root / "bin/audio.dll"],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
11
third_party/voice/windows_link_smoke.cc
vendored
Normal file
11
third_party/voice/windows_link_smoke.cc
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Force a real final link against the prepared GStreamer import library.
|
||||
extern "C" void gst_version(unsigned int*, unsigned int*, unsigned int*, unsigned int*);
|
||||
|
||||
int main() {
|
||||
unsigned int major = 0;
|
||||
unsigned int minor = 0;
|
||||
unsigned int micro = 0;
|
||||
unsigned int nano = 0;
|
||||
gst_version(&major, &minor, µ, &nano);
|
||||
return major == 1 ? 0 : 1;
|
||||
}
|
||||
129
third_party/voice/windows_native.bzl
vendored
Normal file
129
third_party/voice/windows_native.bzl
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Native Windows audio actions with complete, explicitly provisioned tool inputs."""
|
||||
|
||||
load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo")
|
||||
load("@rules_python//python:py_runtime_info.bzl", "PyRuntimeInfo")
|
||||
|
||||
WindowsBuildToolsInfo = provider(
|
||||
doc = "Selected native Windows tools, their support closure and recipe input document.",
|
||||
fields = {
|
||||
"files": "Complete declared executable, support, header and library files.",
|
||||
"inputs": "Existing windows_build_inputs schema, with execroot-relative tool paths.",
|
||||
"manifest": "Imported installed-tree manifest File.",
|
||||
"installed_files": "Files supplied by the explicitly selected installed tool repository.",
|
||||
"python": "Declared PyRuntimeInfo used by the actions.",
|
||||
"environment": "Explicit Windows OS environment; never a developer PATH.",
|
||||
},
|
||||
)
|
||||
|
||||
def _windows_tools_impl(ctx):
|
||||
architectures = {"x86_64-pc-windows-msvc": "ml64.exe", "aarch64-pc-windows-msvc": "armasm64.exe"}
|
||||
if ctx.attr.target not in architectures:
|
||||
fail("Windows native tools require a supported MSVC target")
|
||||
python = ctx.attr._python[PyRuntimeInfo]
|
||||
if not python.interpreter:
|
||||
fail("Windows native tools require a declared native Python interpreter")
|
||||
system_root = ctx.configuration.default_shell_env.get("SystemRoot")
|
||||
if not system_root:
|
||||
fail("Pass --action_env=SystemRoot=<actual Windows directory> as a fixed value")
|
||||
msvc = ctx.attr.msvc[DirectoryInfo]
|
||||
sdk = ctx.attr.sdk[DirectoryInfo]
|
||||
tools = {
|
||||
name: msvc.get_file(filename)
|
||||
for name, filename in {
|
||||
"cc": "cl.exe",
|
||||
"cxx": "cl.exe",
|
||||
"link": "link.exe",
|
||||
"lib": "lib.exe",
|
||||
"dumpbin": "dumpbin.exe",
|
||||
"bootstrap_make": "nmake.exe",
|
||||
"assembler": architectures[ctx.attr.target],
|
||||
}.items()
|
||||
}
|
||||
tools.update({"rc": sdk.get_file("rc.exe"), "mt": sdk.get_file("mt.exe")})
|
||||
tools.update({"cmake": ctx.file.cmake, "python": python.interpreter})
|
||||
installed = ctx.files.installed_tools
|
||||
manifests = [file for file in installed if file.basename == "voice-tools.json"]
|
||||
if len(manifests) != 1:
|
||||
fail("Select a complete installed tool repository with --//third_party/voice:windows_installed_tools=<tools label>")
|
||||
pkgconf_root = manifests[0].dirname + "/pkgconf-image/"
|
||||
pkgconf_files = [file for file in installed if file.path.startswith(pkgconf_root)]
|
||||
pkgconf = [file for file in pkgconf_files if file.basename == "pkgconf.exe"]
|
||||
if len(pkgconf) != 1:
|
||||
fail("Installed tool repository must declare exactly one pkgconf-image pkgconf.exe")
|
||||
tools["pkg_config"] = pkgconf[0]
|
||||
if ctx.file.cmake not in ctx.files.cmake_data:
|
||||
fail("CMake executable must belong to its declared support tree")
|
||||
includes = [target[DirectoryInfo] for target in ctx.attr.includes]
|
||||
libraries = [target[DirectoryInfo] for target in ctx.attr.libraries]
|
||||
files = depset(
|
||||
tools.values() + installed,
|
||||
transitive = [msvc.transitive_files, sdk.transitive_files, python.files, ctx.attr.cmake_data.files] +
|
||||
[directory.transitive_files for directory in includes + libraries],
|
||||
)
|
||||
return [DefaultInfo(
|
||||
files = depset(pkgconf),
|
||||
runfiles = ctx.runfiles(files = pkgconf_files),
|
||||
), WindowsBuildToolsInfo(
|
||||
files = files,
|
||||
python = python,
|
||||
manifest = manifests[0],
|
||||
installed_files = installed,
|
||||
environment = {"SystemRoot": system_root, "SYSTEMROOT": system_root},
|
||||
inputs = {
|
||||
"schemaVersion": 1,
|
||||
"target": ctx.attr.target,
|
||||
"systemRoot": system_root,
|
||||
"tools": {name: file.path for name, file in tools.items()},
|
||||
"INCLUDE": [directory.path for directory in includes],
|
||||
"LIB": [directory.path for directory in libraries],
|
||||
},
|
||||
)]
|
||||
|
||||
windows_build_tools = rule(
|
||||
implementation = _windows_tools_impl,
|
||||
attrs = {
|
||||
"target": attr.string(mandatory = True),
|
||||
"msvc": attr.label(mandatory = True, providers = [DirectoryInfo]),
|
||||
"sdk": attr.label(mandatory = True, providers = [DirectoryInfo]),
|
||||
"includes": attr.label_list(mandatory = True, providers = [DirectoryInfo]),
|
||||
"libraries": attr.label_list(mandatory = True, providers = [DirectoryInfo]),
|
||||
"installed_tools": attr.label(mandatory = True),
|
||||
"cmake": attr.label(default = "@cmake-3.31.8-windows-x86_64//:cmake_bin", allow_single_file = True),
|
||||
"cmake_data": attr.label(default = "@cmake-3.31.8-windows-x86_64//:cmake_data"),
|
||||
"_python": attr.label(default = "@python_3_12//:py3_runtime", cfg = "exec"),
|
||||
},
|
||||
)
|
||||
|
||||
def _windows_prefix_impl(ctx):
|
||||
tools = ctx.attr.build_tools[WindowsBuildToolsInfo]
|
||||
prefix = ctx.actions.declare_file(ctx.label.name + "/prefix.tar")
|
||||
receipt = ctx.actions.declare_file(ctx.label.name + "/built.json")
|
||||
config = ctx.actions.declare_file(ctx.label.name + ".json")
|
||||
ctx.actions.write(config, json.encode({
|
||||
"inputs": tools.inputs,
|
||||
"manifest": tools.manifest.path,
|
||||
"installed_files": [file.path for file in tools.installed_files],
|
||||
"archives": [file.path for file in ctx.files.archives],
|
||||
"prefix": prefix.path,
|
||||
"receipt": receipt.path,
|
||||
}))
|
||||
ctx.actions.run(
|
||||
executable = tools.python.interpreter,
|
||||
arguments = [ctx.file._driver.path, "build", config.path],
|
||||
inputs = depset([config, ctx.file._driver] + ctx.files.archives + ctx.files._recipe, transitive = [tools.files]),
|
||||
outputs = [prefix, receipt],
|
||||
env = tools.environment,
|
||||
execution_requirements = {"no-remote-exec": "1"},
|
||||
mnemonic = "VoiceWindowsPrefix",
|
||||
)
|
||||
return [DefaultInfo(files = depset([prefix])), OutputGroupInfo(receipt = depset([receipt]))]
|
||||
|
||||
windows_native_prefix = rule(
|
||||
implementation = _windows_prefix_impl,
|
||||
attrs = {
|
||||
"build_tools": attr.label(mandatory = True, cfg = "exec", providers = [WindowsBuildToolsInfo]),
|
||||
"archives": attr.label_list(mandatory = True, allow_files = True),
|
||||
"_driver": attr.label(default = "//third_party/voice:bazel_windows.py", allow_single_file = True),
|
||||
"_recipe": attr.label(default = "//third_party/voice:native_recipe"),
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user