comparison third_party/emsdk/bazel/emscripten_toolchain/link_wrapper.py @ 186:8cf4ec5e2191 hg-web

Fixed merge conflict.
author MrJuneJune <me@mrjunejune.com>
date Fri, 23 Jan 2026 22:38:59 -0800
parents 8d17f6e6e290
children
comparison
equal deleted inserted replaced
176:fed99fc04e12 186:8cf4ec5e2191
1 #!/usr/bin/env python
2 """wrapper around emcc link step.
3
4 This wrapper currently serves the following purposes.
5
6 1. When building with --config=wasm the final output is multiple files, usually
7 at least one .js and one .wasm file. Since the cc_binary link step only
8 allows a single output, we must tar up the outputs into a single file.
9
10 2. Add quotes around arguments that need them in the response file to work
11 around a bazel quirk.
12
13 3. Ensure the external_debug_info section of the wasm points at the correct
14 bazel path.
15 """
16
17
18 import argparse
19 import os
20 import subprocess
21 import sys
22
23 # Only argument should be @path/to/parameter/file
24 assert sys.argv[1][0] == '@', sys.argv
25 param_filename = sys.argv[1][1:]
26 param_file_args = [line.strip() for line in open(param_filename).readlines()]
27
28 # Re-write response file if needed.
29 if any(' ' in a for a in param_file_args):
30 new_param_filename = param_filename + '.modified'
31 with open(new_param_filename, 'w') as f:
32 for param in param_file_args:
33 if ' ' in param:
34 f.write('"%s"' % param)
35 else:
36 f.write(param)
37 f.write('\n')
38 sys.argv[1] = '@' + new_param_filename
39
40 emcc_py = os.path.join(os.environ['EMSCRIPTEN'], 'emcc.py')
41 rtn = subprocess.call([sys.executable, emcc_py] + sys.argv[1:])
42 if rtn != 0:
43 sys.exit(1)
44
45 # Parse the arguments that we gave to the linker to determine what the output
46 # file is named and what the output format is.
47 parser = argparse.ArgumentParser(add_help=False)
48 parser.add_argument('-o')
49 parser.add_argument('--oformat')
50 options = parser.parse_known_args(param_file_args)[0]
51 output_file = options.o
52 oformat = options.oformat
53 outdir = os.path.normpath(os.path.dirname(output_file))
54 base_name = os.path.basename(output_file)
55
56 # The output file name is the name of the build rule that was built.
57 # Add an appropriate file extension based on --oformat.
58 if oformat is not None:
59 base_name_split = os.path.splitext(base_name)
60
61 # If the output name has no extension, give it the appropriate extension.
62 if not base_name_split[1]:
63 os.replace(output_file, output_file + '.' + oformat)
64
65 # If the output name does have an extension and it matches the output format,
66 # change the base_name so it doesn't have an extension.
67 elif base_name_split[1] == '.' + oformat:
68 base_name = base_name_split[0]
69
70 # If the output name does have an extension and it does not match the output
71 # format, change the base_name so it doesn't have an extension and rename
72 # the output_file so it has the proper extension.
73 # Note that if you do something like name your build rule "foo.js" and pass
74 # "--oformat=html", emscripten will write to the same file for both the js and
75 # html output, overwriting the js output entirely with the html.
76 # Please don't do that.
77 else:
78 base_name = base_name_split[0]
79 os.replace(output_file, os.path.join(outdir, base_name + '.' + oformat))
80
81 files = []
82 extensions = [
83 '.js',
84 '.wasm',
85 '.wasm.map',
86 '.data',
87 '.js.symbols',
88 '.wasm.debug.wasm',
89 '.html',
90 ]
91
92 for ext in extensions:
93 filename = base_name + ext
94 if os.path.exists(os.path.join(outdir, filename)):
95 files.append(filename)
96
97 wasm_base = os.path.join(outdir, base_name + '.wasm')
98 if os.path.exists(wasm_base + '.debug.wasm') and os.path.exists(wasm_base):
99 # If we have a .wasm.debug.wasm file and a .wasm file, we need to rewrite the
100 # section in the .wasm file that refers to it. The path that's in there
101 # is the blaze output path; we want it to be just the filename.
102
103 llvm_objcopy = os.path.join(
104 os.environ['EM_BIN_PATH'], 'bin/llvm-objcopy')
105 # First, check to make sure the .wasm file has the header that needs to be
106 # rewritten.
107 rtn = subprocess.call([
108 llvm_objcopy,
109 '--dump-section=external_debug_info=/dev/null',
110 wasm_base], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
111 if rtn == 0:
112 # If llvm-objcopy did not return an error, the external_debug_info section
113 # must exist, so we're good to continue.
114
115 # Next we need to convert length of the filename to LEB128.
116 # Start by converting the length of the filename to a bit string.
117 bit_string = '{0:b}'.format(len(base_name + '.wasm.debug.wasm'))
118
119 # Pad the bit string with 0s so that its length is a multiple of 7.
120 while len(bit_string) % 7 != 0:
121 bit_string = '0' + bit_string
122
123 # Break up our bit string into chunks of 7.
124 # We do this backwards because the final format is little-endian.
125 final_bytes = bytearray()
126 for i in reversed(range(0, len(bit_string), 7)):
127 binary_part = bit_string[i:i + 7]
128 if i != 0:
129 # Every chunk except the last one needs to be prepended with '1'.
130 # The length of each chunk is 7, so that one has an implicit '0'.
131 binary_part = '1' + binary_part
132 final_bytes.append(int(binary_part, 2))
133 # Finally, add the actual filename.
134 final_bytes.extend((base_name + '.wasm.debug.wasm').encode())
135
136 # Write our length + filename bytes to a temp file.
137 with open(base_name + '_debugsection.tmp', 'wb+') as f:
138 f.write(final_bytes)
139 f.close()
140
141 # First delete the old section.
142 subprocess.check_call([
143 llvm_objcopy,
144 wasm_base,
145 '--remove-section=external_debug_info'])
146 # Rewrite section with the new size and filename from the temp file.
147 subprocess.check_call([
148 llvm_objcopy,
149 wasm_base,
150 '--add-section=external_debug_info=' + base_name + '_debugsection.tmp'])
151
152 # Make sure we have at least one output file.
153 if not files:
154 print('emcc.py did not appear to output any known files!')
155 sys.exit(1)
156
157 # cc_binary must output exactly one file; put all the output files in a tarball.
158 cmd = ['tar', 'cf', base_name + '.tar'] + files
159 subprocess.check_call(cmd, cwd=outdir)
160 os.replace(os.path.join(outdir, base_name + '.tar'), output_file)
161
162 sys.exit(0)