add a wasm bazel toolchain (#603)
This commit is contained in:
@@ -124,6 +124,26 @@ jobs:
|
||||
docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
|
||||
make -C ./docker version=${CIRCLE_TAG} alias=latest push
|
||||
|
||||
test-bazel:
|
||||
executor: bionic
|
||||
steps:
|
||||
- checkout
|
||||
- run: apt-get install -q -y curl gnupg
|
||||
- run: curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
|
||||
- run: mv bazel.gpg /etc/apt/trusted.gpg.d/
|
||||
- run: echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list
|
||||
- run:
|
||||
name: install pip
|
||||
command: |
|
||||
apt-get update -q
|
||||
apt-get install -q -y python3-pip
|
||||
- run: pip3 install absl-py
|
||||
- run:
|
||||
name: install bazel
|
||||
command: |
|
||||
apt-get install -q -y bazel
|
||||
- run: scripts/test_bazel.sh
|
||||
|
||||
workflows:
|
||||
flake8:
|
||||
jobs:
|
||||
@@ -146,3 +166,6 @@ workflows:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /.*/
|
||||
test-bazel:
|
||||
jobs:
|
||||
- test-bazel
|
||||
|
||||
63
bazel/README.md
Normal file
63
bazel/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Bazel Emscripten toolchain
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
1. Merge the `WORKSPACE` file in with your own at the root of your bazel
|
||||
directory structure. If you don't have one, simply copy the file.
|
||||
2. Merge the `bazelrc` file in with your `.bazelrc` file at the root of your
|
||||
bazel directory structure. If you don't have one, simply copy the file and
|
||||
rename it to `.bazelrc`. (Note the `.`)
|
||||
3. Copy the `emscripten_toolchain` folder along with its contents to the root of
|
||||
your bazel directory.
|
||||
|
||||
Your directory structure should look like this:
|
||||
```
|
||||
bazel_root/
|
||||
├── .bazelrc
|
||||
├── WORKSPACE
|
||||
├── emscripten_toolchain/
|
||||
│ ├── BUILD.bazel
|
||||
│ ├── builddefs.bzl
|
||||
│ ├── crosstool.bzl
|
||||
│ ├── emar.sh
|
||||
│ ├── emcc.sh
|
||||
│ ├── emcc_link.sh
|
||||
│ ├── emscripten.BUILD
|
||||
│ ├── emscripten_config
|
||||
│ ├── env.sh
|
||||
│ ├── link_wrapper.py
|
||||
│ ├── wasm_binary.py
|
||||
│ ├── wasm_cc_binary.bzl
|
||||
│ ├── wasm_rules.bzl
|
||||
├── your_project_folder/
|
||||
│ ├── your_project.file
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Using --config=wasm
|
||||
Simply pass `--config=wasm` when building a normal `cc_binary`. The result of
|
||||
this build will be a tar archive containing any files produced by emscripten.
|
||||
|
||||
### Using wasm_cc_binary
|
||||
First, write a new rule wrapping your `cc_binary`.
|
||||
|
||||
```
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary")
|
||||
load("//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
|
||||
|
||||
cc_binary(
|
||||
name = "hello-world",
|
||||
srcs = ["hello-world.cc"],
|
||||
)
|
||||
|
||||
wasm_cc_binary(
|
||||
name = "hello-world-wasm",
|
||||
cc_target = ":hello-world",
|
||||
)
|
||||
```
|
||||
|
||||
Now you can run `bazel build :hello-world-wasm`. The result of this build will
|
||||
be the individual files produced by emscripten. Note that some of these files
|
||||
may be empty. This is because bazel has no concept of optional outputs for
|
||||
rules.
|
||||
25
bazel/WORKSPACE
Normal file
25
bazel/WORKSPACE
Normal file
@@ -0,0 +1,25 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
sha256 = "0f2de53628e848c1691e5729b515022f5a77369c76a09fbe55611e12731c90e3",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.0.1/rules_nodejs-2.0.1.tar.gz"],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")
|
||||
|
||||
# emscripten 2.0.2
|
||||
http_archive(
|
||||
name = "emscripten",
|
||||
sha256 = "9febbc252372efee82e62d548f510dfc85ade6661f5f3be28823848d4757d614",
|
||||
strip_prefix = "install",
|
||||
url = "https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/ede25d889a0abe63360d4c5d420087c8753b8bbe/wasm-binaries.tbz2",
|
||||
build_file = "//emscripten_toolchain:emscripten.BUILD",
|
||||
type = "tar.bz2",
|
||||
)
|
||||
|
||||
npm_install(
|
||||
name = "npm",
|
||||
package_json = "@emscripten//:emscripten/package.json",
|
||||
package_lock_json = "@emscripten//:emscripten/package-lock.json",
|
||||
)
|
||||
5
bazel/bazelrc
Normal file
5
bazel/bazelrc
Normal file
@@ -0,0 +1,5 @@
|
||||
build:wasm --crosstool_top=//emscripten_toolchain:everything
|
||||
|
||||
build:wasm --cpu=wasm
|
||||
|
||||
build:wasm --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
|
||||
80
bazel/emscripten_toolchain/BUILD.bazel
Normal file
80
bazel/emscripten_toolchain/BUILD.bazel
Normal file
@@ -0,0 +1,80 @@
|
||||
load(":crosstool.bzl", "emscripten_cc_toolchain_config_rule")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "common-script-includes",
|
||||
srcs = [
|
||||
"emar.sh",
|
||||
"emcc.sh",
|
||||
"emscripten_config",
|
||||
"env.sh",
|
||||
"@emscripten//:all",
|
||||
"@nodejs//:node_files",
|
||||
"@npm//:node_modules",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "compile-emscripten",
|
||||
srcs = [":common-script-includes"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "link-emscripten",
|
||||
srcs = [
|
||||
"emcc_link.sh",
|
||||
"link_wrapper.py",
|
||||
":common-script-includes",
|
||||
"@emscripten//:all",
|
||||
"@nodejs//:node_files",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "every-file",
|
||||
srcs = [
|
||||
":compile-emscripten",
|
||||
":link-emscripten",
|
||||
"@emscripten//:all",
|
||||
"@nodejs//:node_files",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(name = "empty")
|
||||
|
||||
# dlmalloc.bc is implictly added by the emscripten toolchain
|
||||
cc_library(name = "malloc")
|
||||
|
||||
emscripten_cc_toolchain_config_rule(
|
||||
name = "wasm",
|
||||
cpu = "wasm",
|
||||
emscripten_version = "emscripten",
|
||||
)
|
||||
|
||||
cc_toolchain(
|
||||
name = "cc-compiler-wasm",
|
||||
all_files = ":every-file",
|
||||
ar_files = ":common-script-includes",
|
||||
as_files = ":empty",
|
||||
compiler_files = ":compile-emscripten",
|
||||
dwp_files = ":empty",
|
||||
linker_files = ":link-emscripten",
|
||||
objcopy_files = ":empty",
|
||||
strip_files = ":empty",
|
||||
toolchain_config = "wasm",
|
||||
toolchain_identifier = "emscripten-wasm",
|
||||
)
|
||||
|
||||
cc_toolchain_suite(
|
||||
name = "everything",
|
||||
toolchains = {
|
||||
"wasm": ":cc-compiler-wasm",
|
||||
"wasm|emscripten": ":cc-compiler-wasm",
|
||||
},
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "wasm_binary",
|
||||
srcs = ["wasm_binary.py"],
|
||||
)
|
||||
1126
bazel/emscripten_toolchain/crosstool.bzl
Normal file
1126
bazel/emscripten_toolchain/crosstool.bzl
Normal file
File diff suppressed because it is too large
Load Diff
5
bazel/emscripten_toolchain/emar.sh
Executable file
5
bazel/emscripten_toolchain/emar.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
source emscripten_toolchain/env.sh
|
||||
|
||||
exec python3 $EMSCRIPTEN/emar.py "$@"
|
||||
5
bazel/emscripten_toolchain/emcc.sh
Executable file
5
bazel/emscripten_toolchain/emcc.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
source emscripten_toolchain/env.sh
|
||||
|
||||
exec python3 external/emscripten/emscripten/emcc.py "$@"
|
||||
5
bazel/emscripten_toolchain/emcc_link.sh
Executable file
5
bazel/emscripten_toolchain/emcc_link.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
source emscripten_toolchain/env.sh
|
||||
|
||||
exec python3 emscripten_toolchain/link_wrapper.py "$@"
|
||||
6
bazel/emscripten_toolchain/emscripten.BUILD
Normal file
6
bazel/emscripten_toolchain/emscripten.BUILD
Normal file
@@ -0,0 +1,6 @@
|
||||
package(default_visibility = ['//visibility:public'])
|
||||
|
||||
filegroup(
|
||||
name = "all",
|
||||
srcs = glob(["**"]),
|
||||
)
|
||||
9
bazel/emscripten_toolchain/emscripten_config
Normal file
9
bazel/emscripten_toolchain/emscripten_config
Normal file
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
|
||||
ROOT_DIR = os.environ["ROOT_DIR"]
|
||||
EMSCRIPTEN_ROOT = os.environ["EMSCRIPTEN"]
|
||||
|
||||
LLVM_ROOT = ROOT_DIR + "/external/emscripten/bin"
|
||||
EMSCRIPTEN_NATIVE_OPTIMIZER = LLVM_ROOT + "/optimizer"
|
||||
NODE_JS = ROOT_DIR + "/external/nodejs_linux_amd64/bin/node"
|
||||
BINARYEN_ROOT = ROOT_DIR + "/external/emscripten"
|
||||
7
bazel/emscripten_toolchain/env.sh
Executable file
7
bazel/emscripten_toolchain/env.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
export ROOT_DIR=`(pwd -P)`
|
||||
export EMSCRIPTEN=${ROOT_DIR}/external/emscripten/emscripten
|
||||
|
||||
export EM_CONFIG=${ROOT_DIR}/emscripten_toolchain/emscripten_config
|
||||
export EM_CACHE=${ROOT_DIR}/emscripten_toolchain/cache
|
||||
146
bazel/emscripten_toolchain/link_wrapper.py
Normal file
146
bazel/emscripten_toolchain/link_wrapper.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python
|
||||
"""wrapper around emcc link step.
|
||||
|
||||
This wrapper currently serves the following purposes.
|
||||
|
||||
1. Ensures we always link to file with .js extension. The upstream default
|
||||
it to link to an llvm bitcode file which is never (AFAICT) want to do that.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. Add quotes around arguments that need them in the response file to work
|
||||
around a bazel quirk.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Only argument should be @path/to/parameter/file
|
||||
assert sys.argv[1][0] == '@'
|
||||
param_filename = sys.argv[1][1:]
|
||||
param_file_args = [l.strip() for l in open(param_filename, 'r').readlines()]
|
||||
|
||||
output_index = param_file_args.index('-o') + 1
|
||||
orig_output = js_output = param_file_args[output_index]
|
||||
outdir = os.path.dirname(orig_output)
|
||||
|
||||
# google3-only(TODO(b/139440956): Default to False once the bug is fixed)
|
||||
replace_response_file = any(' ' in a for a in param_file_args)
|
||||
|
||||
if not os.path.splitext(orig_output)[1]:
|
||||
js_output = orig_output + '.js'
|
||||
param_file_args[output_index] = js_output
|
||||
replace_response_file = True
|
||||
|
||||
# Re-write response file if needed.
|
||||
if replace_response_file:
|
||||
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(['python3', emcc_py] + sys.argv[1:])
|
||||
if rtn != 0:
|
||||
sys.exit(1)
|
||||
|
||||
js_name = os.path.basename(js_output)
|
||||
base_name = os.path.splitext(js_name)[0]
|
||||
|
||||
files = []
|
||||
extensions = [
|
||||
'.js',
|
||||
'.wasm',
|
||||
'.wasm.map',
|
||||
'.js.mem',
|
||||
'.fetch.js',
|
||||
'.worker.js',
|
||||
'.data',
|
||||
'.js.symbols',
|
||||
'.wasm.debug.wasm'
|
||||
]
|
||||
|
||||
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['EMSCRIPTEN'], 'llvm-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')
|
||||
|
||||
# Write our length + filename bytes to a temp file.
|
||||
with open('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=debugsection.tmp'])
|
||||
|
||||
# If we have more than one output file then create tarball
|
||||
if len(files) > 1:
|
||||
cmd = ['tar', 'cf', 'tmp.tar'] + files
|
||||
subprocess.check_call(cmd, cwd=outdir)
|
||||
os.rename(os.path.join(outdir, 'tmp.tar'), orig_output)
|
||||
elif len(files) == 1:
|
||||
# Otherwise, if only have a single output than move it to the expected name
|
||||
if files[0] != os.path.basename(orig_output):
|
||||
os.rename(os.path.join(outdir, files[0]), orig_output)
|
||||
else:
|
||||
print('emcc.py did not appear to output any known files!')
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
84
bazel/emscripten_toolchain/wasm_binary.py
Normal file
84
bazel/emscripten_toolchain/wasm_binary.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""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 path.
|
||||
|
||||
The name of archive is expected to be of the format `foo` or `foo.XXX` and
|
||||
the contents are expected to be foo.js and foo.wasm.
|
||||
|
||||
Several optional files may also be in the archive, including but not limited to
|
||||
foo.js.mem, pthread-main.js, and foo.wasm.map.
|
||||
|
||||
If the file is not a tar archive, the passed file will simply be copied to its
|
||||
destination.
|
||||
|
||||
This script and its accompanying Bazel rule should allow you to extract a
|
||||
WebAssembly binary into a larger web application.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('archive', None, 'The the archive to extract from.')
|
||||
flags.DEFINE_string('output_path', None, 'The path to extract into.')
|
||||
|
||||
|
||||
def ensure(f):
|
||||
if not os.path.exists(f):
|
||||
with open(f, 'w'):
|
||||
pass
|
||||
|
||||
|
||||
def check(f):
|
||||
if not os.path.exists(f):
|
||||
raise Exception('Expected file in archive: %s' % f)
|
||||
|
||||
|
||||
def main(argv):
|
||||
basename = os.path.basename(FLAGS.archive)
|
||||
stem = basename.split('.')[0]
|
||||
|
||||
# Check the type of the input file
|
||||
mimetype_bytes = subprocess.check_output(['file', '-Lib', FLAGS.archive])
|
||||
mimetype = mimetype_bytes.decode(sys.stdout.encoding)
|
||||
|
||||
# If we have a tar, extract all files. If we have just a single file, copy it.
|
||||
if 'tar' in mimetype:
|
||||
subprocess.check_call(
|
||||
['tar', 'xf', FLAGS.archive, '-C', FLAGS.output_path])
|
||||
elif 'binary' in mimetype:
|
||||
subprocess.check_call([
|
||||
'cp',
|
||||
FLAGS.archive,
|
||||
os.path.join(FLAGS.output_path, stem + '.wasm')])
|
||||
elif 'text' in mimetype:
|
||||
subprocess.check_call([
|
||||
'cp',
|
||||
FLAGS.archive,
|
||||
os.path.join(FLAGS.output_path, stem + '.js')])
|
||||
else:
|
||||
subprocess.check_call(['cp', FLAGS.archive, FLAGS.output_path])
|
||||
|
||||
# At least one of these two files should exist at this point.
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.js'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.wasm'))
|
||||
|
||||
# And can optionally contain these extra files.
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.wasm.map'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.worker.js'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.js.mem'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.data'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.fetch.js'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.js.symbols'))
|
||||
ensure(os.path.join(FLAGS.output_path, stem + '.wasm.debug.wasm'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
150
bazel/emscripten_toolchain/wasm_cc_binary.bzl
Normal file
150
bazel/emscripten_toolchain/wasm_cc_binary.bzl
Normal file
@@ -0,0 +1,150 @@
|
||||
"""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")
|
||||
|
||||
return {
|
||||
"//command_line_option:compiler": "emscripten",
|
||||
"//command_line_option:crosstool_top": "//emscripten_toolchain:everything",
|
||||
"//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": [],
|
||||
"//command_line_option:custom_malloc": "//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:crosstool_top",
|
||||
"//command_line_option:features",
|
||||
"//command_line_option:dynamic_mode",
|
||||
"//command_line_option:linkopt",
|
||||
"//command_line_option:platforms",
|
||||
"//command_line_option:custom_malloc",
|
||||
],
|
||||
)
|
||||
|
||||
def _wasm_binary_impl(ctx):
|
||||
cc_target = ctx.attr.cc_target[0]
|
||||
|
||||
args = [
|
||||
"--output_path={}".format(ctx.outputs.loader.dirname),
|
||||
] + [
|
||||
ctx.expand_location("--archive=$(location {})".format(
|
||||
cc_target.label,
|
||||
), [cc_target]),
|
||||
]
|
||||
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.actions.run(
|
||||
inputs = ctx.files.cc_target,
|
||||
outputs = outputs,
|
||||
arguments = args,
|
||||
executable = ctx.executable._wasm_binary_extractor,
|
||||
)
|
||||
|
||||
return DefaultInfo(
|
||||
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)),
|
||||
)
|
||||
|
||||
def _wasm_binary_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),
|
||||
}
|
||||
|
||||
return 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.
|
||||
wasm_cc_binary = rule(
|
||||
implementation = _wasm_binary_impl,
|
||||
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,
|
||||
),
|
||||
"_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("//emscripten_toolchain:wasm_binary"),
|
||||
),
|
||||
},
|
||||
outputs = _wasm_binary_outputs,
|
||||
)
|
||||
6
bazel/emscripten_toolchain/wasm_rules.bzl
Normal file
6
bazel/emscripten_toolchain/wasm_rules.bzl
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Rules related to C++ and WebAssembly.
|
||||
"""
|
||||
|
||||
load("//emscripten_toolchain:wasm_cc_binary.bzl", _wasm_cc_binary = "wasm_cc_binary")
|
||||
|
||||
wasm_cc_binary = _wasm_cc_binary
|
||||
12
bazel/hello-world/BUILD
Normal file
12
bazel/hello-world/BUILD
Normal file
@@ -0,0 +1,12 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary")
|
||||
load("//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
|
||||
|
||||
cc_binary(
|
||||
name = "hello-world",
|
||||
srcs = ["hello-world.cc"],
|
||||
)
|
||||
|
||||
wasm_cc_binary(
|
||||
name = "hello-world-wasm",
|
||||
cc_target = ":hello-world",
|
||||
)
|
||||
6
bazel/hello-world/hello-world.cc
Normal file
6
bazel/hello-world/hello-world.cc
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::cout << "hello world!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
9
scripts/test_bazel.sh
Executable file
9
scripts/test_bazel.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "test bazel"
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
cd bazel
|
||||
bazel build //hello-world:hello-world-wasm
|
||||
63
scripts/update_bazel_workspace.sh
Executable file
63
scripts/update_bazel_workspace.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# This script will update emsdk/bazel/WORKSPACE to the latest version of
|
||||
# emscripten. It reads emsdk/emscripten-releases-tags.txt to get the latest
|
||||
# version number. Then, it downloads the prebuilts for that version and computes
|
||||
# the sha256sum for the archive. It then puts all this information into the
|
||||
# emsdk/bazel/WORKSPACE file.
|
||||
|
||||
ERR=0
|
||||
# Attempt to change to the emsdk root directory
|
||||
cd $(dirname $0)/..
|
||||
|
||||
# If the previous command succeeded. We are in the emsdk root. Check to make
|
||||
# sure the files and directories we need are present.
|
||||
if [[ $? = 0 ]]; then
|
||||
if [[ ! -f emscripten-releases-tags.txt ]]; then
|
||||
echo "Cannot find emscripten-releases-tags.txt."
|
||||
ERR=1
|
||||
fi
|
||||
|
||||
if [[ ! -d bazel ]]; then
|
||||
echo "Cannot find the bazel directory."
|
||||
ERR=1
|
||||
elif [[ ! -f bazel/WORKSPACE ]]; then
|
||||
echo "Cannot find bazel/WORKSPACE."
|
||||
ERR=1
|
||||
fi
|
||||
else
|
||||
ERR=1
|
||||
fi
|
||||
|
||||
if [[ $ERR = 1 ]]; then
|
||||
echo "Unable to cd into the emsdk root directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL1=https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/
|
||||
URL2=/wasm-binaries.tbz2
|
||||
|
||||
# Get the latest version number from emscripten-releases-tag.txt.
|
||||
VER=$(grep -oP '(?<=latest\": \")([\d\.]+)(?=\")' \
|
||||
emscripten-releases-tags.txt \
|
||||
| sed --expression "s/\./\\\./g")
|
||||
# Based on the latest version number, get the commit hash for that version.
|
||||
HASH=$(grep "${VER}" emscripten-releases-tags.txt \
|
||||
| grep -v latest \
|
||||
| cut -f4 -d\")
|
||||
# Download and compute the sha256sum for the archive with the prebuilts.
|
||||
SHA=$(curl "${URL1}${HASH}${URL2}" 2>/dev/null \
|
||||
| sha256sum \
|
||||
| awk '{print $1}')
|
||||
# Get the line number on which the sha256 sum lives for emscripten.
|
||||
# This will always be one line after the name of the rule.
|
||||
SHALINE=$(($(grep -n 'name = "emscripten"' bazel/WORKSPACE \
|
||||
| sed 's/^\([[:digit:]]*\).*$/\1/')+1))
|
||||
|
||||
# Insert the new commit hash into the url.
|
||||
sed -i "s!\(${URL1}\)\([[:alnum:]]*\)\(${URL2}\)!\1${HASH}\3!" bazel/WORKSPACE
|
||||
# Insert the new version number.
|
||||
sed -i "s!\(# emscripten \)\(.*\)!\1${VER}!" bazel/WORKSPACE
|
||||
# Insert the new sha256 sum.
|
||||
sed -i "${SHALINE}s!\"[[:alnum:]]*\"!\"${SHA}\"!" bazel/WORKSPACE
|
||||
|
||||
echo "Done!"
|
||||
Reference in New Issue
Block a user