Mercurial
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) |