[Bazel] Support generating a secondary cache (#1405)
This is a working solution for generating a separate Emscripten cache.
Note that this requires an additional entry in the workspace as follows:
```starlark
load("@emsdk//:emscripten_cache.bzl", emsdk_emscripten_cache = "emscripten_cache")
emsdk_emscripten_cache()
```
When used like this, the default Emscripten cache will be used. However,
if the entry is as follows:
```starlark
load("@emsdk//:emscripten_cache.bzl", emsdk_emscripten_cache = "emscripten_cache")
emsdk_emscripten_cache(flags = ["--lto"])
```
Then embuilder will be called to build all system libraries and ports
(i.e., the `ALL` option to embuilder) with the LTO option enabled. This
can take awhile, so I have also made possible to specify which libraries
you want to build explicitly:
```starlark
load("@emsdk//:emscripten_cache.bzl", emsdk_emscripten_cache = "emscripten_cache")
emsdk_emscripten_cache(
flags = ["--lto"],
libraries = [
"crtbegin",
"libprintf_long_double-debug",
"libstubs-debug",
"libnoexit",
"libc-debug",
"libdlmalloc",
"libcompiler_rt",
"libc++-noexcept",
"libc++abi-debug-noexcept",
"libsockets"
]
)
```
Resolves #807, resolves #971, resolves #1099, resolves #1362, resolves
#1401diff --git a/bazel/README.md b/bazel/README.md
index 2fd291c..ce8d210 100644
--- a/bazel/README.md
+++ b/bazel/README.md
@@ -66,4 +66,30 @@
and all of its dependencies, and does not require amending `.bazelrc`. This
is the preferred way, since it also unpacks the resulting tarball.
+The Emscripten cache shipped by default does not include LTO, 64-bit or PIC
+builds of the system libraries and ports. If you wish to use these features you
+will need to declare the cache when you register the toolchain as follows. Note
+that the configuration consists of the same flags that can be passed to
+embuilder. If `targets` is not provided, all system libraries and ports will be
+built, i.e., the `ALL` option to embuilder.
+
+```starlark
+load("@emsdk//:toolchains.bzl", "register_emscripten_toolchains")
+register_emscripten_toolchains(cache = {
+ "configuration": ["--lto"],
+ "targets": [
+ "crtbegin",
+ "libprintf_long_double-debug",
+ "libstubs-debug",
+ "libnoexit",
+ "libc-debug",
+ "libdlmalloc",
+ "libcompiler_rt",
+ "libc++-noexcept",
+ "libc++abi-debug-noexcept",
+ "libsockets"
+ ]
+})
+```
+
See `test_external/` for an example using [embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html).
diff --git a/bazel/emscripten_deps.bzl b/bazel/emscripten_deps.bzl
index 6d36945..16fd8ee 100644
--- a/bazel/emscripten_deps.bzl
+++ b/bazel/emscripten_deps.bzl
@@ -27,6 +27,7 @@
name = "emcc_common",
srcs = [
"emscripten/emcc.py",
+ "emscripten/embuilder.py",
"emscripten/emscripten-version.txt",
"emscripten/cache/sysroot_install.stamp",
"emscripten/src/settings.js",
diff --git a/bazel/emscripten_toolchain/BUILD.bazel b/bazel/emscripten_toolchain/BUILD.bazel
index fb8a6c1..3cd0860 100644
--- a/bazel/emscripten_toolchain/BUILD.bazel
+++ b/bazel/emscripten_toolchain/BUILD.bazel
@@ -5,7 +5,7 @@
filegroup(
name = "common_files",
srcs = [
- "emscripten_config",
+ "@emscripten_cache//:emscripten_config",
"env.sh",
"env.bat",
"@nodejs//:node_files",
@@ -60,7 +60,7 @@
emscripten_cc_toolchain_config_rule(
name = "wasm",
cpu = "wasm",
- em_config = "emscripten_config",
+ em_config = "@emscripten_cache//:emscripten_config",
emscripten_binaries = "@emsdk//:compiler_files",
script_extension = select({
"@bazel_tools//src/conditions:host_windows": "bat",
diff --git a/bazel/emscripten_toolchain/emscripten_config b/bazel/emscripten_toolchain/default_config
similarity index 100%
rename from bazel/emscripten_toolchain/emscripten_config
rename to bazel/emscripten_toolchain/default_config
diff --git a/bazel/test_secondary_lto_cache/.bazelrc b/bazel/test_secondary_lto_cache/.bazelrc
new file mode 100644
index 0000000..fbd75a7
--- /dev/null
+++ b/bazel/test_secondary_lto_cache/.bazelrc
@@ -0,0 +1 @@
+build --incompatible_enable_cc_toolchain_resolution
diff --git a/bazel/test_secondary_lto_cache/.gitignore b/bazel/test_secondary_lto_cache/.gitignore
new file mode 100644
index 0000000..9d50f0b
--- /dev/null
+++ b/bazel/test_secondary_lto_cache/.gitignore
@@ -0,0 +1,4 @@
+bazel-bin
+bazel-out
+bazel-test_secondary_lto_cache
+bazel-testlogs
\ No newline at end of file
diff --git a/bazel/test_secondary_lto_cache/BUILD b/bazel/test_secondary_lto_cache/BUILD
new file mode 100644
index 0000000..f364f37
--- /dev/null
+++ b/bazel/test_secondary_lto_cache/BUILD
@@ -0,0 +1,19 @@
+load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
+
+cc_binary(
+ name = "hello-world",
+ srcs = ["hello-world.cc"],
+ linkopts = [
+ "-sAUTO_NATIVE_LIBRARIES=0",
+ "-flto",
+ ],
+)
+
+wasm_cc_binary(
+ name = "hello-world-wasm",
+ cc_target = ":hello-world",
+ outputs = [
+ "hello-world.js",
+ "hello-world.wasm",
+ ],
+)
diff --git a/bazel/test_secondary_lto_cache/MODULE.bazel b/bazel/test_secondary_lto_cache/MODULE.bazel
new file mode 100644
index 0000000..7f51ceb
--- /dev/null
+++ b/bazel/test_secondary_lto_cache/MODULE.bazel
@@ -0,0 +1 @@
+bazel_dep(name = "platforms", version = "0.0.9")
diff --git a/bazel/test_secondary_lto_cache/WORKSPACE b/bazel/test_secondary_lto_cache/WORKSPACE
new file mode 100644
index 0000000..d0bdd3b
--- /dev/null
+++ b/bazel/test_secondary_lto_cache/WORKSPACE
@@ -0,0 +1,30 @@
+local_repository(
+ name = "emsdk",
+ path = "..",
+)
+
+load("@emsdk//:deps.bzl", "deps")
+
+deps()
+
+load("@emsdk//:emscripten_deps.bzl", "emscripten_deps")
+
+emscripten_deps()
+
+load("@emsdk//:toolchains.bzl", "register_emscripten_toolchains")
+
+register_emscripten_toolchains(cache = {
+ "configuration": ["--lto"],
+ "targets": [
+ "crtbegin",
+ "libprintf_long_double-debug",
+ "libstubs-debug",
+ "libnoexit",
+ "libc-debug",
+ "libdlmalloc",
+ "libcompiler_rt",
+ "libc++-noexcept",
+ "libc++abi-debug-noexcept",
+ "libsockets"
+ ]
+})
diff --git a/bazel/test_secondary_lto_cache/hello-world.cc b/bazel/test_secondary_lto_cache/hello-world.cc
new file mode 100644
index 0000000..ee72c53
--- /dev/null
+++ b/bazel/test_secondary_lto_cache/hello-world.cc
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main(int argc, char** argv) {
+ std::cout << "hello world!" << std::endl;
+ return 0;
+}
diff --git a/bazel/toolchains.bzl b/bazel/toolchains.bzl
index 6d50b3d..ae71413 100644
--- a/bazel/toolchains.bzl
+++ b/bazel/toolchains.bzl
@@ -1,2 +1,96 @@
-def register_emscripten_toolchains():
+BUILD_FILE_CONTENT_TEMPLATE = """
+package(default_visibility = ['//visibility:public'])
+exports_files(['emscripten_config'])
+"""
+
+EMBUILDER_CONFIG_TEMPLATE = """
+CACHE = '{cache}'
+BINARYEN_ROOT = '{binaryen_root}'
+LLVM_ROOT = '{llvm_root}'
+"""
+
+def get_root_and_script_ext(repository_ctx):
+ if repository_ctx.os.name.startswith('linux'):
+ if 'amd64' in repository_ctx.os.arch or 'x86_64' in repository_ctx.os.arch:
+ return (repository_ctx.path(Label("@emscripten_bin_linux//:BUILD.bazel")).dirname, '')
+ elif 'aarch64' in repository_ctx.os.arch:
+ return (repository_ctx.path(Label("@emscripten_bin_linux_arm64//:BUILD.bazel")).dirname, '')
+ else:
+ fail('Unsupported architecture for Linux')
+ elif repository_ctx.os.name.startswith('mac'):
+ if 'amd64' in repository_ctx.os.arch or 'x86_64' in repository_ctx.os.arch:
+ return (repository_ctx.path(Label("@emscripten_bin_mac//:BUILD.bazel")).dirname, '')
+ elif 'aarch64' in repository_ctx.os.arch:
+ return (repository_ctx.path(Label("@emscripten_bin_mac_arm64//:BUILD.bazel")).dirname, '')
+ else:
+ fail('Unsupported architecture for MacOS')
+ elif repository_ctx.os.name.startswith('windows'):
+ return (repository_ctx.path(Label("@emscripten_bin_win//:BUILD.bazel")).dirname, '.bat')
+ else:
+ fail('Unsupported operating system')
+
+def _emscripten_cache_impl(repository_ctx):
+ # Read the default emscripten configuration file
+ default_config = repository_ctx.read(
+ repository_ctx.path(
+ Label("@emsdk//emscripten_toolchain:default_config")
+ )
+ )
+
+ if repository_ctx.attr.targets or repository_ctx.attr.configuration:
+ root, script_ext = get_root_and_script_ext(repository_ctx)
+ llvm_root = root.get_child("bin")
+ cache = repository_ctx.path("cache")
+ # Create configuration file
+ embuilder_config_content = EMBUILDER_CONFIG_TEMPLATE.format(
+ cache=cache,
+ binaryen_root=root,
+ llvm_root=llvm_root,
+ )
+ repository_ctx.file("embuilder_config", embuilder_config_content)
+ embuilder_config_path = repository_ctx.path("embuilder_config")
+ embuilder_path = "{}{}".format(root.get_child("emscripten").get_child("embuilder"), script_ext)
+ # Prepare the command line
+ if repository_ctx.attr.targets:
+ targets = repository_ctx.attr.targets
+ else:
+ # If no targets are requested, build everything
+ targets = ["ALL"]
+ flags = ["--em-config", embuilder_config_path] + repository_ctx.attr.configuration
+ embuilder_args = [embuilder_path] + flags + ["build"] + targets
+ # Run embuilder
+ repository_ctx.report_progress("Building secondary cache")
+ result = repository_ctx.execute(
+ embuilder_args,
+ quiet=True,
+ environment = {
+ "EM_IGNORE_SANITY": "1",
+ "EM_NODE_JS": "empty",
+ }
+ )
+ if result.return_code != 0:
+ fail("Embuilder exited with a non-zero return code")
+ # Override Emscripten's cache with the secondary cache
+ default_config += "CACHE = '{}'\n".format(cache)
+
+ # Create the configuration file for the toolchain and export
+ repository_ctx.file('emscripten_config', default_config)
+ repository_ctx.file('BUILD.bazel', BUILD_FILE_CONTENT_TEMPLATE)
+
+_emscripten_cache = repository_rule(
+ implementation = _emscripten_cache_impl,
+ attrs = {
+ "configuration": attr.string_list(),
+ "targets": attr.string_list(),
+ },
+ local = True
+)
+
+def register_emscripten_toolchains(cache = {}):
+ _emscripten_cache(
+ name = "emscripten_cache",
+ configuration = cache["configuration"] if "configuration" in cache else [],
+ targets = cache["targets"] if "targets" in cache else [],
+ )
+
native.register_toolchains(str(Label("//emscripten_toolchain:cc-toolchain-wasm")))
diff --git a/test/test_bazel.ps1 b/test/test_bazel.ps1
index b417262..5c20a15 100644
--- a/test/test_bazel.ps1
+++ b/test/test_bazel.ps1
@@ -22,3 +22,9 @@
# Test use of the closure compiler
bazel build //:hello-embind-wasm --compilation_mode opt # release
if (-not $?) { Exit $LastExitCode }
+
+Set-Location ..\test_secondary_lto_cache
+
+bazel build //:hello-world-wasm
+if (-not $?) { Exit $LastExitCode }
+
diff --git a/test/test_bazel.sh b/test/test_bazel.sh
index 51500ac..b99a029 100755
--- a/test/test_bazel.sh
+++ b/test/test_bazel.sh
@@ -30,3 +30,7 @@
bazel build //:hello-embind-wasm --compilation_mode opt # release
# This function should not be minified if the externs file is loaded correctly.
grep "customJSFunctionToTestClosure" bazel-bin/hello-embind-wasm/hello-embind.js
+
+cd ../test_secondary_lto_cache
+bazel build //:hello-world-wasm
+
diff --git a/test/test_bazel_mac.sh b/test/test_bazel_mac.sh
index fa31103..2b596ee 100755
--- a/test/test_bazel_mac.sh
+++ b/test/test_bazel_mac.sh
@@ -24,3 +24,7 @@
cd test_external
bazel build //long_command_line:long_command_line_wasm
bazel build //:hello-world-wasm
+
+cd ../test_secondary_lto_cache
+bazel build //:hello-world-wasm
+