Re-indent python using 4-space indentation. NFC (#1799)
This change was generated using:
```
$ pip3 install reindent
$ git ls-files '*.py' | xargs reindent -n
```
Why is this good idea? Mostly for consistency with the rest of the
python world. Other projects in our ecosystem already use 4-space
indentation:
- binaryen: Converted to 4-space a while back
- llvm-project: Has always used 4-space IIUC
- emscripten-releases: Converted to 4-space a while back
- wabt: Has always used 4-space
- chromium: Start converted to 4-space several years back.
This is also kind a trial run for doing the same thing in emscripten.
Hopefully it won't make rebasing outstanding branches too painful (AI
agents are really good a re-basing these days anyway). I confirmed that
an agent was able to immediately rebase each of my outstanding 18
changes on top of this change despite them all (obviously) have
conflicts.
diff --git a/.flake8 b/.flake8
index a430d98..512adc8 100644
--- a/.flake8
+++ b/.flake8
@@ -18,8 +18,4 @@
./temp,
./upstream,
-# E111: Indentation is not a multiple of four
-# E114: Indentation is not a multiple of four (comment)
-extend-ignore = E111, E114
-
max-line-length = 160
diff --git a/bazel/emscripten_toolchain/link_wrapper.py b/bazel/emscripten_toolchain/link_wrapper.py
index e473206..79a3fa7 100644
--- a/bazel/emscripten_toolchain/link_wrapper.py
+++ b/bazel/emscripten_toolchain/link_wrapper.py
@@ -27,20 +27,20 @@
# 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(f'"{param}"')
- else:
- f.write(param)
- f.write('\n')
- sys.argv[1] = '@' + new_param_filename
+ 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(f'"{param}"')
+ else:
+ f.write(param)
+ f.write('\n')
+ sys.argv[1] = '@' + new_param_filename
emxx_py = os.path.join(os.environ['EMSCRIPTEN'], 'em++.py')
rtn = subprocess.call([sys.executable, emxx_py, *sys.argv[1:]])
if rtn != 0:
- sys.exit(1)
+ 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.
@@ -56,27 +56,27 @@
# 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)
+ 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 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 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))
+ # 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 = [
@@ -92,69 +92,69 @@
]
for ext in extensions:
- filename = base_name + ext
- if os.path.exists(os.path.join(outdir, filename)):
- files.append(filename)
+ 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.
+ # 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 = f"{len(base_name + '.wasm.debug.wasm'):b}"
-
- # 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 = 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,
- 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'])
+ '--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 = f"{len(base_name + '.wasm.debug.wasm'):b}"
+
+ # 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 files:
- print('em++.py did not appear to output any known files!')
- sys.exit(1)
+ print('em++.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]
diff --git a/bazel/emscripten_toolchain/wasm_binary.py b/bazel/emscripten_toolchain/wasm_binary.py
index 6c285f3..b67b88a 100644
--- a/bazel/emscripten_toolchain/wasm_binary.py
+++ b/bazel/emscripten_toolchain/wasm_binary.py
@@ -17,43 +17,43 @@
def ensure(f):
- if not os.path.exists(f):
- with open(f, 'w'):
- pass
+ 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()
+ 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(",")
+ args.archive = os.path.normpath(args.archive)
+ args.outputs = args.outputs.split(",")
- tar = tarfile.open(args.archive)
+ 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 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(f"[ERROR] Archive does not contain file with extname: {extname}")
+ for output in args.outputs:
+ extname = '.' + output.split('.', 1)[1]
+ if args.allow_empty_outputs:
+ ensure(output)
+ else:
+ print(f"[ERROR] Archive does not contain file with extname: {extname}")
if __name__ == '__main__':
- main()
+ main()
diff --git a/emsdk.py b/emsdk.py
index 4edf312..bd98b8c 100644
--- a/emsdk.py
+++ b/emsdk.py
@@ -20,15 +20,15 @@
import zipfile
if os.name == 'nt':
- import ctypes.wintypes
- import winreg
+ import ctypes.wintypes
+ import winreg
from urllib.parse import urljoin
from urllib.request import urlopen
if sys.version_info < (3, 10): # ruff: ignore[outdated-version-block]
- print(f'error: emsdk requires python 3.10 or above ({sys.executable} {sys.version})', file=sys.stderr)
- sys.exit(1)
+ print(f'error: emsdk requires python 3.10 or above ({sys.executable} {sys.version})', file=sys.stderr)
+ sys.exit(1)
emsdk_packages_url = 'https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/'
@@ -47,9 +47,9 @@
def get_env_boolean(name):
- env_var = os.getenv(name)
- assert env_var in {None, '1', '0'}, f'invalid environment variable setting ${env_var} for ${name}'
- return env_var == '1'
+ env_var = os.getenv(name)
+ assert env_var in {None, '1', '0'}, f'invalid environment variable setting ${env_var} for ${name}'
+ return env_var == '1'
# Enable this to do very verbose printing about the different steps that are
@@ -57,23 +57,23 @@
VERBOSE = get_env_boolean('EMSDK_VERBOSE')
QUIET = get_env_boolean('EMSDK_QUIET')
if get_env_boolean('EMSDK_NOTTY'):
- TTY_OUTPUT = False
+ TTY_OUTPUT = False
else:
- TTY_OUTPUT = sys.stdout.isatty()
+ TTY_OUTPUT = sys.stdout.isatty()
def info(msg):
- if not QUIET:
- print(msg, file=sys.stderr)
+ if not QUIET:
+ print(msg, file=sys.stderr)
def errlog(msg):
- print(msg, file=sys.stderr)
+ print(msg, file=sys.stderr)
def exit_with_error(msg):
- errlog(f'error: {msg}')
- sys.exit(1)
+ errlog(f'error: {msg}')
+ sys.exit(1)
WINDOWS = False
@@ -84,36 +84,36 @@
os_override = os.environ.get('EMSDK_OS')
if os_override:
- if os_override == 'windows':
- WINDOWS = True
- elif os_override == 'linux':
- LINUX = True
- elif os_override == 'macos':
- MACOS = True
- else:
- assert False, 'EMSDK_OS must be one of: windows, linux, macos'
+ if os_override == 'windows':
+ WINDOWS = True
+ elif os_override == 'linux':
+ LINUX = True
+ elif os_override == 'macos':
+ MACOS = True
+ else:
+ assert False, 'EMSDK_OS must be one of: windows, linux, macos'
else:
- if os.name == 'nt' or ('windows' in os.getenv('SYSTEMROOT', '').lower()) or ('windows' in os.getenv('COMSPEC', '').lower()):
- WINDOWS = True
+ if os.name == 'nt' or ('windows' in os.getenv('SYSTEMROOT', '').lower()) or ('windows' in os.getenv('COMSPEC', '').lower()):
+ WINDOWS = True
- msystem = os.getenv('MSYSTEM')
- if msystem:
- MSYS = True
- # Some functions like os.path.normpath() exhibit different behavior between
- # different versions of Python, so we need to distinguish between the MinGW
- # and MSYS versions of Python
- if sysconfig.get_platform() == 'mingw':
- MINGW = True
- if msystem not in {'MSYS', 'MINGW64'}:
- # https://stackoverflow.com/questions/37460073/msys-vs-mingw-internal-environment-variables
- errlog(f'Warning: MSYSTEM environment variable is present, and is set to "{msystem}". '
- 'This shell has not been tested with emsdk and may not work.')
+ msystem = os.getenv('MSYSTEM')
+ if msystem:
+ MSYS = True
+ # Some functions like os.path.normpath() exhibit different behavior between
+ # different versions of Python, so we need to distinguish between the MinGW
+ # and MSYS versions of Python
+ if sysconfig.get_platform() == 'mingw':
+ MINGW = True
+ if msystem not in {'MSYS', 'MINGW64'}:
+ # https://stackoverflow.com/questions/37460073/msys-vs-mingw-internal-environment-variables
+ errlog(f'Warning: MSYSTEM environment variable is present, and is set to "{msystem}". '
+ 'This shell has not been tested with emsdk and may not work.')
- if platform.mac_ver()[0]:
- MACOS = True
+ if platform.mac_ver()[0]:
+ MACOS = True
- if not MACOS and (platform.system() == 'Linux'):
- LINUX = True
+ if not MACOS and (platform.system() == 'Linux'):
+ LINUX = True
UNIX = (MACOS or LINUX)
@@ -126,32 +126,32 @@
FISH = get_env_boolean('EMSDK_FISH')
if WINDOWS and BASH:
- MSYS = True
+ MSYS = True
if not CSH and not POWERSHELL and not BASH and not CMD and not FISH:
- # Fall back to default of `cmd` on windows and `bash` otherwise
- if WINDOWS and not MSYS:
- CMD = True
- else:
- BASH = True
+ # Fall back to default of `cmd` on windows and `bash` otherwise
+ if WINDOWS and not MSYS:
+ CMD = True
+ else:
+ BASH = True
if WINDOWS:
- ENVPATH_SEPARATOR = ';'
+ ENVPATH_SEPARATOR = ';'
else:
- ENVPATH_SEPARATOR = ':'
+ ENVPATH_SEPARATOR = ':'
# platform.machine() may return AMD64 on windows, so standardize the case.
machine = os.getenv('EMSDK_ARCH', platform.machine().lower())
if machine.startswith(('x64', 'amd64', 'x86_64')):
- ARCH = 'x86_64'
+ ARCH = 'x86_64'
elif machine.endswith('86'):
- ARCH = 'x86'
+ ARCH = 'x86'
elif machine.startswith('aarch64') or machine.lower().startswith('arm64'):
- ARCH = 'arm64'
+ ARCH = 'arm64'
elif machine.startswith('arm'):
- ARCH = 'arm'
+ ARCH = 'arm'
else:
- exit_with_error('unknown machine architecture: ' + machine)
+ exit_with_error('unknown machine architecture: ' + machine)
# Don't saturate all cores to not steal the whole system, but be aggressive.
@@ -176,1432 +176,1432 @@
def os_name_short():
- if WINDOWS:
- return 'win'
- elif LINUX:
- return 'linux'
- elif MACOS:
- return 'mac'
- assert False, 'unknown OS'
+ if WINDOWS:
+ return 'win'
+ elif LINUX:
+ return 'linux'
+ elif MACOS:
+ return 'mac'
+ assert False, 'unknown OS'
def os_name():
- if WINDOWS:
- return 'windows'
- if LINUX:
- return 'linux'
- if MACOS:
- return 'macos'
- assert False, 'unknown OS'
+ if WINDOWS:
+ return 'windows'
+ if LINUX:
+ return 'linux'
+ if MACOS:
+ return 'macos'
+ assert False, 'unknown OS'
def debug_print(msg):
- if VERBOSE:
- errlog(msg)
+ if VERBOSE:
+ errlog(msg)
def to_unix_path(p):
- return p.replace('\\', '/')
+ return p.replace('\\', '/')
EMSDK_PATH = to_unix_path(os.path.dirname(os.path.realpath(__file__)))
EMSDK_SET_ENV = ""
if POWERSHELL:
- EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.ps1')
+ EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.ps1')
else:
- EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.bat')
+ EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.bat')
def parse_github_url_and_refspec(url):
- """Parse a github URL a tuple of its components.
+ """Parse a github URL a tuple of its components.
- Parses https://github.com/emscripten-core/emscripten/tree/d6aced8 to the
- tuple (https://github.com/emscripten-core/emscripten, d6aced8, emscripten-core)
- or https://github.com/emscripten-core/emscripten/commit/00b76f81f6474113fcf540db69297cfeb180347e
- to (https://github.com/emscripten-core/emscripten, 00b76f81f6474113fcf540db69297cfeb180347e, emscripten-core)
- """
- if not url:
- return ('', '', None)
+ Parses https://github.com/emscripten-core/emscripten/tree/d6aced8 to the
+ tuple (https://github.com/emscripten-core/emscripten, d6aced8, emscripten-core)
+ or https://github.com/emscripten-core/emscripten/commit/00b76f81f6474113fcf540db69297cfeb180347e
+ to (https://github.com/emscripten-core/emscripten, 00b76f81f6474113fcf540db69297cfeb180347e, emscripten-core)
+ """
+ if not url:
+ return ('', '', None)
- if url.endswith(('/tree/', '/tree', '/commit/', '/commit')):
- raise Exception(f'Malformed git URL and refspec {url}!')
+ if url.endswith(('/tree/', '/tree', '/commit/', '/commit')):
+ raise Exception(f'Malformed git URL and refspec {url}!')
- if '/tree/' in url:
- if url.endswith('/'):
- raise Exception(f'Malformed git URL and refspec {url}!')
- url, refspec = url.split('/tree/')
- remote_name = url.split('/')[-2]
- return (url, refspec, remote_name)
- elif '/commit/' in url:
- if url.endswith('/'):
- raise Exception(f'Malformed git URL and refspec {url}!')
- url, refspec = url.split('/commit/')
- remote_name = url.split('/')[-2]
- return (url, refspec, remote_name)
- else:
- return (url, 'main', None) # Assume the default branch is main in the absence of a refspec
+ if '/tree/' in url:
+ if url.endswith('/'):
+ raise Exception(f'Malformed git URL and refspec {url}!')
+ url, refspec = url.split('/tree/')
+ remote_name = url.split('/')[-2]
+ return (url, refspec, remote_name)
+ elif '/commit/' in url:
+ if url.endswith('/'):
+ raise Exception(f'Malformed git URL and refspec {url}!')
+ url, refspec = url.split('/commit/')
+ remote_name = url.split('/')[-2]
+ return (url, refspec, remote_name)
+ else:
+ return (url, 'main', None) # Assume the default branch is main in the absence of a refspec
ARCHIVE_SUFFIXES = ('zip', '.tar', '.gz', '.xz', '.tbz2', '.bz2')
def vswhere(version):
- program_files = os.getenv('ProgramFiles(x86)')
- if not program_files:
- program_files = os.environ['ProgramFiles']
- vswhere_path = os.path.join(program_files, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
- # Source: https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022
- tools_arch = 'ARM64' if ARCH == 'arm64' else 'x86.x64'
- try:
- # The "-products *" allows detection of Build Tools, the "-prerelease" allows detection of Preview version
- # of Visual Studio and Build Tools.
- stdout = run_get_output([vswhere_path, '-latest', '-products', '*',
- '-prerelease', '-version', f'[{version}.0,{version + 1}.0)',
- '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.' + tools_arch,
- '-property', 'installationPath', '-format', 'json'])
- json_output = json.loads(stdout)
- return str(json_output[0]['installationPath'])
- except Exception:
- return ''
+ program_files = os.getenv('ProgramFiles(x86)')
+ if not program_files:
+ program_files = os.environ['ProgramFiles']
+ vswhere_path = os.path.join(program_files, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
+ # Source: https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022
+ tools_arch = 'ARM64' if ARCH == 'arm64' else 'x86.x64'
+ try:
+ # The "-products *" allows detection of Build Tools, the "-prerelease" allows detection of Preview version
+ # of Visual Studio and Build Tools.
+ stdout = run_get_output([vswhere_path, '-latest', '-products', '*',
+ '-prerelease', '-version', f'[{version}.0,{version + 1}.0)',
+ '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.' + tools_arch,
+ '-property', 'installationPath', '-format', 'json'])
+ json_output = json.loads(stdout)
+ return str(json_output[0]['installationPath'])
+ except Exception:
+ return ''
CMAKE_GENERATOR = 'Unix Makefiles'
if WINDOWS:
- # Detect which CMake generator to use when building on Windows
- if '--mingw' in sys.argv:
- CMAKE_GENERATOR = 'MinGW Makefiles'
- elif '--vs2022' in sys.argv:
- CMAKE_GENERATOR = 'Visual Studio 17'
- elif '--vs2019' in sys.argv:
- CMAKE_GENERATOR = 'Visual Studio 16'
- elif vswhere(17):
- CMAKE_GENERATOR = 'Visual Studio 17'
- elif vswhere(16):
- CMAKE_GENERATOR = 'Visual Studio 16'
- elif shutil.which('mingw32-make') is not None and shutil.which('g++') is not None:
- CMAKE_GENERATOR = 'MinGW Makefiles'
- else:
- # No detected generator
- CMAKE_GENERATOR = ''
+ # Detect which CMake generator to use when building on Windows
+ if '--mingw' in sys.argv:
+ CMAKE_GENERATOR = 'MinGW Makefiles'
+ elif '--vs2022' in sys.argv:
+ CMAKE_GENERATOR = 'Visual Studio 17'
+ elif '--vs2019' in sys.argv:
+ CMAKE_GENERATOR = 'Visual Studio 16'
+ elif vswhere(17):
+ CMAKE_GENERATOR = 'Visual Studio 17'
+ elif vswhere(16):
+ CMAKE_GENERATOR = 'Visual Studio 16'
+ elif shutil.which('mingw32-make') is not None and shutil.which('g++') is not None:
+ CMAKE_GENERATOR = 'MinGW Makefiles'
+ else:
+ # No detected generator
+ CMAKE_GENERATOR = ''
sys.argv = [a for a in sys.argv if a not in {'--mingw', '--vs2019', '--vs2022'}]
def cmake_generator_prefix():
- """Computes a suitable path prefix to use when building with a given generator."""
- if CMAKE_GENERATOR == 'Visual Studio 17':
- return '_vs2022'
- if CMAKE_GENERATOR == 'Visual Studio 16':
- return '_vs2019'
- elif CMAKE_GENERATOR == 'MinGW Makefiles':
- return '_mingw'
- # Unix Makefiles do not specify a path prefix for backwards path compatibility
- return ''
+ """Computes a suitable path prefix to use when building with a given generator."""
+ if CMAKE_GENERATOR == 'Visual Studio 17':
+ return '_vs2022'
+ if CMAKE_GENERATOR == 'Visual Studio 16':
+ return '_vs2019'
+ elif CMAKE_GENERATOR == 'MinGW Makefiles':
+ return '_mingw'
+ # Unix Makefiles do not specify a path prefix for backwards path compatibility
+ return ''
def remove_tree(d):
- """Removes a directory tree even if it was readonly, and doesn't throw exception
- on failure.
- """
- debug_print(f'remove_tree({d})')
- if not os.path.exists(d):
- return
- try:
- def remove_readonly_and_try_again(func, path, exc_info):
- if not (os.stat(path).st_mode & stat.S_IWRITE):
- os.chmod(path, stat.S_IWRITE)
- func(path)
- else:
- raise exc_info[1]
- shutil.rmtree(d, onerror=remove_readonly_and_try_again)
- except Exception as e:
- debug_print('remove_tree threw an exception, ignoring: ' + str(e))
+ """Removes a directory tree even if it was readonly, and doesn't throw exception
+ on failure.
+ """
+ debug_print(f'remove_tree({d})')
+ if not os.path.exists(d):
+ return
+ try:
+ def remove_readonly_and_try_again(func, path, exc_info):
+ if not (os.stat(path).st_mode & stat.S_IWRITE):
+ os.chmod(path, stat.S_IWRITE)
+ func(path)
+ else:
+ raise exc_info[1]
+ shutil.rmtree(d, onerror=remove_readonly_and_try_again)
+ except Exception as e:
+ debug_print('remove_tree threw an exception, ignoring: ' + str(e))
def win_set_environment_variable_direct(key, value, system=True):
- folder = None
- try:
- if system:
- # Read globally from ALL USERS section.
- folder = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 0, winreg.KEY_ALL_ACCESS)
- else:
- # Register locally from CURRENT USER section.
- folder = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS)
- winreg.SetValueEx(folder, key, 0, winreg.REG_EXPAND_SZ, value)
- debug_print(f'Set key={key} with value {value} in registry.')
- return True
- except Exception as e:
- # 'Access is denied.'
- if e.args[3] == 5:
- exit_with_error(f"failed to set the environment variable '{key}'! "
- 'Setting environment variables permanently requires administrator access. '
- 'Please rerun this command with administrative privileges. '
- 'This can be done for example by holding down the Ctrl and Shift keys '
- 'while opening a command prompt in start menu.')
- errlog(f'Failed to write environment variable {key}:')
- errlog(str(e))
- return False
- finally:
- if folder is not None:
- folder.Close()
+ folder = None
+ try:
+ if system:
+ # Read globally from ALL USERS section.
+ folder = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 0, winreg.KEY_ALL_ACCESS)
+ else:
+ # Register locally from CURRENT USER section.
+ folder = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS)
+ winreg.SetValueEx(folder, key, 0, winreg.REG_EXPAND_SZ, value)
+ debug_print(f'Set key={key} with value {value} in registry.')
+ return True
+ except Exception as e:
+ # 'Access is denied.'
+ if e.args[3] == 5:
+ exit_with_error(f"failed to set the environment variable '{key}'! "
+ 'Setting environment variables permanently requires administrator access. '
+ 'Please rerun this command with administrative privileges. '
+ 'This can be done for example by holding down the Ctrl and Shift keys '
+ 'while opening a command prompt in start menu.')
+ errlog(f'Failed to write environment variable {key}:')
+ errlog(str(e))
+ return False
+ finally:
+ if folder is not None:
+ folder.Close()
def win_get_environment_variable(key, system=True, user=True, fallback=True):
- if (not system and not user and fallback):
- # if no --system or --permanent flag is provided use shell's value
- return os.environ[key]
- try:
- folder = None
- try:
- if system:
- # Read globally from ALL USERS section.
- folder = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
- else:
- # Register locally from CURRENT USER section.
- folder = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment')
- value = str(winreg.QueryValueEx(folder, key)[0])
- except Exception:
- # If reading registry fails for some reason - read via os.environ. This has the drawback
- # that expansion items such as %PROGRAMFILES% will have been expanded, so
- # need to be precise not to set these back to system registry, or
- # expansion items would be lost.
- if fallback:
+ if (not system and not user and fallback):
+ # if no --system or --permanent flag is provided use shell's value
return os.environ[key]
- return None
- finally:
- if folder is not None:
- folder.Close()
+ try:
+ folder = None
+ try:
+ if system:
+ # Read globally from ALL USERS section.
+ folder = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
+ else:
+ # Register locally from CURRENT USER section.
+ folder = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment')
+ value = str(winreg.QueryValueEx(folder, key)[0])
+ except Exception:
+ # If reading registry fails for some reason - read via os.environ. This has the drawback
+ # that expansion items such as %PROGRAMFILES% will have been expanded, so
+ # need to be precise not to set these back to system registry, or
+ # expansion items would be lost.
+ if fallback:
+ return os.environ[key]
+ return None
+ finally:
+ if folder is not None:
+ folder.Close()
- except Exception as e:
- # this catch is if both the registry key threw an exception and the key is not in os.environ
- if e.args[0] != 2:
- # 'The system cannot find the file specified.'
- errlog(f'Failed to read environment variable {key}:')
- errlog(str(e))
- return None
- return value
+ except Exception as e:
+ # this catch is if both the registry key threw an exception and the key is not in os.environ
+ if e.args[0] != 2:
+ # 'The system cannot find the file specified.'
+ errlog(f'Failed to read environment variable {key}:')
+ errlog(str(e))
+ return None
+ return value
def win_set_environment_variable(key, value, system, user):
- debug_print(f'set {key}={value}, in system={system}')
- previous_value = win_get_environment_variable(key, system=system, user=user)
- if previous_value == value:
- debug_print(' no need to set, since same value already exists.')
- # No need to elevate UAC for nothing to set the same value, skip.
- return False
+ debug_print(f'set {key}={value}, in system={system}')
+ previous_value = win_get_environment_variable(key, system=system, user=user)
+ if previous_value == value:
+ debug_print(' no need to set, since same value already exists.')
+ # No need to elevate UAC for nothing to set the same value, skip.
+ return False
- if not value:
+ if not value:
+ try:
+ if system:
+ cmd = ['REG', 'DELETE', 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', '/V', key, '/f']
+ else:
+ cmd = ['REG', 'DELETE', 'HKCU\\Environment', '/V', key, '/f']
+ debug_print(str(cmd))
+ subprocess.call(cmd, stdout=subprocess.PIPE)
+ except Exception:
+ return False
+ return True
+
try:
- if system:
- cmd = ['REG', 'DELETE', 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', '/V', key, '/f']
- else:
- cmd = ['REG', 'DELETE', 'HKCU\\Environment', '/V', key, '/f']
- debug_print(str(cmd))
- subprocess.call(cmd, stdout=subprocess.PIPE)
- except Exception:
- return False
- return True
+ if win_set_environment_variable_direct(key, value, system):
+ return True
+ # Escape % signs so that we don't expand references to environment variables.
+ value = value.replace('%', '^%')
+ if len(value) >= 1024:
+ exit_with_error(f'the new environment variable {key} is more than 1024 characters long! '
+ 'A value this long cannot be set via command line: please add the environment variable '
+ 'specified above to system environment manually via Control Panel.')
+ cmd = ['SETX', key, value]
+ debug_print(str(cmd))
+ retcode = subprocess.call(cmd, stdout=subprocess.PIPE)
+ if retcode != 0:
+ errlog(f'ERROR! Failed to set environment variable {key}={value}. You may need to set it manually.')
+ else:
+ return True
+ except Exception as e:
+ errlog(f'ERROR! Failed to set environment variable {key}={value}:')
+ errlog(str(e))
+ errlog('You may need to set it manually.')
- try:
- if win_set_environment_variable_direct(key, value, system):
- return True
- # Escape % signs so that we don't expand references to environment variables.
- value = value.replace('%', '^%')
- if len(value) >= 1024:
- exit_with_error(f'the new environment variable {key} is more than 1024 characters long! '
- 'A value this long cannot be set via command line: please add the environment variable '
- 'specified above to system environment manually via Control Panel.')
- cmd = ['SETX', key, value]
- debug_print(str(cmd))
- retcode = subprocess.call(cmd, stdout=subprocess.PIPE)
- if retcode != 0:
- errlog(f'ERROR! Failed to set environment variable {key}={value}. You may need to set it manually.')
- else:
- return True
- except Exception as e:
- errlog(f'ERROR! Failed to set environment variable {key}={value}:')
- errlog(str(e))
- errlog('You may need to set it manually.')
-
- return False
+ return False
def win_set_environment_variables(env_vars_to_add, system, user):
- if not env_vars_to_add:
- return
+ if not env_vars_to_add:
+ return
- changed = False
+ changed = False
- for key, value in env_vars_to_add:
- if win_set_environment_variable(key, value, system, user):
- if not changed:
- changed = True
- print('Setting global environment variables:')
+ for key, value in env_vars_to_add:
+ if win_set_environment_variable(key, value, system, user):
+ if not changed:
+ changed = True
+ print('Setting global environment variables:')
- print(f'{key} = {value}')
+ print(f'{key} = {value}')
- if not changed:
- print('Global environment variables up to date')
- return
+ if not changed:
+ print('Global environment variables up to date')
+ return
- # if changes were made then we need to notify other processes
- try:
- HWND_BROADCAST = ctypes.wintypes.HWND(0xFFFF) # win32con.HWND_BROADCAST == 65535
- WM_SETTINGCHANGE = 0x001A # win32con.WM_SETTINGCHANGE == 26
- SMTO_BLOCK = 0x0001 # win32con.SMTO_BLOCK == 1
- ctypes.windll.user32.SendMessageTimeoutA(
- HWND_BROADCAST, # hWnd: notify everyone
- WM_SETTINGCHANGE, # Msg: registry changed
- 0, # wParam: Must be 0 when setting changed is sent by users
- 'Environment', # lParam: Specifically environment variables changed
- SMTO_BLOCK, # fuFlags: Wait for message to be sent or timeout
- 100) # uTimeout: 100ms
- except Exception as e:
- errlog('SendMessageTimeout failed with error: ' + str(e))
+ # if changes were made then we need to notify other processes
+ try:
+ HWND_BROADCAST = ctypes.wintypes.HWND(0xFFFF) # win32con.HWND_BROADCAST == 65535
+ WM_SETTINGCHANGE = 0x001A # win32con.WM_SETTINGCHANGE == 26
+ SMTO_BLOCK = 0x0001 # win32con.SMTO_BLOCK == 1
+ ctypes.windll.user32.SendMessageTimeoutA(
+ HWND_BROADCAST, # hWnd: notify everyone
+ WM_SETTINGCHANGE, # Msg: registry changed
+ 0, # wParam: Must be 0 when setting changed is sent by users
+ 'Environment', # lParam: Specifically environment variables changed
+ SMTO_BLOCK, # fuFlags: Wait for message to be sent or timeout
+ 100) # uTimeout: 100ms
+ except Exception as e:
+ errlog('SendMessageTimeout failed with error: ' + str(e))
def win_delete_environment_variable(key, system=True, user=True):
- debug_print(f'win_delete_environment_variable(key={key}, system={system})')
- return win_set_environment_variable(key, None, system, user)
+ debug_print(f'win_delete_environment_variable(key={key}, system={system})')
+ return win_set_environment_variable(key, None, system, user)
def sdk_path(path):
- """Returns the absolute pathname to the given path inside the Emscripten SDK."""
- if os.path.isabs(path):
- return path
+ """Returns the absolute pathname to the given path inside the Emscripten SDK."""
+ if os.path.isabs(path):
+ return path
- return to_unix_path(os.path.join(EMSDK_PATH, path))
+ return to_unix_path(os.path.join(EMSDK_PATH, path))
def rmfile(filename):
- """Removes a single file, suppressing exceptions on failure."""
- debug_print(f'rmfile({filename})')
- if os.path.lexists(filename):
- os.remove(filename)
+ """Removes a single file, suppressing exceptions on failure."""
+ debug_print(f'rmfile({filename})')
+ if os.path.lexists(filename):
+ os.remove(filename)
def mkdir_p(path):
- debug_print(f'mkdir_p({path})')
- os.makedirs(path, exist_ok=True)
+ debug_print(f'mkdir_p({path})')
+ os.makedirs(path, exist_ok=True)
def is_nonempty_directory(path):
- if not os.path.isdir(path):
- return False
- return len(os.listdir(path)) != 0
+ if not os.path.isdir(path):
+ return False
+ return len(os.listdir(path)) != 0
def run(cmd, cwd=None, check=True):
- debug_print(f'run(cmd={cmd}, cwd={cwd})')
- returncode = subprocess.call(cmd, cwd=cwd, env=os.environ.copy())
- if returncode != 0 and check:
- errlog(f'{cmd} failed with error code {returncode}')
- return returncode
+ debug_print(f'run(cmd={cmd}, cwd={cwd})')
+ returncode = subprocess.call(cmd, cwd=cwd, env=os.environ.copy())
+ if returncode != 0 and check:
+ errlog(f'{cmd} failed with error code {returncode}')
+ return returncode
def untargz(source_filename, dest_dir):
- print(f"Unpacking '{source_filename}' to '{dest_dir}'")
- mkdir_p(dest_dir)
- returncode = run(['tar', '-xvf' if VERBOSE else '-xf', sdk_path(source_filename), '--strip', '1'], cwd=dest_dir)
- # tfile = tarfile.open(source_filename, 'r:gz')
- # tfile.extractall(dest_dir)
- return returncode == 0
+ print(f"Unpacking '{source_filename}' to '{dest_dir}'")
+ mkdir_p(dest_dir)
+ returncode = run(['tar', '-xvf' if VERBOSE else '-xf', sdk_path(source_filename), '--strip', '1'], cwd=dest_dir)
+ # tfile = tarfile.open(source_filename, 'r:gz')
+ # tfile.extractall(dest_dir)
+ return returncode == 0
def fix_potentially_long_windows_pathname(pathname):
- """Convert pathname to use extended-length path prefix on windows.
+ """Convert pathname to use extended-length path prefix on windows.
- On Windows, it is not possible to reference path names that are longer than
- ~260 characters, unless the path is referenced via a "\\?\" prefix.
- See https://msdn.microsoft.com/en-us/library/aa365247.aspx#maxpath and
- http://stackoverflow.com/questions/3555527/python-win32-filename-length-workaround
- In that mode, forward slashes cannot be used as delimiters.
- """
- if (not WINDOWS or MSYS) or os_override:
- return pathname
- # Test if emsdk calls fix_potentially_long_windows_pathname() with long
- # relative paths (which is problematic)
- if not os.path.isabs(pathname) and len(pathname) > 200:
- errlog(f'Warning: Seeing a relative path "{pathname}" which is dangerously long '
- 'for being referenced as a short Windows path name. Refactor emsdk to be able to handle this!')
- if pathname.startswith('\\\\?\\'):
- return pathname
- pathname = os.path.normpath(pathname.replace('/', '\\'))
- if MINGW:
- # MinGW versions of Python return normalized paths with backslashes
- # converted to forward slashes, so we must use forward slashes in our
- # prefix
- return '//?/' + pathname
- return '\\\\?\\' + pathname
+ On Windows, it is not possible to reference path names that are longer than
+ ~260 characters, unless the path is referenced via a "\\?\" prefix.
+ See https://msdn.microsoft.com/en-us/library/aa365247.aspx#maxpath and
+ http://stackoverflow.com/questions/3555527/python-win32-filename-length-workaround
+ In that mode, forward slashes cannot be used as delimiters.
+ """
+ if (not WINDOWS or MSYS) or os_override:
+ return pathname
+ # Test if emsdk calls fix_potentially_long_windows_pathname() with long
+ # relative paths (which is problematic)
+ if not os.path.isabs(pathname) and len(pathname) > 200:
+ errlog(f'Warning: Seeing a relative path "{pathname}" which is dangerously long '
+ 'for being referenced as a short Windows path name. Refactor emsdk to be able to handle this!')
+ if pathname.startswith('\\\\?\\'):
+ return pathname
+ pathname = os.path.normpath(pathname.replace('/', '\\'))
+ if MINGW:
+ # MinGW versions of Python return normalized paths with backslashes
+ # converted to forward slashes, so we must use forward slashes in our
+ # prefix
+ return '//?/' + pathname
+ return '\\\\?\\' + pathname
def move_with_overwrite(src, dest):
- """On windows, rename/move will fail if the destination exists, and there is no
- race-free way to do it. This method removes the destination if it exists, so
- the move always works
- """
- if os.path.exists(dest):
- os.remove(dest)
- os.rename(src, dest)
+ """On windows, rename/move will fail if the destination exists, and there is no
+ race-free way to do it. This method removes the destination if it exists, so
+ the move always works
+ """
+ if os.path.exists(dest):
+ os.remove(dest)
+ os.rename(src, dest)
# http://stackoverflow.com/questions/12886768/simple-way-to-unzip-file-in-python-on-all-oses
def unzip(source_filename, dest_dir):
- print(f"Unpacking '{source_filename}' to '{dest_dir}'")
- mkdir_p(dest_dir)
- common_subdir = None
- try:
- with zipfile.ZipFile(source_filename) as zf:
- # Implement '--strip 1' behavior to unzipping by testing if all the files
- # in the zip reside in a common subdirectory, and if so, we move the
- # output tree at the end of uncompression step.
- for member in zf.infolist():
- words = member.filename.split('/')
- if len(words) > 1: # If there is a directory component?
- if common_subdir is None:
- common_subdir = words[0]
- elif common_subdir != words[0]:
- common_subdir = None
- break
- else:
- common_subdir = None
- break
+ print(f"Unpacking '{source_filename}' to '{dest_dir}'")
+ mkdir_p(dest_dir)
+ common_subdir = None
+ try:
+ with zipfile.ZipFile(source_filename) as zf:
+ # Implement '--strip 1' behavior to unzipping by testing if all the files
+ # in the zip reside in a common subdirectory, and if so, we move the
+ # output tree at the end of uncompression step.
+ for member in zf.infolist():
+ words = member.filename.split('/')
+ if len(words) > 1: # If there is a directory component?
+ if common_subdir is None:
+ common_subdir = words[0]
+ elif common_subdir != words[0]:
+ common_subdir = None
+ break
+ else:
+ common_subdir = None
+ break
- unzip_to_dir = dest_dir
- if common_subdir:
- unzip_to_dir = os.path.join(os.path.dirname(dest_dir), 'unzip_temp')
+ unzip_to_dir = dest_dir
+ if common_subdir:
+ unzip_to_dir = os.path.join(os.path.dirname(dest_dir), 'unzip_temp')
- # Now do the actual decompress.
- unzip_to_dir = fix_potentially_long_windows_pathname(unzip_to_dir)
- for member in zf.infolist():
- zf.extract(member, unzip_to_dir)
- dst_filename = os.path.join(unzip_to_dir, os.path.normpath(member.filename))
+ # Now do the actual decompress.
+ unzip_to_dir = fix_potentially_long_windows_pathname(unzip_to_dir)
+ for member in zf.infolist():
+ zf.extract(member, unzip_to_dir)
+ dst_filename = os.path.join(unzip_to_dir, os.path.normpath(member.filename))
- # See: https://stackoverflow.com/questions/42326428/zipfile-in-python-file-permission
- unix_attributes = member.external_attr >> 16
- if unix_attributes:
- os.chmod(dst_filename, unix_attributes)
+ # See: https://stackoverflow.com/questions/42326428/zipfile-in-python-file-permission
+ unix_attributes = member.external_attr >> 16
+ if unix_attributes:
+ os.chmod(dst_filename, unix_attributes)
- # Move the extracted file to its final location without the base
- # directory name, if we are stripping that away.
- if common_subdir:
- assert member.filename.startswith(common_subdir), f'unexpected filename {member.filename}'
- stripped_filename = '.' + member.filename[len(common_subdir):]
- final_dst_filename = os.path.join(dest_dir, stripped_filename)
- # Check if a directory
- if stripped_filename.endswith('/'):
- d = fix_potentially_long_windows_pathname(final_dst_filename)
- if not os.path.isdir(d):
- os.mkdir(d)
- else:
- parent_dir = os.path.dirname(fix_potentially_long_windows_pathname(final_dst_filename))
- if parent_dir and not os.path.exists(parent_dir):
- os.makedirs(parent_dir)
- move_with_overwrite(fix_potentially_long_windows_pathname(dst_filename), fix_potentially_long_windows_pathname(final_dst_filename))
+ # Move the extracted file to its final location without the base
+ # directory name, if we are stripping that away.
+ if common_subdir:
+ assert member.filename.startswith(common_subdir), f'unexpected filename {member.filename}'
+ stripped_filename = '.' + member.filename[len(common_subdir):]
+ final_dst_filename = os.path.join(dest_dir, stripped_filename)
+ # Check if a directory
+ if stripped_filename.endswith('/'):
+ d = fix_potentially_long_windows_pathname(final_dst_filename)
+ if not os.path.isdir(d):
+ os.mkdir(d)
+ else:
+ parent_dir = os.path.dirname(fix_potentially_long_windows_pathname(final_dst_filename))
+ if parent_dir and not os.path.exists(parent_dir):
+ os.makedirs(parent_dir)
+ move_with_overwrite(fix_potentially_long_windows_pathname(dst_filename), fix_potentially_long_windows_pathname(final_dst_filename))
- if common_subdir:
- remove_tree(unzip_to_dir)
- except zipfile.BadZipfile as e:
- errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}! Removing the corrupted zip file.")
- rmfile(source_filename)
- return False
- except Exception as e:
- errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}")
- return False
+ if common_subdir:
+ remove_tree(unzip_to_dir)
+ except zipfile.BadZipfile as e:
+ errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}! Removing the corrupted zip file.")
+ rmfile(source_filename)
+ return False
+ except Exception as e:
+ errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}")
+ return False
- return True
+ return True
def path_points_to_directory(path):
- """This function interprets whether the given string looks like a path to a
- directory instead of a file, without looking at the actual filesystem.
- 'a/b/c' points to directory, so does 'a/b/c/', but 'a/b/c.x' is parsed as a
- filename
- """
- if path == '.':
- return True
- last_slash = max(path.rfind('/'), path.rfind('\\'))
- last_dot = path.rfind('.')
- no_suffix = last_dot < last_slash or last_dot == -1
- if no_suffix:
- return True
- suffix = path[last_dot:]
- # Very simple logic for the only file suffixes used by emsdk downloader. Other
- # suffixes, like 'clang-3.2' are treated as dirs.
- if suffix in {'.exe', '.zip', '.txt'}:
- return False
- else:
- return True
+ """This function interprets whether the given string looks like a path to a
+ directory instead of a file, without looking at the actual filesystem.
+ 'a/b/c' points to directory, so does 'a/b/c/', but 'a/b/c.x' is parsed as a
+ filename
+ """
+ if path == '.':
+ return True
+ last_slash = max(path.rfind('/'), path.rfind('\\'))
+ last_dot = path.rfind('.')
+ no_suffix = last_dot < last_slash or last_dot == -1
+ if no_suffix:
+ return True
+ suffix = path[last_dot:]
+ # Very simple logic for the only file suffixes used by emsdk downloader. Other
+ # suffixes, like 'clang-3.2' are treated as dirs.
+ if suffix in {'.exe', '.zip', '.txt'}:
+ return False
+ else:
+ return True
def get_content_length(download):
- try:
- return int(download.headers.get('Content-Length', 0))
- except ValueError:
- return 0
+ try:
+ return int(download.headers.get('Content-Length', 0))
+ except ValueError:
+ return 0
def get_download_target(url, dstpath, filename_prefix=''):
- file_name = filename_prefix + url.split('/')[-1]
- if path_points_to_directory(dstpath):
- file_name = os.path.join(dstpath, file_name)
- else:
- file_name = dstpath
+ file_name = filename_prefix + url.split('/')[-1]
+ if path_points_to_directory(dstpath):
+ file_name = os.path.join(dstpath, file_name)
+ else:
+ file_name = dstpath
- # Treat all relative destination paths as relative to the SDK root directory,
- # not the current working directory.
- file_name = sdk_path(file_name)
+ # Treat all relative destination paths as relative to the SDK root directory,
+ # not the current working directory.
+ file_name = sdk_path(file_name)
- return file_name
+ return file_name
def download_with_curl(url, file_name):
- print(f"Downloading: {file_name} from {url}")
- if not shutil.which('curl'):
- exit_with_error('curl not found in PATH')
- # -#: show progress bar
- # -L: Follow HTTP 3XX redirections
- # -f: Fail on HTTP errors
- subprocess.check_call(['curl', '-#', '-f', '-L', '-o', file_name, url])
+ print(f"Downloading: {file_name} from {url}")
+ if not shutil.which('curl'):
+ exit_with_error('curl not found in PATH')
+ # -#: show progress bar
+ # -L: Follow HTTP 3XX redirections
+ # -f: Fail on HTTP errors
+ subprocess.check_call(['curl', '-#', '-f', '-L', '-o', file_name, url])
def download_with_urllib(url, file_name):
- u = urlopen(url)
- file_size = get_content_length(u)
- if file_size > 0:
- print(f"Downloading: {file_name} from {url}, {file_size} Bytes")
- else:
- print(f"Downloading: {file_name} from {url}")
+ u = urlopen(url)
+ file_size = get_content_length(u)
+ if file_size > 0:
+ print(f"Downloading: {file_name} from {url}, {file_size} Bytes")
+ else:
+ print(f"Downloading: {file_name} from {url}")
- file_size_dl = 0
- # Draw a progress bar 80 chars wide (in non-TTY mode)
- progress_max = 80 - 4
- progress_shown = 0
- block_sz = 256 * 1024
- if not TTY_OUTPUT:
- print(' [', end='')
+ file_size_dl = 0
+ # Draw a progress bar 80 chars wide (in non-TTY mode)
+ progress_max = 80 - 4
+ progress_shown = 0
+ block_sz = 256 * 1024
+ if not TTY_OUTPUT:
+ print(' [', end='')
- with open(file_name, 'wb') as f:
- while True:
- buffer = u.read(block_sz)
- if not buffer:
- break
+ with open(file_name, 'wb') as f:
+ while True:
+ buffer = u.read(block_sz)
+ if not buffer:
+ break
- file_size_dl += len(buffer)
- f.write(buffer)
- if file_size:
- percent = file_size_dl * 100.0 / file_size
- if TTY_OUTPUT:
- status = f' {file_size_dl:10d} [{percent:3.02f}%]'
- print(status, end='\r')
- else:
- while progress_shown < progress_max * percent / 100:
- print('-', end='')
- sys.stdout.flush()
- progress_shown += 1
+ file_size_dl += len(buffer)
+ f.write(buffer)
+ if file_size:
+ percent = file_size_dl * 100.0 / file_size
+ if TTY_OUTPUT:
+ status = f' {file_size_dl:10d} [{percent:3.02f}%]'
+ print(status, end='\r')
+ else:
+ while progress_shown < progress_max * percent / 100:
+ print('-', end='')
+ sys.stdout.flush()
+ progress_shown += 1
- if not TTY_OUTPUT:
- print(']')
- sys.stdout.flush()
+ if not TTY_OUTPUT:
+ print(']')
+ sys.stdout.flush()
- debug_print(f'finished downloading ({file_size_dl} bytes)')
+ debug_print(f'finished downloading ({file_size_dl} bytes)')
def download_file(url, dstpath, filename_prefix=''):
- """On success, returns the filename on the disk pointing to the destination file that was produced
- On failure, returns None.
- """
- debug_print(f'download_file(url={url}, dstpath={dstpath})')
- file_name = get_download_target(url, dstpath, filename_prefix)
+ """On success, returns the filename on the disk pointing to the destination file that was produced
+ On failure, returns None.
+ """
+ debug_print(f'download_file(url={url}, dstpath={dstpath})')
+ file_name = get_download_target(url, dstpath, filename_prefix)
- if KEEP_DOWNLOADS and os.path.exists(file_name):
- print(f"File '{file_name}' already downloaded, skipping.")
+ if KEEP_DOWNLOADS and os.path.exists(file_name):
+ print(f"File '{file_name}' already downloaded, skipping.")
+ return file_name
+
+ mkdir_p(os.path.dirname(file_name))
+
+ try:
+ # Use curl on macOS or when EMSDK_USE_CURL is set to avoid
+ # CERTIFICATE_VERIFY_FAILED issue with python's urllib:
+ # https://stackoverflow.com/questions/40684543/how-to-make-python-use-ca-certificates-from-mac-os-truststore
+ # Unlike on linux or windows, curl is always available on macOS systems.
+ if MACOS or 'EMSDK_USE_CURL' in os.environ:
+ download_with_curl(url, file_name)
+ else:
+ download_with_urllib(url, file_name)
+ except Exception as e:
+ errlog(f"Error: Downloading URL '{url}': {e}")
+ return None
+ except KeyboardInterrupt:
+ rmfile(file_name)
+ raise
+
return file_name
- mkdir_p(os.path.dirname(file_name))
-
- try:
- # Use curl on macOS or when EMSDK_USE_CURL is set to avoid
- # CERTIFICATE_VERIFY_FAILED issue with python's urllib:
- # https://stackoverflow.com/questions/40684543/how-to-make-python-use-ca-certificates-from-mac-os-truststore
- # Unlike on linux or windows, curl is always available on macOS systems.
- if MACOS or 'EMSDK_USE_CURL' in os.environ:
- download_with_curl(url, file_name)
- else:
- download_with_urllib(url, file_name)
- except Exception as e:
- errlog(f"Error: Downloading URL '{url}': {e}")
- return None
- except KeyboardInterrupt:
- rmfile(file_name)
- raise
-
- return file_name
-
def run_get_output(cmd, cwd=None):
- debug_print(f'run_get_output(cmd={cmd}, cwd={cwd})')
- return subprocess.check_output(cmd, cwd=cwd, text=True)
+ debug_print(f'run_get_output(cmd={cmd}, cwd={cwd})')
+ return subprocess.check_output(cmd, cwd=cwd, text=True)
def GIT():
- git = shutil.which('git')
- if not git:
- msg = 'git executable was not found. Please install git for this operation!'
- if WINDOWS:
- msg += "This can be done from http://git-scm.com/"
- elif MACOS:
- msg += "This can be done from http://git-scm.com/, or by installing XCode and then the XCode Command Line Tools"
- elif LINUX:
- msg += "This can be probably be done using your package manager, see http://git-scm.com/book/en/Getting-Started-Installing-Git"
- exit_with_error(msg)
+ git = shutil.which('git')
+ if not git:
+ msg = 'git executable was not found. Please install git for this operation!'
+ if WINDOWS:
+ msg += "This can be done from http://git-scm.com/"
+ elif MACOS:
+ msg += "This can be done from http://git-scm.com/, or by installing XCode and then the XCode Command Line Tools"
+ elif LINUX:
+ msg += "This can be probably be done using your package manager, see http://git-scm.com/book/en/Getting-Started-Installing-Git"
+ exit_with_error(msg)
- return git
+ return git
def git_repo_version(repo_path):
- stdout = run_get_output([GIT(), 'log', '-n', '1', '--pretty="%aD %H"'], cwd=repo_path)
- return stdout.strip()
+ stdout = run_get_output([GIT(), 'log', '-n', '1', '--pretty="%aD %H"'], cwd=repo_path)
+ return stdout.strip()
def git_recent_commits(repo_path, n=20):
- stdout = run_get_output([GIT(), 'log', '-n', str(n), '--pretty="%H"'], cwd=repo_path)
- return stdout.strip().replace('\r', '').replace('"', '').split('\n')
+ stdout = run_get_output([GIT(), 'log', '-n', str(n), '--pretty="%H"'], cwd=repo_path)
+ return stdout.strip().replace('\r', '').replace('"', '').split('\n')
def get_git_remotes(repo_path):
- remotes = []
- output = run_get_output([GIT(), 'remote', '-v'], cwd=repo_path)
- for line in output.splitlines():
- remotes += [line.split()[0]]
- return remotes
+ remotes = []
+ output = run_get_output([GIT(), 'remote', '-v'], cwd=repo_path)
+ for line in output.splitlines():
+ remotes += [line.split()[0]]
+ return remotes
def git_clone(url, dstpath, branch, remote_name='origin'):
- debug_print(f'git_clone(url={url}, dstpath={dstpath})')
- if os.path.exists(os.path.join(dstpath, '.git')):
- remotes = get_git_remotes(dstpath)
- if remote_name in remotes:
- debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, skipping.')
- return True
- else:
- debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, but remote has not yet been added. Creating.')
- return run([GIT(), 'remote', 'add', remote_name, url], cwd=dstpath) == 0
+ debug_print(f'git_clone(url={url}, dstpath={dstpath})')
+ if os.path.exists(os.path.join(dstpath, '.git')):
+ remotes = get_git_remotes(dstpath)
+ if remote_name in remotes:
+ debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, skipping.')
+ return True
+ else:
+ debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, but remote has not yet been added. Creating.')
+ return run([GIT(), 'remote', 'add', remote_name, url], cwd=dstpath) == 0
- mkdir_p(dstpath)
- git_clone_args = ['--recurse-submodules', '--branch', branch] # Do not check out a branch (installer will issue a checkout command right after)
- if GIT_CLONE_SHALLOW:
- git_clone_args += ['--depth', '1']
- print(f'Cloning from {url}...')
- return run([GIT(), 'clone', '-o', remote_name, *git_clone_args, url, dstpath]) == 0
+ mkdir_p(dstpath)
+ git_clone_args = ['--recurse-submodules', '--branch', branch] # Do not check out a branch (installer will issue a checkout command right after)
+ if GIT_CLONE_SHALLOW:
+ git_clone_args += ['--depth', '1']
+ print(f'Cloning from {url}...')
+ return run([GIT(), 'clone', '-o', remote_name, *git_clone_args, url, dstpath]) == 0
def git_pull(repo_path, branch_or_tag, remote_name='origin'):
- debug_print(f'git_pull(repo_path={repo_path}, branch/tag={branch_or_tag}, remote_name={remote_name})')
- ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path)
- if ret != 0:
- return False
- try:
- print(f"Fetching latest changes to the branch/tag '{branch_or_tag}' for '{repo_path}'...")
+ debug_print(f'git_pull(repo_path={repo_path}, branch/tag={branch_or_tag}, remote_name={remote_name})')
ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path)
if ret != 0:
- return False
- # Test if branch_or_tag is a branch, or if it is a tag that needs to be updated
- target_is_tag = run([GIT(), 'symbolic-ref', '-q', 'HEAD'], repo_path, check=False)
+ return False
+ try:
+ print(f"Fetching latest changes to the branch/tag '{branch_or_tag}' for '{repo_path}'...")
+ ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path)
+ if ret != 0:
+ return False
+ # Test if branch_or_tag is a branch, or if it is a tag that needs to be updated
+ target_is_tag = run([GIT(), 'symbolic-ref', '-q', 'HEAD'], repo_path, check=False)
- if target_is_tag:
- ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', branch_or_tag], repo_path)
- else:
- local_branch_prefix = (remote_name + '_') if remote_name != 'origin' else ''
- ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', '-B', local_branch_prefix + branch_or_tag,
- '--track', remote_name + '/' + branch_or_tag], repo_path)
- if ret != 0:
- return False
- if not target_is_tag:
- # update branch to latest (not needed for tags)
- # this line assumes that the user has not gone and made local changes to the repo
- ret = run([GIT(), 'merge', '--ff-only', remote_name + '/' + branch_or_tag], repo_path)
- if ret != 0:
- return False
- run([GIT(), 'submodule', 'update', '--init'], repo_path, check=False)
- except Exception:
- errlog('git operation failed!')
- return False
- print(f"Successfully updated and checked out branch/tag '{branch_or_tag}' on repository '{repo_path}'")
- print("Current repository version: " + git_repo_version(repo_path))
- return True
+ if target_is_tag:
+ ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', branch_or_tag], repo_path)
+ else:
+ local_branch_prefix = (remote_name + '_') if remote_name != 'origin' else ''
+ ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', '-B', local_branch_prefix + branch_or_tag,
+ '--track', remote_name + '/' + branch_or_tag], repo_path)
+ if ret != 0:
+ return False
+ if not target_is_tag:
+ # update branch to latest (not needed for tags)
+ # this line assumes that the user has not gone and made local changes to the repo
+ ret = run([GIT(), 'merge', '--ff-only', remote_name + '/' + branch_or_tag], repo_path)
+ if ret != 0:
+ return False
+ run([GIT(), 'submodule', 'update', '--init'], repo_path, check=False)
+ except Exception:
+ errlog('git operation failed!')
+ return False
+ print(f"Successfully updated and checked out branch/tag '{branch_or_tag}' on repository '{repo_path}'")
+ print("Current repository version: " + git_repo_version(repo_path))
+ return True
def git_clone_checkout_and_pull(url, dstpath, branch, override_remote_name='origin'):
- debug_print(f'git_clone_checkout_and_pull(url={url}, dstpath={dstpath}, branch={branch}, override_remote_name={override_remote_name})')
+ debug_print(f'git_clone_checkout_and_pull(url={url}, dstpath={dstpath}, branch={branch}, override_remote_name={override_remote_name})')
- # Make sure the repository is cloned first
- success = git_clone(url, dstpath, branch, override_remote_name)
- if not success:
- return False
+ # Make sure the repository is cloned first
+ success = git_clone(url, dstpath, branch, override_remote_name)
+ if not success:
+ return False
- # And/or issue a pull/checkout to get to latest code.
- return git_pull(dstpath, branch, override_remote_name)
+ # And/or issue a pull/checkout to get to latest code.
+ return git_pull(dstpath, branch, override_remote_name)
def decide_cmake_build_type(tool):
- """Each tool can have its own build type, or it can be overridden on the command
- line.
- """
- if CMAKE_BUILD_TYPE_OVERRIDE:
- return CMAKE_BUILD_TYPE_OVERRIDE
- else:
- return tool.cmake_build_type
+ """Each tool can have its own build type, or it can be overridden on the command
+ line.
+ """
+ if CMAKE_BUILD_TYPE_OVERRIDE:
+ return CMAKE_BUILD_TYPE_OVERRIDE
+ else:
+ return tool.cmake_build_type
def llvm_build_dir(tool):
- """The root directory of the build."""
- generator_suffix = cmake_generator_prefix()
- bitness_suffix = '_32' if tool.bitness == 32 else '_64'
+ """The root directory of the build."""
+ generator_suffix = cmake_generator_prefix()
+ bitness_suffix = '_32' if tool.bitness == 32 else '_64'
- if tool.git_branch:
- build_dir = 'build_' + tool.git_branch.replace(os.sep, '-') + generator_suffix + bitness_suffix
- else:
- build_dir = 'build_' + tool.version + generator_suffix + bitness_suffix
- return build_dir
+ if tool.git_branch:
+ build_dir = 'build_' + tool.git_branch.replace(os.sep, '-') + generator_suffix + bitness_suffix
+ else:
+ build_dir = 'build_' + tool.version + generator_suffix + bitness_suffix
+ return build_dir
def exe_suffix(filename):
- if WINDOWS and not filename.endswith('.exe'):
- filename += '.exe'
- return filename
+ if WINDOWS and not filename.endswith('.exe'):
+ filename += '.exe'
+ return filename
def llvm_build_bin_dir(tool):
- """The directory where the binaries are produced. (relative to the installation
- root directory of the tool)
- """
- build_dir = llvm_build_dir(tool)
- if WINDOWS and 'Visual Studio' in CMAKE_GENERATOR:
- old_llvm_bin_dir = os.path.join(build_dir, 'bin', decide_cmake_build_type(tool))
+ """The directory where the binaries are produced. (relative to the installation
+ root directory of the tool)
+ """
+ build_dir = llvm_build_dir(tool)
+ if WINDOWS and 'Visual Studio' in CMAKE_GENERATOR:
+ old_llvm_bin_dir = os.path.join(build_dir, 'bin', decide_cmake_build_type(tool))
- new_llvm_bin_dir = None
- default_cmake_build_type = decide_cmake_build_type(tool)
- cmake_build_types = [default_cmake_build_type, 'Release', 'RelWithDebInfo', 'MinSizeRel', 'Debug']
- for build_type in cmake_build_types:
- d = os.path.join(build_dir, build_type, 'bin')
- if os.path.isfile(os.path.join(tool.installation_path(), d, exe_suffix('clang'))):
- new_llvm_bin_dir = d
- break
+ new_llvm_bin_dir = None
+ default_cmake_build_type = decide_cmake_build_type(tool)
+ cmake_build_types = [default_cmake_build_type, 'Release', 'RelWithDebInfo', 'MinSizeRel', 'Debug']
+ for build_type in cmake_build_types:
+ d = os.path.join(build_dir, build_type, 'bin')
+ if os.path.isfile(os.path.join(tool.installation_path(), d, exe_suffix('clang'))):
+ new_llvm_bin_dir = d
+ break
- if new_llvm_bin_dir and os.path.exists(os.path.join(tool.installation_path(), new_llvm_bin_dir)):
- return new_llvm_bin_dir
- elif os.path.exists(os.path.join(tool.installation_path(), old_llvm_bin_dir)):
- return old_llvm_bin_dir
- return os.path.join(build_dir, default_cmake_build_type, 'bin')
- else:
- return os.path.join(build_dir, 'bin')
+ if new_llvm_bin_dir and os.path.exists(os.path.join(tool.installation_path(), new_llvm_bin_dir)):
+ return new_llvm_bin_dir
+ elif os.path.exists(os.path.join(tool.installation_path(), old_llvm_bin_dir)):
+ return old_llvm_bin_dir
+ return os.path.join(build_dir, default_cmake_build_type, 'bin')
+ else:
+ return os.path.join(build_dir, 'bin')
def build_env():
- if WINDOWS and 'Visual Studio' in CMAKE_GENERATOR:
- env = os.environ.copy()
- # MSBuild.exe has an internal mechanism to avoid N^2 oversubscription of threads in its two-tier build model, see
- # https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
- env['UseMultiToolTask'] = 'true'
- env['EnforceProcessCountAcrossBuilds'] = 'true'
- return env
+ if WINDOWS and 'Visual Studio' in CMAKE_GENERATOR:
+ env = os.environ.copy()
+ # MSBuild.exe has an internal mechanism to avoid N^2 oversubscription of threads in its two-tier build model, see
+ # https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
+ env['UseMultiToolTask'] = 'true'
+ env['EnforceProcessCountAcrossBuilds'] = 'true'
+ return env
- # By default, just inherit the parent env
- return None
+ # By default, just inherit the parent env
+ return None
def find_cmake():
- """Find path to cmake executable, as one of the activated tools, in PATH, or from installed tools."""
- def locate_cmake_from_tool(tool):
- tool_path = get_required_path([tool])
- tool_path = tool_path[-1]
- cmake_exe = 'cmake.exe' if WINDOWS else 'cmake'
- cmake_exe = os.path.join(tool_path, cmake_exe)
- if os.path.isfile(cmake_exe):
- return cmake_exe
+ """Find path to cmake executable, as one of the activated tools, in PATH, or from installed tools."""
+ def locate_cmake_from_tool(tool):
+ tool_path = get_required_path([tool])
+ tool_path = tool_path[-1]
+ cmake_exe = 'cmake.exe' if WINDOWS else 'cmake'
+ cmake_exe = os.path.join(tool_path, cmake_exe)
+ if os.path.isfile(cmake_exe):
+ return cmake_exe
- # 1. If user has activated a specific cmake tool, then use that tool to configure the build.
- for tool in reversed(tools):
- if tool.id == 'cmake' and tool.is_active():
- cmake_exe = locate_cmake_from_tool(tool)
- if cmake_exe:
- info(f'Found installed+activated CMake tool at "{cmake_exe}"')
+ # 1. If user has activated a specific cmake tool, then use that tool to configure the build.
+ for tool in reversed(tools):
+ if tool.id == 'cmake' and tool.is_active():
+ cmake_exe = locate_cmake_from_tool(tool)
+ if cmake_exe:
+ info(f'Found installed+activated CMake tool at "{cmake_exe}"')
+ return cmake_exe
+
+ # 2. If cmake already exists in PATH, then use that cmake to configure the build.
+ cmake_exe = shutil.which('cmake')
+ if cmake_exe:
+ info(f'Found CMake from PATH at "{cmake_exe}"')
return cmake_exe
- # 2. If cmake already exists in PATH, then use that cmake to configure the build.
- cmake_exe = shutil.which('cmake')
- if cmake_exe:
- info(f'Found CMake from PATH at "{cmake_exe}"')
- return cmake_exe
+ # 3. Finally, if user has installed a cmake tool, but has not activated that, then use
+ # that tool. This enables a single-liner directive
+ # "emsdk install cmake-4.2.0-rc3-64bit llvm-git-main-64bit" to first install CMake, and
+ # then use it to configure to build LLVM.
+ for tool in reversed(tools):
+ if tool.id == 'cmake' and tool.is_installed():
+ cmake_exe = locate_cmake_from_tool(tool)
+ if cmake_exe:
+ info(f'Found installed CMake tool at "{cmake_exe}"')
+ return cmake_exe
- # 3. Finally, if user has installed a cmake tool, but has not activated that, then use
- # that tool. This enables a single-liner directive
- # "emsdk install cmake-4.2.0-rc3-64bit llvm-git-main-64bit" to first install CMake, and
- # then use it to configure to build LLVM.
- for tool in reversed(tools):
- if tool.id == 'cmake' and tool.is_installed():
- cmake_exe = locate_cmake_from_tool(tool)
- if cmake_exe:
- info(f'Found installed CMake tool at "{cmake_exe}"')
- return cmake_exe
-
- exit_with_error('Unable to find "cmake" in PATH, or as installed/activated tool! Please install CMake first')
+ exit_with_error('Unable to find "cmake" in PATH, or as installed/activated tool! Please install CMake first')
def make_build(build_root, build_type):
- debug_print(f'make_build(build_root={build_root}, build_type={build_type})')
- if CPU_CORES > 1:
- print(f'Performing a parallel build with {CPU_CORES} cores.')
- else:
- print('Performing a singlethreaded build.')
+ debug_print(f'make_build(build_root={build_root}, build_type={build_type})')
+ if CPU_CORES > 1:
+ print(f'Performing a parallel build with {CPU_CORES} cores.')
+ else:
+ print('Performing a singlethreaded build.')
- make = [find_cmake(), '--build', '.', '--config', build_type]
- if 'Visual Studio' in CMAKE_GENERATOR:
- # Visual Studio historically has had a two-tier problem in its build system design. A single MSBuild.exe instance only governs
- # the build of a single project (.exe/.lib/.dll) in a solution. Passing the -j parameter above will only enable multiple MSBuild.exe
- # instances to be spawned to build multiple projects in parallel, but each MSBuild.exe is still singlethreaded.
- # To enable each MSBuild.exe instance to also compile several .cpp files in parallel inside a single project, pass the extra
- # MSBuild.exe specific "Multi-ToolTask" (MTT) setting /p:CL_MPCount. This enables each MSBuild.exe to parallelize builds wide.
- # This requires CMake 3.12 or newer.
- make += ['-j', str(CPU_CORES), '--', '/p:CL_MPCount=' + str(CPU_CORES)]
- else:
- # Pass -j to native make, CMake might not support -j option.
- make += ['--', '-j', str(CPU_CORES)]
+ make = [find_cmake(), '--build', '.', '--config', build_type]
+ if 'Visual Studio' in CMAKE_GENERATOR:
+ # Visual Studio historically has had a two-tier problem in its build system design. A single MSBuild.exe instance only governs
+ # the build of a single project (.exe/.lib/.dll) in a solution. Passing the -j parameter above will only enable multiple MSBuild.exe
+ # instances to be spawned to build multiple projects in parallel, but each MSBuild.exe is still singlethreaded.
+ # To enable each MSBuild.exe instance to also compile several .cpp files in parallel inside a single project, pass the extra
+ # MSBuild.exe specific "Multi-ToolTask" (MTT) setting /p:CL_MPCount. This enables each MSBuild.exe to parallelize builds wide.
+ # This requires CMake 3.12 or newer.
+ make += ['-j', str(CPU_CORES), '--', '/p:CL_MPCount=' + str(CPU_CORES)]
+ else:
+ # Pass -j to native make, CMake might not support -j option.
+ make += ['--', '-j', str(CPU_CORES)]
- # Build
- print('Running build: ' + str(make))
- ret = subprocess.call(make, cwd=build_root, env=build_env())
- if ret != 0:
- errlog(f'Build failed with exit code {ret}!')
- errlog('Working directory: ' + build_root)
- return False
+ # Build
+ print('Running build: ' + str(make))
+ ret = subprocess.call(make, cwd=build_root, env=build_env())
+ if ret != 0:
+ errlog(f'Build failed with exit code {ret}!')
+ errlog('Working directory: ' + build_root)
+ return False
- return True
+ return True
def write_file(filename, content):
- with open(filename, 'w', encoding='utf-8') as f:
- f.write(content)
+ with open(filename, 'w', encoding='utf-8') as f:
+ f.write(content)
def read_file(filename):
- with open(filename, encoding='utf-8') as f:
- return f.read()
+ with open(filename, encoding='utf-8') as f:
+ return f.read()
def cmake_configure(generator, build_root, src_root, build_type, extra_cmake_args):
- debug_print(f'cmake_configure(generator={generator}, build_root={build_root}, '
- f'src_root={src_root}, build_type={build_type}, extra_cmake_args={extra_cmake_args})')
- # Configure
- if not os.path.isdir(build_root):
- # Create build output directory if it doesn't yet exist.
- os.mkdir(build_root)
- cmdline = [find_cmake(), '-DCMAKE_BUILD_TYPE=' + build_type, '-DPYTHON_EXECUTABLE=' + sys.executable]
- if generator:
- cmdline += ['-G', generator]
- # Target macOS 11.0 Big Sur at minimum, to support older Mac devices.
- # See https://en.wikipedia.org/wiki/MacOS#Hardware_compatibility for min-spec details.
- cmdline += ['-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0']
- cmdline += [*extra_cmake_args, src_root]
+ debug_print(f'cmake_configure(generator={generator}, build_root={build_root}, '
+ f'src_root={src_root}, build_type={build_type}, extra_cmake_args={extra_cmake_args})')
+ # Configure
+ if not os.path.isdir(build_root):
+ # Create build output directory if it doesn't yet exist.
+ os.mkdir(build_root)
+ cmdline = [find_cmake(), '-DCMAKE_BUILD_TYPE=' + build_type, '-DPYTHON_EXECUTABLE=' + sys.executable]
+ if generator:
+ cmdline += ['-G', generator]
+ # Target macOS 11.0 Big Sur at minimum, to support older Mac devices.
+ # See https://en.wikipedia.org/wiki/MacOS#Hardware_compatibility for min-spec details.
+ cmdline += ['-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0']
+ cmdline += [*extra_cmake_args, src_root]
- print(f'Running CMake: {cmdline}')
+ print(f'Running CMake: {cmdline}')
- # Specify the deployment target also as an env. var, since some Xcode versions
- # read this instead of the CMake field.
- os.environ['MACOSX_DEPLOYMENT_TARGET'] = '11.0'
+ # Specify the deployment target also as an env. var, since some Xcode versions
+ # read this instead of the CMake field.
+ os.environ['MACOSX_DEPLOYMENT_TARGET'] = '11.0'
- def quote_parens(x):
- if ' ' in x:
- return '"' + x.replace('"', '\\"') + '"'
- else:
- return x
+ def quote_parens(x):
+ if ' ' in x:
+ return '"' + x.replace('"', '\\"') + '"'
+ else:
+ return x
- # Create a file 'recmake.bat/sh' in the build root that user can call to
- # manually recmake the build tree with the previous build params
- re_cmake_script = os.path.join(build_root, 'recmake.' + ('bat' if WINDOWS else 'sh'))
- write_file(re_cmake_script, ' '.join(map(quote_parens, cmdline)))
- try:
- subprocess.check_call(cmdline, cwd=build_root, env=build_env())
- except Exception as e:
- errlog('CMake invocation failed due to exception')
- errlog(f'Working directory: {build_root}')
- errlog(str(e))
- return False
+ # Create a file 'recmake.bat/sh' in the build root that user can call to
+ # manually recmake the build tree with the previous build params
+ re_cmake_script = os.path.join(build_root, 'recmake.' + ('bat' if WINDOWS else 'sh'))
+ write_file(re_cmake_script, ' '.join(map(quote_parens, cmdline)))
+ try:
+ subprocess.check_call(cmdline, cwd=build_root, env=build_env())
+ except Exception as e:
+ errlog('CMake invocation failed due to exception')
+ errlog(f'Working directory: {build_root}')
+ errlog(str(e))
+ return False
- return True
+ return True
def xcode_sdk_version():
- try:
- output = run_get_output(['xcrun', '--show-sdk-version'])
- return output.strip().split('.')
- except Exception:
- return subprocess.checkplatform.mac_ver()[0].split('.')
+ try:
+ output = run_get_output(['xcrun', '--show-sdk-version'])
+ return output.strip().split('.')
+ except Exception:
+ return subprocess.checkplatform.mac_ver()[0].split('.')
def cmake_target_platform(tool):
- # Source: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html#platform-selection
- if tool.arch:
- if tool.arch == 'arm64':
- return 'ARM64'
- elif tool.arch == 'x86_64':
- return 'x64'
- elif tool.arch == 'x86':
- return 'Win32'
- if ARCH == 'arm64':
- return 'ARM64'
- else:
- return 'x64' if tool.bitness == 64 else 'Win32'
+ # Source: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html#platform-selection
+ if tool.arch:
+ if tool.arch == 'arm64':
+ return 'ARM64'
+ elif tool.arch == 'x86_64':
+ return 'x64'
+ elif tool.arch == 'x86':
+ return 'Win32'
+ if ARCH == 'arm64':
+ return 'ARM64'
+ else:
+ return 'x64' if tool.bitness == 64 else 'Win32'
def cmake_host_platform():
- # Source: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html#toolset-selection
- arch_to_cmake_host_platform = {
- 'arm64': 'ARM64',
- 'arm': 'ARM',
- 'x86_64': 'x64',
- 'x86': 'x86',
- }
- return arch_to_cmake_host_platform[ARCH]
+ # Source: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html#toolset-selection
+ arch_to_cmake_host_platform = {
+ 'arm64': 'ARM64',
+ 'arm': 'ARM',
+ 'x86_64': 'x64',
+ 'x86': 'x86',
+ }
+ return arch_to_cmake_host_platform[ARCH]
def get_generator_and_config_args(tool):
- args = []
- cmake_generator = CMAKE_GENERATOR
- if 'Visual Studio 16' in CMAKE_GENERATOR or 'Visual Studio 17' in CMAKE_GENERATOR: # VS2019 or VS2022
- # With Visual Studio 16 2019, CMake changed the way they specify target arch.
- # Instead of appending it into the CMake generator line, it is specified
- # with a -A arch parameter.
- args += ['-A', cmake_target_platform(tool)]
- args += ['-Thost=' + cmake_host_platform()]
- elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
- cmake_generator += ' Win64'
- args += ['-Thost=x64']
- return (cmake_generator, args)
+ args = []
+ cmake_generator = CMAKE_GENERATOR
+ if 'Visual Studio 16' in CMAKE_GENERATOR or 'Visual Studio 17' in CMAKE_GENERATOR: # VS2019 or VS2022
+ # With Visual Studio 16 2019, CMake changed the way they specify target arch.
+ # Instead of appending it into the CMake generator line, it is specified
+ # with a -A arch parameter.
+ args += ['-A', cmake_target_platform(tool)]
+ args += ['-Thost=' + cmake_host_platform()]
+ elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
+ cmake_generator += ' Win64'
+ args += ['-Thost=x64']
+ return (cmake_generator, args)
def cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args):
- success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args)
- if success:
- success = make_build(build_root, build_type)
-
- if not success and get_env_boolean('EMSDK_RETRY_CLEAN_BUILD'):
- # Delete the CMake build directory to force a full re-cmake + clean rebuild.
- shutil.rmtree(build_root)
-
- # And re-try configure + build again.
success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args)
if success:
- success = make_build(build_root, build_type)
+ success = make_build(build_root, build_type)
- return success
+ if not success and get_env_boolean('EMSDK_RETRY_CLEAN_BUILD'):
+ # Delete the CMake build directory to force a full re-cmake + clean rebuild.
+ shutil.rmtree(build_root)
+
+ # And re-try configure + build again.
+ success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args)
+ if success:
+ success = make_build(build_root, build_type)
+
+ return success
def build_llvm(tool):
- debug_print(f'build_llvm({tool})')
- llvm_root = tool.installation_path()
- llvm_src_root = os.path.join(llvm_root, 'src')
- success = git_clone_checkout_and_pull(tool.url, llvm_src_root, tool.git_branch)
- if not success:
- return False
+ debug_print(f'build_llvm({tool})')
+ llvm_root = tool.installation_path()
+ llvm_src_root = os.path.join(llvm_root, 'src')
+ success = git_clone_checkout_and_pull(tool.url, llvm_src_root, tool.git_branch)
+ if not success:
+ return False
- build_dir = llvm_build_dir(tool)
- build_root = os.path.join(llvm_root, build_dir)
+ build_dir = llvm_build_dir(tool)
+ build_root = os.path.join(llvm_root, build_dir)
- build_type = decide_cmake_build_type(tool)
+ build_type = decide_cmake_build_type(tool)
- # Configure
- tests_arg = 'ON' if BUILD_FOR_TESTING else 'OFF'
+ # Configure
+ tests_arg = 'ON' if BUILD_FOR_TESTING else 'OFF'
- enable_assertions = ENABLE_LLVM_ASSERTIONS.lower() == 'on' or (ENABLE_LLVM_ASSERTIONS == 'auto' and build_type.lower() not in {'release', 'minsizerel'})
+ enable_assertions = ENABLE_LLVM_ASSERTIONS.lower() == 'on' or (ENABLE_LLVM_ASSERTIONS == 'auto' and build_type.lower() not in {'release', 'minsizerel'})
- if ARCH in {'x86', 'x86_64'}:
- targets_to_build = 'WebAssembly;X86'
- elif ARCH == 'arm':
- targets_to_build = 'WebAssembly;ARM'
- elif ARCH == 'arm64':
- targets_to_build = 'WebAssembly;AArch64'
- else:
- targets_to_build = 'WebAssembly'
- cmake_generator, args = get_generator_and_config_args(tool)
- args += ['-DLLVM_TARGETS_TO_BUILD=' + targets_to_build,
- '-DLLVM_INCLUDE_EXAMPLES=OFF',
- '-DLLVM_INCLUDE_TESTS=' + tests_arg,
- '-DCLANG_INCLUDE_TESTS=' + tests_arg,
- '-DLLVM_ENABLE_ASSERTIONS=' + ('ON' if enable_assertions else 'OFF'),
- # Disable optional LLVM dependencies, these can cause unwanted .so dependencies
- # that prevent distributing the generated compiler for end users.
- '-DLLVM_ENABLE_LIBXML2=OFF', '-DLLDB_ENABLE_LIBEDIT=OFF',
- '-DLLVM_ENABLE_LIBEDIT=OFF', '-DLLVM_ENABLE_LIBPFM=OFF']
- # LLVM build system bug: compiler-rt does not build on Windows. It insists on performing a CMake install step that writes to C:\Program Files. Attempting
- # to reroute that to build_root directory then fails on an error
- # file INSTALL cannot find
- # "C:/code/emsdk/llvm/git/build_master_vs2017_64/$(Configuration)/lib/clang/10.0.0/lib/windows/clang_rt.ubsan_standalone-x86_64.lib".
- # (there instead of $(Configuration), one would need ${CMAKE_BUILD_TYPE} ?)
- # It looks like compiler-rt is not compatible to build on Windows?
- args += ['-DLLVM_ENABLE_PROJECTS=clang;lld']
- # To enable widest possible chance of success for building, let the code
- # compile through with older toolchains that are about to be deprecated by
- # upstream LLVM.
- args += ['-DLLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN=ON']
+ if ARCH in {'x86', 'x86_64'}:
+ targets_to_build = 'WebAssembly;X86'
+ elif ARCH == 'arm':
+ targets_to_build = 'WebAssembly;ARM'
+ elif ARCH == 'arm64':
+ targets_to_build = 'WebAssembly;AArch64'
+ else:
+ targets_to_build = 'WebAssembly'
+ cmake_generator, args = get_generator_and_config_args(tool)
+ args += ['-DLLVM_TARGETS_TO_BUILD=' + targets_to_build,
+ '-DLLVM_INCLUDE_EXAMPLES=OFF',
+ '-DLLVM_INCLUDE_TESTS=' + tests_arg,
+ '-DCLANG_INCLUDE_TESTS=' + tests_arg,
+ '-DLLVM_ENABLE_ASSERTIONS=' + ('ON' if enable_assertions else 'OFF'),
+ # Disable optional LLVM dependencies, these can cause unwanted .so dependencies
+ # that prevent distributing the generated compiler for end users.
+ '-DLLVM_ENABLE_LIBXML2=OFF', '-DLLDB_ENABLE_LIBEDIT=OFF',
+ '-DLLVM_ENABLE_LIBEDIT=OFF', '-DLLVM_ENABLE_LIBPFM=OFF']
+ # LLVM build system bug: compiler-rt does not build on Windows. It insists on performing a CMake install step that writes to C:\Program Files. Attempting
+ # to reroute that to build_root directory then fails on an error
+ # file INSTALL cannot find
+ # "C:/code/emsdk/llvm/git/build_master_vs2017_64/$(Configuration)/lib/clang/10.0.0/lib/windows/clang_rt.ubsan_standalone-x86_64.lib".
+ # (there instead of $(Configuration), one would need ${CMAKE_BUILD_TYPE} ?)
+ # It looks like compiler-rt is not compatible to build on Windows?
+ args += ['-DLLVM_ENABLE_PROJECTS=clang;lld']
+ # To enable widest possible chance of success for building, let the code
+ # compile through with older toolchains that are about to be deprecated by
+ # upstream LLVM.
+ args += ['-DLLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN=ON']
- if os.getenv('LLVM_CMAKE_ARGS'):
- extra_args = os.environ['LLVM_CMAKE_ARGS'].split(',')
- print('Passing the following extra arguments to LLVM CMake configuration: ' + str(extra_args))
- args += extra_args
+ if os.getenv('LLVM_CMAKE_ARGS'):
+ extra_args = os.environ['LLVM_CMAKE_ARGS'].split(',')
+ print('Passing the following extra arguments to LLVM CMake configuration: ' + str(extra_args))
+ args += extra_args
- cmakelists_dir = os.path.join(llvm_src_root, 'llvm')
+ cmakelists_dir = os.path.join(llvm_src_root, 'llvm')
- success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args)
+ success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args)
- return success
+ return success
def build_ninja(tool):
- debug_print(f'build_ninja({tool})')
- root = os.path.normpath(tool.installation_path())
- src_root = os.path.join(root, 'src')
- success = git_clone_checkout_and_pull(tool.url, src_root, tool.git_branch)
- if not success:
- return False
+ debug_print(f'build_ninja({tool})')
+ root = os.path.normpath(tool.installation_path())
+ src_root = os.path.join(root, 'src')
+ success = git_clone_checkout_and_pull(tool.url, src_root, tool.git_branch)
+ if not success:
+ return False
- build_dir = llvm_build_dir(tool)
- build_root = os.path.join(root, build_dir)
+ build_dir = llvm_build_dir(tool)
+ build_root = os.path.join(root, build_dir)
- build_type = decide_cmake_build_type(tool)
+ build_type = decide_cmake_build_type(tool)
- # Configure
- cmake_generator, args = get_generator_and_config_args(tool)
+ # Configure
+ cmake_generator, args = get_generator_and_config_args(tool)
- cmakelists_dir = os.path.join(src_root)
+ cmakelists_dir = os.path.join(src_root)
- success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args)
+ success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args)
- if success:
- bin_dir = os.path.join(root, 'bin')
- mkdir_p(bin_dir)
- exe_paths = [os.path.join(build_root, 'Release', 'ninja'), os.path.join(build_root, 'ninja')]
- for e in exe_paths:
- for s in ['.exe', '']:
- ninja = e + s
- if os.path.isfile(ninja):
- dst = os.path.join(bin_dir, 'ninja' + s)
- shutil.copyfile(ninja, dst)
- os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC)
+ if success:
+ bin_dir = os.path.join(root, 'bin')
+ mkdir_p(bin_dir)
+ exe_paths = [os.path.join(build_root, 'Release', 'ninja'), os.path.join(build_root, 'ninja')]
+ for e in exe_paths:
+ for s in ['.exe', '']:
+ ninja = e + s
+ if os.path.isfile(ninja):
+ dst = os.path.join(bin_dir, 'ninja' + s)
+ shutil.copyfile(ninja, dst)
+ os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC)
- return success
+ return success
def build_ccache(tool):
- debug_print(f'build_ccache({tool})')
- root = os.path.normpath(tool.installation_path())
- src_root = os.path.join(root, 'src')
- success = git_clone_checkout_and_pull(tool.url, src_root, tool.git_branch)
- if not success:
- return False
+ debug_print(f'build_ccache({tool})')
+ root = os.path.normpath(tool.installation_path())
+ src_root = os.path.join(root, 'src')
+ success = git_clone_checkout_and_pull(tool.url, src_root, tool.git_branch)
+ if not success:
+ return False
- build_dir = llvm_build_dir(tool)
- build_root = os.path.join(root, build_dir)
+ build_dir = llvm_build_dir(tool)
+ build_root = os.path.join(root, build_dir)
- build_type = decide_cmake_build_type(tool)
+ build_type = decide_cmake_build_type(tool)
- # Configure
- cmake_generator, args = get_generator_and_config_args(tool)
- args += ['-DZSTD_FROM_INTERNET=ON']
+ # Configure
+ cmake_generator, args = get_generator_and_config_args(tool)
+ args += ['-DZSTD_FROM_INTERNET=ON']
- cmakelists_dir = os.path.join(src_root)
+ cmakelists_dir = os.path.join(src_root)
- success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args)
+ success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args)
- if success:
- bin_dir = os.path.join(root, 'bin')
- mkdir_p(bin_dir)
- exe_paths = [os.path.join(build_root, 'Release', 'ccache'), os.path.join(build_root, 'ccache')]
- for e in exe_paths:
- for s in ['.exe', '']:
- ccache = e + s
- if os.path.isfile(ccache):
- dst = os.path.join(bin_dir, 'ccache' + s)
- shutil.copyfile(ccache, dst)
- os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC)
+ if success:
+ bin_dir = os.path.join(root, 'bin')
+ mkdir_p(bin_dir)
+ exe_paths = [os.path.join(build_root, 'Release', 'ccache'), os.path.join(build_root, 'ccache')]
+ for e in exe_paths:
+ for s in ['.exe', '']:
+ ccache = e + s
+ if os.path.isfile(ccache):
+ dst = os.path.join(bin_dir, 'ccache' + s)
+ shutil.copyfile(ccache, dst)
+ os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC)
- cache_dir = os.path.join(root, 'cache')
- write_file(os.path.join(root, 'emcc_ccache.conf'), f'''\
-# Set maximum cache size to 10 GB:
-max_size = 10G
-cache_dir = {cache_dir}
-''')
- mkdir_p(cache_dir)
+ cache_dir = os.path.join(root, 'cache')
+ write_file(os.path.join(root, 'emcc_ccache.conf'), f'''\
+ # Set maximum cache size to 10 GB:
+ max_size = 10G
+ cache_dir = {cache_dir}
+ ''')
+ mkdir_p(cache_dir)
- return success
+ return success
def download_firefox(tool):
- debug_print(f'download_firefox({tool})')
+ debug_print(f'download_firefox({tool})')
- # Use mozdownload to acquire Firefox versions.
- try:
- from mozdownload import FactoryScraper
- except ImportError:
- # If mozdownload is not available, invoke pip to install it.
- subprocess.check_call([sys.executable, "-m", "pip", "install", "mozdownload"])
- from mozdownload import FactoryScraper
+ # Use mozdownload to acquire Firefox versions.
+ try:
+ from mozdownload import FactoryScraper
+ except ImportError:
+ # If mozdownload is not available, invoke pip to install it.
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "mozdownload"])
+ from mozdownload import FactoryScraper
- if WINDOWS:
- extension = 'exe'
- elif MACOS:
- extension = 'dmg'
- else:
- # N.b. on Linux even when we ask .tar.xz, we might sometimes get .tar.bz2,
- # depending on what is available on Firefox servers for the particular
- # version. So prepare to handle both further down below.
- extension = 'tar.xz'
-
- platform = None
- if LINUX and 'arm' in ARCH:
- platform = 'linux-arm64'
- if WINDOWS and 'arm' in ARCH:
- platform = 'win64-aarch64'
-
- if tool.version == 'nightly':
- scraper = FactoryScraper('daily', extension=extension, locale='en-US', platform=platform)
- else:
- scraper = FactoryScraper('release', extension=extension, locale='en-US', platform=platform, version=tool.version)
-
- if tool.version == 'nightly':
- firefox_version = os.path.basename(scraper.filename).split(".en-US")[0]
- else:
- firefox_version = os.path.basename(scraper.filename).split("firefox-")[1].split(".en-US")[0]
-
- print('Target Firefox version: ' + firefox_version)
- if tool.version in {'latest', 'latest-esr', 'latest-beta', 'nightly'}:
- pretend_version_dir = os.path.normpath(tool.installation_path())
- orig_version = tool.version
- tool.version = firefox_version
- root = os.path.normpath(tool.installation_path())
- tool.version = orig_version
- else:
- pretend_version_dir = None
- root = os.path.normpath(tool.installation_path())
-
- def save_actual_version():
- """For moving installer packages, e.g. "nightly", "latest", "latest-esr",
- store a text file to specify the actual installation directory.
- """
- if os.path.isfile(firefox_exe) and pretend_version_dir:
- print(pretend_version_dir)
- os.makedirs(pretend_version_dir, exist_ok=True)
- write_file(os.path.join(pretend_version_dir, 'actual.txt'), os.path.relpath(root, EMSDK_PATH))
-
- # Check if already installed
- print('Firefox installation root directory: ' + root)
- exe_dir = os.path.join(root, 'Contents', 'MacOS') if MACOS else root
- firefox_exe = os.path.join(exe_dir, exe_suffix('firefox'))
- if os.path.isfile(firefox_exe):
- print(f'{firefox_exe} is already installed, skipping..')
- save_actual_version()
- return True
-
- print('Downloading Firefox from ' + scraper.url)
- filename = scraper.download()
- print('Finished downloading ' + filename)
-
- if not MACOS:
- os.makedirs(root, exist_ok=True)
-
- if extension == 'exe':
- # Uncompress the NSIS installer to 'install' Firefox
- run(['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y', filename, '-o' + root])
-
- if '.tar.' in filename:
- if filename.endswith('.tar.bz2'):
- tar_type = 'r:bz2'
- elif filename.endswith('.tar.xz'):
- tar_type = 'r:xz'
+ if WINDOWS:
+ extension = 'exe'
+ elif MACOS:
+ extension = 'dmg'
else:
- raise Exception('Unknown archive type')
+ # N.b. on Linux even when we ask .tar.xz, we might sometimes get .tar.bz2,
+ # depending on what is available on Firefox servers for the particular
+ # version. So prepare to handle both further down below.
+ extension = 'tar.xz'
- with tarfile.open(filename, tar_type) as tar:
- tar.extractall(path=root)
- collapse_subdir = os.path.join(root, 'firefox')
+ platform = None
+ if LINUX and 'arm' in ARCH:
+ platform = 'linux-arm64'
+ if WINDOWS and 'arm' in ARCH:
+ platform = 'win64-aarch64'
- elif filename.endswith('.dmg'):
- mount_point = '/Volumes/Firefox Nightly' if tool.version == 'nightly' else '/Volumes/Firefox'
- app_name = 'Firefox Nightly.app' if tool.version == 'nightly' else 'Firefox.app'
+ if tool.version == 'nightly':
+ scraper = FactoryScraper('daily', extension=extension, locale='en-US', platform=platform)
+ else:
+ scraper = FactoryScraper('release', extension=extension, locale='en-US', platform=platform, version=tool.version)
- # If a previous mount point exists, detach it first
- if os.path.exists(mount_point):
- run(['hdiutil', 'detach', mount_point])
+ if tool.version == 'nightly':
+ firefox_version = os.path.basename(scraper.filename).split(".en-US")[0]
+ else:
+ firefox_version = os.path.basename(scraper.filename).split("firefox-")[1].split(".en-US")[0]
- # Abort if detaching was not successful
- if os.path.exists(mount_point):
- raise Exception(f'Previous mount of Firefox already exists at "{mount_point}", unable to proceed.')
+ print('Target Firefox version: ' + firefox_version)
+ if tool.version in {'latest', 'latest-esr', 'latest-beta', 'nightly'}:
+ pretend_version_dir = os.path.normpath(tool.installation_path())
+ orig_version = tool.version
+ tool.version = firefox_version
+ root = os.path.normpath(tool.installation_path())
+ tool.version = orig_version
+ else:
+ pretend_version_dir = None
+ root = os.path.normpath(tool.installation_path())
- # Mount the archive
- run(['hdiutil', 'attach', filename])
- firefox_dir = os.path.join(mount_point, app_name)
- if not os.path.isdir(firefox_dir):
- raise Exception(f'Unable to find Firefox directory "{firefox_dir}" inside app image.')
+ def save_actual_version():
+ """For moving installer packages, e.g. "nightly", "latest", "latest-esr",
+ store a text file to specify the actual installation directory.
+ """
+ if os.path.isfile(firefox_exe) and pretend_version_dir:
+ print(pretend_version_dir)
+ os.makedirs(pretend_version_dir, exist_ok=True)
+ write_file(os.path.join(pretend_version_dir, 'actual.txt'), os.path.relpath(root, EMSDK_PATH))
- # And install by copying the files from the archive
- shutil.copytree(firefox_dir, root)
- run(['hdiutil', 'detach', mount_point])
- collapse_subdir = None
+ # Check if already installed
+ print('Firefox installation root directory: ' + root)
+ exe_dir = os.path.join(root, 'Contents', 'MacOS') if MACOS else root
+ firefox_exe = os.path.join(exe_dir, exe_suffix('firefox'))
+ if os.path.isfile(firefox_exe):
+ print(f'{firefox_exe} is already installed, skipping..')
+ save_actual_version()
+ return True
- elif filename.endswith('.exe'):
- # NSIS installer package has a core/ directory, remove it as redundant.
- collapse_subdir = os.path.join(root, 'core')
+ print('Downloading Firefox from ' + scraper.url)
+ filename = scraper.download()
+ print('Finished downloading ' + filename)
- # Remove a redundant subdirectory by moving installed files up one directory.
- if collapse_subdir and os.path.isdir(collapse_subdir):
- # Rename the parent subdirectory first, since we will be handling a nested `firefox/firefox/`
- collapse = collapse_subdir + '_temp_renamed'
- os.rename(collapse_subdir, collapse)
+ if not MACOS:
+ os.makedirs(root, exist_ok=True)
- # Move all files up by one directory
- for f in os.listdir(collapse):
- shutil.move(os.path.join(collapse, f), os.path.dirname(collapse))
+ if extension == 'exe':
+ # Uncompress the NSIS installer to 'install' Firefox
+ run(['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y', filename, '-o' + root])
- # The root directory should now be empty
- os.rmdir(collapse)
+ if '.tar.' in filename:
+ if filename.endswith('.tar.bz2'):
+ tar_type = 'r:bz2'
+ elif filename.endswith('.tar.xz'):
+ tar_type = 'r:xz'
+ else:
+ raise Exception('Unknown archive type')
- # Original installer is now done.
- os.remove(filename)
+ with tarfile.open(filename, tar_type) as tar:
+ tar.extractall(path=root)
+ collapse_subdir = os.path.join(root, 'firefox')
- # Write a policy file that prevents Firefox from auto-updating itself.
- if MACOS:
- distribution_path = os.path.join(root, 'Contents', 'Resources', 'distribution')
- else:
- distribution_path = os.path.join(root, 'distribution')
- os.makedirs(distribution_path, exist_ok=True)
- write_file(os.path.join(distribution_path, 'policies.json'), '''\
-{
- "policies": {
- "AppAutoUpdate": false,
- "DisableAppUpdate": true
- }
-}''')
+ elif filename.endswith('.dmg'):
+ mount_point = '/Volumes/Firefox Nightly' if tool.version == 'nightly' else '/Volumes/Firefox'
+ app_name = 'Firefox Nightly.app' if tool.version == 'nightly' else 'Firefox.app'
- if MACOS:
- # Disable a macOS feature where if the browser is terminated mid-execution, (e.g. by
- # CI aborting), then the next time the browser is launched, macOS might bring up a dialog
- # "The last time you opened Firefox, it unexpectedly quit while reopening windows.
- # Do you want to try to reopen its windows again?"
- # that will block automated CI runs.
- # Disable this feature by changing the behavior of the program with macOS 'defaults'.
- run(['defaults', 'write', '-app', root, 'ApplePersistenceIgnoreState', 'YES'])
- run(['defaults', 'write', '-app', root, 'NSQuitAlwaysKeepsWindows', '-bool', 'false'])
+ # If a previous mount point exists, detach it first
+ if os.path.exists(mount_point):
+ run(['hdiutil', 'detach', mount_point])
- save_actual_version()
+ # Abort if detaching was not successful
+ if os.path.exists(mount_point):
+ raise Exception(f'Previous mount of Firefox already exists at "{mount_point}", unable to proceed.')
- # If we didn't get a Firefox executable, then installation failed.
- return os.path.isfile(firefox_exe)
+ # Mount the archive
+ run(['hdiutil', 'attach', filename])
+ firefox_dir = os.path.join(mount_point, app_name)
+ if not os.path.isdir(firefox_dir):
+ raise Exception(f'Unable to find Firefox directory "{firefox_dir}" inside app image.')
+
+ # And install by copying the files from the archive
+ shutil.copytree(firefox_dir, root)
+ run(['hdiutil', 'detach', mount_point])
+ collapse_subdir = None
+
+ elif filename.endswith('.exe'):
+ # NSIS installer package has a core/ directory, remove it as redundant.
+ collapse_subdir = os.path.join(root, 'core')
+
+ # Remove a redundant subdirectory by moving installed files up one directory.
+ if collapse_subdir and os.path.isdir(collapse_subdir):
+ # Rename the parent subdirectory first, since we will be handling a nested `firefox/firefox/`
+ collapse = collapse_subdir + '_temp_renamed'
+ os.rename(collapse_subdir, collapse)
+
+ # Move all files up by one directory
+ for f in os.listdir(collapse):
+ shutil.move(os.path.join(collapse, f), os.path.dirname(collapse))
+
+ # The root directory should now be empty
+ os.rmdir(collapse)
+
+ # Original installer is now done.
+ os.remove(filename)
+
+ # Write a policy file that prevents Firefox from auto-updating itself.
+ if MACOS:
+ distribution_path = os.path.join(root, 'Contents', 'Resources', 'distribution')
+ else:
+ distribution_path = os.path.join(root, 'distribution')
+ os.makedirs(distribution_path, exist_ok=True)
+ write_file(os.path.join(distribution_path, 'policies.json'), '''\
+ {
+ "policies": {
+ "AppAutoUpdate": false,
+ "DisableAppUpdate": true
+ }
+ }''')
+
+ if MACOS:
+ # Disable a macOS feature where if the browser is terminated mid-execution, (e.g. by
+ # CI aborting), then the next time the browser is launched, macOS might bring up a dialog
+ # "The last time you opened Firefox, it unexpectedly quit while reopening windows.
+ # Do you want to try to reopen its windows again?"
+ # that will block automated CI runs.
+ # Disable this feature by changing the behavior of the program with macOS 'defaults'.
+ run(['defaults', 'write', '-app', root, 'ApplePersistenceIgnoreState', 'YES'])
+ run(['defaults', 'write', '-app', root, 'NSQuitAlwaysKeepsWindows', '-bool', 'false'])
+
+ save_actual_version()
+
+ # If we didn't get a Firefox executable, then installation failed.
+ return os.path.isfile(firefox_exe)
def is_firefox_installed(tool):
- actual_file = os.path.join(tool.installation_dir(), 'actual.txt')
- if not os.path.isfile(actual_file):
- return False
+ actual_file = os.path.join(tool.installation_dir(), 'actual.txt')
+ if not os.path.isfile(actual_file):
+ return False
- actual_installation_dir = sdk_path(read_file(actual_file))
- exe_dir = os.path.join(actual_installation_dir, 'Contents', 'MacOS') if MACOS else actual_installation_dir
- firefox_exe = os.path.join(exe_dir, exe_suffix('firefox'))
- return os.path.isfile(firefox_exe)
+ actual_installation_dir = sdk_path(read_file(actual_file))
+ exe_dir = os.path.join(actual_installation_dir, 'Contents', 'MacOS') if MACOS else actual_installation_dir
+ firefox_exe = os.path.join(exe_dir, exe_suffix('firefox'))
+ return os.path.isfile(firefox_exe)
def find_latest_installed_tool(name):
- """Finds the newest installed version of a given tool"""
- for t in reversed(tools):
- if t.id == name and t.is_installed():
- return t
+ """Finds the newest installed version of a given tool"""
+ for t in reversed(tools):
+ if t.id == name and t.is_installed():
+ return t
def get_node_env():
- node_tool = find_latest_installed_tool('node')
- if node_tool:
- node_path = node_tool.expand_vars(node_tool.activated_path)
- else:
- npm_fallback = shutil.which('npm')
- if not npm_fallback:
- errlog('Failed to find npm command')
- errlog('npm is required for Emscripten setup. Please install node.js first')
- return None, None
- node_path = os.path.dirname(npm_fallback)
+ node_tool = find_latest_installed_tool('node')
+ if node_tool:
+ node_path = node_tool.expand_vars(node_tool.activated_path)
+ else:
+ npm_fallback = shutil.which('npm')
+ if not npm_fallback:
+ errlog('Failed to find npm command')
+ errlog('npm is required for Emscripten setup. Please install node.js first')
+ return None, None
+ node_path = os.path.dirname(npm_fallback)
- env = os.environ.copy()
- env["PATH"] = node_path + os.pathsep + env["PATH"]
- return env, node_path
+ env = os.environ.copy()
+ env["PATH"] = node_path + os.pathsep + env["PATH"]
+ return env, node_path
def emscripten_npm_setup(directory):
- env, node_path = get_node_env()
- if not env:
- return False
- npm = os.path.join(node_path, 'npm' + ('.cmd' if WINDOWS else ''))
- print('Running post-install step: npm ci ...')
- try:
- subprocess.check_call([npm, 'ci', '--production'], cwd=directory, env=env)
- except subprocess.CalledProcessError as e:
- errlog(f'Error running {e.cmd}')
- return False
+ env, node_path = get_node_env()
+ if not env:
+ return False
+ npm = os.path.join(node_path, 'npm' + ('.cmd' if WINDOWS else ''))
+ print('Running post-install step: npm ci ...')
+ try:
+ subprocess.check_call([npm, 'ci', '--production'], cwd=directory, env=env)
+ except subprocess.CalledProcessError as e:
+ errlog(f'Error running {e.cmd}')
+ return False
- print('Done running: npm ci')
- return True
+ print('Done running: npm ci')
+ return True
def sdk_post_install():
- """Older versions of the sdk did not include the node_modules directory
- and require `npm ci` to be run post-install
- """
- emscripten_dir = os.path.join(EMSDK_PATH, 'upstream', 'emscripten')
- if os.path.exists(os.path.join(emscripten_dir, 'node_modules')):
- return True
+ """Older versions of the sdk did not include the node_modules directory
+ and require `npm ci` to be run post-install
+ """
+ emscripten_dir = os.path.join(EMSDK_PATH, 'upstream', 'emscripten')
+ if os.path.exists(os.path.join(emscripten_dir, 'node_modules')):
+ return True
- return emscripten_npm_setup(emscripten_dir)
+ return emscripten_npm_setup(emscripten_dir)
def emscripten_install(tool):
- """Modern versions of emscripten require bootstrap.py to be run before they
- can be used from a git checkout. On older versions we just run `npm ci`
- """
- directory = tool.installation_path()
- if os.path.isfile(os.path.join(directory, 'bootstrap.py')):
- env = get_node_env()[0]
- if not env:
- return False
- try:
- subprocess.check_call([sys.executable, os.path.join(directory, 'bootstrap.py')],
- cwd=directory, stdin=subprocess.DEVNULL, env=env, text=True)
- except subprocess.CalledProcessError as e:
- errlog(f'Error running {e!s}')
- return False
+ """Modern versions of emscripten require bootstrap.py to be run before they
+ can be used from a git checkout. On older versions we just run `npm ci`
+ """
+ directory = tool.installation_path()
+ if os.path.isfile(os.path.join(directory, 'bootstrap.py')):
+ env = get_node_env()[0]
+ if not env:
+ return False
+ try:
+ subprocess.check_call([sys.executable, os.path.join(directory, 'bootstrap.py')],
+ cwd=directory, stdin=subprocess.DEVNULL, env=env, text=True)
+ except subprocess.CalledProcessError as e:
+ errlog(f'Error running {e!s}')
+ return False
- print('Done running: Emscripten bootstrap')
- else:
- return emscripten_npm_setup(directory)
+ print('Done running: Emscripten bootstrap')
+ else:
+ return emscripten_npm_setup(directory)
- return True
+ return True
# Binaryen build scripts:
def binaryen_build_root(tool):
- build_root = tool.installation_path().strip()
- if build_root.endswith(('/', '\\')):
- build_root = build_root[:-1]
- generator_prefix = cmake_generator_prefix()
- build_root = f'{build_root}{generator_prefix}_{tool.bitness}bit_binaryen'
- return build_root
+ build_root = tool.installation_path().strip()
+ if build_root.endswith(('/', '\\')):
+ build_root = build_root[:-1]
+ generator_prefix = cmake_generator_prefix()
+ build_root = f'{build_root}{generator_prefix}_{tool.bitness}bit_binaryen'
+ return build_root
def uninstall_binaryen(tool):
- debug_print(f'uninstall_binaryen({tool})')
- build_root = binaryen_build_root(tool)
- print(f"Deleting path '{build_root}'")
- remove_tree(build_root)
+ debug_print(f'uninstall_binaryen({tool})')
+ build_root = binaryen_build_root(tool)
+ print(f"Deleting path '{build_root}'")
+ remove_tree(build_root)
def is_binaryen_installed(tool):
- build_root = binaryen_build_root(tool)
- return os.path.exists(build_root)
+ build_root = binaryen_build_root(tool)
+ return os.path.exists(build_root)
def build_binaryen_tool(tool):
- debug_print(f'build_binaryen_tool({tool})')
- src_root = tool.installation_path()
- build_root = binaryen_build_root(tool)
- build_type = decide_cmake_build_type(tool)
+ debug_print(f'build_binaryen_tool({tool})')
+ src_root = tool.installation_path()
+ build_root = binaryen_build_root(tool)
+ build_type = decide_cmake_build_type(tool)
- # Configure
- cmake_generator, args = get_generator_and_config_args(tool)
- args += ['-DENABLE_WERROR=0'] # -Werror is not useful for end users
- args += ['-DBUILD_TESTS=0'] # We don't want to build or run tests
+ # Configure
+ cmake_generator, args = get_generator_and_config_args(tool)
+ args += ['-DENABLE_WERROR=0'] # -Werror is not useful for end users
+ args += ['-DBUILD_TESTS=0'] # We don't want to build or run tests
- if 'Visual Studio' in CMAKE_GENERATOR:
- if BUILD_FOR_TESTING:
- args += ['-DRUN_STATIC_ANALYZER=1']
+ if 'Visual Studio' in CMAKE_GENERATOR:
+ if BUILD_FOR_TESTING:
+ args += ['-DRUN_STATIC_ANALYZER=1']
- success = cmake_configure_and_build(cmake_generator, build_root, src_root, build_type, args)
+ success = cmake_configure_and_build(cmake_generator, build_root, src_root, build_type, args)
- if success:
- # Deploy scripts needed from source repository to build directory
- remove_tree(os.path.join(build_root, 'scripts'))
- shutil.copytree(os.path.join(src_root, 'scripts'), os.path.join(build_root, 'scripts'))
- remove_tree(os.path.join(build_root, 'src', 'js'))
- shutil.copytree(os.path.join(src_root, 'src', 'js'), os.path.join(build_root, 'src', 'js'))
+ if success:
+ # Deploy scripts needed from source repository to build directory
+ remove_tree(os.path.join(build_root, 'scripts'))
+ shutil.copytree(os.path.join(src_root, 'scripts'), os.path.join(build_root, 'scripts'))
+ remove_tree(os.path.join(build_root, 'src', 'js'))
+ shutil.copytree(os.path.join(src_root, 'src', 'js'), os.path.join(build_root, 'src', 'js'))
- return success
+ return success
def download_and_extract(archive, dest_dir, filename_prefix='', clobber=True):
- debug_print(f'download_and_extract(archive={archive}, dest_dir={dest_dir})')
+ debug_print(f'download_and_extract(archive={archive}, dest_dir={dest_dir})')
- url = urljoin(emsdk_packages_url, archive)
+ url = urljoin(emsdk_packages_url, archive)
- def try_download(url):
- return download_file(url, download_dir, filename_prefix)
+ def try_download(url):
+ return download_file(url, download_dir, filename_prefix)
- # Special hack for the wasm-binaries we transitioned from `.bzip2` to
- # `.xz`, but we can't tell from the version/url which one to use, so
- # try one and then fall back to the other.
- success = False
- if 'wasm-binaries' in archive and os.path.splitext(archive)[1] == '.xz':
- success = try_download(url)
+ # Special hack for the wasm-binaries we transitioned from `.bzip2` to
+ # `.xz`, but we can't tell from the version/url which one to use, so
+ # try one and then fall back to the other.
+ success = False
+ if 'wasm-binaries' in archive and os.path.splitext(archive)[1] == '.xz':
+ success = try_download(url)
+ if not success:
+ alt_url = url.replace('.tar.xz', '.tbz2')
+ success = try_download(alt_url)
+ if success:
+ url = alt_url
+
if not success:
- alt_url = url.replace('.tar.xz', '.tbz2')
- success = try_download(alt_url)
- if success:
- url = alt_url
+ success = try_download(url)
- if not success:
- success = try_download(url)
+ if not success:
+ return False
- if not success:
- return False
+ # Remove the old directory, since we have some SDKs that install into the
+ # same directory. If we didn't do this contents of the previous install
+ # could remain.
+ if clobber:
+ remove_tree(dest_dir)
- # Remove the old directory, since we have some SDKs that install into the
- # same directory. If we didn't do this contents of the previous install
- # could remain.
- if clobber:
- remove_tree(dest_dir)
-
- download_target = get_download_target(url, download_dir, filename_prefix)
- if archive.endswith('.zip'):
- return unzip(download_target, dest_dir)
- else:
- return untargz(download_target, dest_dir)
+ download_target = get_download_target(url, download_dir, filename_prefix)
+ if archive.endswith('.zip'):
+ return unzip(download_target, dest_dir)
+ else:
+ return untargz(download_target, dest_dir)
def to_native_path(p):
- if (WINDOWS and not MSYS) and not os_override:
- return to_unix_path(p).replace('/', '\\')
- else:
- return to_unix_path(p)
+ if (WINDOWS and not MSYS) and not os_override:
+ return to_unix_path(p).replace('/', '\\')
+ else:
+ return to_unix_path(p)
def get_required_path(active_tools):
- """Finds and returns a list of the directories that need to be added to PATH for
- the given set of tools.
- """
- path_add = [to_native_path(EMSDK_PATH)]
- for tool in active_tools:
- if tool.activated_path:
- path = to_native_path(tool.expand_vars(tool.activated_path))
- # If the tool has an activated_path_skip attribute then we don't add
- # the tools path to the users path if a program by that name is found
- # in the existing PATH. This allows us to, for example, add our version
- # node to the users PATH if, and only if, they don't already have a
- # another version of node in their PATH.
- if tool.activated_path_skip:
- current_path = shutil.which(tool.activated_path_skip)
- # We found an executable by this name in the current PATH, but we
- # ignore our own version for this purpose.
- if current_path and os.path.dirname(current_path) != path:
- continue
- path_add.append(path)
- return path_add
+ """Finds and returns a list of the directories that need to be added to PATH for
+ the given set of tools.
+ """
+ path_add = [to_native_path(EMSDK_PATH)]
+ for tool in active_tools:
+ if tool.activated_path:
+ path = to_native_path(tool.expand_vars(tool.activated_path))
+ # If the tool has an activated_path_skip attribute then we don't add
+ # the tools path to the users path if a program by that name is found
+ # in the existing PATH. This allows us to, for example, add our version
+ # node to the users PATH if, and only if, they don't already have a
+ # another version of node in their PATH.
+ if tool.activated_path_skip:
+ current_path = shutil.which(tool.activated_path_skip)
+ # We found an executable by this name in the current PATH, but we
+ # ignore our own version for this purpose.
+ if current_path and os.path.dirname(current_path) != path:
+ continue
+ path_add.append(path)
+ return path_add
# Returns the absolute path to the file '.emscripten' for the current user on
@@ -1611,603 +1611,603 @@
def parse_key_value(line):
- if not line:
- return ('', '')
- eq = line.find('=')
- if eq != -1:
- key = line[0:eq].strip()
- value = line[eq + 1:].strip()
- return (key, value)
- else:
- return (key, '')
+ if not line:
+ return ('', '')
+ eq = line.find('=')
+ if eq != -1:
+ key = line[0:eq].strip()
+ value = line[eq + 1:].strip()
+ return (key, value)
+ else:
+ return (key, '')
def load_em_config():
- EM_CONFIG_DICT.clear()
- lines = []
- try:
- lines = read_file(EM_CONFIG_PATH).splitlines()
- except Exception:
- pass
- for line in lines:
+ EM_CONFIG_DICT.clear()
+ lines = []
try:
- key, value = parse_key_value(line)
- if value:
- EM_CONFIG_DICT[key] = value
+ lines = read_file(EM_CONFIG_PATH).splitlines()
except Exception:
- pass
+ pass
+ for line in lines:
+ try:
+ key, value = parse_key_value(line)
+ if value:
+ EM_CONFIG_DICT[key] = value
+ except Exception:
+ pass
def find_emscripten_root(active_tools):
- """Find the currently active emscripten root.
+ """Find the currently active emscripten root.
- If there is more than one tool that defines EMSCRIPTEN_ROOT (this
- should not happen under normal circumstances), assume the last one takes
- precedence.
- """
- root = None
- for tool in active_tools:
- config = tool.activated_config()
- if 'EMSCRIPTEN_ROOT' in config:
- root = config['EMSCRIPTEN_ROOT']
- return root
+ If there is more than one tool that defines EMSCRIPTEN_ROOT (this
+ should not happen under normal circumstances), assume the last one takes
+ precedence.
+ """
+ root = None
+ for tool in active_tools:
+ config = tool.activated_config()
+ if 'EMSCRIPTEN_ROOT' in config:
+ root = config['EMSCRIPTEN_ROOT']
+ return root
def fetch_nightly_node_versions():
- url = "https://nodejs.org/download/nightly/"
- with urlopen(url) as response:
- html = response.read().decode("utf-8")
+ url = "https://nodejs.org/download/nightly/"
+ with urlopen(url) as response:
+ html = response.read().decode("utf-8")
- # Regex to capture href values like v7.0.0-nightly2016080175c6d9dd95/
- pattern = re.compile(r'<a href="(v[0-9]+\.[0-9]+\.[0-9]+-nightly[0-9a-f]+)/">')
- matches = pattern.findall(html)
- return matches
+ # Regex to capture href values like v7.0.0-nightly2016080175c6d9dd95/
+ pattern = re.compile(r'<a href="(v[0-9]+\.[0-9]+\.[0-9]+-nightly[0-9a-f]+)/">')
+ matches = pattern.findall(html)
+ return matches
def dir_installed_nightly_node_versions():
- path = os.path.abspath('node')
- try:
- return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name)) and name.startswith("nightly-")]
- except Exception:
- return []
+ path = os.path.abspath('node')
+ try:
+ return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name)) and name.startswith("nightly-")]
+ except Exception:
+ return []
def extract_newest_node_nightly_version(versions):
- def parse(v):
- # example: v7.0.0-nightly2016080175c6d9dd95
- m = re.search(r'v(\d+)\.(\d+)\.(\d+)-nightly(\d{8})', v)
- if m:
- major, minor, patch, nightly = m.groups()
- return [int(major), int(minor), int(patch), int(nightly)]
- else:
- return []
+ def parse(v):
+ # example: v7.0.0-nightly2016080175c6d9dd95
+ m = re.search(r'v(\d+)\.(\d+)\.(\d+)-nightly(\d{8})', v)
+ if m:
+ major, minor, patch, nightly = m.groups()
+ return [int(major), int(minor), int(patch), int(nightly)]
+ else:
+ return []
- try:
- return max(versions, key=parse)
- except Exception:
- return None
+ try:
+ return max(versions, key=parse)
+ except Exception:
+ return None
def download_node_nightly(tool):
- nightly_versions = fetch_nightly_node_versions()
- latest_nightly = extract_newest_node_nightly_version(nightly_versions)
- print(f'Latest Node.js Nightly download available is "{latest_nightly}"')
+ nightly_versions = fetch_nightly_node_versions()
+ latest_nightly = extract_newest_node_nightly_version(nightly_versions)
+ print(f'Latest Node.js Nightly download available is "{latest_nightly}"')
- output_dir = os.path.abspath('node/nightly-' + latest_nightly)
- # Node.js zip structure quirk: Linux and macOS archives have a /bin,
- # Windows does not. Unify the file structures.
- if WINDOWS:
- output_dir += '/bin'
+ output_dir = os.path.abspath('node/nightly-' + latest_nightly)
+ # Node.js zip structure quirk: Linux and macOS archives have a /bin,
+ # Windows does not. Unify the file structures.
+ if WINDOWS:
+ output_dir += '/bin'
- if os.path.isdir(output_dir):
+ if os.path.isdir(output_dir):
+ return True
+
+ url = tool.url.replace('%version%', latest_nightly)
+ if WINDOWS:
+ os_ = 'win'
+ elif LINUX:
+ os_ = 'linux'
+ elif MACOS:
+ os_ = 'darwin'
+ else:
+ os_ = ''
+ if platform.machine().lower() in {'x86_64', 'amd64'}:
+ arch = 'x64'
+ elif platform.machine().lower() in {'arm64', 'aarch64'}:
+ arch = 'arm64'
+ if WINDOWS:
+ zip_suffix = 'zip'
+ else:
+ zip_suffix = 'tar.gz'
+ url = url.replace('%os%', os_)
+ url = url.replace('%arch%', arch)
+ url = url.replace('%zip_suffix%', zip_suffix)
+ download_and_extract(url, output_dir)
+ write_file(tool.get_version_file_path(), 'node-nightly-64bit')
return True
- url = tool.url.replace('%version%', latest_nightly)
- if WINDOWS:
- os_ = 'win'
- elif LINUX:
- os_ = 'linux'
- elif MACOS:
- os_ = 'darwin'
- else:
- os_ = ''
- if platform.machine().lower() in {'x86_64', 'amd64'}:
- arch = 'x64'
- elif platform.machine().lower() in {'arm64', 'aarch64'}:
- arch = 'arm64'
- if WINDOWS:
- zip_suffix = 'zip'
- else:
- zip_suffix = 'tar.gz'
- url = url.replace('%os%', os_)
- url = url.replace('%arch%', arch)
- url = url.replace('%zip_suffix%', zip_suffix)
- download_and_extract(url, output_dir)
- write_file(tool.get_version_file_path(), 'node-nightly-64bit')
- return True
-
def get_emsdk_shell_env_configs():
- """returns a tuple (string,string) of config files paths that need to used
- to activate emsdk env depending on $SHELL, defaults to bash.
- """
- default_emsdk_env = sdk_path('emsdk_env.sh')
- default_shell_config_file = '$HOME/.bash_profile'
- shell = os.getenv('SHELL', '')
- if 'zsh' in shell:
- return (default_emsdk_env, '$HOME/.zprofile')
- elif 'csh' in shell:
- return (sdk_path('emsdk_env.csh'), '$HOME/.cshrc')
- elif 'fish' in shell:
- return (sdk_path('emsdk_env.fish'), '$HOME/.config/fish/config.fish')
- else:
- return (default_emsdk_env, default_shell_config_file)
+ """returns a tuple (string,string) of config files paths that need to used
+ to activate emsdk env depending on $SHELL, defaults to bash.
+ """
+ default_emsdk_env = sdk_path('emsdk_env.sh')
+ default_shell_config_file = '$HOME/.bash_profile'
+ shell = os.getenv('SHELL', '')
+ if 'zsh' in shell:
+ return (default_emsdk_env, '$HOME/.zprofile')
+ elif 'csh' in shell:
+ return (sdk_path('emsdk_env.csh'), '$HOME/.cshrc')
+ elif 'fish' in shell:
+ return (sdk_path('emsdk_env.fish'), '$HOME/.config/fish/config.fish')
+ else:
+ return (default_emsdk_env, default_shell_config_file)
def generate_em_config(active_tools, permanently_activate, system):
- emroot = find_emscripten_root(active_tools)
- version = None
- if emroot and os.path.exists(os.path.join(emroot, 'emscripten-version.txt')):
- version = parse_emscripten_version(emroot)
+ emroot = find_emscripten_root(active_tools)
+ version = None
+ if emroot and os.path.exists(os.path.join(emroot, 'emscripten-version.txt')):
+ version = parse_emscripten_version(emroot)
- supports_cfgdir = version and version >= [6, 0, 4]
+ supports_cfgdir = version and version >= [6, 0, 4]
- cfg = ''
- if not supports_cfgdir:
- cfg += 'import os\n'
- cfg += "emsdk_path = os.path.dirname(os.getenv('EM_CONFIG')).replace('\\\\', '/')\n"
+ cfg = ''
+ if not supports_cfgdir:
+ cfg += 'import os\n'
+ cfg += "emsdk_path = os.path.dirname(os.getenv('EM_CONFIG')).replace('\\\\', '/')\n"
- # Different tools may provide the same activated configs; the latest to be
- # activated is the relevant one.
- activated_config = {}
- for tool in active_tools:
- activated_config.update(tool.activated_config())
+ # Different tools may provide the same activated configs; the latest to be
+ # activated is the relevant one.
+ activated_config = {}
+ for tool in active_tools:
+ activated_config.update(tool.activated_config())
- if 'NODE_JS' not in activated_config:
- node_fallback = shutil.which('nodejs')
- if not node_fallback:
- node_fallback = 'node'
- activated_config['NODE_JS'] = node_fallback
+ if 'NODE_JS' not in activated_config:
+ node_fallback = shutil.which('nodejs')
+ if not node_fallback:
+ node_fallback = 'node'
+ activated_config['NODE_JS'] = node_fallback
- for name, value in activated_config.items():
- if value.startswith('['):
- cfg += f'{name} = {value}\n'
+ for name, value in activated_config.items():
+ if value.startswith('['):
+ cfg += f'{name} = {value}\n'
+ else:
+ cfg += f"{name} = '{value}'\n"
+
+ if version:
+ # Older emscripten versions of emscripten depend on certain config
+ # keys that are no longer used.
+ # See https://github.com/emscripten-core/emscripten/pull/9469
+ if version < [1, 38, 46]:
+ cfg += 'COMPILER_ENGINE = NODE_JS\n'
+ # See https://github.com/emscripten-core/emscripten/pull/9542
+ if version < [1, 38, 48]:
+ cfg += 'JS_ENGINES = [NODE_JS]\n'
+
+ if supports_cfgdir:
+ cfg = cfg.replace(EMSDK_PATH, '$CFGDIR')
else:
- cfg += f"{name} = '{value}'\n"
+ cfg = cfg.replace("'" + EMSDK_PATH, "emsdk_path + '")
- if version:
- # Older emscripten versions of emscripten depend on certain config
- # keys that are no longer used.
- # See https://github.com/emscripten-core/emscripten/pull/9469
- if version < [1, 38, 46]:
- cfg += 'COMPILER_ENGINE = NODE_JS\n'
- # See https://github.com/emscripten-core/emscripten/pull/9542
- if version < [1, 38, 48]:
- cfg += 'JS_ENGINES = [NODE_JS]\n'
+ if os.path.exists(EM_CONFIG_PATH):
+ backup_path = EM_CONFIG_PATH + ".old"
+ move_with_overwrite(EM_CONFIG_PATH, backup_path)
- if supports_cfgdir:
- cfg = cfg.replace(EMSDK_PATH, '$CFGDIR')
- else:
- cfg = cfg.replace("'" + EMSDK_PATH, "emsdk_path + '")
+ write_file(EM_CONFIG_PATH, cfg)
- if os.path.exists(EM_CONFIG_PATH):
- backup_path = EM_CONFIG_PATH + ".old"
- move_with_overwrite(EM_CONFIG_PATH, backup_path)
+ # Clear old emscripten content.
+ rmfile(os.path.join(EMSDK_PATH, ".emscripten_sanity"))
- write_file(EM_CONFIG_PATH, cfg)
+ path_add = get_required_path(active_tools)
- # Clear old emscripten content.
- rmfile(os.path.join(EMSDK_PATH, ".emscripten_sanity"))
-
- path_add = get_required_path(active_tools)
-
- # Give some recommended next step, depending on the platform
- if WINDOWS:
- if not permanently_activate and not system:
- print('Next steps:')
- print('- Consider running `emsdk activate` with --permanent or --system')
- print(' to have emsdk settings available on startup.')
- else:
- print('Next steps:')
- print('- To conveniently access emsdk tools from the command line,')
- print(' consider adding the following directories to your PATH:')
- for p in path_add:
- print(' ' + p)
- print('- This can be done for the current shell by running:')
- emsdk_env, shell_config_file = get_emsdk_shell_env_configs()
- print(f' source "{emsdk_env}"')
- print('- Configure emsdk in your shell startup scripts by running:')
- print(f' echo \'source "{emsdk_env}"\' >> {shell_config_file}')
+ # Give some recommended next step, depending on the platform
+ if WINDOWS:
+ if not permanently_activate and not system:
+ print('Next steps:')
+ print('- Consider running `emsdk activate` with --permanent or --system')
+ print(' to have emsdk settings available on startup.')
+ else:
+ print('Next steps:')
+ print('- To conveniently access emsdk tools from the command line,')
+ print(' consider adding the following directories to your PATH:')
+ for p in path_add:
+ print(' ' + p)
+ print('- This can be done for the current shell by running:')
+ emsdk_env, shell_config_file = get_emsdk_shell_env_configs()
+ print(f' source "{emsdk_env}"')
+ print('- Configure emsdk in your shell startup scripts by running:')
+ print(f' echo \'source "{emsdk_env}"\' >> {shell_config_file}')
class Tool:
- os = None
- bitness = None
- append_bitness = True
- is_old = False
- version = None
- activated_path = None
- cmake_build_type = None
- install_path = None
- activated_path_skip = False
- activated_cfg = None
- activated_env = None
- arch = None
- custom_is_installed_script = None
- custom_install_script = None
- custom_uninstall_script = None
- emscripten_releases_hash = None
- git_branch = None
- url = None
+ os = None
+ bitness = None
+ append_bitness = True
+ is_old = False
+ version = None
+ activated_path = None
+ cmake_build_type = None
+ install_path = None
+ activated_path_skip = False
+ activated_cfg = None
+ activated_env = None
+ arch = None
+ custom_is_installed_script = None
+ custom_install_script = None
+ custom_uninstall_script = None
+ emscripten_releases_hash = None
+ git_branch = None
+ url = None
- def __init__(self, data):
- self.deps = []
+ def __init__(self, data):
+ self.deps = []
- # Convert the dictionary representation of the tool in 'data' to members of
- # this class. Base attributes are assigned first, then host OS-matching suffixed
- # attributes (e.g. url_windows, activated_path_windows) are applied on top as overrides.
- # Foreign OS keys are ignored.
- os_overrides = {}
- all_os_names = {'windows', 'macos', 'linux'}
+ # Convert the dictionary representation of the tool in 'data' to members of
+ # this class. Base attributes are assigned first, then host OS-matching suffixed
+ # attributes (e.g. url_windows, activated_path_windows) are applied on top as overrides.
+ # Foreign OS keys are ignored.
+ os_overrides = {}
+ all_os_names = {'windows', 'macos', 'linux'}
- for key, value in data.items():
- if '_' in key:
- prefix, suffix = key.rsplit('_', 1)
- if suffix in all_os_names:
- if suffix == os_name():
- os_overrides[prefix] = value
- continue
- setattr(self, key, value)
+ for key, value in data.items():
+ if '_' in key:
+ prefix, suffix = key.rsplit('_', 1)
+ if suffix in all_os_names:
+ if suffix == os_name():
+ os_overrides[prefix] = value
+ continue
+ setattr(self, key, value)
- for prefix, value in os_overrides.items():
- setattr(self, prefix, value)
+ for prefix, value in os_overrides.items():
+ setattr(self, prefix, value)
- # Cache the name ID of this Tool (these are read very often)
- self.name = self.id
- if self.version:
- self.name += f'-{self.version}'
- if self.bitness:
- self.name += f'-{self.bitness}bit'
+ # Cache the name ID of this Tool (these are read very often)
+ self.name = self.id
+ if self.version:
+ self.name += f'-{self.version}'
+ if self.bitness:
+ self.name += f'-{self.bitness}bit'
- def __str__(self):
- return self.name
+ def __str__(self):
+ return self.name
- def expand_vars(self, str):
- if '%installation_dir%' in str:
- str = str.replace('%installation_dir%', sdk_path(self.installation_dir()))
- if '%macos_app_bundle_prefix%' in str:
- str = str.replace('%macos_app_bundle_prefix%', 'Contents/MacOS/' if MACOS else '')
- if '%actual_installation_dir%' in str:
- actual_file = os.path.join(self.installation_dir(), 'actual.txt')
- if os.path.isfile(actual_file):
- str = str.replace('%actual_installation_dir%', sdk_path(read_file(actual_file)))
- else:
- str = str.replace('%actual_installation_dir%', '__NOT_INSTALLED__')
- if '%generator_prefix%' in str:
- str = str.replace('%generator_prefix%', cmake_generator_prefix())
- str = str.replace('%.exe%', '.exe' if WINDOWS else '')
- if '%llvm_build_bin_dir%' in str:
- str = str.replace('%llvm_build_bin_dir%', llvm_build_bin_dir(self))
- if '%latest_downloaded_node_nightly_dir%' in str:
- installed_node_nightlys = dir_installed_nightly_node_versions()
- latest_node_nightly = extract_newest_node_nightly_version(installed_node_nightlys)
- if latest_node_nightly:
- str = str.replace('%latest_downloaded_node_nightly_dir%', latest_node_nightly)
+ def expand_vars(self, str):
+ if '%installation_dir%' in str:
+ str = str.replace('%installation_dir%', sdk_path(self.installation_dir()))
+ if '%macos_app_bundle_prefix%' in str:
+ str = str.replace('%macos_app_bundle_prefix%', 'Contents/MacOS/' if MACOS else '')
+ if '%actual_installation_dir%' in str:
+ actual_file = os.path.join(self.installation_dir(), 'actual.txt')
+ if os.path.isfile(actual_file):
+ str = str.replace('%actual_installation_dir%', sdk_path(read_file(actual_file)))
+ else:
+ str = str.replace('%actual_installation_dir%', '__NOT_INSTALLED__')
+ if '%generator_prefix%' in str:
+ str = str.replace('%generator_prefix%', cmake_generator_prefix())
+ str = str.replace('%.exe%', '.exe' if WINDOWS else '')
+ if '%llvm_build_bin_dir%' in str:
+ str = str.replace('%llvm_build_bin_dir%', llvm_build_bin_dir(self))
+ if '%latest_downloaded_node_nightly_dir%' in str:
+ installed_node_nightlys = dir_installed_nightly_node_versions()
+ latest_node_nightly = extract_newest_node_nightly_version(installed_node_nightlys)
+ if latest_node_nightly:
+ str = str.replace('%latest_downloaded_node_nightly_dir%', latest_node_nightly)
- return str
+ return str
- def needs_compilation(self):
- """Return true if this tool requires building from source, and false if this is a precompiled tool."""
- if self.cmake_build_type:
- return True
+ def needs_compilation(self):
+ """Return true if this tool requires building from source, and false if this is a precompiled tool."""
+ if self.cmake_build_type:
+ return True
- return any(dep.needs_compilation() for dep in self.dependencies())
+ return any(dep.needs_compilation() for dep in self.dependencies())
- def installation_path(self):
- """Specifies the target path where this tool will be installed to. This could
- either be a directory or a filename (e.g. in case of node.js)
- """
- if self.install_path:
- pth = self.expand_vars(self.install_path)
- return sdk_path(pth)
- p = self.version
- if self.bitness and self.append_bitness:
- p += f'_{self.bitness}bit'
- return sdk_path(os.path.join(self.id, p))
+ def installation_path(self):
+ """Specifies the target path where this tool will be installed to. This could
+ either be a directory or a filename (e.g. in case of node.js)
+ """
+ if self.install_path:
+ pth = self.expand_vars(self.install_path)
+ return sdk_path(pth)
+ p = self.version
+ if self.bitness and self.append_bitness:
+ p += f'_{self.bitness}bit'
+ return sdk_path(os.path.join(self.id, p))
- def installation_dir(self):
- """Specifies the target directory this tool will be installed to."""
- dir = self.installation_path()
- if path_points_to_directory(dir):
- return dir
- else:
- return os.path.dirname(dir)
+ def installation_dir(self):
+ """Specifies the target directory this tool will be installed to."""
+ dir = self.installation_path()
+ if path_points_to_directory(dir):
+ return dir
+ else:
+ return os.path.dirname(dir)
- def activated_config(self):
- """Returns the configuration item that needs to be added to .emscripten to make
- this Tool active for the current user.
- """
- if not self.activated_cfg:
- return {}
+ def activated_config(self):
+ """Returns the configuration item that needs to be added to .emscripten to make
+ this Tool active for the current user.
+ """
+ if not self.activated_cfg:
+ return {}
- config = {}
- expanded = to_unix_path(self.expand_vars(self.activated_cfg))
- for specific_cfg in expanded.split(';'):
- name, value = specific_cfg.split('=')
- config[name] = value.strip("'")
- return config
+ config = {}
+ expanded = to_unix_path(self.expand_vars(self.activated_cfg))
+ for specific_cfg in expanded.split(';'):
+ name, value = specific_cfg.split('=')
+ config[name] = value.strip("'")
+ return config
- def activated_environment(self):
- if not self.activated_env:
- return []
+ def activated_environment(self):
+ if not self.activated_env:
+ return []
- return self.expand_vars(self.activated_env).split(';')
+ return self.expand_vars(self.activated_env).split(';')
- def compatible_with_this_arch(self):
- return self.arch == ARCH or not self.arch
+ def compatible_with_this_arch(self):
+ return self.arch == ARCH or not self.arch
- def compatible_with_this_os(self):
- if not self.compatible_with_this_arch():
- return False
+ def compatible_with_this_os(self):
+ if not self.compatible_with_this_arch():
+ return False
- if self.os:
- assert self.os in {'linux', 'windows', 'macos'}
- return self.os == os_name()
+ if self.os:
+ assert self.os in {'linux', 'windows', 'macos'}
+ return self.os == os_name()
- # Tools without an explicit 'os' are compatible with the current
- # OS if they have URL for the current OS *or* a set of dependencies
- # for the current OS.
- return self.url is not None or bool(self.deps)
+ # Tools without an explicit 'os' are compatible with the current
+ # OS if they have URL for the current OS *or* a set of dependencies
+ # for the current OS.
+ return self.url is not None or bool(self.deps)
- def get_version_file_path(self):
- """the "version file" is a file inside install dirs that indicates the
- version installed there. this helps disambiguate when there is more than
- one version that may be installed to the same directory (which is used
- to avoid accumulating builds over time in some cases, with new builds
- overwriting the old)
- """
- return os.path.join(self.installation_path(), '.emsdk_version')
+ def get_version_file_path(self):
+ """the "version file" is a file inside install dirs that indicates the
+ version installed there. this helps disambiguate when there is more than
+ one version that may be installed to the same directory (which is used
+ to avoid accumulating builds over time in some cases, with new builds
+ overwriting the old)
+ """
+ return os.path.join(self.installation_path(), '.emsdk_version')
- def is_installed_version(self):
- version_file_path = self.get_version_file_path()
- if os.path.isfile(version_file_path):
- return read_file(version_file_path).strip() == self.name
- return False
-
- def update_installed_version(self):
- write_file(self.get_version_file_path(), self.name + '\n')
-
- def is_installed(self, skip_version_check=False):
- # If this tool/sdk depends on other tools, require that all dependencies are
- # installed for this tool to count as being installed.
- for dep in self.dependencies():
- if not dep.is_installed():
+ def is_installed_version(self):
+ version_file_path = self.get_version_file_path()
+ if os.path.isfile(version_file_path):
+ return read_file(version_file_path).strip() == self.name
return False
- if self.url is None:
- debug_print(f'{self} has no files to download, so is installed by default.')
- return True
+ def update_installed_version(self):
+ write_file(self.get_version_file_path(), self.name + '\n')
- content_exists = is_nonempty_directory(self.installation_path())
- debug_print(f'{self} installation path is {self.installation_path()}, exists: {content_exists}.')
+ def is_installed(self, skip_version_check=False):
+ # If this tool/sdk depends on other tools, require that all dependencies are
+ # installed for this tool to count as being installed.
+ for dep in self.dependencies():
+ if not dep.is_installed():
+ return False
- # For e.g. fastcomp clang from git repo, the activated PATH is the
- # directory where the compiler is built to, and installation_path is
- # the directory where the source tree exists. To distinguish between
- # multiple packages sharing the same source (clang-main-32bit,
- # clang-main-64bit, clang-main-32bit and clang-main-64bit each
- # share the same git repo), require that in addition to the installation
- # directory, each item in the activated PATH must exist.
- if self.activated_path and not os.path.exists(self.expand_vars(self.activated_path)):
- content_exists = False
+ if self.url is None:
+ debug_print(f'{self} has no files to download, so is installed by default.')
+ return True
- if self.custom_is_installed_script:
- if self.custom_is_installed_script == 'is_binaryen_installed':
- return is_binaryen_installed(self)
- elif self.custom_is_installed_script == 'is_firefox_installed':
- return is_firefox_installed(self)
- else:
- raise Exception(f'Unknown custom_is_installed_script: "{self.custom_is_installed_script}"')
+ content_exists = is_nonempty_directory(self.installation_path())
+ debug_print(f'{self} installation path is {self.installation_path()}, exists: {content_exists}.')
- return content_exists and (skip_version_check or self.is_installed_version())
+ # For e.g. fastcomp clang from git repo, the activated PATH is the
+ # directory where the compiler is built to, and installation_path is
+ # the directory where the source tree exists. To distinguish between
+ # multiple packages sharing the same source (clang-main-32bit,
+ # clang-main-64bit, clang-main-32bit and clang-main-64bit each
+ # share the same git repo), require that in addition to the installation
+ # directory, each item in the activated PATH must exist.
+ if self.activated_path and not os.path.exists(self.expand_vars(self.activated_path)):
+ content_exists = False
- def is_active(self):
- if not self.is_installed():
- return False
+ if self.custom_is_installed_script:
+ if self.custom_is_installed_script == 'is_binaryen_installed':
+ return is_binaryen_installed(self)
+ elif self.custom_is_installed_script == 'is_firefox_installed':
+ return is_firefox_installed(self)
+ else:
+ raise Exception(f'Unknown custom_is_installed_script: "{self.custom_is_installed_script}"')
- # All dependencies of this tool must be active as well.
- deps = self.dependencies()
- for tool in deps:
- if not tool.is_active():
- return False
+ return content_exists and (skip_version_check or self.is_installed_version())
- activated_cfg = self.activated_config()
- if not activated_cfg:
- return len(deps) > 0
+ def is_active(self):
+ if not self.is_installed():
+ return False
- for key, value in activated_cfg.items():
- if key not in EM_CONFIG_DICT:
- debug_print(f'{self} is not active, because key="{key}" does not exist in .emscripten')
- return False
+ # All dependencies of this tool must be active as well.
+ deps = self.dependencies()
+ for tool in deps:
+ if not tool.is_active():
+ return False
- # all paths are stored dynamically relative to the emsdk root, so
- # normalize those first.
- config_value = EM_CONFIG_DICT[key].replace("emsdk_path + '", "'" + EMSDK_PATH).replace("$CFGDIR", EMSDK_PATH)
- config_value = config_value.strip("'")
- if config_value != value:
- debug_print(f'{self} is not active, because key="{key}" has value "{config_value}" but should have value "{value}"')
- return False
- return True
+ activated_cfg = self.activated_config()
+ if not activated_cfg:
+ return len(deps) > 0
- def is_env_active(self):
- """Returns true if the system environment variables requires by this tool are currently active."""
- envs = self.activated_environment()
- for env in envs:
- key, value = parse_key_value(env)
- if key not in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value):
- debug_print(f'{self} is not active, because environment variable key="{key}" has value "{os.getenv(key)}" but should have value "{value}"')
- return False
+ for key, value in activated_cfg.items():
+ if key not in EM_CONFIG_DICT:
+ debug_print(f'{self} is not active, because key="{key}" does not exist in .emscripten')
+ return False
- if self.activated_path:
- path = to_unix_path(self.expand_vars(self.activated_path))
- for p in path:
- path_items = os.environ['PATH'].replace('\\', '/').split(ENVPATH_SEPARATOR)
- if not normalized_contains(path_items, p):
- debug_print(f'{self} is not active, because environment variable PATH item "{p}" is not present (PATH={os.environ["PATH"]})')
- return False
- return True
+ # all paths are stored dynamically relative to the emsdk root, so
+ # normalize those first.
+ config_value = EM_CONFIG_DICT[key].replace("emsdk_path + '", "'" + EMSDK_PATH).replace("$CFGDIR", EMSDK_PATH)
+ config_value = config_value.strip("'")
+ if config_value != value:
+ debug_print(f'{self} is not active, because key="{key}" has value "{config_value}" but should have value "{value}"')
+ return False
+ return True
- def can_be_installed(self):
- """If this tool can be installed on this system, this function returns True.
- Otherwise, this function returns a string that describes the reason why this
- tool is not available.
- """
- if self.bitness == 64 and not is_os_64bit():
- return "this tool is only provided for 64-bit OSes"
- return True
+ def is_env_active(self):
+ """Returns true if the system environment variables requires by this tool are currently active."""
+ envs = self.activated_environment()
+ for env in envs:
+ key, value = parse_key_value(env)
+ if key not in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value):
+ debug_print(f'{self} is not active, because environment variable key="{key}" has value "{os.getenv(key)}" but should have value "{value}"')
+ return False
- def install(self):
- """Returns True if the Tool was installed of False if was skipped due to
- already being installed.
- """
- if self.can_be_installed() is not True:
- exit_with_error(f"The tool '{self}' is not available due to the reason: {self.can_be_installed()}")
+ if self.activated_path:
+ path = to_unix_path(self.expand_vars(self.activated_path))
+ for p in path:
+ path_items = os.environ['PATH'].replace('\\', '/').split(ENVPATH_SEPARATOR)
+ if not normalized_contains(path_items, p):
+ debug_print(f'{self} is not active, because environment variable PATH item "{p}" is not present (PATH={os.environ["PATH"]})')
+ return False
+ return True
- if self.id == 'sdk':
- return self.install_sdk()
- else:
- return self.install_tool()
+ def can_be_installed(self):
+ """If this tool can be installed on this system, this function returns True.
+ Otherwise, this function returns a string that describes the reason why this
+ tool is not available.
+ """
+ if self.bitness == 64 and not is_os_64bit():
+ return "this tool is only provided for 64-bit OSes"
+ return True
- def install_sdk(self):
- """Returns True if any SDK component was installed of False all componented
- were already installed.
- """
- print(f"Installing SDK '{self}'..")
- installed = False
+ def install(self):
+ """Returns True if the Tool was installed of False if was skipped due to
+ already being installed.
+ """
+ if self.can_be_installed() is not True:
+ exit_with_error(f"The tool '{self}' is not available due to the reason: {self.can_be_installed()}")
- for dep in self.dependencies():
- installed |= dep.install()
+ if self.id == 'sdk':
+ return self.install_sdk()
+ else:
+ return self.install_tool()
- if not installed:
- print(f"All SDK components already installed: '{self}'.")
- return False
+ def install_sdk(self):
+ """Returns True if any SDK component was installed of False all componented
+ were already installed.
+ """
+ print(f"Installing SDK '{self}'..")
+ installed = False
- if self.custom_install_script == 'sdk_post_install':
- if not sdk_post_install():
- exit_with_error('post-install step failed: sdk_post_install')
+ for dep in self.dependencies():
+ installed |= dep.install()
- print(f"Done installing SDK '{self}'.")
- return True
+ if not installed:
+ print(f"All SDK components already installed: '{self}'.")
+ return False
- def install_tool(self):
- """Returns True if the SDK was installed of False if was skipped due to
- already being installed.
- """
- # Avoid doing a redundant reinstall of the tool, if it has already been installed.
- # However all tools that are sourced directly from git branches do need to be
- # installed every time when requested, since the install step is then used to git
- # pull the tool to a newer version.
- if self.is_installed() and not self.git_branch:
- print(f"Skipped installing {self.name}, already installed.")
- return False
+ if self.custom_install_script == 'sdk_post_install':
+ if not sdk_post_install():
+ exit_with_error('post-install step failed: sdk_post_install')
- print(f"Installing tool '{self}'..")
+ print(f"Done installing SDK '{self}'.")
+ return True
- custom_install_scripts = {
- 'build_llvm': build_llvm,
- 'build_ninja': build_ninja,
- 'build_ccache': build_ccache,
- 'download_node_nightly': download_node_nightly,
- 'download_firefox': download_firefox,
- }
- if self.custom_install_script in custom_install_scripts:
- success = custom_install_scripts[self.custom_install_script](self)
- elif self.git_branch:
- success = git_clone_checkout_and_pull(self.url, self.installation_path(), self.git_branch, getattr(self, 'remote_name', 'origin'))
- elif self.url.endswith(ARCHIVE_SUFFIXES):
- success = download_and_extract(self.url, self.installation_path(),
- filename_prefix=getattr(self, 'download_prefix', ''))
- else:
- assert False, 'unhandled url type: ' + self.url
+ def install_tool(self):
+ """Returns True if the SDK was installed of False if was skipped due to
+ already being installed.
+ """
+ # Avoid doing a redundant reinstall of the tool, if it has already been installed.
+ # However all tools that are sourced directly from git branches do need to be
+ # installed every time when requested, since the install step is then used to git
+ # pull the tool to a newer version.
+ if self.is_installed() and not self.git_branch:
+ print(f"Skipped installing {self.name}, already installed.")
+ return False
- if not success:
- exit_with_error("installation failed!")
+ print(f"Installing tool '{self}'..")
- if self.custom_install_script:
- if self.custom_install_script == 'emscripten_install':
- success = emscripten_install(self)
- elif self.custom_install_script in {'build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly', 'download_firefox'}:
- # 'build_llvm' is a special one that does the download on its
- # own, others do the download manually.
- pass
- elif self.custom_install_script == 'build_binaryen':
- success = build_binaryen_tool(self)
- else:
- raise Exception(f'Unknown custom_install_script: "{self.custom_install_script}"')
+ custom_install_scripts = {
+ 'build_llvm': build_llvm,
+ 'build_ninja': build_ninja,
+ 'build_ccache': build_ccache,
+ 'download_node_nightly': download_node_nightly,
+ 'download_firefox': download_firefox,
+ }
+ if self.custom_install_script in custom_install_scripts:
+ success = custom_install_scripts[self.custom_install_script](self)
+ elif self.git_branch:
+ success = git_clone_checkout_and_pull(self.url, self.installation_path(), self.git_branch, getattr(self, 'remote_name', 'origin'))
+ elif self.url.endswith(ARCHIVE_SUFFIXES):
+ success = download_and_extract(self.url, self.installation_path(),
+ filename_prefix=getattr(self, 'download_prefix', ''))
+ else:
+ assert False, 'unhandled url type: ' + self.url
- if not success:
- exit_with_error("installation failed!")
+ if not success:
+ exit_with_error("installation failed!")
- # Install an emscripten-version.txt file if told to, and if there is one.
- # (If this is not an actual release, but some other build, then we do not
- # write anything.)
- if self.emscripten_releases_hash:
- emscripten_version_file_path = os.path.join(to_native_path(self.expand_vars(self.activated_path)), 'emscripten-version.txt')
- version = get_emscripten_release_version(self.emscripten_releases_hash)
- if version:
- write_file(emscripten_version_file_path, f'"{version}"\n')
+ if self.custom_install_script:
+ if self.custom_install_script == 'emscripten_install':
+ success = emscripten_install(self)
+ elif self.custom_install_script in {'build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly', 'download_firefox'}:
+ # 'build_llvm' is a special one that does the download on its
+ # own, others do the download manually.
+ pass
+ elif self.custom_install_script == 'build_binaryen':
+ success = build_binaryen_tool(self)
+ else:
+ raise Exception(f'Unknown custom_install_script: "{self.custom_install_script}"')
- print(f"Done installing tool '{self}'.")
+ if not success:
+ exit_with_error("installation failed!")
- # Sanity check that the installation succeeded, and if so, remove unneeded
- # leftover installation files.
- if not self.is_installed(skip_version_check=True):
- exit_with_error(f"installation of '{self}' failed, but no error was detected. "
- "Either something went wrong with the installation, or this may indicate an internal emsdk error.")
+ # Install an emscripten-version.txt file if told to, and if there is one.
+ # (If this is not an actual release, but some other build, then we do not
+ # write anything.)
+ if self.emscripten_releases_hash:
+ emscripten_version_file_path = os.path.join(to_native_path(self.expand_vars(self.activated_path)), 'emscripten-version.txt')
+ version = get_emscripten_release_version(self.emscripten_releases_hash)
+ if version:
+ write_file(emscripten_version_file_path, f'"{version}"\n')
- self.cleanup_temp_install_files()
- self.update_installed_version()
- return True
+ print(f"Done installing tool '{self}'.")
- def cleanup_temp_install_files(self):
- if KEEP_DOWNLOADS:
- return
- if self.url.endswith(ARCHIVE_SUFFIXES):
- download_target = get_download_target(self.url, download_dir, getattr(self, 'download_prefix', ''))
- debug_print(f"Deleting temporary download: {download_target}")
- rmfile(download_target)
+ # Sanity check that the installation succeeded, and if so, remove unneeded
+ # leftover installation files.
+ if not self.is_installed(skip_version_check=True):
+ exit_with_error(f"installation of '{self}' failed, but no error was detected. "
+ "Either something went wrong with the installation, or this may indicate an internal emsdk error.")
- def uninstall(self):
- if not self.is_installed():
- print(f"Tool '{self}' was not installed. No need to uninstall.")
- return
- print(f"Uninstalling tool '{self}'..")
- if self.custom_uninstall_script:
- if self.custom_uninstall_script == 'uninstall_binaryen':
- uninstall_binaryen(self)
- else:
- raise Exception(f'Unknown custom_uninstall_script directive "{self.custom_uninstall_script}"!')
- print(f"Deleting path '{self.installation_path()}'")
- remove_tree(self.installation_path())
- print(f"Done uninstalling '{self}'.")
+ self.cleanup_temp_install_files()
+ self.update_installed_version()
+ return True
- def dependencies(self):
- deps = []
+ def cleanup_temp_install_files(self):
+ if KEEP_DOWNLOADS:
+ return
+ if self.url.endswith(ARCHIVE_SUFFIXES):
+ download_target = get_download_target(self.url, download_dir, getattr(self, 'download_prefix', ''))
+ debug_print(f"Deleting temporary download: {download_target}")
+ rmfile(download_target)
- for dep in self.deps:
- tool = find_tool(dep)
- assert tool, f"dependency '{dep}' of '{self}' not found"
- deps += [tool]
- return deps
+ def uninstall(self):
+ if not self.is_installed():
+ print(f"Tool '{self}' was not installed. No need to uninstall.")
+ return
+ print(f"Uninstalling tool '{self}'..")
+ if self.custom_uninstall_script:
+ if self.custom_uninstall_script == 'uninstall_binaryen':
+ uninstall_binaryen(self)
+ else:
+ raise Exception(f'Unknown custom_uninstall_script directive "{self.custom_uninstall_script}"!')
+ print(f"Deleting path '{self.installation_path()}'")
+ remove_tree(self.installation_path())
+ print(f"Done uninstalling '{self}'.")
- def recursive_dependencies(self):
- deps = []
- for dep in self.deps:
- tool = find_tool(dep)
- assert tool, f"dependency '{dep}' of '{self}' not found"
- deps += [tool]
- deps += tool.recursive_dependencies()
- return deps
+ def dependencies(self):
+ deps = []
+
+ for dep in self.deps:
+ tool = find_tool(dep)
+ assert tool, f"dependency '{dep}' of '{self}' not found"
+ deps += [tool]
+ return deps
+
+ def recursive_dependencies(self):
+ deps = []
+ for dep in self.deps:
+ tool = find_tool(dep)
+ assert tool, f"dependency '{dep}' of '{self}' not found"
+ deps += [tool]
+ deps += tool.recursive_dependencies()
+ return deps
# A global registry of all known Emscripten SDK tools available in the SDK manifest.
@@ -2216,12 +2216,12 @@
def add_tool(tool):
- tool.is_sdk = False
- existing = find_tool(str(tool))
- if existing:
- raise Exception(f'Duplicate tool {tool}! Existing:\n{vars(existing)}, New:\n{vars(tool)}')
- tools.append(tool)
- tools_map[str(tool)] = tool
+ tool.is_sdk = False
+ existing = find_tool(str(tool))
+ if existing:
+ raise Exception(f'Duplicate tool {tool}! Existing:\n{vars(existing)}, New:\n{vars(tool)}')
+ tools.append(tool)
+ tools_map[str(tool)] = tool
# A global registry of all known SDK toolsets.
@@ -2230,1126 +2230,1126 @@
def add_sdk(sdk):
- sdk.is_sdk = True
- existing = find_sdk(str(sdk))
- if existing:
- raise Exception(f'Duplicate sdk {sdk}! Existing:\n{vars(existing)}, New:\n{vars(sdk)}')
- sdks.append(sdk)
- sdks_map[str(sdk)] = sdk
+ sdk.is_sdk = True
+ existing = find_sdk(str(sdk))
+ if existing:
+ raise Exception(f'Duplicate sdk {sdk}! Existing:\n{vars(existing)}, New:\n{vars(sdk)}')
+ sdks.append(sdk)
+ sdks_map[str(sdk)] = sdk
# N.B. In both tools and sdks list above, we take the convention that the newest
# items are at the back of the list (ascending chronological order)
def find_tool(name):
- return tools_map.get(name)
+ return tools_map.get(name)
def find_sdk(name):
- return sdks_map.get(name)
+ return sdks_map.get(name)
def is_os_64bit():
- return ARCH.endswith('64')
+ return ARCH.endswith('64')
def find_latest_version():
- return resolve_sdk_aliases('latest')
+ return resolve_sdk_aliases('latest')
def find_latest_hash():
- version = find_latest_version()
- releases_info = load_releases_info()
- return releases_info['releases'][version]
+ version = find_latest_version()
+ releases_info = load_releases_info()
+ return releases_info['releases'][version]
def resolve_sdk_aliases(name, verbose=False):
- releases_info = load_releases_info()
- while name in releases_info['aliases']:
- if verbose:
- print(f"Resolving SDK alias '{name}' to '{releases_info['aliases'][name]}'")
- name = releases_info['aliases'][name]
- return name
+ releases_info = load_releases_info()
+ while name in releases_info['aliases']:
+ if verbose:
+ print(f"Resolving SDK alias '{name}' to '{releases_info['aliases'][name]}'")
+ name = releases_info['aliases'][name]
+ return name
def find_latest_sdk():
- return f'sdk-releases-{find_latest_hash()}-64bit'
+ return f'sdk-releases-{find_latest_hash()}-64bit'
def find_tot_sdk():
- debug_print('Fetching emscripten-releases repository...')
- global extra_release_tag
- extra_release_tag = get_emscripten_releases_tot()
- return f'sdk-releases-{extra_release_tag}-64bit'
+ debug_print('Fetching emscripten-releases repository...')
+ global extra_release_tag
+ extra_release_tag = get_emscripten_releases_tot()
+ return f'sdk-releases-{extra_release_tag}-64bit'
def parse_emscripten_version(emscripten_root):
- version_file = os.path.join(emscripten_root, 'emscripten-version.txt')
- version = read_file(version_file).strip()
- version = version.strip('"')
- suffix = None
- if '-' in version:
- version, suffix = version.split('-')
- version = [int(v) for v in version.split('.')]
- if suffix == 'git':
- # Treat 1.2.3-git as 1.2.2 since it doesn't contain all the changes
- # present in the final 1.2.3 release.
- version[-1] -= 1
- return version
+ version_file = os.path.join(emscripten_root, 'emscripten-version.txt')
+ version = read_file(version_file).strip()
+ version = version.strip('"')
+ suffix = None
+ if '-' in version:
+ version, suffix = version.split('-')
+ version = [int(v) for v in version.split('.')]
+ if suffix == 'git':
+ # Treat 1.2.3-git as 1.2.2 since it doesn't contain all the changes
+ # present in the final 1.2.3 release.
+ version[-1] -= 1
+ return version
def get_emscripten_release_version(emscripten_releases_hash):
- """Given a git hash in emscripten-releases, find the emscripten
- version for it. There may not be one if this is not the hash of
- a release, in which case we return None.
- """
- releases_info = load_releases_info()
- for key, value in dict(releases_info['releases']).items():
- if value == emscripten_releases_hash:
- return key.split('-')[0]
- return None
+ """Given a git hash in emscripten-releases, find the emscripten
+ version for it. There may not be one if this is not the hash of
+ a release, in which case we return None.
+ """
+ releases_info = load_releases_info()
+ for key, value in dict(releases_info['releases']).items():
+ if value == emscripten_releases_hash:
+ return key.split('-')[0]
+ return None
def get_emscripten_releases_tot():
- """Get the tip-of-tree build identifier."""
- success = git_clone_checkout_and_pull(emscripten_releases_repo, sdk_path('releases'), 'main')
- if not success:
- exit_with_error('error checking out emscripten-releases repo')
- recent_releases = git_recent_commits(sdk_path('releases'))
- # The recent releases are the latest hashes in the git repo. There
- # may not be a build for the most recent ones yet; find the last
- # that has uploaded binaries for all platforms.
- all_platforms = [
- ('linux', '', 'tar.xz'),
- ('linux', '-arm64', 'tar.xz'),
- ('mac', '', 'tar.xz'),
- ('mac', '-arm64', 'tar.xz'),
- ('win', '', 'zip'),
- ]
+ """Get the tip-of-tree build identifier."""
+ success = git_clone_checkout_and_pull(emscripten_releases_repo, sdk_path('releases'), 'main')
+ if not success:
+ exit_with_error('error checking out emscripten-releases repo')
+ recent_releases = git_recent_commits(sdk_path('releases'))
+ # The recent releases are the latest hashes in the git repo. There
+ # may not be a build for the most recent ones yet; find the last
+ # that has uploaded binaries for all platforms.
+ all_platforms = [
+ ('linux', '', 'tar.xz'),
+ ('linux', '-arm64', 'tar.xz'),
+ ('mac', '', 'tar.xz'),
+ ('mac', '-arm64', 'tar.xz'),
+ ('win', '', 'zip'),
+ ]
- def check_binary(release, os_name, arch, ext):
- url = emscripten_releases_download_url_template % (os_name, release, arch, ext)
- try:
- urlopen(url)
- return True
- except Exception:
- return False
+ def check_binary(release, os_name, arch, ext):
+ url = emscripten_releases_download_url_template % (os_name, release, arch, ext)
+ try:
+ urlopen(url)
+ return True
+ except Exception:
+ return False
- for release in recent_releases:
- if all(check_binary(release, os_name, arch, ext) for os_name, arch, ext in all_platforms):
- return release
+ for release in recent_releases:
+ if all(check_binary(release, os_name, arch, ext) for os_name, arch, ext in all_platforms):
+ return release
- # No recent release has binaries for all platforms
- exit_with_error('failed to find build of any recent emsdk revision')
+ # No recent release has binaries for all platforms
+ exit_with_error('failed to find build of any recent emsdk revision')
def get_release_hash(arg, releases_info):
- return releases_info.get(arg, None) or releases_info.get(f'sdk-{arg}-64bit')
+ return releases_info.get(arg, None) or releases_info.get(f'sdk-{arg}-64bit')
def version_key(ver):
- return tuple(map(int, re.split('[._-]', ver)[:3]))
+ return tuple(map(int, re.split('[._-]', ver)[:3]))
def is_emsdk_sourced_from_github():
- return os.path.exists(os.path.join(EMSDK_PATH, '.git'))
+ return os.path.exists(os.path.join(EMSDK_PATH, '.git'))
def update_emsdk():
- if is_emsdk_sourced_from_github():
- errlog('You seem to have bootstrapped Emscripten SDK by cloning from GitHub. '
- 'In this case, use "git pull" instead of "emsdk update" to update emsdk. '
- '(Not doing that automatically in case you have local changes)')
- sys.exit(1)
- if not download_and_extract(emsdk_zip_download_url, EMSDK_PATH, clobber=False):
- sys.exit(1)
+ if is_emsdk_sourced_from_github():
+ errlog('You seem to have bootstrapped Emscripten SDK by cloning from GitHub. '
+ 'In this case, use "git pull" instead of "emsdk update" to update emsdk. '
+ '(Not doing that automatically in case you have local changes)')
+ sys.exit(1)
+ if not download_and_extract(emsdk_zip_download_url, EMSDK_PATH, clobber=False):
+ sys.exit(1)
def load_legacy_emscripten_tags():
- """Lists all legacy (pre-emscripten-releases) tagged versions directly in the Git
- repositories. These we can pull and compile from source.
- """
- return read_file(sdk_path('legacy-emscripten-tags.txt')).splitlines()
+ """Lists all legacy (pre-emscripten-releases) tagged versions directly in the Git
+ repositories. These we can pull and compile from source.
+ """
+ return read_file(sdk_path('legacy-emscripten-tags.txt')).splitlines()
def load_legacy_binaryen_tags():
- return read_file(sdk_path('legacy-binaryen-tags.txt')).splitlines()
+ return read_file(sdk_path('legacy-binaryen-tags.txt')).splitlines()
def remove_prefix(s, prefix):
- if s.startswith(prefix):
- return s[len(prefix):]
- else:
- return s
+ if s.startswith(prefix):
+ return s[len(prefix):]
+ else:
+ return s
def remove_suffix(s, suffix):
- if s.endswith(suffix):
- return s[:len(s) - len(suffix)]
- else:
- return s
+ if s.endswith(suffix):
+ return s[:len(s) - len(suffix)]
+ else:
+ return s
def load_file_index_list(filename):
- """filename should be one of: 'llvm-precompiled-tags-32bit.txt', 'llvm-precompiled-tags-64bit.txt'"""
- items = read_file(sdk_path(filename)).splitlines()
- items = [remove_suffix(remove_suffix(remove_prefix(x, 'emscripten-llvm-e'), '.tar.gz'), '.zip').strip() for x in items]
- items = [x for x in items if 'latest' not in x and len(x) > 0]
+ """filename should be one of: 'llvm-precompiled-tags-32bit.txt', 'llvm-precompiled-tags-64bit.txt'"""
+ items = read_file(sdk_path(filename)).splitlines()
+ items = [remove_suffix(remove_suffix(remove_prefix(x, 'emscripten-llvm-e'), '.tar.gz'), '.zip').strip() for x in items]
+ items = [x for x in items if 'latest' not in x and len(x) > 0]
- # Sort versions from oldest to newest (the default sort would be
- # lexicographic, i.e. '1.37.1 < 1.37.10 < 1.37.2')
- return sorted(items, key=version_key)
+ # Sort versions from oldest to newest (the default sort would be
+ # lexicographic, i.e. '1.37.1 < 1.37.10 < 1.37.2')
+ return sorted(items, key=version_key)
def load_releases_info():
- """Load the json info for emscripten-releases."""
- if not hasattr(load_releases_info, 'cached_info'):
- try:
- text = read_file(sdk_path('emscripten-releases-tags.json'))
- load_releases_info.cached_info = json.loads(text)
- except Exception as e:
- print('Error parsing emscripten-releases-tags.json!')
- exit_with_error(str(e))
+ """Load the json info for emscripten-releases."""
+ if not hasattr(load_releases_info, 'cached_info'):
+ try:
+ text = read_file(sdk_path('emscripten-releases-tags.json'))
+ load_releases_info.cached_info = json.loads(text)
+ except Exception as e:
+ print('Error parsing emscripten-releases-tags.json!')
+ exit_with_error(str(e))
- return load_releases_info.cached_info
+ return load_releases_info.cached_info
def get_installed_sdk_version():
- version_file = sdk_path(os.path.join('upstream', '.emsdk_version'))
- if not os.path.exists(version_file):
- return None
- version = read_file(version_file)
- return version.split('-')[1]
+ version_file = sdk_path(os.path.join('upstream', '.emsdk_version'))
+ if not os.path.exists(version_file):
+ return None
+ version = read_file(version_file)
+ return version.split('-')[1]
def load_releases_tags():
- """Get a list of tags for emscripten-releases."""
- tags = []
- info = load_releases_info()
+ """Get a list of tags for emscripten-releases."""
+ tags = []
+ info = load_releases_info()
- for _version, sha in sorted(info['releases'].items(), key=lambda x: version_key(x[0])):
- tags.append(sha)
+ for _version, sha in sorted(info['releases'].items(), key=lambda x: version_key(x[0])):
+ tags.append(sha)
- if extra_release_tag:
- tags.append(extra_release_tag)
+ if extra_release_tag:
+ tags.append(extra_release_tag)
- # Explicitly add the currently installed SDK version. This could be a custom
- # version (installed explicitly) so it might not be part of the main list
- # loaded above.
- installed = get_installed_sdk_version()
- if installed and installed not in tags:
- tags.append(installed)
+ # Explicitly add the currently installed SDK version. This could be a custom
+ # version (installed explicitly) so it might not be part of the main list
+ # loaded above.
+ installed = get_installed_sdk_version()
+ if installed and installed not in tags:
+ tags.append(installed)
- return tags
+ return tags
def load_releases_versions():
- info = load_releases_info()
- versions = list(info['releases'].keys())
- return versions
+ info = load_releases_info()
+ versions = list(info['releases'].keys())
+ return versions
def load_sdk_manifest():
- try:
- manifest = json.loads(read_file(sdk_path('emsdk_manifest.json')))
- except Exception:
- errlog('Error parsing emsdk_manifest.json!')
- raise
+ try:
+ manifest = json.loads(read_file(sdk_path('emsdk_manifest.json')))
+ except Exception:
+ errlog('Error parsing emsdk_manifest.json!')
+ raise
- emscripten_tags = load_legacy_emscripten_tags()
- llvm_precompiled_tags_32bit = []
- llvm_precompiled_tags_64bit = load_file_index_list('llvm-tags-64bit.txt')
- llvm_precompiled_tags = llvm_precompiled_tags_32bit + llvm_precompiled_tags_64bit
- binaryen_tags = load_legacy_binaryen_tags()
- releases_tags = load_releases_tags()
+ emscripten_tags = load_legacy_emscripten_tags()
+ llvm_precompiled_tags_32bit = []
+ llvm_precompiled_tags_64bit = load_file_index_list('llvm-tags-64bit.txt')
+ llvm_precompiled_tags = llvm_precompiled_tags_32bit + llvm_precompiled_tags_64bit
+ binaryen_tags = load_legacy_binaryen_tags()
+ releases_tags = load_releases_tags()
- def dependencies_exist(sdk):
- for dep in sdk.deps:
- tool = find_tool(dep)
- if not tool:
- debug_print('missing dependency: ' + dep)
- return False
- return True
+ def dependencies_exist(sdk):
+ for dep in sdk.deps:
+ tool = find_tool(dep)
+ if not tool:
+ debug_print('missing dependency: ' + dep)
+ return False
+ return True
- def cmp_version(ver, cmp_operand, reference):
- if cmp_operand == '<=':
- return version_key(ver) <= version_key(reference)
- if cmp_operand == '<':
- return version_key(ver) < version_key(reference)
- if cmp_operand == '>=':
- return version_key(ver) >= version_key(reference)
- if cmp_operand == '>':
- return version_key(ver) > version_key(reference)
- if cmp_operand == '==':
- return version_key(ver) == version_key(reference)
- if cmp_operand == '!=':
- return version_key(ver) != version_key(reference)
- raise Exception(f'Invalid cmp_operand "{cmp_operand}"!')
+ def cmp_version(ver, cmp_operand, reference):
+ if cmp_operand == '<=':
+ return version_key(ver) <= version_key(reference)
+ if cmp_operand == '<':
+ return version_key(ver) < version_key(reference)
+ if cmp_operand == '>=':
+ return version_key(ver) >= version_key(reference)
+ if cmp_operand == '>':
+ return version_key(ver) > version_key(reference)
+ if cmp_operand == '==':
+ return version_key(ver) == version_key(reference)
+ if cmp_operand == '!=':
+ return version_key(ver) != version_key(reference)
+ raise Exception(f'Invalid cmp_operand "{cmp_operand}"!')
- def passes_filters(param, ver, filters):
- for v in filters:
- if v[0] == param and not cmp_version(ver, v[1], v[2]):
- return False
- return True
+ def passes_filters(param, ver, filters):
+ for v in filters:
+ if v[0] == param and not cmp_version(ver, v[1], v[2]):
+ return False
+ return True
- def expand_category_param(param, category_list, t, is_sdk):
- """A 'category parameter' is a %foo%-encoded identifier that specifies
- a class of tools instead of just one tool, e.g. %tag%
- """
- for i, ver in enumerate(category_list):
- if not ver.strip():
- continue
- t2 = copy.copy(t)
- found_param = False
- for p, v in vars(t2).items():
- if isinstance(v, str) and param in v:
- t2.__dict__[p] = v.replace(param, ver)
- found_param = True
- if not found_param:
- continue
- t2.is_old = i < len(category_list) - 2
- t2.deps = [x.replace(param, ver) for x in t2.deps]
+ def expand_category_param(param, category_list, t, is_sdk):
+ """A 'category parameter' is a %foo%-encoded identifier that specifies
+ a class of tools instead of just one tool, e.g. %tag%
+ """
+ for i, ver in enumerate(category_list):
+ if not ver.strip():
+ continue
+ t2 = copy.copy(t)
+ found_param = False
+ for p, v in vars(t2).items():
+ if isinstance(v, str) and param in v:
+ t2.__dict__[p] = v.replace(param, ver)
+ found_param = True
+ if not found_param:
+ continue
+ t2.is_old = i < len(category_list) - 2
+ t2.deps = [x.replace(param, ver) for x in t2.deps]
- if is_sdk:
- if dependencies_exist(t2):
- if not find_sdk(t2.name):
- add_sdk(t2)
- else:
- debug_print(f'SDK {t2} already existed in manifest, not adding twice')
- else:
- if not find_tool(t2.name):
- add_tool(t2)
- else:
- debug_print(f'Tool {t2} already existed in manifest, not adding twice')
+ if is_sdk:
+ if dependencies_exist(t2):
+ if not find_sdk(t2.name):
+ add_sdk(t2)
+ else:
+ debug_print(f'SDK {t2} already existed in manifest, not adding twice')
+ else:
+ if not find_tool(t2.name):
+ add_tool(t2)
+ else:
+ debug_print(f'Tool {t2} already existed in manifest, not adding twice')
- for tool in manifest['tools']:
- t = Tool(tool)
- if t.compatible_with_this_os():
- # Expand the metapackages that refer to tags
- if '%tag%' in t.version:
- expand_category_param('%tag%', emscripten_tags, t, is_sdk=False)
- elif '%precompiled_tag%' in t.version:
- expand_category_param('%precompiled_tag%', llvm_precompiled_tags, t, is_sdk=False)
- elif '%precompiled_tag32%' in t.version:
- expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, t, is_sdk=False)
- elif '%precompiled_tag64%' in t.version:
- expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, t, is_sdk=False)
- elif '%binaryen_tag%' in t.version:
- expand_category_param('%binaryen_tag%', binaryen_tags, t, is_sdk=False)
- elif '%releases-tag%' in t.version:
- expand_category_param('%releases-tag%', releases_tags, t, is_sdk=False)
- else:
- add_tool(t)
+ for tool in manifest['tools']:
+ t = Tool(tool)
+ if t.compatible_with_this_os():
+ # Expand the metapackages that refer to tags
+ if '%tag%' in t.version:
+ expand_category_param('%tag%', emscripten_tags, t, is_sdk=False)
+ elif '%precompiled_tag%' in t.version:
+ expand_category_param('%precompiled_tag%', llvm_precompiled_tags, t, is_sdk=False)
+ elif '%precompiled_tag32%' in t.version:
+ expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, t, is_sdk=False)
+ elif '%precompiled_tag64%' in t.version:
+ expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, t, is_sdk=False)
+ elif '%binaryen_tag%' in t.version:
+ expand_category_param('%binaryen_tag%', binaryen_tags, t, is_sdk=False)
+ elif '%releases-tag%' in t.version:
+ expand_category_param('%releases-tag%', releases_tags, t, is_sdk=False)
+ else:
+ add_tool(t)
- for sdk_str in manifest['sdks']:
- sdk_str['id'] = 'sdk'
- sdk = Tool(sdk_str)
- if sdk.compatible_with_this_os():
- if not hasattr(sdk, 'is_old'):
- sdk.is_old = False
+ for sdk_str in manifest['sdks']:
+ sdk_str['id'] = 'sdk'
+ sdk = Tool(sdk_str)
+ if sdk.compatible_with_this_os():
+ if not hasattr(sdk, 'is_old'):
+ sdk.is_old = False
- if '%tag%' in sdk.version:
- expand_category_param('%tag%', emscripten_tags, sdk, is_sdk=True)
- elif '%precompiled_tag%' in sdk.version:
- expand_category_param('%precompiled_tag%', llvm_precompiled_tags, sdk, is_sdk=True)
- elif '%precompiled_tag32%' in sdk.version:
- expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, sdk, is_sdk=True)
- elif '%precompiled_tag64%' in sdk.version:
- expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, sdk, is_sdk=True)
- elif '%releases-tag%' in sdk.version:
- expand_category_param('%releases-tag%', releases_tags, sdk, is_sdk=True)
- else:
- add_sdk(sdk)
+ if '%tag%' in sdk.version:
+ expand_category_param('%tag%', emscripten_tags, sdk, is_sdk=True)
+ elif '%precompiled_tag%' in sdk.version:
+ expand_category_param('%precompiled_tag%', llvm_precompiled_tags, sdk, is_sdk=True)
+ elif '%precompiled_tag32%' in sdk.version:
+ expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, sdk, is_sdk=True)
+ elif '%precompiled_tag64%' in sdk.version:
+ expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, sdk, is_sdk=True)
+ elif '%releases-tag%' in sdk.version:
+ expand_category_param('%releases-tag%', releases_tags, sdk, is_sdk=True)
+ else:
+ add_sdk(sdk)
def can_simultaneously_activate(tool1, tool2):
- """Tests if the two given tools can be active at the same time.
- Currently only a simple check for name for same tool with different versions,
- possibly adds more logic in the future.
- """
- return tool1.id != tool2.id
+ """Tests if the two given tools can be active at the same time.
+ Currently only a simple check for name for same tool with different versions,
+ possibly adds more logic in the future.
+ """
+ return tool1.id != tool2.id
def process_tool_list(tools_to_activate):
- """Expands dependencies for each tool, and removes ones that don't exist."""
- i = 0
- # Gather dependencies for each tool
- while i < len(tools_to_activate):
- tool = tools_to_activate[i]
- deps = tool.recursive_dependencies()
- tools_to_activate = tools_to_activate[:i] + deps + tools_to_activate[i:]
- i += len(deps) + 1
+ """Expands dependencies for each tool, and removes ones that don't exist."""
+ i = 0
+ # Gather dependencies for each tool
+ while i < len(tools_to_activate):
+ tool = tools_to_activate[i]
+ deps = tool.recursive_dependencies()
+ tools_to_activate = tools_to_activate[:i] + deps + tools_to_activate[i:]
+ i += len(deps) + 1
- for tool in tools_to_activate:
- if not tool.is_installed():
- exit_with_error(f"error: tool is not installed and therefore cannot be activated: '{tool}'")
+ for tool in tools_to_activate:
+ if not tool.is_installed():
+ exit_with_error(f"error: tool is not installed and therefore cannot be activated: '{tool}'")
- # Remove conflicting tools
- i = 0
- while i < len(tools_to_activate):
- j = 0
- while j < i:
- secondary_tool = tools_to_activate[j]
- primary_tool = tools_to_activate[i]
- if not can_simultaneously_activate(primary_tool, secondary_tool):
- tools_to_activate.pop(j)
- j -= 1
- i -= 1
- j += 1
- i += 1
- return tools_to_activate
+ # Remove conflicting tools
+ i = 0
+ while i < len(tools_to_activate):
+ j = 0
+ while j < i:
+ secondary_tool = tools_to_activate[j]
+ primary_tool = tools_to_activate[i]
+ if not can_simultaneously_activate(primary_tool, secondary_tool):
+ tools_to_activate.pop(j)
+ j -= 1
+ i -= 1
+ j += 1
+ i += 1
+ return tools_to_activate
def write_set_env_script(env_string):
- assert CMD or POWERSHELL
- write_file(EMSDK_SET_ENV, env_string)
+ assert CMD or POWERSHELL
+ write_file(EMSDK_SET_ENV, env_string)
def set_active_tools(tools_to_activate, permanently_activate, system):
- """Reconfigure .emscripten to choose the currently activated toolset, set PATH
- and other environment variables.
- Returns the full list of deduced tools that are now active.
- """
- tools_to_activate = process_tool_list(tools_to_activate)
+ """Reconfigure .emscripten to choose the currently activated toolset, set PATH
+ and other environment variables.
+ Returns the full list of deduced tools that are now active.
+ """
+ tools_to_activate = process_tool_list(tools_to_activate)
- if tools_to_activate:
- tools = [x for x in tools_to_activate if not x.is_sdk]
- print('Setting the following tools as active:')
- for t in tools:
- print(f' {t}')
- print('')
+ if tools_to_activate:
+ tools = [x for x in tools_to_activate if not x.is_sdk]
+ print('Setting the following tools as active:')
+ for t in tools:
+ print(f' {t}')
+ print('')
- generate_em_config(tools_to_activate, permanently_activate, system)
+ generate_em_config(tools_to_activate, permanently_activate, system)
- # Construct a .bat or .ps1 script that will be invoked to set env. vars and PATH
- # We only do this on cmd or powershell since emsdk.bat/ps1 is able to modify the
- # calling shell environment. On other shell `source emsdk_env.sh` is
- # required.
- if CMD or POWERSHELL:
- # always set local environment variables since permanently activating will only set the registry settings and
- # will not affect the current session
- env_vars_to_add = get_env_vars_to_add(tools_to_activate, system, user=permanently_activate)
- env_string = construct_env_with_vars(env_vars_to_add)
- write_set_env_script(env_string)
+ # Construct a .bat or .ps1 script that will be invoked to set env. vars and PATH
+ # We only do this on cmd or powershell since emsdk.bat/ps1 is able to modify the
+ # calling shell environment. On other shell `source emsdk_env.sh` is
+ # required.
+ if CMD or POWERSHELL:
+ # always set local environment variables since permanently activating will only set the registry settings and
+ # will not affect the current session
+ env_vars_to_add = get_env_vars_to_add(tools_to_activate, system, user=permanently_activate)
+ env_string = construct_env_with_vars(env_vars_to_add)
+ write_set_env_script(env_string)
- if WINDOWS and permanently_activate:
- win_set_environment_variables(env_vars_to_add, system, user=permanently_activate)
+ if WINDOWS and permanently_activate:
+ win_set_environment_variables(env_vars_to_add, system, user=permanently_activate)
- return tools_to_activate
+ return tools_to_activate
def currently_active_sdk():
- for sdk in reversed(sdks):
- if sdk.is_active():
- return sdk
- return None
+ for sdk in reversed(sdks):
+ if sdk.is_active():
+ return sdk
+ return None
def currently_active_tools():
- active_tools = []
- for tool in tools:
- if tool.is_active():
- active_tools += [tool]
- return active_tools
+ active_tools = []
+ for tool in tools:
+ if tool.is_active():
+ active_tools += [tool]
+ return active_tools
# http://stackoverflow.com/questions/480214/how-do-you-remove-duplicates-from-a-list-in-python-whilst-preserving-order
def unique_items(seq):
- seen = set()
- seen_add = seen.add
- return [x for x in seq if x not in seen and not seen_add(x)]
+ seen = set()
+ seen_add = seen.add
+ return [x for x in seq if x not in seen and not seen_add(x)]
def normalized_contains(lst, elem):
- """Tests if a path is contained in the given list, but with separators normalized."""
- elem = to_unix_path(elem)
- for e in lst:
- if elem == to_unix_path(e):
- return True
- return False
+ """Tests if a path is contained in the given list, but with separators normalized."""
+ elem = to_unix_path(elem)
+ for e in lst:
+ if elem == to_unix_path(e):
+ return True
+ return False
def to_msys_path(p):
- p = to_unix_path(p)
- new_path = re.sub(r'([a-zA-Z]):/(.*)', r'/\1/\2', p)
- if len(new_path) > 3 and new_path[0] == '/' and new_path[2] == '/':
- new_path = new_path[0] + new_path[1].lower() + new_path[2:]
- return new_path
+ p = to_unix_path(p)
+ new_path = re.sub(r'([a-zA-Z]):/(.*)', r'/\1/\2', p)
+ if len(new_path) > 3 and new_path[0] == '/' and new_path[2] == '/':
+ new_path = new_path[0] + new_path[1].lower() + new_path[2:]
+ return new_path
def adjusted_path(tools_to_activate, system=False, user=False):
- """Looks at the current PATH and adds and removes entries so that the PATH reflects
- the set of given active tools.
- """
- # These directories should be added to PATH
- path_add = get_required_path(tools_to_activate)
- # These already exist.
- if WINDOWS and not MSYS:
- existing_path = win_get_environment_variable('PATH', system=system, user=user, fallback=True).split(ENVPATH_SEPARATOR)
- else:
- existing_path = os.environ['PATH'].split(ENVPATH_SEPARATOR)
-
- existing_emsdk_tools = []
- existing_nonemsdk_path = []
- for entry in existing_path:
- if to_unix_path(entry).startswith(EMSDK_PATH):
- existing_emsdk_tools.append(entry)
+ """Looks at the current PATH and adds and removes entries so that the PATH reflects
+ the set of given active tools.
+ """
+ # These directories should be added to PATH
+ path_add = get_required_path(tools_to_activate)
+ # These already exist.
+ if WINDOWS and not MSYS:
+ existing_path = win_get_environment_variable('PATH', system=system, user=user, fallback=True).split(ENVPATH_SEPARATOR)
else:
- existing_nonemsdk_path.append(entry)
+ existing_path = os.environ['PATH'].split(ENVPATH_SEPARATOR)
- new_emsdk_tools = []
- kept_emsdk_tools = []
- for entry in path_add:
- if not normalized_contains(existing_emsdk_tools, entry):
- new_emsdk_tools.append(entry)
- else:
- kept_emsdk_tools.append(entry)
+ existing_emsdk_tools = []
+ existing_nonemsdk_path = []
+ for entry in existing_path:
+ if to_unix_path(entry).startswith(EMSDK_PATH):
+ existing_emsdk_tools.append(entry)
+ else:
+ existing_nonemsdk_path.append(entry)
- whole_path = unique_items(new_emsdk_tools + kept_emsdk_tools + existing_nonemsdk_path)
+ new_emsdk_tools = []
+ kept_emsdk_tools = []
+ for entry in path_add:
+ if not normalized_contains(existing_emsdk_tools, entry):
+ new_emsdk_tools.append(entry)
+ else:
+ kept_emsdk_tools.append(entry)
- if MSYS:
- # XXX Hack: If running native Windows Python in MSYS prompt where PATH
- # entries look like "/c/Windows/System32", os.environ['PATH']
- # in Python will transform to show them as "C:\\Windows\\System32", so need
- # to reconvert path delimiter back to forward slashes.
- whole_path = [to_msys_path(p) for p in whole_path]
- new_emsdk_tools = [to_msys_path(p) for p in new_emsdk_tools]
+ whole_path = unique_items(new_emsdk_tools + kept_emsdk_tools + existing_nonemsdk_path)
- separator = ':' if MSYS else ENVPATH_SEPARATOR
- return (separator.join(whole_path), new_emsdk_tools)
+ if MSYS:
+ # XXX Hack: If running native Windows Python in MSYS prompt where PATH
+ # entries look like "/c/Windows/System32", os.environ['PATH']
+ # in Python will transform to show them as "C:\\Windows\\System32", so need
+ # to reconvert path delimiter back to forward slashes.
+ whole_path = [to_msys_path(p) for p in whole_path]
+ new_emsdk_tools = [to_msys_path(p) for p in new_emsdk_tools]
+
+ separator = ':' if MSYS else ENVPATH_SEPARATOR
+ return (separator.join(whole_path), new_emsdk_tools)
def get_env_vars_to_add(tools_to_activate, system, user):
- env_vars_to_add = []
+ env_vars_to_add = []
- newpath, added_path = adjusted_path(tools_to_activate, system, user)
+ newpath, added_path = adjusted_path(tools_to_activate, system, user)
- # Don't bother setting the path if there are no changes.
- if os.environ['PATH'] != newpath:
- env_vars_to_add += [('PATH', newpath)]
+ # Don't bother setting the path if there are no changes.
+ if os.environ['PATH'] != newpath:
+ env_vars_to_add += [('PATH', newpath)]
- if added_path:
- info('Adding directories to PATH:')
- for item in added_path:
- info('PATH += ' + item)
- info('')
+ if added_path:
+ info('Adding directories to PATH:')
+ for item in added_path:
+ info('PATH += ' + item)
+ info('')
- # A core variable EMSDK points to the root of Emscripten SDK directory.
- env_vars_to_add += [('EMSDK', EMSDK_PATH)]
+ # A core variable EMSDK points to the root of Emscripten SDK directory.
+ env_vars_to_add += [('EMSDK', EMSDK_PATH)]
- for tool in tools_to_activate:
- for env in tool.activated_environment():
- key, value = parse_key_value(env)
- value = to_native_path(tool.expand_vars(value))
- env_vars_to_add += [(key, value)]
+ for tool in tools_to_activate:
+ for env in tool.activated_environment():
+ key, value = parse_key_value(env)
+ value = to_native_path(tool.expand_vars(value))
+ env_vars_to_add += [(key, value)]
- emroot = find_emscripten_root(tools_to_activate)
- if emroot:
- # For older emscripten versions that don't use an embedded cache by
- # default we need to export EM_CACHE.
- #
- # Sadly, we can't put this in the config file since those older versions
- # also didn't read the `CACHE` key from the config file:
- #
- # History:
- # - 'CACHE' config started being honored in 1.39.16
- # https://github.com/emscripten-core/emscripten/pull/11091
- # - Default to embedded cache also started in 1.39.16
- # https://github.com/emscripten-core/emscripten/pull/11126
- # - Emscripten supports automatically locating the embedded
- # config in 1.39.13:
- # https://github.com/emscripten-core/emscripten/pull/10935
- #
- # Since setting EM_CACHE in the environment effects the entire machine
- # we want to avoid this except when installing these older emscripten
- # versions that really need it.
- version = parse_emscripten_version(emroot)
- if version < [1, 39, 16]:
- em_cache_dir = os.path.join(emroot, 'cache')
- env_vars_to_add += [('EM_CACHE', em_cache_dir)]
- if version < [1, 39, 13]:
- env_vars_to_add += [('EM_CONFIG', os.path.normpath(EM_CONFIG_PATH))]
+ emroot = find_emscripten_root(tools_to_activate)
+ if emroot:
+ # For older emscripten versions that don't use an embedded cache by
+ # default we need to export EM_CACHE.
+ #
+ # Sadly, we can't put this in the config file since those older versions
+ # also didn't read the `CACHE` key from the config file:
+ #
+ # History:
+ # - 'CACHE' config started being honored in 1.39.16
+ # https://github.com/emscripten-core/emscripten/pull/11091
+ # - Default to embedded cache also started in 1.39.16
+ # https://github.com/emscripten-core/emscripten/pull/11126
+ # - Emscripten supports automatically locating the embedded
+ # config in 1.39.13:
+ # https://github.com/emscripten-core/emscripten/pull/10935
+ #
+ # Since setting EM_CACHE in the environment effects the entire machine
+ # we want to avoid this except when installing these older emscripten
+ # versions that really need it.
+ version = parse_emscripten_version(emroot)
+ if version < [1, 39, 16]:
+ em_cache_dir = os.path.join(emroot, 'cache')
+ env_vars_to_add += [('EM_CACHE', em_cache_dir)]
+ if version < [1, 39, 13]:
+ env_vars_to_add += [('EM_CONFIG', os.path.normpath(EM_CONFIG_PATH))]
- return env_vars_to_add
+ return env_vars_to_add
def construct_env(tools_to_activate, system, user):
- info('Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)')
- return construct_env_with_vars(get_env_vars_to_add(tools_to_activate, system, user))
+ info('Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)')
+ return construct_env_with_vars(get_env_vars_to_add(tools_to_activate, system, user))
def unset_env(key):
- if POWERSHELL:
- return f'Remove-Item env:{key} -ErrorAction SilentlyContinue\n'
- if CMD:
- return f'set {key}=\n'
- if CSH:
- return f'unsetenv {key};\n'
- if FISH:
- return f'set -e {key};\n'
- if BASH:
- return f'unset {key};\n'
- assert False
+ if POWERSHELL:
+ return f'Remove-Item env:{key} -ErrorAction SilentlyContinue\n'
+ if CMD:
+ return f'set {key}=\n'
+ if CSH:
+ return f'unsetenv {key};\n'
+ if FISH:
+ return f'set -e {key};\n'
+ if BASH:
+ return f'unset {key};\n'
+ assert False
def construct_env_with_vars(env_vars_to_add):
- env_string = ''
- if env_vars_to_add:
- info('Setting environment variables:')
+ env_string = ''
+ if env_vars_to_add:
+ info('Setting environment variables:')
- for key, value in env_vars_to_add:
- # Don't set env vars which are already set to the correct value.
- if key in os.environ and to_unix_path(os.environ[key]) == to_unix_path(value):
- continue
- info(f'{key} = {value}')
- if POWERSHELL:
- env_string += f'$env:{key}="{value}"\n'
- elif CMD:
- env_string += f'SET {key}={value}\n'
- elif CSH:
- env_string += f'setenv {key} "{value}";\n'
- elif FISH:
- env_string += f'set -gx {key} "{value}";\n'
- elif BASH:
- env_string += f'export {key}="{value}";\n'
- else:
- assert False
+ for key, value in env_vars_to_add:
+ # Don't set env vars which are already set to the correct value.
+ if key in os.environ and to_unix_path(os.environ[key]) == to_unix_path(value):
+ continue
+ info(f'{key} = {value}')
+ if POWERSHELL:
+ env_string += f'$env:{key}="{value}"\n'
+ elif CMD:
+ env_string += f'SET {key}={value}\n'
+ elif CSH:
+ env_string += f'setenv {key} "{value}";\n'
+ elif FISH:
+ env_string += f'set -gx {key} "{value}";\n'
+ elif BASH:
+ env_string += f'export {key}="{value}";\n'
+ else:
+ assert False
- if 'EMSDK_PYTHON' in env_vars_to_add:
- # When using our bundled python we never want the user's
- # PYTHONHOME or PYTHONPATH
- # See https://github.com/emscripten-core/emsdk/issues/598
- env_string += unset_env('PYTHONHOME')
- env_string += unset_env('PYTHONPATH')
+ if 'EMSDK_PYTHON' in env_vars_to_add:
+ # When using our bundled python we never want the user's
+ # PYTHONHOME or PYTHONPATH
+ # See https://github.com/emscripten-core/emsdk/issues/598
+ env_string += unset_env('PYTHONHOME')
+ env_string += unset_env('PYTHONPATH')
- # Remove any environment variables that might have been set by old or
- # inactive tools/sdks. For example, we set EM_CACHE for older versions
- # of the SDK but we want to remove that from the current environment
- # if no such tool is active.
- # Ignore certain keys that are inputs to emsdk itself.
- ignore_keys = {'EMSDK_POWERSHELL', 'EMSDK_CSH', 'EMSDK_CMD', 'EMSDK_BASH', 'EMSDK_FISH',
- 'EMSDK_NUM_CORES', 'EMSDK_NOTTY', 'EMSDK_KEEP_DOWNLOADS'}
- env_keys_to_add = {pair[0] for pair in env_vars_to_add}
- for key in os.environ:
- if key.startswith('EMSDK_') or key in {'EM_CACHE', 'EM_CONFIG'}:
- if key not in env_keys_to_add and key not in ignore_keys:
- info(f'Clearing existing environment variable: {key}')
- env_string += unset_env(key)
+ # Remove any environment variables that might have been set by old or
+ # inactive tools/sdks. For example, we set EM_CACHE for older versions
+ # of the SDK but we want to remove that from the current environment
+ # if no such tool is active.
+ # Ignore certain keys that are inputs to emsdk itself.
+ ignore_keys = {'EMSDK_POWERSHELL', 'EMSDK_CSH', 'EMSDK_CMD', 'EMSDK_BASH', 'EMSDK_FISH',
+ 'EMSDK_NUM_CORES', 'EMSDK_NOTTY', 'EMSDK_KEEP_DOWNLOADS'}
+ env_keys_to_add = {pair[0] for pair in env_vars_to_add}
+ for key in os.environ:
+ if key.startswith('EMSDK_') or key in {'EM_CACHE', 'EM_CONFIG'}:
+ if key not in env_keys_to_add and key not in ignore_keys:
+ info(f'Clearing existing environment variable: {key}')
+ env_string += unset_env(key)
- return env_string
+ return env_string
def error_on_missing_tool(name):
- if name.endswith('-64bit') and not is_os_64bit():
- exit_with_error(f"'{name}' is only provided for 64-bit OSes")
- else:
- exit_with_error(f"tool or SDK not found: '{name}'")
+ if name.endswith('-64bit') and not is_os_64bit():
+ exit_with_error(f"'{name}' is only provided for 64-bit OSes")
+ else:
+ exit_with_error(f"tool or SDK not found: '{name}'")
def expand_sdk_name(name, activating):
- if 'upstream-master' in name:
- errlog('upstream-master SDK has been renamed main')
- name = name.replace('upstream-master', 'main')
- if 'fastcomp' in name:
- exit_with_error('the fastcomp backend is no longer supported. '
- 'Please use an older version of emsdk (for example 3.1.29) '
- 'if you want to install the old fastcomp-based SDK')
- if name in {'tot', 'sdk-tot', 'tot-upstream'}:
- if activating:
- # When we are activating a tot release, assume that the currently
- # installed SDK, if any, is the tot release we want to activate.
- # Without this `install tot && activate tot` will race with the builders
- # that are producing new builds.
- installed = get_installed_sdk_version()
- if installed:
- debug_print('activating currently installed SDK; not updating tot version')
- return f'sdk-releases-{installed}-64bit'
- return find_tot_sdk()
+ if 'upstream-master' in name:
+ errlog('upstream-master SDK has been renamed main')
+ name = name.replace('upstream-master', 'main')
+ if 'fastcomp' in name:
+ exit_with_error('the fastcomp backend is no longer supported. '
+ 'Please use an older version of emsdk (for example 3.1.29) '
+ 'if you want to install the old fastcomp-based SDK')
+ if name in {'tot', 'sdk-tot', 'tot-upstream'}:
+ if activating:
+ # When we are activating a tot release, assume that the currently
+ # installed SDK, if any, is the tot release we want to activate.
+ # Without this `install tot && activate tot` will race with the builders
+ # that are producing new builds.
+ installed = get_installed_sdk_version()
+ if installed:
+ debug_print('activating currently installed SDK; not updating tot version')
+ return f'sdk-releases-{installed}-64bit'
+ return find_tot_sdk()
- if '-upstream' in name:
- name = name.replace('-upstream', '')
+ if '-upstream' in name:
+ name = name.replace('-upstream', '')
- name = resolve_sdk_aliases(name, verbose=True)
+ name = resolve_sdk_aliases(name, verbose=True)
- # check if it's a release handled by an emscripten-releases version,
- # and if so use that by using the right hash. we support a few notations,
- # x.y.z
- # sdk-x.y.z-64bit
- # TODO: support short notation for old builds too?
- fullname = name
- version = fullname.replace('sdk-', '').replace('releases-', '').replace('-64bit', '').replace('tag-', '')
- sdk = 'sdk-' if not name.startswith('releases-') else ''
- releases_info = load_releases_info()['releases']
- release_hash = get_release_hash(version, releases_info)
- if release_hash:
- # Known release hash
- full_name = f'{sdk}releases-{release_hash}-64bit'
- print(f"Resolving SDK version '{version}' to '{full_name}'")
- return full_name
+ # check if it's a release handled by an emscripten-releases version,
+ # and if so use that by using the right hash. we support a few notations,
+ # x.y.z
+ # sdk-x.y.z-64bit
+ # TODO: support short notation for old builds too?
+ fullname = name
+ version = fullname.replace('sdk-', '').replace('releases-', '').replace('-64bit', '').replace('tag-', '')
+ sdk = 'sdk-' if not name.startswith('releases-') else ''
+ releases_info = load_releases_info()['releases']
+ release_hash = get_release_hash(version, releases_info)
+ if release_hash:
+ # Known release hash
+ full_name = f'{sdk}releases-{release_hash}-64bit'
+ print(f"Resolving SDK version '{version}' to '{full_name}'")
+ return full_name
- if len(version) == 40:
- global extra_release_tag
- extra_release_tag = version
- return f'{sdk}releases-{version}-64bit'
+ if len(version) == 40:
+ global extra_release_tag
+ extra_release_tag = version
+ return f'{sdk}releases-{version}-64bit'
- return name
+ return name
def main(args): # ruff: ignore[complex-structure, too-many-return-statements, too-many-branches]
- if not args:
- errlog("Missing command; Type 'emsdk help' to get a list of commands.")
- return 1
-
- debug_print(f'emsdk.py running under `{sys.executable}`')
- cmd = args.pop(0)
-
- if cmd in {'help', '--help', '-h'}:
- print(' emsdk: Available commands:')
-
- print('''
- emsdk list [--old] [--deps] - Lists all available SDKs and tools and their
- current installation status. With the --old
- parameter, also historical versions are
- shown. If --deps is passed, displays the
- composition of different SDK packages and
- dependencies.
-
- emsdk update - Updates emsdk to the newest version. If you have
- bootstrapped emsdk via cloning directly from
- GitHub, call "git pull" instead to update emsdk.
-
- emsdk install [options] <tool 1> <tool 2> <tool 3> ...
- - Downloads and installs given tools or SDKs.
- Options can contain:
-
- -j<num>: Specifies the number of cores to use when
- building the tool. Default: use one less
- than the # of detected cores.
-
- --build=<type>: Controls what kind of build of LLVM to
- perform. Pass either 'Debug', 'Release',
- 'MinSizeRel' or 'RelWithDebInfo'. Default:
- 'Release'.
-
- --generator=<type>: Specifies the CMake Generator to be used
- during the build. Possible values are the
- same as what your CMake supports and whether
- the generator is valid depends on the tools
- you have installed. Defaults to 'Unix Makefiles'
- on *nix systems. If generator name is multiple
- words, enclose with single or double quotes.
-
- --shallow: When installing tools from one of the git
- development branches, this parameter can be
- passed to perform a shallow git clone instead
- of a full one. This reduces the amount of
- network transfer that is needed. This option
- should only be used when you are interested in
- downloading one of the development branches,
- but are not looking to develop Emscripten
- yourself. Default: disabled, i.e. do a full
- clone.
-
- --build-tests: If enabled, LLVM is built with internal tests
- included. Pass this to enable running test
- other.test_llvm_lit in the Emscripten test
- suite. Default: disabled.
- --enable-assertions: If specified, LLVM is built with assert()
- checks enabled. Useful for development
- purposes. Default: Enabled
- --disable-assertions: Forces assertions off during the build.
-
- --vs2019/--vs2022: If building from source, overrides to build
- using the specified compiler. When installing
- precompiled packages, this has no effect.
- Note: The same compiler specifier must be
- passed to the emsdk activate command to
- activate the desired version.
-
- Notes on building from source:
-
- To pass custom CMake directives when configuring
- LLVM build, specify the environment variable
- LLVM_CMAKE_ARGS="param1=value1,param2=value2"
- in the environment where the build is invoked.
- See README.md for details.
-
- --override-repository: Specifies the git URL to use for a given Tool. E.g.
- --override-repository emscripten-main@https://github.com/<fork>/emscripten/tree/<refspec>
-
-
- emsdk deactivate tool/sdk - Removes the given tool or SDK from the current set of activated tools.
-
-
- emsdk uninstall <tool/sdk> - Removes the given tool or SDK from disk.''')
-
- if WINDOWS:
- print('''
- emsdk activate [--permanent] [--system] [--build=type] [--vs2019/--vs2022] <tool/sdk>
-
- - Activates the given tool or SDK in the
- environment of the current shell.
-
- - If the `--permanent` option is passed, then the environment
- variables are set permanently for the current user.
-
- - If the `--system` option is passed, the registration
- is done for all users of the system.
- This needs admin privileges
- (uses Machine environment variables).
-
- - If a custom compiler version was used to override
- the compiler to use, pass the same --vs2019/--vs2022
- parameter here to choose which version to activate.
-
- emcmdprompt.bat - Spawns a new command prompt window with the
- Emscripten environment active.''')
- else:
- print(''' emsdk activate [--build=type] <tool/sdk>
-
- - Activates the given tool or SDK in the
- environment of the current shell.''')
-
- print('''
- Both commands 'install' and 'activate' accept an optional parameter
- '--build=type', which can be used to override what kind of installation
- or activation to perform. Possible values for type are Debug, Release,
- MinSizeRel or RelWithDebInfo. Note: When overriding a custom build type,
- be sure to match the same --build= option to both 'install' and
- 'activate' commands and the invocation of 'emsdk_env', or otherwise
- these commands will default to operating on the default build type
- which is RelWithDebInfo.''')
-
- print('''
-
- Environment:
- EMSDK_KEEP_DOWNLOADS=1 - if you want to keep the downloaded archives.
- EMSDK_NOTTY=1 - override isatty() result (mainly to log progress).
- EMSDK_NUM_CORES=n - limit parallelism to n cores.
- EMSDK_VERBOSE=1 - very verbose output, useful for debugging.
- EMSDK_RETRY_CLEAN_BUILD=1 - performs a clean rebuild of compiled tools if
- incremental build fails. Useful on CI.''')
- return 0
-
- def extract_bool_arg(name):
- """Extracts a boolean command line argument from args and returns True if it was present"""
- if name in args:
- args.remove(name)
- return True
- return False
-
- def extract_string_arg(name):
- for i in range(len(args)):
- if args[i] == name:
- value = args[i + 1]
- del args[i:i + 2]
- return value
-
- arg_old = extract_bool_arg('--old')
- arg_deps = extract_bool_arg('--deps')
- arg_permanent = extract_bool_arg('--permanent')
- arg_global = extract_bool_arg('--global')
- arg_system = extract_bool_arg('--system')
- if arg_global:
- print('--global is deprecated. Use `--system` to set the environment variables for all users')
- arg_system = True
- if arg_system:
- arg_permanent = True
- if extract_bool_arg('--embedded'):
- errlog('embedded mode is now the only mode available')
- if extract_bool_arg('--no-embedded'):
- errlog('embedded mode is now the only mode available')
- return 1
-
- arg_notty = extract_bool_arg('--notty')
- if arg_notty:
- global TTY_OUTPUT
- TTY_OUTPUT = False
-
- # Replace meta-packages with the real package names.
- if cmd in {'update', 'install', 'activate'}:
- activating = cmd == 'activate'
- args = [expand_sdk_name(a, activating=activating) for a in args]
-
- load_em_config()
- load_sdk_manifest()
-
- # Apply any overrides to git branch names to clone from.
- forked_url = extract_string_arg('--override-repository')
- while forked_url:
- tool_name, url_and_refspec = forked_url.split('@')
- t = find_tool(tool_name)
- if not t:
- errlog(f'Failed to find tool {tool_name}')
- return False
- else:
- t.url, t.git_branch, t.remote_name = parse_github_url_and_refspec(url_and_refspec)
- debug_print(f'Reading git repository URL "{t.url}" and git branch "{t.git_branch}" for Tool "{tool_name}".')
-
- forked_url = extract_string_arg('--override-repository')
-
- # Process global args
- for i in range(len(args)):
- if args[i].startswith('--generator='):
- build_generator = re.match(r'''^--generator=['"]?([^'"]+)['"]?$''', args[i])
- if build_generator:
- global CMAKE_GENERATOR
- CMAKE_GENERATOR = build_generator.group(1)
- args[i] = ''
- else:
- errlog(f"Cannot parse CMake generator string: {args[i]}. Try wrapping generator string with quotes")
+ if not args:
+ errlog("Missing command; Type 'emsdk help' to get a list of commands.")
return 1
- elif args[i].startswith('--build='):
- build_type = re.match(r'^--build=(.+)$', args[i])
- if build_type:
- global CMAKE_BUILD_TYPE_OVERRIDE
- build_type = build_type.group(1)
- build_types = ['Debug', 'MinSizeRel', 'RelWithDebInfo', 'Release']
- try:
- build_type_index = [x.lower() for x in build_types].index(build_type.lower())
- CMAKE_BUILD_TYPE_OVERRIDE = build_types[build_type_index]
- args[i] = ''
- except Exception:
- errlog(f'Unknown CMake build type "{build_type}" specified! Please specify one of {build_types}')
- return 1
- else:
- errlog(f'Invalid command line parameter {args[i]} specified!')
- return 1
- args = [x for x in args if x]
- if cmd == 'list':
- print('')
+ debug_print(f'emsdk.py running under `{sys.executable}`')
+ cmd = args.pop(0)
- def installed_sdk_text(name):
- sdk = find_sdk(name)
- return 'INSTALLED' if sdk and sdk.is_installed() else ''
+ if cmd in {'help', '--help', '-h'}:
+ print(' emsdk: Available commands:')
- if (LINUX or MACOS or WINDOWS) and ARCH in {'x86', 'x86_64'}:
- print(f'The *recommended* precompiled SDK download is {find_latest_version()} ({find_latest_hash()}).')
- print()
- print('To install/activate it use:')
- print(' latest')
- print('')
- print('This is equivalent to installing/activating:')
- print(f' {find_latest_version()} {installed_sdk_text(find_latest_sdk())}')
- print('')
- else:
- print('Warning: your platform does not have precompiled SDKs available.')
- print('You may install components from source.')
- print('')
+ print('''
+ emsdk list [--old] [--deps] - Lists all available SDKs and tools and their
+ current installation status. With the --old
+ parameter, also historical versions are
+ shown. If --deps is passed, displays the
+ composition of different SDK packages and
+ dependencies.
- print('All recent (non-legacy) installable versions are:')
- releases_versions = sorted(load_releases_versions(), key=version_key, reverse=True)
- releases_info = load_releases_info()['releases']
- for ver in releases_versions:
- sdk_name = f'sdk-releases-{get_release_hash(ver, releases_info)}-64bit'
- print(f' {ver} {installed_sdk_text(sdk_name)}')
- print()
+ emsdk update - Updates emsdk to the newest version. If you have
+ bootstrapped emsdk via cloning directly from
+ GitHub, call "git pull" instead to update emsdk.
- # Use array to work around the lack of being able to mutate from enclosing
- # function.
- has_partially_active_tools = [False]
+ emsdk install [options] <tool 1> <tool 2> <tool 3> ...
+ - Downloads and installs given tools or SDKs.
+ Options can contain:
- if sdks:
- def find_sdks(needs_compilation):
- s = []
- for sdk in sdks:
- if sdk.is_old and not arg_old:
- continue
- if sdk.needs_compilation() == needs_compilation:
- s += [sdk]
- return s
+ -j<num>: Specifies the number of cores to use when
+ building the tool. Default: use one less
+ than the # of detected cores.
- def print_sdks(s):
- for sdk in s:
- installed = '\tINSTALLED' if sdk.is_installed() else ''
- active = '*' if sdk.is_active() else ' '
- print(f' {active} {sdk!s: <25}{installed}')
- if arg_deps:
- for dep in sdk.deps:
- print(f' - {dep: <25}')
- print('')
- print('The additional following precompiled SDKs are also available for download:')
- print_sdks(find_sdks(False))
+ --build=<type>: Controls what kind of build of LLVM to
+ perform. Pass either 'Debug', 'Release',
+ 'MinSizeRel' or 'RelWithDebInfo'. Default:
+ 'Release'.
- print('The following SDKs can be compiled from source:')
- print_sdks(find_sdks(True))
+ --generator=<type>: Specifies the CMake Generator to be used
+ during the build. Possible values are the
+ same as what your CMake supports and whether
+ the generator is valid depends on the tools
+ you have installed. Defaults to 'Unix Makefiles'
+ on *nix systems. If generator name is multiple
+ words, enclose with single or double quotes.
- if tools:
- def find_tools(needs_compilation):
- t = []
- for tool in tools:
- if tool.is_old and not arg_old:
- continue
- if tool.needs_compilation() != needs_compilation:
- continue
- t += [tool]
- return t
+ --shallow: When installing tools from one of the git
+ development branches, this parameter can be
+ passed to perform a shallow git clone instead
+ of a full one. This reduces the amount of
+ network transfer that is needed. This option
+ should only be used when you are interested in
+ downloading one of the development branches,
+ but are not looking to develop Emscripten
+ yourself. Default: disabled, i.e. do a full
+ clone.
- def print_tools(t):
- for tool in t:
- if tool.is_old and not arg_old:
- continue
- if tool.can_be_installed() is True:
- installed = '\tINSTALLED' if tool.is_installed() else ''
- else:
- installed = '\tNot available: ' + tool.can_be_installed()
- tool_is_active = tool.is_active()
- tool_is_env_active = tool_is_active and tool.is_env_active()
- if tool_is_env_active:
- active = ' * '
- elif tool_is_active:
- active = '(*)'
- has_partially_active_tools[0] = has_partially_active_tools[0] or True
- else:
- active = ' '
- print(f' {active} {tool!s: <25}{installed}')
- print('')
+ --build-tests: If enabled, LLVM is built with internal tests
+ included. Pass this to enable running test
+ other.test_llvm_lit in the Emscripten test
+ suite. Default: disabled.
+ --enable-assertions: If specified, LLVM is built with assert()
+ checks enabled. Useful for development
+ purposes. Default: Enabled
+ --disable-assertions: Forces assertions off during the build.
- print('The following precompiled tool packages are available for download:')
- print_tools(find_tools(needs_compilation=False))
- print('The following tools can be compiled from source:')
- print_tools(find_tools(needs_compilation=True))
- else:
- if is_emsdk_sourced_from_github():
- print("There are no tools available. Run 'git pull' to fetch the latest set of tools.")
- else:
- print("There are no tools available. Run 'emsdk update' to fetch the latest set of tools.")
- print('')
+ --vs2019/--vs2022: If building from source, overrides to build
+ using the specified compiler. When installing
+ precompiled packages, this has no effect.
+ Note: The same compiler specifier must be
+ passed to the emsdk activate command to
+ activate the desired version.
- print('Items marked with * are activated for the current user.')
- if has_partially_active_tools[0]:
- env_cmd = 'emsdk_env.bat' if WINDOWS else 'source ./emsdk_env.sh'
- print('Items marked with (*) are selected for use, '
- 'but your current shell environment is not configured to use them. '
- f'Type "{env_cmd}" to set up your current shell to use them' +
- (', or call "emsdk activate --permanent <name_of_sdk>" to permanently activate them.' if WINDOWS else '.'))
- if not arg_old:
- print('')
- print("To access the historical archived versions, type 'emsdk list --old'")
+ Notes on building from source:
- print('')
- if is_emsdk_sourced_from_github():
- print('Run "git pull" to pull in the latest list.')
- else:
- print('Run "./emsdk update" to pull in the latest list.')
+ To pass custom CMake directives when configuring
+ LLVM build, specify the environment variable
+ LLVM_CMAKE_ARGS="param1=value1,param2=value2"
+ in the environment where the build is invoked.
+ See README.md for details.
- return 0
- elif cmd == 'construct_env':
- # Clean up old temp file up front, in case of failure later before we get
- # to write out the new one.
- tools_to_activate = currently_active_tools()
- tools_to_activate = process_tool_list(tools_to_activate)
- env_string = construct_env(tools_to_activate, arg_system, arg_permanent)
- if CMD or POWERSHELL:
- write_set_env_script(env_string)
- else:
- sys.stdout.write(env_string)
- return 0
- elif cmd == 'update':
- update_emsdk()
- if WINDOWS:
- # Clean up litter after old emsdk update which may have left this temp
- # file around.
- rmfile(sdk_path(EMSDK_SET_ENV))
- return 0
- elif cmd == 'update-tags':
- errlog('`update-tags` is not longer needed. To install the latest tot release just run `install tot`')
- return 0
- elif cmd in {'activate', 'deactivate'}:
- if arg_permanent:
- print('Registering active Emscripten environment permanently')
- print('')
+ --override-repository: Specifies the git URL to use for a given Tool. E.g.
+ --override-repository emscripten-main@https://github.com/<fork>/emscripten/tree/<refspec>
- tools_to_activate = currently_active_tools()
- for arg in args:
- tool = find_tool(arg)
- if tool is None:
- tool = find_sdk(arg)
- if tool is None:
- error_on_missing_tool(arg)
- if cmd == 'activate':
- tools_to_activate += [tool]
- elif tool in tools_to_activate:
- print(f'Deactivating tool {tool}.')
- tools_to_activate.remove(tool)
- else:
- print(f'Tool "{arg}" was not active, no need to deactivate.')
- if not tools_to_activate:
- errlog('No tools/SDKs specified to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]')
- return 1
- active_tools = set_active_tools(tools_to_activate, permanently_activate=arg_permanent, system=arg_system)
- if not active_tools:
- errlog('No tools/SDKs found to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]')
- return 1
- if WINDOWS and not arg_permanent:
- errlog('The changes made to environment variables only apply to the currently running shell instance. '
- 'Use the \'emsdk_env.bat\' to re-enter this environment later, '
- 'or if you\'d like to register this environment permanently, rerun this command with the option --permanent.')
- return 0
- elif cmd == 'install':
- global BUILD_FOR_TESTING, ENABLE_LLVM_ASSERTIONS, CPU_CORES, GIT_CLONE_SHALLOW
+ emsdk deactivate tool/sdk - Removes the given tool or SDK from the current set of activated tools.
- # Process args
- for i in range(len(args)):
- if args[i].startswith('-j'):
- multicore = re.match(r'^-j(\d+)$', args[i])
- if multicore:
- CPU_CORES = int(multicore.group(1))
- args[i] = ''
+
+ emsdk uninstall <tool/sdk> - Removes the given tool or SDK from disk.''')
+
+ if WINDOWS:
+ print('''
+ emsdk activate [--permanent] [--system] [--build=type] [--vs2019/--vs2022] <tool/sdk>
+
+ - Activates the given tool or SDK in the
+ environment of the current shell.
+
+ - If the `--permanent` option is passed, then the environment
+ variables are set permanently for the current user.
+
+ - If the `--system` option is passed, the registration
+ is done for all users of the system.
+ This needs admin privileges
+ (uses Machine environment variables).
+
+ - If a custom compiler version was used to override
+ the compiler to use, pass the same --vs2019/--vs2022
+ parameter here to choose which version to activate.
+
+ emcmdprompt.bat - Spawns a new command prompt window with the
+ Emscripten environment active.''')
else:
- errlog(f'Invalid command line parameter {args[i]} specified!')
- return 1
- elif args[i] == '--shallow':
- GIT_CLONE_SHALLOW = True
- args[i] = ''
- elif args[i] == '--build-tests':
- BUILD_FOR_TESTING = True
- args[i] = ''
- elif args[i] == '--enable-assertions':
- ENABLE_LLVM_ASSERTIONS = 'ON'
- args[i] = ''
- elif args[i] == '--disable-assertions':
- ENABLE_LLVM_ASSERTIONS = 'OFF'
- args[i] = ''
+ print(''' emsdk activate [--build=type] <tool/sdk>
+
+ - Activates the given tool or SDK in the
+ environment of the current shell.''')
+
+ print('''
+ Both commands 'install' and 'activate' accept an optional parameter
+ '--build=type', which can be used to override what kind of installation
+ or activation to perform. Possible values for type are Debug, Release,
+ MinSizeRel or RelWithDebInfo. Note: When overriding a custom build type,
+ be sure to match the same --build= option to both 'install' and
+ 'activate' commands and the invocation of 'emsdk_env', or otherwise
+ these commands will default to operating on the default build type
+ which is RelWithDebInfo.''')
+
+ print('''
+
+ Environment:
+ EMSDK_KEEP_DOWNLOADS=1 - if you want to keep the downloaded archives.
+ EMSDK_NOTTY=1 - override isatty() result (mainly to log progress).
+ EMSDK_NUM_CORES=n - limit parallelism to n cores.
+ EMSDK_VERBOSE=1 - very verbose output, useful for debugging.
+ EMSDK_RETRY_CLEAN_BUILD=1 - performs a clean rebuild of compiled tools if
+ incremental build fails. Useful on CI.''')
+ return 0
+
+ def extract_bool_arg(name):
+ """Extracts a boolean command line argument from args and returns True if it was present"""
+ if name in args:
+ args.remove(name)
+ return True
+ return False
+
+ def extract_string_arg(name):
+ for i in range(len(args)):
+ if args[i] == name:
+ value = args[i + 1]
+ del args[i:i + 2]
+ return value
+
+ arg_old = extract_bool_arg('--old')
+ arg_deps = extract_bool_arg('--deps')
+ arg_permanent = extract_bool_arg('--permanent')
+ arg_global = extract_bool_arg('--global')
+ arg_system = extract_bool_arg('--system')
+ if arg_global:
+ print('--global is deprecated. Use `--system` to set the environment variables for all users')
+ arg_system = True
+ if arg_system:
+ arg_permanent = True
+ if extract_bool_arg('--embedded'):
+ errlog('embedded mode is now the only mode available')
+ if extract_bool_arg('--no-embedded'):
+ errlog('embedded mode is now the only mode available')
+ return 1
+
+ arg_notty = extract_bool_arg('--notty')
+ if arg_notty:
+ global TTY_OUTPUT
+ TTY_OUTPUT = False
+
+ # Replace meta-packages with the real package names.
+ if cmd in {'update', 'install', 'activate'}:
+ activating = cmd == 'activate'
+ args = [expand_sdk_name(a, activating=activating) for a in args]
+
+ load_em_config()
+ load_sdk_manifest()
+
+ # Apply any overrides to git branch names to clone from.
+ forked_url = extract_string_arg('--override-repository')
+ while forked_url:
+ tool_name, url_and_refspec = forked_url.split('@')
+ t = find_tool(tool_name)
+ if not t:
+ errlog(f'Failed to find tool {tool_name}')
+ return False
+ else:
+ t.url, t.git_branch, t.remote_name = parse_github_url_and_refspec(url_and_refspec)
+ debug_print(f'Reading git repository URL "{t.url}" and git branch "{t.git_branch}" for Tool "{tool_name}".')
+
+ forked_url = extract_string_arg('--override-repository')
+
+ # Process global args
+ for i in range(len(args)):
+ if args[i].startswith('--generator='):
+ build_generator = re.match(r'''^--generator=['"]?([^'"]+)['"]?$''', args[i])
+ if build_generator:
+ global CMAKE_GENERATOR
+ CMAKE_GENERATOR = build_generator.group(1)
+ args[i] = ''
+ else:
+ errlog(f"Cannot parse CMake generator string: {args[i]}. Try wrapping generator string with quotes")
+ return 1
+ elif args[i].startswith('--build='):
+ build_type = re.match(r'^--build=(.+)$', args[i])
+ if build_type:
+ global CMAKE_BUILD_TYPE_OVERRIDE
+ build_type = build_type.group(1)
+ build_types = ['Debug', 'MinSizeRel', 'RelWithDebInfo', 'Release']
+ try:
+ build_type_index = [x.lower() for x in build_types].index(build_type.lower())
+ CMAKE_BUILD_TYPE_OVERRIDE = build_types[build_type_index]
+ args[i] = ''
+ except Exception:
+ errlog(f'Unknown CMake build type "{build_type}" specified! Please specify one of {build_types}')
+ return 1
+ else:
+ errlog(f'Invalid command line parameter {args[i]} specified!')
+ return 1
args = [x for x in args if x]
- if not args:
- errlog("Missing parameter. Type 'emsdk install <tool name>' to install a tool or an SDK. "
- "Type 'emsdk list' to obtain a list of available tools. "
- "Type 'emsdk install latest' to automatically install the newest version of the SDK.")
- return 1
- if LINUX and ARCH == 'arm64' and args != ['latest']:
- errlog('WARNING: arm64-linux binaries are not available for all releases.')
- errlog('See https://github.com/emscripten-core/emsdk/issues/547')
+ if cmd == 'list':
+ print('')
- for t in args:
- tool = find_tool(t)
- if tool is None:
- tool = find_sdk(t)
- if tool is None:
- error_on_missing_tool(t)
- tool.install()
- return 0
- elif cmd == 'uninstall':
- if not args:
- errlog("Syntax error. Call 'emsdk uninstall <tool name>'. Call 'emsdk list' to obtain a list of available tools.")
- return 1
- tool = find_tool(args[0])
- if tool is None:
- errlog(f"Error: Tool by name '{args[0]}' was not found.")
- return 1
- tool.uninstall()
- return 0
+ def installed_sdk_text(name):
+ sdk = find_sdk(name)
+ return 'INSTALLED' if sdk and sdk.is_installed() else ''
- errlog(f"Unknown command '{cmd}' given! Type 'emsdk help' to get a list of commands.")
- return 1
+ if (LINUX or MACOS or WINDOWS) and ARCH in {'x86', 'x86_64'}:
+ print(f'The *recommended* precompiled SDK download is {find_latest_version()} ({find_latest_hash()}).')
+ print()
+ print('To install/activate it use:')
+ print(' latest')
+ print('')
+ print('This is equivalent to installing/activating:')
+ print(f' {find_latest_version()} {installed_sdk_text(find_latest_sdk())}')
+ print('')
+ else:
+ print('Warning: your platform does not have precompiled SDKs available.')
+ print('You may install components from source.')
+ print('')
+
+ print('All recent (non-legacy) installable versions are:')
+ releases_versions = sorted(load_releases_versions(), key=version_key, reverse=True)
+ releases_info = load_releases_info()['releases']
+ for ver in releases_versions:
+ sdk_name = f'sdk-releases-{get_release_hash(ver, releases_info)}-64bit'
+ print(f' {ver} {installed_sdk_text(sdk_name)}')
+ print()
+
+ # Use array to work around the lack of being able to mutate from enclosing
+ # function.
+ has_partially_active_tools = [False]
+
+ if sdks:
+ def find_sdks(needs_compilation):
+ s = []
+ for sdk in sdks:
+ if sdk.is_old and not arg_old:
+ continue
+ if sdk.needs_compilation() == needs_compilation:
+ s += [sdk]
+ return s
+
+ def print_sdks(s):
+ for sdk in s:
+ installed = '\tINSTALLED' if sdk.is_installed() else ''
+ active = '*' if sdk.is_active() else ' '
+ print(f' {active} {sdk!s: <25}{installed}')
+ if arg_deps:
+ for dep in sdk.deps:
+ print(f' - {dep: <25}')
+ print('')
+ print('The additional following precompiled SDKs are also available for download:')
+ print_sdks(find_sdks(False))
+
+ print('The following SDKs can be compiled from source:')
+ print_sdks(find_sdks(True))
+
+ if tools:
+ def find_tools(needs_compilation):
+ t = []
+ for tool in tools:
+ if tool.is_old and not arg_old:
+ continue
+ if tool.needs_compilation() != needs_compilation:
+ continue
+ t += [tool]
+ return t
+
+ def print_tools(t):
+ for tool in t:
+ if tool.is_old and not arg_old:
+ continue
+ if tool.can_be_installed() is True:
+ installed = '\tINSTALLED' if tool.is_installed() else ''
+ else:
+ installed = '\tNot available: ' + tool.can_be_installed()
+ tool_is_active = tool.is_active()
+ tool_is_env_active = tool_is_active and tool.is_env_active()
+ if tool_is_env_active:
+ active = ' * '
+ elif tool_is_active:
+ active = '(*)'
+ has_partially_active_tools[0] = has_partially_active_tools[0] or True
+ else:
+ active = ' '
+ print(f' {active} {tool!s: <25}{installed}')
+ print('')
+
+ print('The following precompiled tool packages are available for download:')
+ print_tools(find_tools(needs_compilation=False))
+ print('The following tools can be compiled from source:')
+ print_tools(find_tools(needs_compilation=True))
+ else:
+ if is_emsdk_sourced_from_github():
+ print("There are no tools available. Run 'git pull' to fetch the latest set of tools.")
+ else:
+ print("There are no tools available. Run 'emsdk update' to fetch the latest set of tools.")
+ print('')
+
+ print('Items marked with * are activated for the current user.')
+ if has_partially_active_tools[0]:
+ env_cmd = 'emsdk_env.bat' if WINDOWS else 'source ./emsdk_env.sh'
+ print('Items marked with (*) are selected for use, '
+ 'but your current shell environment is not configured to use them. '
+ f'Type "{env_cmd}" to set up your current shell to use them' +
+ (', or call "emsdk activate --permanent <name_of_sdk>" to permanently activate them.' if WINDOWS else '.'))
+ if not arg_old:
+ print('')
+ print("To access the historical archived versions, type 'emsdk list --old'")
+
+ print('')
+ if is_emsdk_sourced_from_github():
+ print('Run "git pull" to pull in the latest list.')
+ else:
+ print('Run "./emsdk update" to pull in the latest list.')
+
+ return 0
+ elif cmd == 'construct_env':
+ # Clean up old temp file up front, in case of failure later before we get
+ # to write out the new one.
+ tools_to_activate = currently_active_tools()
+ tools_to_activate = process_tool_list(tools_to_activate)
+ env_string = construct_env(tools_to_activate, arg_system, arg_permanent)
+ if CMD or POWERSHELL:
+ write_set_env_script(env_string)
+ else:
+ sys.stdout.write(env_string)
+ return 0
+ elif cmd == 'update':
+ update_emsdk()
+ if WINDOWS:
+ # Clean up litter after old emsdk update which may have left this temp
+ # file around.
+ rmfile(sdk_path(EMSDK_SET_ENV))
+ return 0
+ elif cmd == 'update-tags':
+ errlog('`update-tags` is not longer needed. To install the latest tot release just run `install tot`')
+ return 0
+ elif cmd in {'activate', 'deactivate'}:
+ if arg_permanent:
+ print('Registering active Emscripten environment permanently')
+ print('')
+
+ tools_to_activate = currently_active_tools()
+ for arg in args:
+ tool = find_tool(arg)
+ if tool is None:
+ tool = find_sdk(arg)
+ if tool is None:
+ error_on_missing_tool(arg)
+
+ if cmd == 'activate':
+ tools_to_activate += [tool]
+ elif tool in tools_to_activate:
+ print(f'Deactivating tool {tool}.')
+ tools_to_activate.remove(tool)
+ else:
+ print(f'Tool "{arg}" was not active, no need to deactivate.')
+ if not tools_to_activate:
+ errlog('No tools/SDKs specified to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]')
+ return 1
+ active_tools = set_active_tools(tools_to_activate, permanently_activate=arg_permanent, system=arg_system)
+ if not active_tools:
+ errlog('No tools/SDKs found to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]')
+ return 1
+ if WINDOWS and not arg_permanent:
+ errlog('The changes made to environment variables only apply to the currently running shell instance. '
+ 'Use the \'emsdk_env.bat\' to re-enter this environment later, '
+ 'or if you\'d like to register this environment permanently, rerun this command with the option --permanent.')
+ return 0
+ elif cmd == 'install':
+ global BUILD_FOR_TESTING, ENABLE_LLVM_ASSERTIONS, CPU_CORES, GIT_CLONE_SHALLOW
+
+ # Process args
+ for i in range(len(args)):
+ if args[i].startswith('-j'):
+ multicore = re.match(r'^-j(\d+)$', args[i])
+ if multicore:
+ CPU_CORES = int(multicore.group(1))
+ args[i] = ''
+ else:
+ errlog(f'Invalid command line parameter {args[i]} specified!')
+ return 1
+ elif args[i] == '--shallow':
+ GIT_CLONE_SHALLOW = True
+ args[i] = ''
+ elif args[i] == '--build-tests':
+ BUILD_FOR_TESTING = True
+ args[i] = ''
+ elif args[i] == '--enable-assertions':
+ ENABLE_LLVM_ASSERTIONS = 'ON'
+ args[i] = ''
+ elif args[i] == '--disable-assertions':
+ ENABLE_LLVM_ASSERTIONS = 'OFF'
+ args[i] = ''
+ args = [x for x in args if x]
+ if not args:
+ errlog("Missing parameter. Type 'emsdk install <tool name>' to install a tool or an SDK. "
+ "Type 'emsdk list' to obtain a list of available tools. "
+ "Type 'emsdk install latest' to automatically install the newest version of the SDK.")
+ return 1
+
+ if LINUX and ARCH == 'arm64' and args != ['latest']:
+ errlog('WARNING: arm64-linux binaries are not available for all releases.')
+ errlog('See https://github.com/emscripten-core/emsdk/issues/547')
+
+ for t in args:
+ tool = find_tool(t)
+ if tool is None:
+ tool = find_sdk(t)
+ if tool is None:
+ error_on_missing_tool(t)
+ tool.install()
+ return 0
+ elif cmd == 'uninstall':
+ if not args:
+ errlog("Syntax error. Call 'emsdk uninstall <tool name>'. Call 'emsdk list' to obtain a list of available tools.")
+ return 1
+ tool = find_tool(args[0])
+ if tool is None:
+ errlog(f"Error: Tool by name '{args[0]}' was not found.")
+ return 1
+ tool.uninstall()
+ return 0
+
+ errlog(f"Unknown command '{cmd}' given! Type 'emsdk help' to get a list of commands.")
+ return 1
if __name__ == '__main__':
- try:
- sys.exit(main(sys.argv[1:]))
- except KeyboardInterrupt:
- exit_with_error('aborted by user, exiting')
- sys.exit(1)
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except KeyboardInterrupt:
+ exit_with_error('aborted by user, exiting')
+ sys.exit(1)
diff --git a/pyproject.toml b/pyproject.toml
index 7ae8857..6891933 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,6 @@
[tool.ruff]
preview = true
line-length = 160
-indent-width = 2
lint.select = [
"ARG",
@@ -29,8 +28,6 @@
lint.ignore = [
"assert-false", # See https://github.com/PyCQA/flake8-bugbear/issues/66
"module-import-not-at-top-of-file",
- "indentation-with-invalid-multiple", # Does not seem to honor `indent-width = 2` above
- "indentation-with-invalid-multiple-comment", # Does not seem to honor `indent-width = 2` above
"try-except-in-loop",
"import-outside-top-level",
"too-many-public-methods",
diff --git a/scripts/create_release.py b/scripts/create_release.py
index 03af2ee..81ffa8a 100755
--- a/scripts/create_release.py
+++ b/scripts/create_release.py
@@ -16,77 +16,77 @@
def version_key(version_string):
- parts = re.split('[.-]', version_string)
- key = [[int(part) for part in parts[:3]], -len(parts), parts[3:]]
- return key
+ parts = re.split('[.-]', version_string)
+ key = [[int(part) for part in parts[:3]], -len(parts), parts[3:]]
+ return key
def main():
- if subprocess.check_output(['git', 'status', '--porcelain'], cwd=root_dir).strip():
- print('tree is not clean')
- sys.exit(1)
+ if subprocess.check_output(['git', 'status', '--porcelain'], cwd=root_dir).strip():
+ print('tree is not clean')
+ sys.exit(1)
- parser = argparse.ArgumentParser()
- parser.add_argument('-r', '--release-hash')
- parser.add_argument('-a', '--asserts-hash')
- parser.add_argument('-v', '--new-version')
- parser.add_argument('--gh-action', action='store_true')
- options = parser.parse_args()
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-r', '--release-hash')
+ parser.add_argument('-a', '--asserts-hash')
+ parser.add_argument('-v', '--new-version')
+ parser.add_argument('--gh-action', action='store_true')
+ options = parser.parse_args()
- release_info = emsdk.load_releases_info()
- if options.new_version:
- new_version = options.new_version
- else:
- new_version = version_key(release_info['aliases']['latest'])[0]
- new_version[-1] += 1
- new_version = '.'.join(str(part) for part in new_version)
+ release_info = emsdk.load_releases_info()
+ if options.new_version:
+ new_version = options.new_version
+ else:
+ new_version = version_key(release_info['aliases']['latest'])[0]
+ new_version[-1] += 1
+ new_version = '.'.join(str(part) for part in new_version)
- asserts_hash = None
- if options.release_hash:
- new_hash = options.release_hash
- asserts_hash = options.asserts_hash
- else:
- new_hash = emsdk.get_emscripten_releases_tot()
+ asserts_hash = None
+ if options.release_hash:
+ new_hash = options.release_hash
+ asserts_hash = options.asserts_hash
+ else:
+ new_hash = emsdk.get_emscripten_releases_tot()
- print(f'Creating new release: {new_version} -> {new_hash}')
- release_info['releases'][new_version] = new_hash
- if asserts_hash:
- asserts_name = new_version + '-asserts'
- release_info['releases'][asserts_name] = asserts_hash
+ print(f'Creating new release: {new_version} -> {new_hash}')
+ release_info['releases'][new_version] = new_hash
+ if asserts_hash:
+ asserts_name = new_version + '-asserts'
+ release_info['releases'][asserts_name] = asserts_hash
- releases = [(k, v) for k, v in release_info['releases'].items()]
- releases.sort(key=lambda pair: version_key(pair[0]))
+ releases = [(k, v) for k, v in release_info['releases'].items()]
+ releases.sort(key=lambda pair: version_key(pair[0]))
- release_info['releases'] = OrderedDict(reversed(releases))
- release_info['aliases']['latest'] = new_version
+ release_info['releases'] = OrderedDict(reversed(releases))
+ release_info['aliases']['latest'] = new_version
- with open(os.path.join(root_dir, 'emscripten-releases-tags.json'), 'w') as f:
- f.write(json.dumps(release_info, indent=2))
- f.write('\n')
+ with open(os.path.join(root_dir, 'emscripten-releases-tags.json'), 'w') as f:
+ f.write(json.dumps(release_info, indent=2))
+ f.write('\n')
- subprocess.check_call(
- [sys.executable, os.path.join(script_dir, 'update_bazel_workspace.py')],
- cwd=root_dir)
+ subprocess.check_call(
+ [sys.executable, os.path.join(script_dir, 'update_bazel_workspace.py')],
+ cwd=root_dir)
- branch_name = 'version_' + new_version
+ branch_name = 'version_' + new_version
- if options.gh_action: # For GitHub Actions workflows
- with open(os.environ['GITHUB_ENV'], 'a') as f:
- f.write(f'RELEASE_VERSION={new_version}')
- else: # Local use
- # Create a new git branch
- subprocess.check_call(['git', 'checkout', '-b', branch_name, 'origin/main'], cwd=root_dir)
+ if options.gh_action: # For GitHub Actions workflows
+ with open(os.environ['GITHUB_ENV'], 'a') as f:
+ f.write(f'RELEASE_VERSION={new_version}')
+ else: # Local use
+ # Create a new git branch
+ subprocess.check_call(['git', 'checkout', '-b', branch_name, 'origin/main'], cwd=root_dir)
- # Create auto-generated changes to the new git branch
- subprocess.check_call(['git', 'add', '-u', '.'], cwd=root_dir)
- subprocess.check_call(['git', 'commit', '-m', new_version], cwd=root_dir)
- print(f'New release created in branch: `{branch_name}`')
+ # Create auto-generated changes to the new git branch
+ subprocess.check_call(['git', 'add', '-u', '.'], cwd=root_dir)
+ subprocess.check_call(['git', 'commit', '-m', new_version], cwd=root_dir)
+ print(f'New release created in branch: `{branch_name}`')
- # Push new branch to origin
- subprocess.check_call(['git', 'push', 'origin', branch_name], cwd=root_dir)
+ # Push new branch to origin
+ subprocess.check_call(['git', 'push', 'origin', branch_name], cwd=root_dir)
- return 0
+ return 0
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(main())
diff --git a/scripts/update_node.py b/scripts/update_node.py
index 2f99d02..b0d1ada 100755
--- a/scripts/update_node.py
+++ b/scripts/update_node.py
@@ -33,24 +33,24 @@
def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--upload', action='store_true', help='Upload binaries to Google Cloud Storage')
- args = parser.parse_args()
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--upload', action='store_true', help='Upload binaries to Google Cloud Storage')
+ args = parser.parse_args()
- for suffix in suffixes:
- filename = f'node-v{version}{suffix}'
- download_url = base + filename
- print('Downloading: ' + download_url)
- urllib.request.urlretrieve(download_url, filename)
+ for suffix in suffixes:
+ filename = f'node-v{version}{suffix}'
+ download_url = base + filename
+ print('Downloading: ' + download_url)
+ urllib.request.urlretrieve(download_url, filename)
- if args.upload:
- upload_url = upload_base + filename
- print('Uploading: ' + upload_url)
- cmd = ['gsutil', 'cp', '-n', filename, upload_url]
- print(' '.join(cmd))
- subprocess.check_call(cmd)
- os.remove(filename)
+ if args.upload:
+ upload_url = upload_base + filename
+ print('Uploading: ' + upload_url)
+ cmd = ['gsutil', 'cp', '-n', filename, upload_url]
+ print(' '.join(cmd))
+ subprocess.check_call(cmd)
+ os.remove(filename)
if __name__ == '__main__':
- main()
+ main()
diff --git a/scripts/update_python.py b/scripts/update_python.py
index 70bf7d1..d69c51f 100755
--- a/scripts/update_python.py
+++ b/scripts/update_python.py
@@ -84,11 +84,11 @@
shutil.rmtree('python-nuget')
if '--upload' in sys.argv:
- upload_url = upload_base + out_filename
- print('Uploading: ' + upload_url)
- cmd = ['gsutil', 'cp', '-n', out_filename, upload_url]
- print(' '.join(cmd))
- check_call(cmd)
+ upload_url = upload_base + out_filename
+ print('Uploading: ' + upload_url)
+ cmd = ['gsutil', 'cp', '-n', out_filename, upload_url]
+ print(' '.join(cmd))
+ check_call(cmd)
def build_python():
@@ -119,23 +119,23 @@
src_dir = 'cpython'
if os.path.exists(src_dir):
- check_call(['git', 'fetch'], cwd=src_dir)
+ check_call(['git', 'fetch'], cwd=src_dir)
else:
- check_call(['git', 'clone', 'https://github.com/python/cpython'])
+ check_call(['git', 'clone', 'https://github.com/python/cpython'])
check_call(['git', 'checkout', 'v' + version], cwd=src_dir)
env = os.environ
if sys.platform.startswith('darwin'):
- # Specify the min OS version we want the build to work on
- min_macos_version = '11.0'
- min_macos_version_line = '-mmacosx-version-min=' + min_macos_version
- build_flags = min_macos_version_line + ' -Werror=partial-availability'
- # Build against latest SDK, but issue an error if using any API that would not work on the min OS version
- env = env.copy()
- env['MACOSX_DEPLOYMENT_TARGET'] = min_macos_version
- configure_args = ['CFLAGS=' + build_flags, 'CXXFLAGS=' + build_flags, 'LDFLAGS=' + min_macos_version_line]
+ # Specify the min OS version we want the build to work on
+ min_macos_version = '11.0'
+ min_macos_version_line = '-mmacosx-version-min=' + min_macos_version
+ build_flags = min_macos_version_line + ' -Werror=partial-availability'
+ # Build against latest SDK, but issue an error if using any API that would not work on the min OS version
+ env = env.copy()
+ env['MACOSX_DEPLOYMENT_TARGET'] = min_macos_version
+ configure_args = ['CFLAGS=' + build_flags, 'CXXFLAGS=' + build_flags, 'LDFLAGS=' + min_macos_version_line]
else:
- configure_args = []
+ configure_args = []
check_call(['./configure', *configure_args], cwd=src_dir, env=env)
check_call(['make', '-j', str(multiprocessing.cpu_count())], cwd=src_dir, env=env)
check_call(['make', 'install', 'DESTDIR=install'], cwd=src_dir, env=env)
@@ -167,8 +167,8 @@
print(f'Created: {tarball}')
if '--upload' in sys.argv:
- print('Uploading: ' + upload_base + tarball)
- check_call(['gsutil', 'cp', '-n', tarball, upload_base + tarball])
+ print('Uploading: ' + upload_base + tarball)
+ check_call(['gsutil', 'cp', '-n', tarball, upload_base + tarball])
def main():
@@ -180,4 +180,4 @@
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(main())
diff --git a/test/test.py b/test/test.py
index 91d6e8e..5b2d601 100755
--- a/test/test.py
+++ b/test/test.py
@@ -18,98 +18,98 @@
assert os.path.exists(emconfig)
if WINDOWS:
- emsdk = 'emsdk.bat'
+ emsdk = 'emsdk.bat'
else:
- emsdk = './emsdk'
+ emsdk = './emsdk'
# Utilities
def listify(x):
- if type(x) in {list, tuple}:
- return x
- return [x]
+ if type(x) in {list, tuple}:
+ return x
+ return [x]
def copy_emsdk_to(targetdir):
- for filename in os.listdir('.'):
- if not filename.startswith('.') and not os.path.isdir(filename):
- shutil.copy2(filename, os.path.join(targetdir, filename))
+ for filename in os.listdir('.'):
+ if not filename.startswith('.') and not os.path.isdir(filename):
+ shutil.copy2(filename, os.path.join(targetdir, filename))
def check_call(cmd, **kwargs):
- if type(cmd) is not list:
- cmd = cmd.split()
- print(f'running: {cmd}')
- subprocess.run(cmd, check=True, text=True, **kwargs)
+ if type(cmd) is not list:
+ cmd = cmd.split()
+ print(f'running: {cmd}')
+ subprocess.run(cmd, check=True, text=True, **kwargs)
def checked_call_with_output(cmd, expected=None, unexpected=None, stderr=None, env=None):
- cmd = cmd.split(' ')
- print(f'running: {cmd}')
- try:
- stdout = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=stderr, check=True, text=True, env=env).stdout
- except subprocess.CalledProcessError as e:
- print(e.stderr)
- print(e.stdout)
- raise e
+ cmd = cmd.split(' ')
+ print(f'running: {cmd}')
+ try:
+ stdout = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=stderr, check=True, text=True, env=env).stdout
+ except subprocess.CalledProcessError as e:
+ print(e.stderr)
+ print(e.stdout)
+ raise e
- if expected:
- for x in listify(expected):
- assert x in stdout, 'expected output missing: ' + stdout + '\n[[[' + x + ']]]'
- if unexpected:
- for x in listify(unexpected):
- assert x not in stdout, 'unexpected output present: ' + stdout + '\n[[[' + x + ']]]'
+ if expected:
+ for x in listify(expected):
+ assert x in stdout, 'expected output missing: ' + stdout + '\n[[[' + x + ']]]'
+ if unexpected:
+ for x in listify(unexpected):
+ assert x not in stdout, 'unexpected output present: ' + stdout + '\n[[[' + x + ']]]'
def failing_call_with_output(cmd, expected, env=None):
- proc = subprocess.run(cmd.split(' '), capture_output=True, text=True, env=env)
- stdout = proc.stdout
- stderr = proc.stderr
- if WINDOWS:
- print('warning: skipping part of failing_call_with_output() due to error codes not being propagated (see #592)')
- else:
- assert proc.returncode, 'call must have failed: ' + str([stdout, '\n========\n', stderr])
- assert expected in stdout or expected in stderr, 'call did not have the expected output: {}: {}'.format(expected, str([stdout, '\n========\n', stderr]))
+ proc = subprocess.run(cmd.split(' '), capture_output=True, text=True, env=env)
+ stdout = proc.stdout
+ stderr = proc.stderr
+ if WINDOWS:
+ print('warning: skipping part of failing_call_with_output() due to error codes not being propagated (see #592)')
+ else:
+ assert proc.returncode, 'call must have failed: ' + str([stdout, '\n========\n', stderr])
+ assert expected in stdout or expected in stderr, 'call did not have the expected output: {}: {}'.format(expected, str([stdout, '\n========\n', stderr]))
def hack_emsdk(marker, replacement):
- with open('emsdk.py') as f:
- src = f.read()
- assert marker in src
- src = src.replace(marker, replacement)
- name = '__test_emsdk'
- with open(name, 'w') as f:
- f.write(src)
- return name
+ with open('emsdk.py') as f:
+ src = f.read()
+ assert marker in src
+ src = src.replace(marker, replacement)
+ name = '__test_emsdk'
+ with open(name, 'w') as f:
+ f.write(src)
+ return name
def get_longest_path_in_dir(dirname):
- longest = ''
- for root, _dirs, files in os.walk(dirname):
- for f in files:
- fullname = os.path.join(root, f)
- if len(fullname) > len(longest):
- longest = fullname
- return longest
+ longest = ''
+ for root, _dirs, files in os.walk(dirname):
+ for f in files:
+ fullname = os.path.join(root, f)
+ if len(fullname) > len(longest):
+ longest = fullname
+ return longest
def remove_file(filename):
- if os.path.exists(filename):
- os.remove(filename)
+ if os.path.exists(filename):
+ os.remove(filename)
def remove_tree(path):
- if not os.path.exists(path):
- return
- if WINDOWS:
- path = '\\\\?\\' + path
+ if not os.path.exists(path):
+ return
+ if WINDOWS:
+ path = '\\\\?\\' + path
- def remove_readonly(func, path2, _):
- os.chmod(path2, stat.S_IRWXU)
- func(path2)
+ def remove_readonly(func, path2, _):
+ os.chmod(path2, stat.S_IRWXU)
+ func(path2)
- shutil.rmtree(path, onerror=remove_readonly)
+ shutil.rmtree(path, onerror=remove_readonly)
# Set up
@@ -120,281 +120,281 @@
def do_lib_building(emcc):
- cache_building_messages = ['generating system library: ']
+ cache_building_messages = ['generating system library: ']
- def do_build(args, is_expected=None):
- unexpected = None
- expected = None
- if is_expected is True:
- expected = cache_building_messages
- elif is_expected is False:
- unexpected = cache_building_messages
- checked_call_with_output(emcc + ' hello_world.c' + args,
- expected=expected,
- unexpected=unexpected,
- stderr=subprocess.STDOUT)
+ def do_build(args, is_expected=None):
+ unexpected = None
+ expected = None
+ if is_expected is True:
+ expected = cache_building_messages
+ elif is_expected is False:
+ unexpected = cache_building_messages
+ checked_call_with_output(emcc + ' hello_world.c' + args,
+ expected=expected,
+ unexpected=unexpected,
+ stderr=subprocess.STDOUT)
- # The emsdk ships all system libraries so we don't expect to see any
- # cache population unless we explicly --clear-cache.
- do_build('', is_expected=False)
- check_call(emcc + ' --clear-cache')
- do_build(' -O2', is_expected=True)
- # Do another build at -O0. In nwers SDK versions this generates
- # different libs, but not in older ones so don't assert here.
- do_build('')
- # Now verify that libs are *not* build
- do_build(' -s WASM=0', is_expected=False)
- do_build(' -O2 -s WASM=0', is_expected=False)
+ # The emsdk ships all system libraries so we don't expect to see any
+ # cache population unless we explicly --clear-cache.
+ do_build('', is_expected=False)
+ check_call(emcc + ' --clear-cache')
+ do_build(' -O2', is_expected=True)
+ # Do another build at -O0. In nwers SDK versions this generates
+ # different libs, but not in older ones so don't assert here.
+ do_build('')
+ # Now verify that libs are *not* build
+ do_build(' -s WASM=0', is_expected=False)
+ do_build(' -O2 -s WASM=0', is_expected=False)
def run_emsdk(cmd):
- if type(cmd) is not list:
- cmd = cmd.split()
- check_call([emsdk, *cmd])
+ if type(cmd) is not list:
+ cmd = cmd.split()
+ check_call([emsdk, *cmd])
def upstream_emcc(root='.'):
- emcc = os.path.join(root, 'upstream', 'emscripten', 'emcc')
- if WINDOWS:
- if os.path.exists(emcc + '.exe'):
- return emcc + '.exe'
- else:
- return emcc + '.bat'
- return emcc
+ emcc = os.path.join(root, 'upstream', 'emscripten', 'emcc')
+ if WINDOWS:
+ if os.path.exists(emcc + '.exe'):
+ return emcc + '.exe'
+ else:
+ return emcc + '.bat'
+ return emcc
class Emsdk(unittest.TestCase):
- def setUp(self):
- run_emsdk('install latest')
- run_emsdk('activate latest')
- with open('hello_world.c', 'w') as f:
- f.write('''\
-#include <stdio.h>
+ def setUp(self):
+ run_emsdk('install latest')
+ run_emsdk('activate latest')
+ with open('hello_world.c', 'w') as f:
+ f.write('''\
+ #include <stdio.h>
-int main() {
- printf("Hello, world!\\n");
- return 0;
-}
-''')
- self.addCleanup(remove_file, 'hello_world.c')
+ int main() {
+ printf("Hello, world!\\n");
+ return 0;
+ }
+ ''')
+ self.addCleanup(remove_file, 'hello_world.c')
- def tearDown(self):
- for f in glob.glob('a.out*'):
- os.remove(f)
+ def tearDown(self):
+ for f in glob.glob('a.out*'):
+ os.remove(f)
- def test_extrememly_long_filenames(self):
- # We have special support for filenames longer than 256 on windows. This
- # test installs emsdk in path that exceeds this limit in order to test
- # this handling.
- longpath = os.path.abspath('very_long_filename_indeed')
+ def test_extrememly_long_filenames(self):
+ # We have special support for filenames longer than 256 on windows. This
+ # test installs emsdk in path that exceeds this limit in order to test
+ # this handling.
+ longpath = os.path.abspath('very_long_filename_indeed')
- additional = 140 - len(longpath)
- longpath += 'x' * additional
+ additional = 140 - len(longpath)
+ longpath += 'x' * additional
- remove_tree(longpath)
- self.addCleanup(remove_tree, longpath)
+ remove_tree(longpath)
+ self.addCleanup(remove_tree, longpath)
- os.makedirs(longpath)
- copy_emsdk_to(longpath)
+ os.makedirs(longpath)
+ copy_emsdk_to(longpath)
- emsdk = os.path.join(longpath, 'emsdk')
- if WINDOWS:
- emsdk += '.bat'
- self.assertTrue(os.path.exists(emsdk))
+ emsdk = os.path.join(longpath, 'emsdk')
+ if WINDOWS:
+ emsdk += '.bat'
+ self.assertTrue(os.path.exists(emsdk))
- check_call([emsdk, 'install', 'latest'])
- check_call([emsdk, 'activate', 'latest'])
+ check_call([emsdk, 'install', 'latest'])
+ check_call([emsdk, 'activate', 'latest'])
- # Check that emcc exists in the expected location
- emcc = upstream_emcc(longpath)
- print(emcc)
- self.assertTrue(os.path.exists(emcc))
+ # Check that emcc exists in the expected location
+ emcc = upstream_emcc(longpath)
+ print(emcc)
+ self.assertTrue(os.path.exists(emcc))
- # Find the longest installed path and assert this is longer than 256
- longest_path = get_longest_path_in_dir(longpath)
- print(f'longest ({len(longest_path)}: {longest_path})')
- self.assertGreater(len(longest_path), 256)
+ # Find the longest installed path and assert this is longer than 256
+ longest_path = get_longest_path_in_dir(longpath)
+ print(f'longest ({len(longest_path)}: {longest_path})')
+ self.assertGreater(len(longest_path), 256)
- # Finally make sure we can actaully compile something
- check_call([emcc, 'hello_world.c'])
+ # Finally make sure we can actaully compile something
+ check_call([emcc, 'hello_world.c'])
- def test_unknown_arch(self):
- env = os.environ.copy()
- env['EMSDK_ARCH'] = 'mips'
- failing_call_with_output(emsdk + ' install latest',
- expected='unknown machine architecture: mips',
- env=env)
+ def test_unknown_arch(self):
+ env = os.environ.copy()
+ env['EMSDK_ARCH'] = 'mips'
+ failing_call_with_output(emsdk + ' install latest',
+ expected='unknown machine architecture: mips',
+ env=env)
- def test_wrong_bitness(self):
- env = os.environ.copy()
- env['EMSDK_ARCH'] = 'x86'
- failing_call_with_output(emsdk + ' install sdk-latest-64bit',
- expected='is only provided for 64-bit OSe',
- env=env)
+ def test_wrong_bitness(self):
+ env = os.environ.copy()
+ env['EMSDK_ARCH'] = 'x86'
+ failing_call_with_output(emsdk + ' install sdk-latest-64bit',
+ expected='is only provided for 64-bit OSe',
+ env=env)
- def test_already_installed(self):
- # Test we don't re-download unnecessarily
- checked_call_with_output(emsdk + ' install latest', expected='already installed', unexpected='Downloading:')
+ def test_already_installed(self):
+ # Test we don't re-download unnecessarily
+ checked_call_with_output(emsdk + ' install latest', expected='already installed', unexpected='Downloading:')
- def test_list(self):
- # Test we report installed tools properly. The latest version should be
- # installed, but not some random old one.
- checked_call_with_output(emsdk + ' list', expected=TAGS['aliases']['latest'] + ' INSTALLED', unexpected='1.39.15 INSTALLED:')
+ def test_list(self):
+ # Test we report installed tools properly. The latest version should be
+ # installed, but not some random old one.
+ checked_call_with_output(emsdk + ' list', expected=TAGS['aliases']['latest'] + ' INSTALLED', unexpected='1.39.15 INSTALLED:')
- def test_config_contents(self):
- print('test .emscripten contents')
- run_emsdk('install 6.0.3')
- run_emsdk('activate 6.0.3')
- with open(emconfig) as f:
- config = f.read()
- self.assertIn('upstream', config)
+ def test_config_contents(self):
+ print('test .emscripten contents')
+ run_emsdk('install 6.0.3')
+ run_emsdk('activate 6.0.3')
+ with open(emconfig) as f:
+ config = f.read()
+ self.assertIn('upstream', config)
- # Older version of emscripten (prior to 6.0.3) don't support $CFGDIR
- self.assertIn('import os', config)
- self.assertIn('emsdk_path + ', config)
- self.assertNotIn('$CFGDIR', config)
+ # Older version of emscripten (prior to 6.0.3) don't support $CFGDIR
+ self.assertIn('import os', config)
+ self.assertIn('emsdk_path + ', config)
+ self.assertNotIn('$CFGDIR', config)
- # For newer versions we use $CFGDIR
- version_file = os.path.join('upstream', 'emscripten', 'emscripten-version.txt')
- with open(version_file) as f:
- old_ver = f.read()
- try:
- # Hack the version file to contain 6.0.4
- with open(version_file, 'w') as f:
- f.write('6.0.4\n')
- # Re-activate 6.0.3 but with the hacked version file which makes it
- # appear to contain 6.0.4.
- run_emsdk('activate 6.0.3')
- with open(emconfig) as f:
- config = f.read()
- self.assertNotIn('import os', config)
- self.assertNotIn('emsdk_path', config)
- self.assertIn('$CFGDIR', config)
- finally:
- with open(version_file, 'w') as f:
- f.write(old_ver)
- run_emsdk('activate 6.0.3')
+ # For newer versions we use $CFGDIR
+ version_file = os.path.join('upstream', 'emscripten', 'emscripten-version.txt')
+ with open(version_file) as f:
+ old_ver = f.read()
+ try:
+ # Hack the version file to contain 6.0.4
+ with open(version_file, 'w') as f:
+ f.write('6.0.4\n')
+ # Re-activate 6.0.3 but with the hacked version file which makes it
+ # appear to contain 6.0.4.
+ run_emsdk('activate 6.0.3')
+ with open(emconfig) as f:
+ config = f.read()
+ self.assertNotIn('import os', config)
+ self.assertNotIn('emsdk_path', config)
+ self.assertIn('$CFGDIR', config)
+ finally:
+ with open(version_file, 'w') as f:
+ f.write(old_ver)
+ run_emsdk('activate 6.0.3')
- def test_lib_building(self):
- print('building proper system libraries')
- do_lib_building(upstream_emcc())
+ def test_lib_building(self):
+ print('building proper system libraries')
+ do_lib_building(upstream_emcc())
- def test_redownload(self):
- print('go back to using upstream')
- run_emsdk('activate latest')
+ def test_redownload(self):
+ print('go back to using upstream')
+ run_emsdk('activate latest')
- # Test the normal tools like node don't re-download on re-install
- print('another install must re-download')
- checked_call_with_output(emsdk + ' uninstall node-24.19.0-64bit')
- checked_call_with_output(emsdk + ' install node-24.19.0-64bit', expected='Downloading:', unexpected='already installed')
- checked_call_with_output(emsdk + ' install node-24.19.0-64bit', unexpected='Downloading:', expected='already installed')
+ # Test the normal tools like node don't re-download on re-install
+ print('another install must re-download')
+ checked_call_with_output(emsdk + ' uninstall node-24.19.0-64bit')
+ checked_call_with_output(emsdk + ' install node-24.19.0-64bit', expected='Downloading:', unexpected='already installed')
+ checked_call_with_output(emsdk + ' install node-24.19.0-64bit', unexpected='Downloading:', expected='already installed')
- def test_tot_upstream(self):
- print('test tot-upstream')
- run_emsdk('install tot-upstream')
- with open(emconfig) as f:
- config = f.read()
- run_emsdk('activate tot-upstream')
- self.addCleanup(remove_file, emconfig + '.old')
- with open(emconfig + '.old') as f:
- old_config = f.read()
- self.assertEqual(config, old_config)
- # TODO; test on latest as well
- check_call(upstream_emcc() + ' hello_world.c')
+ def test_tot_upstream(self):
+ print('test tot-upstream')
+ run_emsdk('install tot-upstream')
+ with open(emconfig) as f:
+ config = f.read()
+ run_emsdk('activate tot-upstream')
+ self.addCleanup(remove_file, emconfig + '.old')
+ with open(emconfig + '.old') as f:
+ old_config = f.read()
+ self.assertEqual(config, old_config)
+ # TODO; test on latest as well
+ check_call(upstream_emcc() + ' hello_world.c')
- def test_closure(self):
- # Specifically test with `--closure` so we know that node_modules is working
- check_call(upstream_emcc() + ' hello_world.c --closure=1')
+ def test_closure(self):
+ # Specifically test with `--closure` so we know that node_modules is working
+ check_call(upstream_emcc() + ' hello_world.c --closure=1')
- def test_specific_version(self):
- if MACOS_ARM64:
- self.skipTest('Old sdk versions do not have ARM64 binaries')
- print('test specific release (new, short name)')
- run_emsdk('install 1.38.33')
- print('another install, but no need for re-download')
- checked_call_with_output(emsdk + ' install 1.38.33', expected='Skipped', unexpected='Downloading:')
- run_emsdk('activate 1.38.33')
+ def test_specific_version(self):
+ if MACOS_ARM64:
+ self.skipTest('Old sdk versions do not have ARM64 binaries')
+ print('test specific release (new, short name)')
+ run_emsdk('install 1.38.33')
+ print('another install, but no need for re-download')
+ checked_call_with_output(emsdk + ' install 1.38.33', expected='Skipped', unexpected='Downloading:')
+ run_emsdk('activate 1.38.33')
- def test_specific_version_full(self):
- if MACOS_ARM64:
- self.skipTest('Old sdk versions do not have ARM64 binaries')
- print('test specific release (new, full name)')
- run_emsdk('install sdk-1.38.33-64bit')
- run_emsdk('activate sdk-1.38.33-64bit')
- print('test specific release (new, tag name)')
- run_emsdk('install sdk-tag-1.38.33-64bit')
- run_emsdk('activate sdk-tag-1.38.33-64bit')
+ def test_specific_version_full(self):
+ if MACOS_ARM64:
+ self.skipTest('Old sdk versions do not have ARM64 binaries')
+ print('test specific release (new, full name)')
+ run_emsdk('install sdk-1.38.33-64bit')
+ run_emsdk('activate sdk-1.38.33-64bit')
+ print('test specific release (new, tag name)')
+ run_emsdk('install sdk-tag-1.38.33-64bit')
+ run_emsdk('activate sdk-tag-1.38.33-64bit')
- def test_binaryen_from_source(self):
- if WINDOWS:
- # It takes over 30 mins to build binaryen using Visual Studio in CI
- self.skipTest('test is too slow under windows')
- run_emsdk(['install', '--build=Release', 'binaryen-main-64bit'])
+ def test_binaryen_from_source(self):
+ if WINDOWS:
+ # It takes over 30 mins to build binaryen using Visual Studio in CI
+ self.skipTest('test is too slow under windows')
+ run_emsdk(['install', '--build=Release', 'binaryen-main-64bit'])
- def test_no_32bit(self):
- print('test 32-bit error')
- emsdk_hacked = hack_emsdk('not is_os_64bit()', 'True')
- self.addCleanup(remove_file, emsdk_hacked)
- failing_call_with_output(f'{sys.executable} {emsdk_hacked} install latest',
- 'this tool is only provided for 64-bit OSes')
+ def test_no_32bit(self):
+ print('test 32-bit error')
+ emsdk_hacked = hack_emsdk('not is_os_64bit()', 'True')
+ self.addCleanup(remove_file, emsdk_hacked)
+ failing_call_with_output(f'{sys.executable} {emsdk_hacked} install latest',
+ 'this tool is only provided for 64-bit OSes')
- def test_update_no_git(self):
- print('test non-git update')
+ def test_update_no_git(self):
+ print('test non-git update')
- temp_dir = tempfile.mkdtemp()
- self.addCleanup(remove_tree, temp_dir)
- copy_emsdk_to(temp_dir)
+ temp_dir = tempfile.mkdtemp()
+ self.addCleanup(remove_tree, temp_dir)
+ copy_emsdk_to(temp_dir)
- olddir = os.getcwd()
- try:
- os.chdir(temp_dir)
- run_emsdk('update')
+ olddir = os.getcwd()
+ try:
+ os.chdir(temp_dir)
+ run_emsdk('update')
- print('second time')
- run_emsdk('update')
- finally:
- os.chdir(olddir)
+ print('second time')
+ run_emsdk('update')
+ finally:
+ os.chdir(olddir)
- def test_install_arbitrary(self):
- # Test that its possible to install arbrary emscripten-releases SDKs
- run_emsdk('install 1b7f7bc6002a3ca73647f41fc10e1fac7f06f804')
+ def test_install_arbitrary(self):
+ # Test that its possible to install arbrary emscripten-releases SDKs
+ run_emsdk('install 1b7f7bc6002a3ca73647f41fc10e1fac7f06f804')
- # Check that its not re-downloaded
- checked_call_with_output(emsdk + ' install 1b7f7bc6002a3ca73647f41fc10e1fac7f06f804', expected='Skipped', unexpected='Downloading:')
+ # Check that its not re-downloaded
+ checked_call_with_output(emsdk + ' install 1b7f7bc6002a3ca73647f41fc10e1fac7f06f804', expected='Skipped', unexpected='Downloading:')
- def test_install_tool(self):
- # Test that its possible to install emscripten as tool instead of SDK
- checked_call_with_output(emsdk + ' install releases-77b065ace39e6ab21446e13f92897f956c80476a', unexpected='Installing SDK')
+ def test_install_tool(self):
+ # Test that its possible to install emscripten as tool instead of SDK
+ checked_call_with_output(emsdk + ' install releases-77b065ace39e6ab21446e13f92897f956c80476a', unexpected='Installing SDK')
- def test_activate_missing(self):
- run_emsdk('install latest')
- expected = "error: tool is not installed and therefore cannot be activated: 'releases-13e29bd55185e3c12802bc090b4507901856b2ba-64bit'"
- failing_call_with_output(emsdk + ' activate 2.0.1', expected=expected)
+ def test_activate_missing(self):
+ run_emsdk('install latest')
+ expected = "error: tool is not installed and therefore cannot be activated: 'releases-13e29bd55185e3c12802bc090b4507901856b2ba-64bit'"
+ failing_call_with_output(emsdk + ' activate 2.0.1', expected=expected)
- def test_keep_downloads(self):
- env = os.environ.copy()
- env['EMSDK_KEEP_DOWNLOADS'] = '1'
- # With EMSDK_KEEP_DOWNLOADS the downloading should happen on the first
- # install of 2.0.28, and again when we install 2.0.29, but not on the
- # second install of 2.0.28 because the zip should already be local.
- shutil.rmtree('downloads')
- checked_call_with_output(emsdk + ' install 3.1.54', expected='Downloading:', env=env)
- checked_call_with_output(emsdk + ' install 3.1.55', expected='Downloading:', env=env)
- checked_call_with_output(emsdk + ' install 3.1.54', expected='already downloaded, skipping', unexpected='Downloading:', env=env)
+ def test_keep_downloads(self):
+ env = os.environ.copy()
+ env['EMSDK_KEEP_DOWNLOADS'] = '1'
+ # With EMSDK_KEEP_DOWNLOADS the downloading should happen on the first
+ # install of 2.0.28, and again when we install 2.0.29, but not on the
+ # second install of 2.0.28 because the zip should already be local.
+ shutil.rmtree('downloads')
+ checked_call_with_output(emsdk + ' install 3.1.54', expected='Downloading:', env=env)
+ checked_call_with_output(emsdk + ' install 3.1.55', expected='Downloading:', env=env)
+ checked_call_with_output(emsdk + ' install 3.1.54', expected='already downloaded, skipping', unexpected='Downloading:', env=env)
- def test_unicode_path(self):
- temp_dir = tempfile.mkdtemp(prefix='test_työpöytä_')
- self.addCleanup(remove_tree, temp_dir)
- copy_emsdk_to(temp_dir)
+ def test_unicode_path(self):
+ temp_dir = tempfile.mkdtemp(prefix='test_työpöytä_')
+ self.addCleanup(remove_tree, temp_dir)
+ copy_emsdk_to(temp_dir)
- olddir = os.getcwd()
- try:
- os.chdir(temp_dir)
- run_emsdk('list')
- run_emsdk('construct_env')
- finally:
- os.chdir(olddir)
+ olddir = os.getcwd()
+ try:
+ os.chdir(temp_dir)
+ run_emsdk('list')
+ run_emsdk('construct_env')
+ finally:
+ os.chdir(olddir)
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2)