Update examples collection

This commit is contained in:
github-actions[bot]
2025-08-19 09:15:28 +00:00
parent 8bc9f30d87
commit 87d3066428
130 changed files with 21502 additions and 0 deletions

View File

@ -0,0 +1,43 @@
load("//:emscripten_deps.bzl", "emscripten_repo_name")
load("//:remote_emscripten_repository.bzl", "create_toolchains", "emscripten_toolchain_name")
load("@rules_python//python:py_binary.bzl", "py_binary")
package(default_visibility = ["//visibility:public"])
# dlmalloc.bc is implicitly added by the emscripten toolchain
cc_library(name = "malloc")
create_toolchains(
name = emscripten_toolchain_name("linux"),
repo_name = emscripten_repo_name("linux"),
exec_compatible_with = [ "@platforms//os:linux", "@platforms//cpu:x86_64"],
)
create_toolchains(
name = emscripten_toolchain_name("linux_arm64"),
repo_name = emscripten_repo_name("linux_arm64"),
exec_compatible_with = ["@platforms//os:linux", "@platforms//cpu:arm64"],
)
create_toolchains(
name = emscripten_toolchain_name("mac"),
repo_name = emscripten_repo_name("mac"),
exec_compatible_with = ["@platforms//os:macos", "@platforms//cpu:x86_64"],
)
create_toolchains(
name = emscripten_toolchain_name("mac_arm64"),
repo_name = emscripten_repo_name("mac_arm64"),
exec_compatible_with = ["@platforms//os:macos", "@platforms//cpu:arm64"],
)
create_toolchains(
name = emscripten_toolchain_name("win"),
repo_name = emscripten_repo_name("win"),
exec_compatible_with = ["@platforms//os:windows", "@platforms//cpu:x86_64"],
)
py_binary(
name = "wasm_binary",
srcs = ["wasm_binary.py"],
)

View File

@ -0,0 +1,16 @@
import os
import platform
ROOT_DIR = os.environ["ROOT_DIR"]
EMSCRIPTEN_ROOT = os.environ["EMSCRIPTEN"]
BINARYEN_ROOT = os.path.join(ROOT_DIR, os.environ["EM_BIN_PATH"])
LLVM_ROOT = os.path.join(BINARYEN_ROOT, "bin")
NODE_JS = os.path.join(ROOT_DIR, os.environ["NODE_JS_PATH"])
FROZEN_CACHE = True
# This works around an issue with Bazel RBE where the symlinks in node_modules/.bin
# are uploaded as the linked files, which means the cli.js cannot load its
# dependencies from the expected locations.
# See https://github.com/emscripten-core/emscripten/pull/16640 for more
CLOSURE_COMPILER = [NODE_JS, os.path.join(EMSCRIPTEN_ROOT, "node_modules",
"google-closure-compiler", "cli.js")]

View File

@ -0,0 +1,5 @@
@ECHO OFF
call %~dp0\env.bat
py -3 %EMSCRIPTEN%\emar.py %*

View File

@ -0,0 +1,5 @@
#!/bin/bash
source $(dirname $0)/env.sh
exec python3 $EMSCRIPTEN/emar.py "$@"

View File

@ -0,0 +1,5 @@
@ECHO OFF
call %~dp0\env.bat
py -3 %EMSCRIPTEN%\emcc.py %*

View File

@ -0,0 +1,5 @@
#!/bin/bash
source $(dirname $0)/env.sh
exec python3 $EMSCRIPTEN/emcc.py "$@"

View File

@ -0,0 +1,5 @@
@ECHO OFF
call %~dp0\env.bat
py -3 %~dp0\link_wrapper.py %*

View File

@ -0,0 +1,5 @@
#!/bin/bash
source $(dirname $0)/env.sh
exec python3 $(dirname $0)/link_wrapper.py "$@"

View File

@ -0,0 +1,6 @@
@ECHO OFF
set ROOT_DIR=%EXT_BUILD_ROOT%
if "%ROOT_DIR%"=="" set ROOT_DIR=%CD%
set EMSCRIPTEN=%ROOT_DIR%\%EM_BIN_PATH%\emscripten
set EM_CONFIG=%ROOT_DIR%\%EM_CONFIG_PATH%

View File

@ -0,0 +1,5 @@
#!/bin/bash
export ROOT_DIR=${EXT_BUILD_ROOT:-$(pwd -P)}
export EMSCRIPTEN=$ROOT_DIR/$EM_BIN_PATH/emscripten
export EM_CONFIG=$ROOT_DIR/$EM_CONFIG_PATH

View File

@ -0,0 +1,167 @@
#!/usr/bin/env python
"""wrapper around emcc link step.
This wrapper currently serves the following purposes.
1. When building with --config=wasm the final output is multiple files, usually
at least one .js and one .wasm file. Since the cc_binary link step only
allows a single output, we must tar up the outputs into a single file.
2. Add quotes around arguments that need them in the response file to work
around a bazel quirk.
3. Ensure the external_debug_info section of the wasm points at the correct
bazel path.
"""
from __future__ import print_function
import argparse
import os
import subprocess
import sys
# Only argument should be @path/to/parameter/file
assert sys.argv[1][0] == '@', sys.argv
param_filename = sys.argv[1][1:]
param_file_args = [line.strip() for line in open(param_filename, 'r').readlines()]
# Re-write response file if needed.
if any(' ' in a for a in param_file_args):
new_param_filename = param_filename + '.modified'
with open(new_param_filename, 'w') as f:
for param in param_file_args:
if ' ' in param:
f.write('"%s"' % param)
else:
f.write(param)
f.write('\n')
sys.argv[1] = '@' + new_param_filename
emcc_py = os.path.join(os.environ['EMSCRIPTEN'], 'emcc.py')
rtn = subprocess.call([sys.executable, emcc_py] + sys.argv[1:])
if rtn != 0:
sys.exit(1)
# Parse the arguments that we gave to the linker to determine what the output
# file is named and what the output format is.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-o')
parser.add_argument('--oformat')
options = parser.parse_known_args(param_file_args)[0]
output_file = options.o
oformat = options.oformat
outdir = os.path.normpath(os.path.dirname(output_file))
base_name = os.path.basename(output_file)
# The output file name is the name of the build rule that was built.
# Add an appropriate file extension based on --oformat.
if oformat is not None:
base_name_split = os.path.splitext(base_name)
# If the output name has no extension, give it the appropriate extension.
if not base_name_split[1]:
os.replace(output_file, output_file + '.' + oformat)
# If the output name does have an extension and it matches the output format,
# change the base_name so it doesn't have an extension.
elif base_name_split[1] == '.' + oformat:
base_name = base_name_split[0]
# If the output name does have an extension and it does not match the output
# format, change the base_name so it doesn't have an extension and rename
# the output_file so it has the proper extension.
# Note that if you do something like name your build rule "foo.js" and pass
# "--oformat=html", emscripten will write to the same file for both the js and
# html output, overwriting the js output entirely with the html.
# Please don't do that.
else:
base_name = base_name_split[0]
os.replace(output_file, os.path.join(outdir, base_name + '.' + oformat))
files = []
extensions = [
'.js',
'.wasm',
'.wasm.map',
'.js.mem',
'.fetch.js',
'.worker.js',
'.data',
'.js.symbols',
'.wasm.debug.wasm',
'.html',
'.aw.js'
]
for ext in extensions:
filename = base_name + ext
if os.path.exists(os.path.join(outdir, filename)):
files.append(filename)
wasm_base = os.path.join(outdir, base_name + '.wasm')
if os.path.exists(wasm_base + '.debug.wasm') and os.path.exists(wasm_base):
# If we have a .wasm.debug.wasm file and a .wasm file, we need to rewrite the
# section in the .wasm file that refers to it. The path that's in there
# is the blaze output path; we want it to be just the filename.
llvm_objcopy = os.path.join(
os.environ['EM_BIN_PATH'], 'bin/llvm-objcopy')
# First, check to make sure the .wasm file has the header that needs to be
# rewritten.
rtn = subprocess.call([
llvm_objcopy,
'--dump-section=external_debug_info=/dev/null',
wasm_base], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if rtn == 0:
# If llvm-objcopy did not return an error, the external_debug_info section
# must exist, so we're good to continue.
# Next we need to convert length of the filename to LEB128.
# Start by converting the length of the filename to a bit string.
bit_string = '{0:b}'.format(len(base_name + '.wasm.debug.wasm'))
# Pad the bit string with 0s so that its length is a multiple of 7.
while len(bit_string) % 7 != 0:
bit_string = '0' + bit_string
# Break up our bit string into chunks of 7.
# We do this backwards because the final format is little-endian.
final_bytes = bytearray()
for i in reversed(range(0, len(bit_string), 7)):
binary_part = bit_string[i:i + 7]
if i != 0:
# Every chunk except the last one needs to be prepended with '1'.
# The length of each chunk is 7, so that one has an implicit '0'.
binary_part = '1' + binary_part
final_bytes.append(int(binary_part, 2))
# Finally, add the actual filename.
final_bytes.extend((base_name + '.wasm.debug.wasm').encode())
# Write our length + filename bytes to a temp file.
with open(base_name + '_debugsection.tmp', 'wb+') as f:
f.write(final_bytes)
f.close()
# First delete the old section.
subprocess.check_call([
llvm_objcopy,
wasm_base,
'--remove-section=external_debug_info'])
# Rewrite section with the new size and filename from the temp file.
subprocess.check_call([
llvm_objcopy,
wasm_base,
'--add-section=external_debug_info=' + base_name + '_debugsection.tmp'])
# Make sure we have at least one output file.
if not len(files):
print('emcc.py did not appear to output any known files!')
sys.exit(1)
# cc_binary must output exactly one file; put all the output files in a tarball.
cmd = ['tar', 'cf', base_name + '.tar'] + files
subprocess.check_call(cmd, cwd=outdir)
os.replace(os.path.join(outdir, base_name + '.tar'), output_file)
sys.exit(0)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
"""Unpackages a bazel emscripten archive for use in a bazel BUILD rule.
This script will take a tar archive containing the output of the emscripten
toolchain. This file contains any output files produced by a wasm_cc_binary or a
cc_binary built with --config=wasm. The files are extracted into the given
output paths.
The contents of the archive are expected to match the given outputs extnames.
This script and its accompanying Bazel rule should allow you to extract a
WebAssembly binary into a larger web application.
"""
import argparse
import os
import tarfile
def ensure(f):
if not os.path.exists(f):
with open(f, 'w'):
pass
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--archive', help='The archive to extract from.')
parser.add_argument('--outputs', help='Comma separated list of files that should be extracted from the archive. Only the extname has to match a file in the archive.')
parser.add_argument('--allow_empty_outputs', help='If an output listed in --outputs does not exist, create it anyways.', action='store_true')
args = parser.parse_args()
args.archive = os.path.normpath(args.archive)
args.outputs = args.outputs.split(",")
tar = tarfile.open(args.archive)
for member in tar.getmembers():
extname = '.' + member.name.split('.', 1)[1]
for idx, output in enumerate(args.outputs):
if output.endswith(extname):
member_file = tar.extractfile(member)
with open(output, "wb") as output_file:
output_file.write(member_file.read())
args.outputs.pop(idx)
break
for output in args.outputs:
extname = '.' + output.split('.', 1)[1]
if args.allow_empty_outputs:
ensure(output)
else:
print("[ERROR] Archive does not contain file with extname: %s" % extname)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,231 @@
"""wasm_cc_binary rule for compiling C++ targets to WebAssembly.
"""
def _wasm_transition_impl(settings, attr):
_ignore = (settings, attr)
features = list(settings["//command_line_option:features"])
linkopts = list(settings["//command_line_option:linkopt"])
if attr.threads == "emscripten":
# threads enabled
features.append("use_pthreads")
elif attr.threads == "off":
# threads disabled
features.append("-use_pthreads")
if attr.exit_runtime == True:
features.append("exit_runtime")
if attr.backend == "llvm":
features.append("llvm_backend")
elif attr.backend == "emscripten":
features.append("-llvm_backend")
if attr.simd:
features.append("wasm_simd")
platform = "@emsdk//:platform_wasm"
if attr.standalone:
platform = "@emsdk//:platform_wasi"
features.append("wasm_standalone")
return {
"//command_line_option:compiler": "emscripten",
"//command_line_option:cpu": "wasm",
"//command_line_option:features": features,
"//command_line_option:dynamic_mode": "off",
"//command_line_option:linkopt": linkopts,
"//command_line_option:platforms": [platform],
# This is hardcoded to an empty cc_library because the malloc library
# is implicitly added by the emscripten toolchain
"//command_line_option:custom_malloc": "@emsdk//emscripten_toolchain:malloc",
}
_wasm_transition = transition(
implementation = _wasm_transition_impl,
inputs = [
"//command_line_option:features",
"//command_line_option:linkopt",
],
outputs = [
"//command_line_option:compiler",
"//command_line_option:cpu",
"//command_line_option:features",
"//command_line_option:dynamic_mode",
"//command_line_option:linkopt",
"//command_line_option:platforms",
"//command_line_option:custom_malloc",
],
)
_ALLOW_OUTPUT_EXTNAMES = [
".js",
".wasm",
".wasm.map",
".worker.js",
".js.mem",
".data",
".fetch.js",
".js.symbols",
".wasm.debug.wasm",
".html",
".aw.js",
]
_WASM_BINARY_COMMON_ATTRS = {
"backend": attr.string(
default = "_default",
values = ["_default", "emscripten", "llvm"],
),
"cc_target": attr.label(
cfg = _wasm_transition,
mandatory = True,
),
"exit_runtime": attr.bool(
default = False,
),
"threads": attr.string(
default = "_default",
values = ["_default", "emscripten", "off"],
),
"simd": attr.bool(
default = False,
),
"standalone": attr.bool(
default = False,
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
"_wasm_binary_extractor": attr.label(
executable = True,
allow_files = True,
cfg = "exec",
default = Label("@emsdk//emscripten_toolchain:wasm_binary"),
),
}
def _wasm_cc_binary_impl(ctx):
args = ctx.actions.args()
cc_target = ctx.attr.cc_target[0]
for output in ctx.outputs.outputs:
valid_extname = False
for allowed_extname in _ALLOW_OUTPUT_EXTNAMES:
if output.path.endswith(allowed_extname):
valid_extname = True
break
if not valid_extname:
fail("Invalid output '{}'. Allowed extnames: {}".format(output.basename, ", ".join(_ALLOW_OUTPUT_EXTNAMES)))
args.add_all("--archive", ctx.files.cc_target)
args.add_joined("--outputs", ctx.outputs.outputs, join_with = ",")
ctx.actions.run(
inputs = ctx.files.cc_target,
outputs = ctx.outputs.outputs,
arguments = [args],
executable = ctx.executable._wasm_binary_extractor,
)
return [
DefaultInfo(
files = depset(ctx.outputs.outputs),
# This is needed since rules like web_test usually have a data
# dependency on this target.
data_runfiles = ctx.runfiles(transitive_files = depset(ctx.outputs.outputs)),
),
OutputGroupInfo(_wasm_tar = cc_target.files),
]
def _wasm_cc_binary_legacy_impl(ctx):
cc_target = ctx.attr.cc_target[0]
outputs = [
ctx.outputs.loader,
ctx.outputs.wasm,
ctx.outputs.map,
ctx.outputs.mem,
ctx.outputs.fetch,
ctx.outputs.worker,
ctx.outputs.data,
ctx.outputs.symbols,
ctx.outputs.dwarf,
ctx.outputs.html,
ctx.outputs.audio_worklet,
]
args = ctx.actions.args()
args.add("--allow_empty_outputs")
args.add_all("--archive", ctx.files.cc_target)
args.add_joined("--outputs", outputs, join_with = ",")
ctx.actions.run(
inputs = ctx.files.cc_target,
outputs = outputs,
arguments = [args],
executable = ctx.executable._wasm_binary_extractor,
)
return [
DefaultInfo(
executable = ctx.outputs.wasm,
files = depset(outputs),
# This is needed since rules like web_test usually have a data
# dependency on this target.
data_runfiles = ctx.runfiles(transitive_files = depset(outputs)),
),
OutputGroupInfo(_wasm_tar = cc_target.files),
]
_wasm_cc_binary = rule(
implementation = _wasm_cc_binary_impl,
attrs = dict(
_WASM_BINARY_COMMON_ATTRS,
outputs = attr.output_list(
allow_empty = False,
mandatory = True,
),
),
)
def _wasm_binary_legacy_outputs(name, cc_target):
basename = cc_target.name
basename = basename.split(".")[0]
outputs = {
"loader": "{}/{}.js".format(name, basename),
"wasm": "{}/{}.wasm".format(name, basename),
"map": "{}/{}.wasm.map".format(name, basename),
"mem": "{}/{}.js.mem".format(name, basename),
"fetch": "{}/{}.fetch.js".format(name, basename),
"worker": "{}/{}.worker.js".format(name, basename),
"data": "{}/{}.data".format(name, basename),
"symbols": "{}/{}.js.symbols".format(name, basename),
"dwarf": "{}/{}.wasm.debug.wasm".format(name, basename),
"html": "{}/{}.html".format(name, basename),
"audio_worklet": "{}/{}.aw.js".format(name, basename)
}
return outputs
_wasm_cc_binary_legacy = rule(
implementation = _wasm_cc_binary_legacy_impl,
attrs = _WASM_BINARY_COMMON_ATTRS,
outputs = _wasm_binary_legacy_outputs,
)
# Wraps a C++ Blaze target, extracting the appropriate files.
#
# This rule will transition to the emscripten toolchain in order
# to build the the cc_target as a WebAssembly binary.
#
# Args:
# name: The name of the rule.
# cc_target: The cc_binary or cc_library to extract files from.
def wasm_cc_binary(outputs = None, **kwargs):
# for backwards compatibility if no outputs are set the deprecated
# implementation is used.
if not outputs:
_wasm_cc_binary_legacy(**kwargs)
else:
_wasm_cc_binary(outputs = outputs, **kwargs)

View File

@ -0,0 +1,6 @@
"""Rules related to C++ and WebAssembly.
"""
load(":wasm_cc_binary.bzl", _wasm_cc_binary = "wasm_cc_binary")
wasm_cc_binary = _wasm_cc_binary