Mercurial
comparison third_party/emsdk/emsdk.py @ 179:8d17f6e6e290
[ThirdParty] Added emsdk bazel rules that can be supported by bazel 9.0.0
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Thu, 22 Jan 2026 21:23:17 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 178:94705b5986b3 | 179:8d17f6e6e290 |
|---|---|
| 1 #!/usr/bin/env python3 | |
| 2 # Copyright 2019 The Emscripten Authors. All rights reserved. | |
| 3 # Emscripten is available under two separate licenses, the MIT license and the | |
| 4 # University of Illinois/NCSA Open Source License. Both these licenses can be | |
| 5 # found in the LICENSE file. | |
| 6 | |
| 7 import copy | |
| 8 import errno | |
| 9 import json | |
| 10 import multiprocessing | |
| 11 import os | |
| 12 import os.path | |
| 13 import platform | |
| 14 import re | |
| 15 import shutil | |
| 16 import stat | |
| 17 import subprocess | |
| 18 import sys | |
| 19 import sysconfig | |
| 20 import tarfile | |
| 21 import zipfile | |
| 22 from collections import OrderedDict | |
| 23 | |
| 24 if os.name == 'nt': | |
| 25 import ctypes.wintypes | |
| 26 import winreg | |
| 27 | |
| 28 from urllib.parse import urljoin | |
| 29 from urllib.request import urlopen | |
| 30 | |
| 31 if sys.version_info < (3, 2): # noqa: UP036 | |
| 32 print(f'error: emsdk requires python 3.2 or above ({sys.executable} {sys.version})', file=sys.stderr) | |
| 33 sys.exit(1) | |
| 34 | |
| 35 emsdk_packages_url = 'https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/' | |
| 36 | |
| 37 emscripten_releases_repo = 'https://chromium.googlesource.com/emscripten-releases' | |
| 38 | |
| 39 emscripten_releases_download_url_template = "https://storage.googleapis.com/webassembly/emscripten-releases-builds/%s/%s/wasm-binaries%s.%s" | |
| 40 | |
| 41 # This was previously `master.zip` but we are transitioning to `main` and | |
| 42 # `HEAD.zip` works for both cases. In future we could switch this to | |
| 43 # `main.zip` perhaps. | |
| 44 emsdk_zip_download_url = 'https://github.com/emscripten-core/emsdk/archive/HEAD.zip' | |
| 45 | |
| 46 download_dir = 'downloads/' | |
| 47 | |
| 48 extra_release_tag = None | |
| 49 | |
| 50 # Enable this to do very verbose printing about the different steps that are | |
| 51 # being run. Useful for debugging. | |
| 52 VERBOSE = int(os.getenv('EMSDK_VERBOSE', '0')) | |
| 53 QUIET = int(os.getenv('EMSDK_QUIET', '0')) | |
| 54 if os.getenv('EMSDK_NOTTY'): | |
| 55 TTY_OUTPUT = False | |
| 56 else: | |
| 57 TTY_OUTPUT = sys.stdout.isatty() | |
| 58 | |
| 59 | |
| 60 def info(msg): | |
| 61 if not QUIET: | |
| 62 print(msg, file=sys.stderr) | |
| 63 | |
| 64 | |
| 65 def errlog(msg): | |
| 66 print(msg, file=sys.stderr) | |
| 67 | |
| 68 | |
| 69 def exit_with_error(msg): | |
| 70 errlog('error: %s' % msg) | |
| 71 sys.exit(1) | |
| 72 | |
| 73 | |
| 74 WINDOWS = False | |
| 75 MINGW = False | |
| 76 MSYS = False | |
| 77 MACOS = False | |
| 78 LINUX = False | |
| 79 | |
| 80 os_override = os.environ.get('EMSDK_OS') | |
| 81 if os_override: | |
| 82 if os_override == 'windows': | |
| 83 WINDOWS = True | |
| 84 elif os_override == 'linux': | |
| 85 LINUX = True | |
| 86 elif os_override == 'macos': | |
| 87 MACOS = True | |
| 88 else: | |
| 89 assert False, 'EMSDK_OS must be one of: windows, linux, macos' | |
| 90 else: | |
| 91 if os.name == 'nt' or ('windows' in os.getenv('SYSTEMROOT', '').lower()) or ('windows' in os.getenv('COMSPEC', '').lower()): | |
| 92 WINDOWS = True | |
| 93 | |
| 94 if os.getenv('MSYSTEM'): | |
| 95 MSYS = True | |
| 96 # Some functions like os.path.normpath() exhibit different behavior between | |
| 97 # different versions of Python, so we need to distinguish between the MinGW | |
| 98 # and MSYS versions of Python | |
| 99 if sysconfig.get_platform() == 'mingw': | |
| 100 MINGW = True | |
| 101 if os.getenv('MSYSTEM') != 'MSYS' and os.getenv('MSYSTEM') != 'MINGW64': | |
| 102 # https://stackoverflow.com/questions/37460073/msys-vs-mingw-internal-environment-variables | |
| 103 errlog('Warning: MSYSTEM environment variable is present, and is set to "' + os.getenv('MSYSTEM') + '". This shell has not been tested with emsdk and may not work.') | |
| 104 | |
| 105 if platform.mac_ver()[0] != '': | |
| 106 MACOS = True | |
| 107 | |
| 108 if not MACOS and (platform.system() == 'Linux'): | |
| 109 LINUX = True | |
| 110 | |
| 111 UNIX = (MACOS or LINUX) | |
| 112 | |
| 113 | |
| 114 # Pick which shell of 4 shells to use | |
| 115 POWERSHELL = bool(os.getenv('EMSDK_POWERSHELL')) | |
| 116 CSH = bool(os.getenv('EMSDK_CSH')) | |
| 117 CMD = bool(os.getenv('EMSDK_CMD')) | |
| 118 BASH = bool(os.getenv('EMSDK_BASH')) | |
| 119 FISH = bool(os.getenv('EMSDK_FISH')) | |
| 120 | |
| 121 if WINDOWS and BASH: | |
| 122 MSYS = True | |
| 123 | |
| 124 if not CSH and not POWERSHELL and not BASH and not CMD and not FISH: | |
| 125 # Fall back to default of `cmd` on windows and `bash` otherwise | |
| 126 if WINDOWS and not MSYS: | |
| 127 CMD = True | |
| 128 else: | |
| 129 BASH = True | |
| 130 | |
| 131 if WINDOWS: | |
| 132 ENVPATH_SEPARATOR = ';' | |
| 133 else: | |
| 134 ENVPATH_SEPARATOR = ':' | |
| 135 | |
| 136 # platform.machine() may return AMD64 on windows, so standardize the case. | |
| 137 machine = os.getenv('EMSDK_ARCH', platform.machine().lower()) | |
| 138 if machine.startswith(('x64', 'amd64', 'x86_64')): | |
| 139 ARCH = 'x86_64' | |
| 140 elif machine.endswith('86'): | |
| 141 ARCH = 'x86' | |
| 142 elif machine.startswith('aarch64') or machine.lower().startswith('arm64'): | |
| 143 ARCH = 'arm64' | |
| 144 elif machine.startswith('arm'): | |
| 145 ARCH = 'arm' | |
| 146 else: | |
| 147 exit_with_error('unknown machine architecture: ' + machine) | |
| 148 | |
| 149 | |
| 150 # Don't saturate all cores to not steal the whole system, but be aggressive. | |
| 151 CPU_CORES = int(os.getenv('EMSDK_NUM_CORES', max(multiprocessing.cpu_count() - 1, 1))) | |
| 152 | |
| 153 CMAKE_BUILD_TYPE_OVERRIDE = None | |
| 154 | |
| 155 # If true, perform a --shallow clone of git. | |
| 156 GIT_CLONE_SHALLOW = False | |
| 157 | |
| 158 # If true, LLVM backend is built with tests enabled, and Binaryen is built with | |
| 159 # Visual Studio static analyzer enabled. | |
| 160 BUILD_FOR_TESTING = False | |
| 161 | |
| 162 # If 'auto', assertions are decided by the build type | |
| 163 # (Release&MinSizeRel=disabled, Debug&RelWithDebInfo=enabled) | |
| 164 # Other valid values are 'ON' and 'OFF' | |
| 165 ENABLE_LLVM_ASSERTIONS = 'auto' | |
| 166 | |
| 167 # If true, keeps the downloaded archive files. | |
| 168 KEEP_DOWNLOADS = bool(os.getenv('EMSDK_KEEP_DOWNLOADS')) | |
| 169 | |
| 170 | |
| 171 def os_name(): | |
| 172 if WINDOWS: | |
| 173 return 'win' | |
| 174 elif LINUX: | |
| 175 return 'linux' | |
| 176 elif MACOS: | |
| 177 return 'mac' | |
| 178 else: | |
| 179 raise Exception('unknown OS') | |
| 180 | |
| 181 | |
| 182 def debug_print(msg): | |
| 183 if VERBOSE: | |
| 184 errlog(msg) | |
| 185 | |
| 186 | |
| 187 def to_unix_path(p): | |
| 188 return p.replace('\\', '/') | |
| 189 | |
| 190 | |
| 191 EMSDK_PATH = to_unix_path(os.path.dirname(os.path.realpath(__file__))) | |
| 192 | |
| 193 EMSDK_SET_ENV = "" | |
| 194 if POWERSHELL: | |
| 195 EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.ps1') | |
| 196 else: | |
| 197 EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.bat') | |
| 198 | |
| 199 | |
| 200 # Parses https://github.com/emscripten-core/emscripten/tree/d6aced8 to a triplet | |
| 201 # (https://github.com/emscripten-core/emscripten, d6aced8, emscripten-core) | |
| 202 # or https://github.com/emscripten-core/emscripten/commit/00b76f81f6474113fcf540db69297cfeb180347e | |
| 203 # to (https://github.com/emscripten-core/emscripten, 00b76f81f6474113fcf540db69297cfeb180347e, emscripten-core) | |
| 204 def parse_github_url_and_refspec(url): | |
| 205 if not url: | |
| 206 return ('', '', None) | |
| 207 | |
| 208 if url.endswith(('/tree/', '/tree', '/commit/', '/commit')): | |
| 209 raise Exception(f'Malformed git URL and refspec {url}!') | |
| 210 | |
| 211 if '/tree/' in url: | |
| 212 if url.endswith('/'): | |
| 213 raise Exception(f'Malformed git URL and refspec {url}!') | |
| 214 url, refspec = url.split('/tree/') | |
| 215 remote_name = url.split('/')[-2] | |
| 216 return (url, refspec, remote_name) | |
| 217 elif '/commit/' in url: | |
| 218 if url.endswith('/'): | |
| 219 raise Exception(f'Malformed git URL and refspec {url}!') | |
| 220 url, refspec = url.split('/commit/') | |
| 221 remote_name = url.split('/')[-2] | |
| 222 return (url, refspec, remote_name) | |
| 223 else: | |
| 224 return (url, 'main', None) # Assume the default branch is main in the absence of a refspec | |
| 225 | |
| 226 | |
| 227 ARCHIVE_SUFFIXES = ('zip', '.tar', '.gz', '.xz', '.tbz2', '.bz2') | |
| 228 | |
| 229 | |
| 230 # Finds the given executable 'program' in PATH. Operates like the Unix tool 'which'. | |
| 231 def which(program): | |
| 232 def is_exe(fpath): | |
| 233 return os.path.isfile(fpath) and (WINDOWS or os.access(fpath, os.X_OK)) | |
| 234 | |
| 235 fpath, fname = os.path.split(program) | |
| 236 if fpath: | |
| 237 if is_exe(program): | |
| 238 return program | |
| 239 else: | |
| 240 for path in os.environ["PATH"].split(os.pathsep): | |
| 241 path = path.strip('"') | |
| 242 exe_file = os.path.join(path, program) | |
| 243 if is_exe(exe_file): | |
| 244 return exe_file | |
| 245 | |
| 246 if WINDOWS and '.' not in fname: | |
| 247 if is_exe(exe_file + '.exe'): | |
| 248 return exe_file + '.exe' | |
| 249 if is_exe(exe_file + '.cmd'): | |
| 250 return exe_file + '.cmd' | |
| 251 if is_exe(exe_file + '.bat'): | |
| 252 return exe_file + '.bat' | |
| 253 | |
| 254 return None | |
| 255 | |
| 256 | |
| 257 def vswhere(version): | |
| 258 try: | |
| 259 program_files = os.getenv('ProgramFiles(x86)') | |
| 260 if not program_files: | |
| 261 program_files = os.environ['ProgramFiles'] | |
| 262 vswhere_path = os.path.join(program_files, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe') | |
| 263 # Source: https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022 | |
| 264 tools_arch = 'ARM64' if ARCH == 'arm64' else 'x86.x64' | |
| 265 # The "-products *" allows detection of Build Tools, the "-prerelease" allows detection of Preview version | |
| 266 # of Visual Studio and Build Tools. | |
| 267 output = json.loads(subprocess.check_output([vswhere_path, '-latest', '-products', '*', '-prerelease', '-version', '[%s.0,%s.0)' % (version, version + 1), '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.' + tools_arch, '-property', 'installationPath', '-format', 'json'])) | |
| 268 return str(output[0]['installationPath']) | |
| 269 except Exception: | |
| 270 return '' | |
| 271 | |
| 272 | |
| 273 def vs_filewhere(installation_path, platform, file): | |
| 274 try: | |
| 275 vcvarsall = os.path.join(installation_path, 'VC\\Auxiliary\\Build\\vcvarsall.bat') | |
| 276 env = subprocess.check_output('cmd /c "%s" %s & where %s' % (vcvarsall, platform, file)) | |
| 277 paths = [path[:-len(file)] for path in env.split('\r\n') if path.endswith(file)] | |
| 278 return paths[0] | |
| 279 except Exception: | |
| 280 return '' | |
| 281 | |
| 282 | |
| 283 CMAKE_GENERATOR = 'Unix Makefiles' | |
| 284 if WINDOWS: | |
| 285 # Detect which CMake generator to use when building on Windows | |
| 286 if '--mingw' in sys.argv: | |
| 287 CMAKE_GENERATOR = 'MinGW Makefiles' | |
| 288 elif '--vs2022' in sys.argv: | |
| 289 CMAKE_GENERATOR = 'Visual Studio 17' | |
| 290 elif '--vs2019' in sys.argv: | |
| 291 CMAKE_GENERATOR = 'Visual Studio 16' | |
| 292 elif len(vswhere(17)) > 0: | |
| 293 CMAKE_GENERATOR = 'Visual Studio 17' | |
| 294 elif len(vswhere(16)) > 0: | |
| 295 CMAKE_GENERATOR = 'Visual Studio 16' | |
| 296 elif which('mingw32-make') is not None and which('g++') is not None: | |
| 297 CMAKE_GENERATOR = 'MinGW Makefiles' | |
| 298 else: | |
| 299 # No detected generator | |
| 300 CMAKE_GENERATOR = '' | |
| 301 | |
| 302 | |
| 303 sys.argv = [a for a in sys.argv if a not in ('--mingw', '--vs2019', '--vs2022')] | |
| 304 | |
| 305 | |
| 306 # Computes a suitable path prefix to use when building with a given generator. | |
| 307 def cmake_generator_prefix(): | |
| 308 if CMAKE_GENERATOR == 'Visual Studio 17': | |
| 309 return '_vs2022' | |
| 310 if CMAKE_GENERATOR == 'Visual Studio 16': | |
| 311 return '_vs2019' | |
| 312 elif CMAKE_GENERATOR == 'MinGW Makefiles': | |
| 313 return '_mingw' | |
| 314 # Unix Makefiles do not specify a path prefix for backwards path compatibility | |
| 315 return '' | |
| 316 | |
| 317 | |
| 318 # Removes a directory tree even if it was readonly, and doesn't throw exception | |
| 319 # on failure. | |
| 320 def remove_tree(d): | |
| 321 debug_print('remove_tree(' + str(d) + ')') | |
| 322 if not os.path.exists(d): | |
| 323 return | |
| 324 try: | |
| 325 def remove_readonly_and_try_again(func, path, exc_info): | |
| 326 if not (os.stat(path).st_mode & stat.S_IWRITE): | |
| 327 os.chmod(path, stat.S_IWRITE) | |
| 328 func(path) | |
| 329 else: | |
| 330 raise exc_info[1] | |
| 331 shutil.rmtree(d, onerror=remove_readonly_and_try_again) | |
| 332 except Exception as e: | |
| 333 debug_print('remove_tree threw an exception, ignoring: ' + str(e)) | |
| 334 | |
| 335 | |
| 336 def win_set_environment_variable_direct(key, value, system=True): | |
| 337 folder = None | |
| 338 try: | |
| 339 if system: | |
| 340 # Read globally from ALL USERS section. | |
| 341 folder = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 0, winreg.KEY_ALL_ACCESS) | |
| 342 else: | |
| 343 # Register locally from CURRENT USER section. | |
| 344 folder = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS) | |
| 345 winreg.SetValueEx(folder, key, 0, winreg.REG_EXPAND_SZ, value) | |
| 346 debug_print(f'Set key={key} with value {value} in registry.') | |
| 347 return True | |
| 348 except Exception as e: | |
| 349 # 'Access is denied.' | |
| 350 if e.args[3] == 5: | |
| 351 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.') | |
| 352 errlog(f'Failed to write environment variable {key}:') | |
| 353 errlog(str(e)) | |
| 354 return False | |
| 355 finally: | |
| 356 if folder is not None: | |
| 357 folder.Close() | |
| 358 | |
| 359 | |
| 360 def win_get_environment_variable(key, system=True, user=True, fallback=True): | |
| 361 if (not system and not user and fallback): | |
| 362 # if no --system or --permanent flag is provided use shell's value | |
| 363 return os.environ[key] | |
| 364 try: | |
| 365 folder = None | |
| 366 try: | |
| 367 if system: | |
| 368 # Read globally from ALL USERS section. | |
| 369 folder = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment') | |
| 370 else: | |
| 371 # Register locally from CURRENT USER section. | |
| 372 folder = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment') | |
| 373 value = str(winreg.QueryValueEx(folder, key)[0]) | |
| 374 except Exception: | |
| 375 # If reading registry fails for some reason - read via os.environ. This has the drawback | |
| 376 # that expansion items such as %PROGRAMFILES% will have been expanded, so | |
| 377 # need to be precise not to set these back to system registry, or | |
| 378 # expansion items would be lost. | |
| 379 if fallback: | |
| 380 return os.environ[key] | |
| 381 return None | |
| 382 finally: | |
| 383 if folder is not None: | |
| 384 folder.Close() | |
| 385 | |
| 386 except Exception as e: | |
| 387 # this catch is if both the registry key threw an exception and the key is not in os.environ | |
| 388 if e.args[0] != 2: | |
| 389 # 'The system cannot find the file specified.' | |
| 390 errlog(f'Failed to read environment variable {key}:') | |
| 391 errlog(str(e)) | |
| 392 return None | |
| 393 return value | |
| 394 | |
| 395 | |
| 396 def win_set_environment_variable(key, value, system, user): | |
| 397 debug_print('set ' + str(key) + '=' + str(value) + ', in system=' + str(system)) | |
| 398 previous_value = win_get_environment_variable(key, system=system, user=user) | |
| 399 if previous_value == value: | |
| 400 debug_print(' no need to set, since same value already exists.') | |
| 401 # No need to elevate UAC for nothing to set the same value, skip. | |
| 402 return False | |
| 403 | |
| 404 if not value: | |
| 405 try: | |
| 406 if system: | |
| 407 cmd = ['REG', 'DELETE', 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', '/V', key, '/f'] | |
| 408 else: | |
| 409 cmd = ['REG', 'DELETE', 'HKCU\\Environment', '/V', key, '/f'] | |
| 410 debug_print(str(cmd)) | |
| 411 value = subprocess.call(cmd, stdout=subprocess.PIPE) | |
| 412 except Exception: | |
| 413 return False | |
| 414 return True | |
| 415 | |
| 416 try: | |
| 417 if win_set_environment_variable_direct(key, value, system): | |
| 418 return True | |
| 419 # Escape % signs so that we don't expand references to environment variables. | |
| 420 value = value.replace('%', '^%') | |
| 421 if len(value) >= 1024: | |
| 422 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.') | |
| 423 cmd = ['SETX', key, value] | |
| 424 debug_print(str(cmd)) | |
| 425 retcode = subprocess.call(cmd, stdout=subprocess.PIPE) | |
| 426 if retcode != 0: | |
| 427 errlog(f'ERROR! Failed to set environment variable {key}={value}. You may need to set it manually.') | |
| 428 else: | |
| 429 return True | |
| 430 except Exception as e: | |
| 431 errlog(f'ERROR! Failed to set environment variable {key}={value}:') | |
| 432 errlog(str(e)) | |
| 433 errlog('You may need to set it manually.') | |
| 434 | |
| 435 return False | |
| 436 | |
| 437 | |
| 438 def win_set_environment_variables(env_vars_to_add, system, user): | |
| 439 if not env_vars_to_add: | |
| 440 return | |
| 441 | |
| 442 changed = False | |
| 443 | |
| 444 for key, value in env_vars_to_add: | |
| 445 if win_set_environment_variable(key, value, system, user): | |
| 446 if not changed: | |
| 447 changed = True | |
| 448 print('Setting global environment variables:') | |
| 449 | |
| 450 print(key + ' = ' + value) | |
| 451 | |
| 452 if not changed: | |
| 453 print('Global environment variables up to date') | |
| 454 return | |
| 455 | |
| 456 # if changes were made then we need to notify other processes | |
| 457 try: | |
| 458 HWND_BROADCAST = ctypes.wintypes.HWND(0xFFFF) # win32con.HWND_BROADCAST == 65535 | |
| 459 WM_SETTINGCHANGE = 0x001A # win32con.WM_SETTINGCHANGE == 26 | |
| 460 SMTO_BLOCK = 0x0001 # win32con.SMTO_BLOCK == 1 | |
| 461 ctypes.windll.user32.SendMessageTimeoutA( | |
| 462 HWND_BROADCAST, # hWnd: notify everyone | |
| 463 WM_SETTINGCHANGE, # Msg: registry changed | |
| 464 0, # wParam: Must be 0 when setting changed is sent by users | |
| 465 'Environment', # lParam: Specifically environment variables changed | |
| 466 SMTO_BLOCK, # fuFlags: Wait for message to be sent or timeout | |
| 467 100) # uTimeout: 100ms | |
| 468 except Exception as e: | |
| 469 errlog('SendMessageTimeout failed with error: ' + str(e)) | |
| 470 | |
| 471 | |
| 472 def win_delete_environment_variable(key, system=True, user=True): | |
| 473 debug_print(f'win_delete_environment_variable(key={key}, system={system})') | |
| 474 return win_set_environment_variable(key, None, system, user) | |
| 475 | |
| 476 | |
| 477 # Returns the absolute pathname to the given path inside the Emscripten SDK. | |
| 478 def sdk_path(path): | |
| 479 if os.path.isabs(path): | |
| 480 return path | |
| 481 | |
| 482 return to_unix_path(os.path.join(EMSDK_PATH, path)) | |
| 483 | |
| 484 | |
| 485 # Removes a single file, suppressing exceptions on failure. | |
| 486 def rmfile(filename): | |
| 487 debug_print(f'rmfile({filename})') | |
| 488 if os.path.lexists(filename): | |
| 489 os.remove(filename) | |
| 490 | |
| 491 | |
| 492 def mkdir_p(path): | |
| 493 debug_print(f'mkdir_p({path})') | |
| 494 os.makedirs(path, exist_ok=True) | |
| 495 | |
| 496 | |
| 497 def is_nonempty_directory(path): | |
| 498 if not os.path.isdir(path): | |
| 499 return False | |
| 500 return len(os.listdir(path)) != 0 | |
| 501 | |
| 502 | |
| 503 def run(cmd, cwd=None, quiet=False): | |
| 504 debug_print('run(cmd=' + str(cmd) + ', cwd=' + str(cwd) + ')') | |
| 505 process = subprocess.Popen(cmd, cwd=cwd, env=os.environ.copy()) | |
| 506 process.communicate() | |
| 507 if process.returncode != 0 and not quiet: | |
| 508 errlog(str(cmd) + ' failed with error code ' + str(process.returncode) + '!') | |
| 509 return process.returncode | |
| 510 | |
| 511 | |
| 512 # http://pythonicprose.blogspot.fi/2009/10/python-extract-targz-archive.html | |
| 513 def untargz(source_filename, dest_dir): | |
| 514 print(f"Unpacking '{source_filename}' to '{dest_dir}'") | |
| 515 mkdir_p(dest_dir) | |
| 516 returncode = run(['tar', '-xvf' if VERBOSE else '-xf', sdk_path(source_filename), '--strip', '1'], cwd=dest_dir) | |
| 517 # tfile = tarfile.open(source_filename, 'r:gz') | |
| 518 # tfile.extractall(dest_dir) | |
| 519 return returncode == 0 | |
| 520 | |
| 521 | |
| 522 # On Windows, it is not possible to reference path names that are longer than | |
| 523 # ~260 characters, unless the path is referenced via a "\\?\" prefix. | |
| 524 # See https://msdn.microsoft.com/en-us/library/aa365247.aspx#maxpath and http://stackoverflow.com/questions/3555527/python-win32-filename-length-workaround | |
| 525 # In that mode, forward slashes cannot be used as delimiters. | |
| 526 def fix_potentially_long_windows_pathname(pathname): | |
| 527 if (not WINDOWS or MSYS) or os_override: | |
| 528 return pathname | |
| 529 # Test if emsdk calls fix_potentially_long_windows_pathname() with long | |
| 530 # relative paths (which is problematic) | |
| 531 if not os.path.isabs(pathname) and len(pathname) > 200: | |
| 532 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!') | |
| 533 if pathname.startswith('\\\\?\\'): | |
| 534 return pathname | |
| 535 pathname = os.path.normpath(pathname.replace('/', '\\')) | |
| 536 if MINGW: | |
| 537 # MinGW versions of Python return normalized paths with backslashes | |
| 538 # converted to forward slashes, so we must use forward slashes in our | |
| 539 # prefix | |
| 540 return '//?/' + pathname | |
| 541 return '\\\\?\\' + pathname | |
| 542 | |
| 543 | |
| 544 # On windows, rename/move will fail if the destination exists, and there is no | |
| 545 # race-free way to do it. This method removes the destination if it exists, so | |
| 546 # the move always works | |
| 547 def move_with_overwrite(src, dest): | |
| 548 if os.path.exists(dest): | |
| 549 os.remove(dest) | |
| 550 os.rename(src, dest) | |
| 551 | |
| 552 | |
| 553 # http://stackoverflow.com/questions/12886768/simple-way-to-unzip-file-in-python-on-all-oses | |
| 554 def unzip(source_filename, dest_dir): | |
| 555 print(f"Unpacking '{source_filename}' to '{dest_dir}'") | |
| 556 mkdir_p(dest_dir) | |
| 557 common_subdir = None | |
| 558 try: | |
| 559 with zipfile.ZipFile(source_filename) as zf: | |
| 560 # Implement '--strip 1' behavior to unzipping by testing if all the files | |
| 561 # in the zip reside in a common subdirectory, and if so, we move the | |
| 562 # output tree at the end of uncompression step. | |
| 563 for member in zf.infolist(): | |
| 564 words = member.filename.split('/') | |
| 565 if len(words) > 1: # If there is a directory component? | |
| 566 if common_subdir is None: | |
| 567 common_subdir = words[0] | |
| 568 elif common_subdir != words[0]: | |
| 569 common_subdir = None | |
| 570 break | |
| 571 else: | |
| 572 common_subdir = None | |
| 573 break | |
| 574 | |
| 575 unzip_to_dir = dest_dir | |
| 576 if common_subdir: | |
| 577 debug_print(f'common_subdir: {common_subdir}') | |
| 578 unzip_to_dir = os.path.join(os.path.dirname(dest_dir), 'unzip_temp') | |
| 579 | |
| 580 # Now do the actual decompress. | |
| 581 for member in zf.infolist(): | |
| 582 zf.extract(member, fix_potentially_long_windows_pathname(unzip_to_dir)) | |
| 583 dst_filename = os.path.join(unzip_to_dir, member.filename) | |
| 584 | |
| 585 # See: https://stackoverflow.com/questions/42326428/zipfile-in-python-file-permission | |
| 586 unix_attributes = member.external_attr >> 16 | |
| 587 if unix_attributes: | |
| 588 os.chmod(dst_filename, unix_attributes) | |
| 589 | |
| 590 # Move the extracted file to its final location without the base | |
| 591 # directory name, if we are stripping that away. | |
| 592 if common_subdir: | |
| 593 assert member.filename.startswith(common_subdir), f'unexpected filename {member.filename}' | |
| 594 stripped_filename = '.' + member.filename[len(common_subdir):] | |
| 595 final_dst_filename = os.path.join(dest_dir, stripped_filename) | |
| 596 # Check if a directory | |
| 597 if stripped_filename.endswith('/'): | |
| 598 d = fix_potentially_long_windows_pathname(final_dst_filename) | |
| 599 if not os.path.isdir(d): | |
| 600 os.mkdir(d) | |
| 601 else: | |
| 602 parent_dir = os.path.dirname(fix_potentially_long_windows_pathname(final_dst_filename)) | |
| 603 if parent_dir and not os.path.exists(parent_dir): | |
| 604 os.makedirs(parent_dir) | |
| 605 move_with_overwrite(fix_potentially_long_windows_pathname(dst_filename), fix_potentially_long_windows_pathname(final_dst_filename)) | |
| 606 | |
| 607 if common_subdir: | |
| 608 remove_tree(unzip_to_dir) | |
| 609 except zipfile.BadZipfile as e: | |
| 610 errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}! Removing the corrupted zip file.") | |
| 611 rmfile(source_filename) | |
| 612 return False | |
| 613 except Exception as e: | |
| 614 errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}") | |
| 615 return False | |
| 616 | |
| 617 return True | |
| 618 | |
| 619 | |
| 620 # This function interprets whether the given string looks like a path to a | |
| 621 # directory instead of a file, without looking at the actual filesystem. | |
| 622 # 'a/b/c' points to directory, so does 'a/b/c/', but 'a/b/c.x' is parsed as a | |
| 623 # filename | |
| 624 def path_points_to_directory(path): | |
| 625 if path == '.': | |
| 626 return True | |
| 627 last_slash = max(path.rfind('/'), path.rfind('\\')) | |
| 628 last_dot = path.rfind('.') | |
| 629 no_suffix = last_dot < last_slash or last_dot == -1 | |
| 630 if no_suffix: | |
| 631 return True | |
| 632 suffix = path[last_dot:] | |
| 633 # Very simple logic for the only file suffixes used by emsdk downloader. Other | |
| 634 # suffixes, like 'clang-3.2' are treated as dirs. | |
| 635 if suffix in ('.exe', '.zip', '.txt'): | |
| 636 return False | |
| 637 else: | |
| 638 return True | |
| 639 | |
| 640 | |
| 641 def get_content_length(download): | |
| 642 try: | |
| 643 meta = download.info() | |
| 644 if hasattr(meta, "getheaders") and hasattr(meta.getheaders, "Content-Length"): | |
| 645 return int(meta.getheaders("Content-Length")[0]) | |
| 646 elif hasattr(download, "getheader") and download.getheader('Content-Length'): | |
| 647 return int(download.getheader('Content-Length')) | |
| 648 elif hasattr(meta, "getheader") and meta.getheader('Content-Length'): | |
| 649 return int(meta.getheader('Content-Length')) | |
| 650 except Exception: | |
| 651 pass | |
| 652 | |
| 653 return 0 | |
| 654 | |
| 655 | |
| 656 def get_download_target(url, dstpath, filename_prefix=''): | |
| 657 file_name = filename_prefix + url.split('/')[-1] | |
| 658 if path_points_to_directory(dstpath): | |
| 659 file_name = os.path.join(dstpath, file_name) | |
| 660 else: | |
| 661 file_name = dstpath | |
| 662 | |
| 663 # Treat all relative destination paths as relative to the SDK root directory, | |
| 664 # not the current working directory. | |
| 665 file_name = sdk_path(file_name) | |
| 666 | |
| 667 return file_name | |
| 668 | |
| 669 | |
| 670 def download_with_curl(url, file_name): | |
| 671 print("Downloading: %s from %s" % (file_name, url)) | |
| 672 if not which('curl'): | |
| 673 exit_with_error('curl not found in PATH') | |
| 674 # -#: show progress bar | |
| 675 # -L: Follow HTTP 3XX redirections | |
| 676 # -f: Fail on HTTP errors | |
| 677 subprocess.check_call(['curl', '-#', '-f', '-L', '-o', file_name, url]) | |
| 678 | |
| 679 | |
| 680 def download_with_urllib(url, file_name): | |
| 681 u = urlopen(url) | |
| 682 file_size = get_content_length(u) | |
| 683 if file_size > 0: | |
| 684 print("Downloading: %s from %s, %s Bytes" % (file_name, url, file_size)) | |
| 685 else: | |
| 686 print("Downloading: %s from %s" % (file_name, url)) | |
| 687 | |
| 688 file_size_dl = 0 | |
| 689 # Draw a progress bar 80 chars wide (in non-TTY mode) | |
| 690 progress_max = 80 - 4 | |
| 691 progress_shown = 0 | |
| 692 block_sz = 256 * 1024 | |
| 693 if not TTY_OUTPUT: | |
| 694 print(' [', end='') | |
| 695 | |
| 696 with open(file_name, 'wb') as f: | |
| 697 while True: | |
| 698 buffer = u.read(block_sz) | |
| 699 if not buffer: | |
| 700 break | |
| 701 | |
| 702 file_size_dl += len(buffer) | |
| 703 f.write(buffer) | |
| 704 if file_size: | |
| 705 percent = file_size_dl * 100.0 / file_size | |
| 706 if TTY_OUTPUT: | |
| 707 status = r" %10d [%3.02f%%]" % (file_size_dl, percent) | |
| 708 print(status, end='\r') | |
| 709 else: | |
| 710 while progress_shown < progress_max * percent / 100: | |
| 711 print('-', end='') | |
| 712 sys.stdout.flush() | |
| 713 progress_shown += 1 | |
| 714 | |
| 715 if not TTY_OUTPUT: | |
| 716 print(']') | |
| 717 sys.stdout.flush() | |
| 718 | |
| 719 debug_print('finished downloading (%d bytes)' % file_size_dl) | |
| 720 | |
| 721 | |
| 722 # On success, returns the filename on the disk pointing to the destination file that was produced | |
| 723 # On failure, returns None. | |
| 724 def download_file(url, dstpath, download_even_if_exists=False, | |
| 725 filename_prefix=''): | |
| 726 debug_print(f'download_file(url={url}, dstpath={dstpath})') | |
| 727 file_name = get_download_target(url, dstpath, filename_prefix) | |
| 728 | |
| 729 if os.path.exists(file_name) and not download_even_if_exists: | |
| 730 print(f"File '{file_name}' already downloaded, skipping.") | |
| 731 return file_name | |
| 732 | |
| 733 mkdir_p(os.path.dirname(file_name)) | |
| 734 | |
| 735 try: | |
| 736 # Use curl on macOS to avoid CERTIFICATE_VERIFY_FAILED issue with | |
| 737 # python's urllib: | |
| 738 # https://stackoverflow.com/questions/40684543/how-to-make-python-use-ca-certificates-from-mac-os-truststore | |
| 739 # Unlike on linux or windows, curl is always available on macOS systems. | |
| 740 if MACOS: | |
| 741 download_with_curl(url, file_name) | |
| 742 else: | |
| 743 download_with_urllib(url, file_name) | |
| 744 except Exception as e: | |
| 745 errlog(f"Error: Downloading URL '{url}': {e}") | |
| 746 return None | |
| 747 except KeyboardInterrupt: | |
| 748 rmfile(file_name) | |
| 749 raise | |
| 750 | |
| 751 return file_name | |
| 752 | |
| 753 | |
| 754 def run_get_output(cmd, cwd=None): | |
| 755 debug_print(f'run_get_output(cmd={cmd}, cwd={cwd})') | |
| 756 process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, env=os.environ.copy(), universal_newlines=True) | |
| 757 stdout, stderr = process.communicate() | |
| 758 return (process.returncode, stdout, stderr) | |
| 759 | |
| 760 | |
| 761 cached_git_executable = None | |
| 762 | |
| 763 | |
| 764 # must_succeed: If false, the search is performed silently without printing out | |
| 765 # errors if not found. Empty string is returned if git is not found. | |
| 766 # If true, the search is required to succeed, and the execution | |
| 767 # will terminate with sys.exit(1) if not found. | |
| 768 def GIT(must_succeed=True): | |
| 769 global cached_git_executable | |
| 770 if cached_git_executable is not None: | |
| 771 return cached_git_executable | |
| 772 # The order in the following is important, and specifies the preferred order | |
| 773 # of using the git tools. Primarily use git from emsdk if installed. If not, | |
| 774 # use system git. | |
| 775 gits = ['git/1.9.4/bin/git.exe', which('git')] | |
| 776 for git in gits: | |
| 777 try: | |
| 778 ret, stdout, stderr = run_get_output([git, '--version']) | |
| 779 if ret == 0: | |
| 780 cached_git_executable = git | |
| 781 return git | |
| 782 except Exception: | |
| 783 pass | |
| 784 if must_succeed: | |
| 785 if WINDOWS: | |
| 786 msg = "git executable was not found. Please install it by typing 'emsdk install git-1.9.4', or alternatively by installing it manually from http://git-scm.com/downloads . If you install git manually, remember to add it to PATH" | |
| 787 elif MACOS: | |
| 788 msg = "git executable was not found. Please install git for this operation! This can be done from http://git-scm.com/ , or by installing XCode and then the XCode Command Line Tools (see http://stackoverflow.com/questions/9329243/xcode-4-4-command-line-tools )" | |
| 789 elif LINUX: | |
| 790 msg = "git executable was not found. Please install git for this operation! This can be probably be done using your package manager, see http://git-scm.com/book/en/Getting-Started-Installing-Git" | |
| 791 else: | |
| 792 msg = "git executable was not found. Please install git for this operation!" | |
| 793 exit_with_error(msg) | |
| 794 # Not found | |
| 795 return '' | |
| 796 | |
| 797 | |
| 798 def git_repo_version(repo_path): | |
| 799 returncode, stdout, stderr = run_get_output([GIT(), 'log', '-n', '1', '--pretty="%aD %H"'], cwd=repo_path) | |
| 800 if returncode == 0: | |
| 801 return stdout.strip() | |
| 802 else: | |
| 803 return "" | |
| 804 | |
| 805 | |
| 806 def git_recent_commits(repo_path, n=20): | |
| 807 returncode, stdout, stderr = run_get_output([GIT(), 'log', '-n', str(n), '--pretty="%H"'], cwd=repo_path) | |
| 808 if returncode == 0: | |
| 809 return stdout.strip().replace('\r', '').replace('"', '').split('\n') | |
| 810 else: | |
| 811 return [] | |
| 812 | |
| 813 | |
| 814 def get_git_remotes(repo_path): | |
| 815 remotes = [] | |
| 816 output = subprocess.check_output([GIT(), 'remote', '-v'], stderr=subprocess.STDOUT, text=True, cwd=repo_path) | |
| 817 for line in output.splitlines(): | |
| 818 remotes += [line.split()[0]] | |
| 819 return remotes | |
| 820 | |
| 821 | |
| 822 def git_clone(url, dstpath, branch, remote_name='origin'): | |
| 823 debug_print(f'git_clone(url={url}, dstpath={dstpath})') | |
| 824 if os.path.isdir(os.path.join(dstpath, '.git')): | |
| 825 remotes = get_git_remotes(dstpath) | |
| 826 if remote_name in remotes: | |
| 827 debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, skipping.') | |
| 828 return True | |
| 829 else: | |
| 830 debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, but remote has not yet been added. Creating.') | |
| 831 return run([GIT(), 'remote', 'add', remote_name, url], cwd=dstpath) == 0 | |
| 832 | |
| 833 mkdir_p(dstpath) | |
| 834 git_clone_args = ['--recurse-submodules', '--branch', branch] # Do not check out a branch (installer will issue a checkout command right after) | |
| 835 if GIT_CLONE_SHALLOW: | |
| 836 git_clone_args += ['--depth', '1'] | |
| 837 print(f'Cloning from {url}...') | |
| 838 return run([GIT(), 'clone', '-o', remote_name] + git_clone_args + [url, dstpath]) == 0 | |
| 839 | |
| 840 | |
| 841 def git_pull(repo_path, branch_or_tag, remote_name='origin'): | |
| 842 debug_print(f'git_pull(repo_path={repo_path}, branch/tag={branch_or_tag}, remote_name={remote_name})') | |
| 843 ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path) | |
| 844 if ret != 0: | |
| 845 return False | |
| 846 try: | |
| 847 print(f"Fetching latest changes to the branch/tag '{branch_or_tag}' for '{repo_path}'...") | |
| 848 ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path) | |
| 849 if ret != 0: | |
| 850 return False | |
| 851 # Test if branch_or_tag is a branch, or if it is a tag that needs to be updated | |
| 852 target_is_tag = run([GIT(), 'symbolic-ref', '-q', 'HEAD'], repo_path, quiet=True) | |
| 853 | |
| 854 if target_is_tag: | |
| 855 ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', branch_or_tag], repo_path) | |
| 856 else: | |
| 857 local_branch_prefix = (remote_name + '_') if remote_name != 'origin' else '' | |
| 858 ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', '-B', local_branch_prefix + branch_or_tag, | |
| 859 '--track', remote_name + '/' + branch_or_tag], repo_path) | |
| 860 if ret != 0: | |
| 861 return False | |
| 862 if not target_is_tag: | |
| 863 # update branch to latest (not needed for tags) | |
| 864 # this line assumes that the user has not gone and made local changes to the repo | |
| 865 ret = run([GIT(), 'merge', '--ff-only', remote_name + '/' + branch_or_tag], repo_path) | |
| 866 if ret != 0: | |
| 867 return False | |
| 868 run([GIT(), 'submodule', 'update', '--init'], repo_path, quiet=True) | |
| 869 except Exception: | |
| 870 errlog('git operation failed!') | |
| 871 return False | |
| 872 print(f"Successfully updated and checked out branch/tag '{branch_or_tag}' on repository '{repo_path}'") | |
| 873 print("Current repository version: " + git_repo_version(repo_path)) | |
| 874 return True | |
| 875 | |
| 876 | |
| 877 def git_clone_checkout_and_pull(url, dstpath, branch, override_remote_name='origin'): | |
| 878 debug_print(f'git_clone_checkout_and_pull(url={url}, dstpath={dstpath}, branch={branch}, override_remote_name={override_remote_name})') | |
| 879 | |
| 880 # Make sure the repository is cloned first | |
| 881 success = git_clone(url, dstpath, branch, override_remote_name) | |
| 882 if not success: | |
| 883 return False | |
| 884 | |
| 885 # And/or issue a pull/checkout to get to latest code. | |
| 886 return git_pull(dstpath, branch, override_remote_name) | |
| 887 | |
| 888 | |
| 889 # Each tool can have its own build type, or it can be overridden on the command | |
| 890 # line. | |
| 891 def decide_cmake_build_type(tool): | |
| 892 if CMAKE_BUILD_TYPE_OVERRIDE: | |
| 893 return CMAKE_BUILD_TYPE_OVERRIDE | |
| 894 else: | |
| 895 return tool.cmake_build_type | |
| 896 | |
| 897 | |
| 898 # The root directory of the build. | |
| 899 def llvm_build_dir(tool): | |
| 900 generator_suffix = cmake_generator_prefix() | |
| 901 bitness_suffix = '_32' if tool.bitness == 32 else '_64' | |
| 902 | |
| 903 if hasattr(tool, 'git_branch'): | |
| 904 build_dir = 'build_' + tool.git_branch.replace(os.sep, '-') + generator_suffix + bitness_suffix | |
| 905 else: | |
| 906 build_dir = 'build_' + tool.version + generator_suffix + bitness_suffix | |
| 907 return build_dir | |
| 908 | |
| 909 | |
| 910 def exe_suffix(filename): | |
| 911 if WINDOWS and not filename.endswith('.exe'): | |
| 912 filename += '.exe' | |
| 913 return filename | |
| 914 | |
| 915 | |
| 916 # The directory where the binaries are produced. (relative to the installation | |
| 917 # root directory of the tool) | |
| 918 def llvm_build_bin_dir(tool): | |
| 919 build_dir = llvm_build_dir(tool) | |
| 920 if WINDOWS and 'Visual Studio' in CMAKE_GENERATOR: | |
| 921 old_llvm_bin_dir = os.path.join(build_dir, 'bin', decide_cmake_build_type(tool)) | |
| 922 | |
| 923 new_llvm_bin_dir = None | |
| 924 default_cmake_build_type = decide_cmake_build_type(tool) | |
| 925 cmake_build_types = [default_cmake_build_type, 'Release', 'RelWithDebInfo', 'MinSizeRel', 'Debug'] | |
| 926 for build_type in cmake_build_types: | |
| 927 d = os.path.join(build_dir, build_type, 'bin') | |
| 928 if os.path.isfile(os.path.join(tool.installation_path(), d, exe_suffix('clang'))): | |
| 929 new_llvm_bin_dir = d | |
| 930 break | |
| 931 | |
| 932 if new_llvm_bin_dir and os.path.exists(os.path.join(tool.installation_path(), new_llvm_bin_dir)): | |
| 933 return new_llvm_bin_dir | |
| 934 elif os.path.exists(os.path.join(tool.installation_path(), old_llvm_bin_dir)): | |
| 935 return old_llvm_bin_dir | |
| 936 return os.path.join(build_dir, default_cmake_build_type, 'bin') | |
| 937 else: | |
| 938 return os.path.join(build_dir, 'bin') | |
| 939 | |
| 940 | |
| 941 def build_env(): | |
| 942 env = os.environ.copy() | |
| 943 | |
| 944 # To work around a build issue with older Mac OS X builds, add -stdlib=libc++ to all builds. | |
| 945 # See https://groups.google.com/forum/#!topic/emscripten-discuss/5Or6QIzkqf0 | |
| 946 if MACOS: | |
| 947 env['CXXFLAGS'] = ((env['CXXFLAGS'] + ' ') if hasattr(env, 'CXXFLAGS') else '') + '-stdlib=libc++' | |
| 948 if WINDOWS: | |
| 949 # MSBuild.exe has an internal mechanism to avoid N^2 oversubscription of threads in its two-tier build model, see | |
| 950 # https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/ | |
| 951 env['UseMultiToolTask'] = 'true' | |
| 952 env['EnforceProcessCountAcrossBuilds'] = 'true' | |
| 953 return env | |
| 954 | |
| 955 | |
| 956 # Find path to cmake executable, as one of the activated tools, in PATH, or from installed tools. | |
| 957 def find_cmake(): | |
| 958 def locate_cmake_from_tool(tool): | |
| 959 tool_path = get_required_path([tool]) | |
| 960 tool_path = tool_path[-1] | |
| 961 cmake_exe = 'cmake.exe' if WINDOWS else 'cmake' | |
| 962 cmake_exe = os.path.join(tool_path, cmake_exe) | |
| 963 if os.path.isfile(cmake_exe): | |
| 964 return cmake_exe | |
| 965 | |
| 966 # 1. If user has activated a specific cmake tool, then use that tool to configure the build. | |
| 967 for tool in reversed(tools): | |
| 968 if tool.id == 'cmake' and tool.is_active(): | |
| 969 cmake_exe = locate_cmake_from_tool(tool) | |
| 970 if cmake_exe: | |
| 971 info(f'Found installed+activated CMake tool at "{cmake_exe}"') | |
| 972 return cmake_exe | |
| 973 | |
| 974 # 2. If cmake already exists in PATH, then use that cmake to configure the build. | |
| 975 cmake_exe = which('cmake') | |
| 976 if cmake_exe: | |
| 977 info(f'Found CMake from PATH at "{cmake_exe}"') | |
| 978 return cmake_exe | |
| 979 | |
| 980 # 3. Finally, if user has installed a cmake tool, but has not activated that, then use | |
| 981 # that tool. This enables a single-liner directive | |
| 982 # "emsdk install cmake-4.2.0-rc3-64bit llvm-git-main-64bit" to first install CMake, and | |
| 983 # then use it to configure to build LLVM. | |
| 984 for tool in reversed(tools): | |
| 985 if tool.id == 'cmake' and tool.is_installed(): | |
| 986 cmake_exe = locate_cmake_from_tool(tool) | |
| 987 if cmake_exe: | |
| 988 info(f'Found installed CMake tool at "{cmake_exe}"') | |
| 989 return cmake_exe | |
| 990 | |
| 991 exit_with_error('Unable to find "cmake" in PATH, or as installed/activated tool! Please install CMake first') | |
| 992 | |
| 993 | |
| 994 def make_build(build_root, build_type): | |
| 995 debug_print(f'make_build(build_root={build_root}, build_type={build_type})') | |
| 996 if CPU_CORES > 1: | |
| 997 print('Performing a parallel build with ' + str(CPU_CORES) + ' cores.') | |
| 998 else: | |
| 999 print('Performing a singlethreaded build.') | |
| 1000 | |
| 1001 make = [find_cmake(), '--build', '.', '--config', build_type] | |
| 1002 if 'Visual Studio' in CMAKE_GENERATOR: | |
| 1003 # Visual Studio historically has had a two-tier problem in its build system design. A single MSBuild.exe instance only governs | |
| 1004 # the build of a single project (.exe/.lib/.dll) in a solution. Passing the -j parameter above will only enable multiple MSBuild.exe | |
| 1005 # instances to be spawned to build multiple projects in parallel, but each MSBuild.exe is still singlethreaded. | |
| 1006 # To enable each MSBuild.exe instance to also compile several .cpp files in parallel inside a single project, pass the extra | |
| 1007 # MSBuild.exe specific "Multi-ToolTask" (MTT) setting /p:CL_MPCount. This enables each MSBuild.exe to parallelize builds wide. | |
| 1008 # This requires CMake 3.12 or newer. | |
| 1009 make += ['-j', str(CPU_CORES), '--', '/p:CL_MPCount=' + str(CPU_CORES)] | |
| 1010 else: | |
| 1011 # Pass -j to native make, CMake might not support -j option. | |
| 1012 make += ['--', '-j', str(CPU_CORES)] | |
| 1013 | |
| 1014 # Build | |
| 1015 try: | |
| 1016 print('Running build: ' + str(make)) | |
| 1017 ret = subprocess.check_call(make, cwd=build_root, env=build_env()) | |
| 1018 if ret != 0: | |
| 1019 errlog('Build failed with exit code {ret}!') | |
| 1020 errlog('Working directory: ' + build_root) | |
| 1021 return False | |
| 1022 except Exception as e: | |
| 1023 errlog('Build failed due to exception!') | |
| 1024 errlog('Working directory: ' + build_root) | |
| 1025 errlog(str(e)) | |
| 1026 return False | |
| 1027 | |
| 1028 return True | |
| 1029 | |
| 1030 | |
| 1031 def cmake_configure(generator, build_root, src_root, build_type, extra_cmake_args): | |
| 1032 debug_print('cmake_configure(generator=' + str(generator) + ', build_root=' + str(build_root) + ', src_root=' + str(src_root) + ', build_type=' + str(build_type) + ', extra_cmake_args=' + str(extra_cmake_args) + ')') | |
| 1033 # Configure | |
| 1034 if not os.path.isdir(build_root): | |
| 1035 # Create build output directory if it doesn't yet exist. | |
| 1036 os.mkdir(build_root) | |
| 1037 try: | |
| 1038 if generator: | |
| 1039 generator = ['-G', generator] | |
| 1040 else: | |
| 1041 generator = [] | |
| 1042 | |
| 1043 cmdline = [find_cmake()] + generator + ['-DCMAKE_BUILD_TYPE=' + build_type, '-DPYTHON_EXECUTABLE=' + sys.executable] | |
| 1044 # Target macOS 11.0 Big Sur at minimum, to support older Mac devices. | |
| 1045 # See https://en.wikipedia.org/wiki/MacOS#Hardware_compatibility for min-spec details. | |
| 1046 cmdline += ['-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0'] | |
| 1047 cmdline += extra_cmake_args + [src_root] | |
| 1048 | |
| 1049 print('Running CMake: ' + str(cmdline)) | |
| 1050 | |
| 1051 # Specify the deployment target also as an env. var, since some Xcode versions | |
| 1052 # read this instead of the CMake field. | |
| 1053 os.environ['MACOSX_DEPLOYMENT_TARGET'] = '11.0' | |
| 1054 | |
| 1055 def quote_parens(x): | |
| 1056 if ' ' in x: | |
| 1057 return '"' + x.replace('"', '\\"') + '"' | |
| 1058 else: | |
| 1059 return x | |
| 1060 | |
| 1061 # Create a file 'recmake.bat/sh' in the build root that user can call to | |
| 1062 # manually recmake the build tree with the previous build params | |
| 1063 open(os.path.join(build_root, 'recmake.' + ('bat' if WINDOWS else 'sh')), 'w').write(' '.join(map(quote_parens, cmdline))) | |
| 1064 ret = subprocess.check_call(cmdline, cwd=build_root, env=build_env()) | |
| 1065 if ret != 0: | |
| 1066 errlog('CMake invocation failed with exit code {ret}!') | |
| 1067 errlog('Working directory: ' + build_root) | |
| 1068 return False | |
| 1069 except OSError as e: | |
| 1070 if e.errno == errno.ENOENT: | |
| 1071 errlog(str(e)) | |
| 1072 errlog('Could not run CMake, perhaps it has not been installed?') | |
| 1073 if WINDOWS: | |
| 1074 errlog('Installing this package requires CMake. Get it from http://www.cmake.org/') | |
| 1075 elif LINUX: | |
| 1076 errlog('Installing this package requires CMake. Get it via your system package manager (e.g. sudo apt-get install cmake), or from http://www.cmake.org/') | |
| 1077 elif MACOS: | |
| 1078 errlog('Installing this package requires CMake. Get it via a macOS package manager (Homebrew: "brew install cmake", or MacPorts: "sudo port install cmake"), or from http://www.cmake.org/') | |
| 1079 return False | |
| 1080 raise | |
| 1081 except Exception as e: | |
| 1082 errlog('CMake invocation failed due to exception!') | |
| 1083 errlog('Working directory: ' + build_root) | |
| 1084 errlog(str(e)) | |
| 1085 return False | |
| 1086 | |
| 1087 return True | |
| 1088 | |
| 1089 | |
| 1090 def xcode_sdk_version(): | |
| 1091 try: | |
| 1092 output = subprocess.check_output(['xcrun', '--show-sdk-version'], universal_newlines=True) | |
| 1093 return output.strip().split('.') | |
| 1094 except Exception: | |
| 1095 return subprocess.checkplatform.mac_ver()[0].split('.') | |
| 1096 | |
| 1097 | |
| 1098 def cmake_target_platform(tool): | |
| 1099 # Source: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html#platform-selection | |
| 1100 if hasattr(tool, 'arch'): | |
| 1101 if tool.arch == 'arm64': | |
| 1102 return 'ARM64' | |
| 1103 elif tool.arch == 'x86_64': | |
| 1104 return 'x64' | |
| 1105 elif tool.arch == 'x86': | |
| 1106 return 'Win32' | |
| 1107 if ARCH == 'arm64': | |
| 1108 return 'ARM64' | |
| 1109 else: | |
| 1110 return 'x64' if tool.bitness == 64 else 'Win32' | |
| 1111 | |
| 1112 | |
| 1113 def cmake_host_platform(): | |
| 1114 # Source: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html#toolset-selection | |
| 1115 arch_to_cmake_host_platform = { | |
| 1116 'arm64': 'ARM64', | |
| 1117 'arm': 'ARM', | |
| 1118 'x86_64': 'x64', | |
| 1119 'x86': 'x86', | |
| 1120 } | |
| 1121 return arch_to_cmake_host_platform[ARCH] | |
| 1122 | |
| 1123 | |
| 1124 def get_generator_and_config_args(tool): | |
| 1125 args = [] | |
| 1126 cmake_generator = CMAKE_GENERATOR | |
| 1127 if 'Visual Studio 16' in CMAKE_GENERATOR or 'Visual Studio 17' in CMAKE_GENERATOR: # VS2019 or VS2022 | |
| 1128 # With Visual Studio 16 2019, CMake changed the way they specify target arch. | |
| 1129 # Instead of appending it into the CMake generator line, it is specified | |
| 1130 # with a -A arch parameter. | |
| 1131 args += ['-A', cmake_target_platform(tool)] | |
| 1132 args += ['-Thost=' + cmake_host_platform()] | |
| 1133 elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64: | |
| 1134 cmake_generator += ' Win64' | |
| 1135 args += ['-Thost=x64'] | |
| 1136 return (cmake_generator, args) | |
| 1137 | |
| 1138 | |
| 1139 def build_llvm(tool): | |
| 1140 debug_print('build_llvm(' + str(tool) + ')') | |
| 1141 llvm_root = tool.installation_path() | |
| 1142 llvm_src_root = os.path.join(llvm_root, 'src') | |
| 1143 success = git_clone_checkout_and_pull(tool.download_url(), llvm_src_root, tool.git_branch) | |
| 1144 if not success: | |
| 1145 return False | |
| 1146 | |
| 1147 build_dir = llvm_build_dir(tool) | |
| 1148 build_root = os.path.join(llvm_root, build_dir) | |
| 1149 | |
| 1150 build_type = decide_cmake_build_type(tool) | |
| 1151 | |
| 1152 # Configure | |
| 1153 tests_arg = 'ON' if BUILD_FOR_TESTING else 'OFF' | |
| 1154 | |
| 1155 enable_assertions = ENABLE_LLVM_ASSERTIONS.lower() == 'on' or (ENABLE_LLVM_ASSERTIONS == 'auto' and build_type.lower() != 'release' and build_type.lower() != 'minsizerel') | |
| 1156 | |
| 1157 if ARCH in ('x86', 'x86_64'): | |
| 1158 targets_to_build = 'WebAssembly;X86' | |
| 1159 elif ARCH == 'arm': | |
| 1160 targets_to_build = 'WebAssembly;ARM' | |
| 1161 elif ARCH == 'arm64': | |
| 1162 targets_to_build = 'WebAssembly;AArch64' | |
| 1163 else: | |
| 1164 targets_to_build = 'WebAssembly' | |
| 1165 cmake_generator, args = get_generator_and_config_args(tool) | |
| 1166 args += ['-DLLVM_TARGETS_TO_BUILD=' + targets_to_build, | |
| 1167 '-DLLVM_INCLUDE_EXAMPLES=OFF', | |
| 1168 '-DLLVM_INCLUDE_TESTS=' + tests_arg, | |
| 1169 '-DCLANG_INCLUDE_TESTS=' + tests_arg, | |
| 1170 '-DLLVM_ENABLE_ASSERTIONS=' + ('ON' if enable_assertions else 'OFF'), | |
| 1171 # Disable optional LLVM dependencies, these can cause unwanted .so dependencies | |
| 1172 # that prevent distributing the generated compiler for end users. | |
| 1173 '-DLLVM_ENABLE_LIBXML2=OFF', '-DLLVM_ENABLE_TERMINFO=OFF', '-DLLDB_ENABLE_LIBEDIT=OFF', | |
| 1174 '-DLLVM_ENABLE_LIBEDIT=OFF', '-DLLVM_ENABLE_LIBPFM=OFF'] | |
| 1175 # 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 | |
| 1176 # to reroute that to build_root directory then fails on an error | |
| 1177 # file INSTALL cannot find | |
| 1178 # "C:/code/emsdk/llvm/git/build_master_vs2017_64/$(Configuration)/lib/clang/10.0.0/lib/windows/clang_rt.ubsan_standalone-x86_64.lib". | |
| 1179 # (there instead of $(Configuration), one would need ${CMAKE_BUILD_TYPE} ?) | |
| 1180 # It looks like compiler-rt is not compatible to build on Windows? | |
| 1181 args += ['-DLLVM_ENABLE_PROJECTS=clang;lld'] | |
| 1182 # To enable widest possible chance of success for building, let the code | |
| 1183 # compile through with older toolchains that are about to be deprecated by | |
| 1184 # upstream LLVM. | |
| 1185 args += ['-DLLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN=ON'] | |
| 1186 | |
| 1187 if os.getenv('LLVM_CMAKE_ARGS'): | |
| 1188 extra_args = os.environ['LLVM_CMAKE_ARGS'].split(',') | |
| 1189 print('Passing the following extra arguments to LLVM CMake configuration: ' + str(extra_args)) | |
| 1190 args += extra_args | |
| 1191 | |
| 1192 cmakelists_dir = os.path.join(llvm_src_root, 'llvm') | |
| 1193 success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args) | |
| 1194 if not success: | |
| 1195 return False | |
| 1196 | |
| 1197 # Make | |
| 1198 success = make_build(build_root, build_type) | |
| 1199 return success | |
| 1200 | |
| 1201 | |
| 1202 def build_ninja(tool): | |
| 1203 debug_print('build_ninja(' + str(tool) + ')') | |
| 1204 root = os.path.normpath(tool.installation_path()) | |
| 1205 src_root = os.path.join(root, 'src') | |
| 1206 success = git_clone_checkout_and_pull(tool.download_url(), src_root, tool.git_branch) | |
| 1207 if not success: | |
| 1208 return False | |
| 1209 | |
| 1210 build_dir = llvm_build_dir(tool) | |
| 1211 build_root = os.path.join(root, build_dir) | |
| 1212 | |
| 1213 build_type = decide_cmake_build_type(tool) | |
| 1214 | |
| 1215 # Configure | |
| 1216 cmake_generator, args = get_generator_and_config_args(tool) | |
| 1217 | |
| 1218 cmakelists_dir = os.path.join(src_root) | |
| 1219 success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args) | |
| 1220 if not success: | |
| 1221 return False | |
| 1222 | |
| 1223 # Make | |
| 1224 success = make_build(build_root, build_type) | |
| 1225 | |
| 1226 if success: | |
| 1227 bin_dir = os.path.join(root, 'bin') | |
| 1228 mkdir_p(bin_dir) | |
| 1229 exe_paths = [os.path.join(build_root, 'Release', 'ninja'), os.path.join(build_root, 'ninja')] | |
| 1230 for e in exe_paths: | |
| 1231 for s in ['.exe', '']: | |
| 1232 ninja = e + s | |
| 1233 if os.path.isfile(ninja): | |
| 1234 dst = os.path.join(bin_dir, 'ninja' + s) | |
| 1235 shutil.copyfile(ninja, dst) | |
| 1236 os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC) | |
| 1237 | |
| 1238 return success | |
| 1239 | |
| 1240 | |
| 1241 def build_ccache(tool): | |
| 1242 debug_print('build_ccache(' + str(tool) + ')') | |
| 1243 root = os.path.normpath(tool.installation_path()) | |
| 1244 src_root = os.path.join(root, 'src') | |
| 1245 success = git_clone_checkout_and_pull(tool.download_url(), src_root, tool.git_branch) | |
| 1246 if not success: | |
| 1247 return False | |
| 1248 | |
| 1249 build_dir = llvm_build_dir(tool) | |
| 1250 build_root = os.path.join(root, build_dir) | |
| 1251 | |
| 1252 build_type = decide_cmake_build_type(tool) | |
| 1253 | |
| 1254 # Configure | |
| 1255 cmake_generator, args = get_generator_and_config_args(tool) | |
| 1256 args += ['-DZSTD_FROM_INTERNET=ON'] | |
| 1257 | |
| 1258 cmakelists_dir = os.path.join(src_root) | |
| 1259 success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args) | |
| 1260 if not success: | |
| 1261 return False | |
| 1262 | |
| 1263 # Make | |
| 1264 success = make_build(build_root, build_type) | |
| 1265 | |
| 1266 if success: | |
| 1267 bin_dir = os.path.join(root, 'bin') | |
| 1268 mkdir_p(bin_dir) | |
| 1269 exe_paths = [os.path.join(build_root, 'Release', 'ccache'), os.path.join(build_root, 'ccache')] | |
| 1270 for e in exe_paths: | |
| 1271 for s in ['.exe', '']: | |
| 1272 ccache = e + s | |
| 1273 if os.path.isfile(ccache): | |
| 1274 dst = os.path.join(bin_dir, 'ccache' + s) | |
| 1275 shutil.copyfile(ccache, dst) | |
| 1276 os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC) | |
| 1277 | |
| 1278 cache_dir = os.path.join(root, 'cache') | |
| 1279 open(os.path.join(root, 'emcc_ccache.conf'), 'w').write('''# Set maximum cache size to 10 GB: | |
| 1280 max_size = 10G | |
| 1281 cache_dir = %s | |
| 1282 ''' % cache_dir) | |
| 1283 mkdir_p(cache_dir) | |
| 1284 | |
| 1285 return success | |
| 1286 | |
| 1287 | |
| 1288 def download_firefox(tool): | |
| 1289 debug_print('download_firefox(' + str(tool) + ')') | |
| 1290 | |
| 1291 # Use mozdownload to acquire Firefox versions. | |
| 1292 try: | |
| 1293 from mozdownload import FactoryScraper | |
| 1294 except ImportError: | |
| 1295 # If mozdownload is not available, invoke pip to install it. | |
| 1296 subprocess.check_call([sys.executable, "-m", "pip", "install", "mozdownload"]) | |
| 1297 from mozdownload import FactoryScraper | |
| 1298 | |
| 1299 if WINDOWS: | |
| 1300 extension = 'exe' | |
| 1301 elif MACOS: | |
| 1302 extension = 'dmg' | |
| 1303 else: | |
| 1304 # N.b. on Linux even when we ask .tar.xz, we might sometimes get .tar.bz2, | |
| 1305 # depending on what is available on Firefox servers for the particular | |
| 1306 # version. So prepare to handle both further down below. | |
| 1307 extension = 'tar.xz' | |
| 1308 | |
| 1309 platform = None | |
| 1310 if LINUX and 'arm' in ARCH: | |
| 1311 platform = 'linux-arm64' | |
| 1312 if WINDOWS and 'arm' in ARCH: | |
| 1313 platform = 'win64-aarch64' | |
| 1314 | |
| 1315 if tool.version == 'nightly': | |
| 1316 scraper = FactoryScraper('daily', extension=extension, locale='en-US', platform=platform) | |
| 1317 else: | |
| 1318 scraper = FactoryScraper('release', extension=extension, locale='en-US', platform=platform, version=tool.version) | |
| 1319 | |
| 1320 if tool.version == 'nightly': | |
| 1321 firefox_version = os.path.basename(scraper.filename).split(".en-US")[0] | |
| 1322 else: | |
| 1323 firefox_version = os.path.basename(scraper.filename).split("firefox-")[1].split(".en-US")[0] | |
| 1324 | |
| 1325 print('Target Firefox version: ' + firefox_version) | |
| 1326 if tool.version in ['latest', 'latest-esr', 'latest-beta', 'nightly']: | |
| 1327 pretend_version_dir = os.path.normpath(tool.installation_path()) | |
| 1328 orig_version = tool.version | |
| 1329 tool.version = firefox_version | |
| 1330 root = os.path.normpath(tool.installation_path()) | |
| 1331 tool.version = orig_version | |
| 1332 else: | |
| 1333 pretend_version_dir = None | |
| 1334 root = os.path.normpath(tool.installation_path()) | |
| 1335 | |
| 1336 # For moving installer packages, e.g. "nightly", "latest", "latest-esr", | |
| 1337 # store a text file to specify the actual installation directory. | |
| 1338 def save_actual_version(): | |
| 1339 if os.path.isfile(firefox_exe) and pretend_version_dir: | |
| 1340 print(pretend_version_dir) | |
| 1341 os.makedirs(pretend_version_dir, exist_ok=True) | |
| 1342 open(os.path.join(pretend_version_dir, 'actual.txt'), 'w').write(os.path.relpath(root, EMSDK_PATH)) | |
| 1343 | |
| 1344 # Check if already installed | |
| 1345 print('Firefox installation root directory: ' + root) | |
| 1346 exe_dir = os.path.join(root, 'Contents', 'MacOS') if MACOS else root | |
| 1347 firefox_exe = os.path.join(exe_dir, exe_suffix('firefox')) | |
| 1348 if os.path.isfile(firefox_exe): | |
| 1349 print(firefox_exe + ' is already installed, skipping..') | |
| 1350 save_actual_version() | |
| 1351 return True | |
| 1352 | |
| 1353 print('Downloading Firefox from ' + scraper.url) | |
| 1354 filename = scraper.download() | |
| 1355 print('Finished downloading ' + filename) | |
| 1356 | |
| 1357 if not MACOS: | |
| 1358 os.makedirs(root, exist_ok=True) | |
| 1359 | |
| 1360 if extension == 'exe': | |
| 1361 # Uncompress the NSIS installer to 'install' Firefox | |
| 1362 run(['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y', filename, '-o' + root]) | |
| 1363 | |
| 1364 if '.tar.' in filename: | |
| 1365 if filename.endswith('.tar.bz2'): | |
| 1366 tar_type = 'r:bz2' | |
| 1367 elif filename.endswith('.tar.xz'): | |
| 1368 tar_type = 'r:xz' | |
| 1369 else: | |
| 1370 raise Exception('Unknown archive type!') | |
| 1371 | |
| 1372 with tarfile.open(filename, tar_type) as tar: | |
| 1373 tar.extractall(path=root) | |
| 1374 collapse_subdir = os.path.join(root, 'firefox') | |
| 1375 | |
| 1376 elif filename.endswith('.dmg'): | |
| 1377 mount_point = '/Volumes/Firefox Nightly' if tool.version == 'nightly' else '/Volumes/Firefox' | |
| 1378 app_name = 'Firefox Nightly.app' if tool.version == 'nightly' else 'Firefox.app' | |
| 1379 | |
| 1380 # If a previous mount point exists, detach it first | |
| 1381 if os.path.exists(mount_point): | |
| 1382 run(['hdiutil', 'detach', mount_point]) | |
| 1383 | |
| 1384 # Abort if detaching was not successful | |
| 1385 if os.path.exists(mount_point): | |
| 1386 raise Exception(f'Previous mount of Firefox already exists at "{mount_point}", unable to proceed.') | |
| 1387 | |
| 1388 # Mount the archive | |
| 1389 run(['hdiutil', 'attach', filename]) | |
| 1390 firefox_dir = os.path.join(mount_point, app_name) | |
| 1391 if not os.path.isdir(firefox_dir): | |
| 1392 raise Exception(f'Unable to find Firefox directory "{firefox_dir}" inside app image.') | |
| 1393 | |
| 1394 # And install by copying the files from the archive | |
| 1395 shutil.copytree(firefox_dir, root) | |
| 1396 run(['hdiutil', 'detach', mount_point]) | |
| 1397 collapse_subdir = None | |
| 1398 | |
| 1399 elif filename.endswith('.exe'): | |
| 1400 # NSIS installer package has a core/ directory, remove it as redundant. | |
| 1401 collapse_subdir = os.path.join(root, 'core') | |
| 1402 | |
| 1403 # Remove a redundant subdirectory by moving installed files up one directory. | |
| 1404 if collapse_subdir and os.path.isdir(collapse_subdir): | |
| 1405 # Rename the parent subdirectory first, since we will be handling a nested `firefox/firefox/` | |
| 1406 collapse = collapse_subdir + '_temp_renamed' | |
| 1407 os.rename(collapse_subdir, collapse) | |
| 1408 | |
| 1409 # Move all files up by one directory | |
| 1410 for f in os.listdir(collapse): | |
| 1411 shutil.move(os.path.join(collapse, f), os.path.dirname(collapse)) | |
| 1412 | |
| 1413 # The root directory should now be empty | |
| 1414 os.rmdir(collapse) | |
| 1415 | |
| 1416 # Original installer is now done. | |
| 1417 os.remove(filename) | |
| 1418 | |
| 1419 # Write a policy file that prevents Firefox from auto-updating itself. | |
| 1420 if MACOS: | |
| 1421 distribution_path = os.path.join(root, 'Contents', 'Resources', 'distribution') | |
| 1422 else: | |
| 1423 distribution_path = os.path.join(root, 'distribution') | |
| 1424 os.makedirs(distribution_path, exist_ok=True) | |
| 1425 open(os.path.join(distribution_path, 'policies.json'), 'w').write('''{ | |
| 1426 "policies": { | |
| 1427 "AppAutoUpdate": false, | |
| 1428 "DisableAppUpdate": true | |
| 1429 } | |
| 1430 }''') | |
| 1431 | |
| 1432 if MACOS: | |
| 1433 # Disable a macOS feature where if the browser is terminated mid-execution, (e.g. by | |
| 1434 # CI aborting), then the next time the browser is launched, macOS might bring up a dialog | |
| 1435 # "The last time you opened Firefox, it unexpectedly quit while reopening windows. | |
| 1436 # Do you want to try to reopen its windows again?" | |
| 1437 # that will block automated CI runs. | |
| 1438 # Disable this feature by changing the behavior of the program with macOS 'defaults'. | |
| 1439 run(['defaults', 'write', '-app', root, 'ApplePersistenceIgnoreState', 'YES']) | |
| 1440 run(['defaults', 'write', '-app', root, 'NSQuitAlwaysKeepsWindows', '-bool', 'false']) | |
| 1441 | |
| 1442 save_actual_version() | |
| 1443 | |
| 1444 # If we didn't get a Firefox executable, then installation failed. | |
| 1445 return os.path.isfile(firefox_exe) | |
| 1446 | |
| 1447 | |
| 1448 def is_firefox_installed(tool): | |
| 1449 actual_file = os.path.join(tool.installation_dir(), 'actual.txt') | |
| 1450 if not os.path.isfile(actual_file): | |
| 1451 return False | |
| 1452 | |
| 1453 actual_installation_dir = sdk_path(open(actual_file).read()) | |
| 1454 exe_dir = os.path.join(actual_installation_dir, 'Contents', 'MacOS') if MACOS else actual_installation_dir | |
| 1455 firefox_exe = os.path.join(exe_dir, exe_suffix('firefox')) | |
| 1456 return os.path.isfile(firefox_exe) | |
| 1457 | |
| 1458 | |
| 1459 # Finds the newest installed version of a given tool | |
| 1460 def find_latest_installed_tool(name): | |
| 1461 for t in reversed(tools): | |
| 1462 if t.id == name and t.is_installed(): | |
| 1463 return t | |
| 1464 | |
| 1465 | |
| 1466 # npm install in Emscripten root directory | |
| 1467 def emscripten_npm_install(tool, directory): | |
| 1468 node_tool = find_latest_installed_tool('node') | |
| 1469 if not node_tool: | |
| 1470 npm_fallback = which('npm') | |
| 1471 if not npm_fallback: | |
| 1472 errlog('Failed to find npm command!') | |
| 1473 errlog('Running "npm ci" in installed Emscripten root directory ' + tool.installation_path() + ' is required!') | |
| 1474 errlog('Please install node.js first!') | |
| 1475 return False | |
| 1476 node_path = os.path.dirname(npm_fallback) | |
| 1477 else: | |
| 1478 node_path = os.path.join(node_tool.installation_path(), 'bin') | |
| 1479 | |
| 1480 npm = os.path.join(node_path, 'npm' + ('.cmd' if WINDOWS else '')) | |
| 1481 env = os.environ.copy() | |
| 1482 env["PATH"] = node_path + os.pathsep + env["PATH"] | |
| 1483 print('Running post-install step: npm ci ...') | |
| 1484 try: | |
| 1485 subprocess.check_output( | |
| 1486 [npm, 'ci', '--production'], | |
| 1487 cwd=directory, stderr=subprocess.STDOUT, env=env, | |
| 1488 universal_newlines=True) | |
| 1489 except subprocess.CalledProcessError as e: | |
| 1490 errlog('Error running %s:\n%s' % (e.cmd, e.output)) | |
| 1491 return False | |
| 1492 | |
| 1493 print('Done running: npm ci') | |
| 1494 | |
| 1495 if os.path.isfile(os.path.join(directory, 'bootstrap.py')): | |
| 1496 try: | |
| 1497 subprocess.check_output([sys.executable, os.path.join(directory, 'bootstrap.py')], | |
| 1498 cwd=directory, stderr=subprocess.STDOUT, env=env, | |
| 1499 universal_newlines=True) | |
| 1500 except subprocess.CalledProcessError as e: | |
| 1501 errlog('Error running %s:\n%s' % (e.cmd, e.output)) | |
| 1502 return False | |
| 1503 | |
| 1504 print('Done running: Emscripten bootstrap') | |
| 1505 return True | |
| 1506 | |
| 1507 | |
| 1508 # Binaryen build scripts: | |
| 1509 def binaryen_build_root(tool): | |
| 1510 build_root = tool.installation_path().strip() | |
| 1511 if build_root.endswith(('/', '\\')): | |
| 1512 build_root = build_root[:-1] | |
| 1513 generator_prefix = cmake_generator_prefix() | |
| 1514 build_root = build_root + generator_prefix + '_' + str(tool.bitness) + 'bit_binaryen' | |
| 1515 return build_root | |
| 1516 | |
| 1517 | |
| 1518 def uninstall_binaryen(tool): | |
| 1519 debug_print('uninstall_binaryen(' + str(tool) + ')') | |
| 1520 build_root = binaryen_build_root(tool) | |
| 1521 print(f"Deleting path '{build_root}'") | |
| 1522 remove_tree(build_root) | |
| 1523 | |
| 1524 | |
| 1525 def is_binaryen_installed(tool): | |
| 1526 build_root = binaryen_build_root(tool) | |
| 1527 return os.path.exists(build_root) | |
| 1528 | |
| 1529 | |
| 1530 def build_binaryen_tool(tool): | |
| 1531 debug_print('build_binaryen_tool(' + str(tool) + ')') | |
| 1532 src_root = tool.installation_path() | |
| 1533 build_root = binaryen_build_root(tool) | |
| 1534 build_type = decide_cmake_build_type(tool) | |
| 1535 | |
| 1536 # Configure | |
| 1537 cmake_generator, args = get_generator_and_config_args(tool) | |
| 1538 args += ['-DENABLE_WERROR=0'] # -Werror is not useful for end users | |
| 1539 args += ['-DBUILD_TESTS=0'] # We don't want to build or run tests | |
| 1540 | |
| 1541 if 'Visual Studio' in CMAKE_GENERATOR: | |
| 1542 if BUILD_FOR_TESTING: | |
| 1543 args += ['-DRUN_STATIC_ANALYZER=1'] | |
| 1544 | |
| 1545 success = cmake_configure(cmake_generator, build_root, src_root, build_type, args) | |
| 1546 if not success: | |
| 1547 return False | |
| 1548 | |
| 1549 # Make | |
| 1550 success = make_build(build_root, build_type) | |
| 1551 | |
| 1552 # Deploy scripts needed from source repository to build directory | |
| 1553 remove_tree(os.path.join(build_root, 'scripts')) | |
| 1554 shutil.copytree(os.path.join(src_root, 'scripts'), os.path.join(build_root, 'scripts')) | |
| 1555 remove_tree(os.path.join(build_root, 'src', 'js')) | |
| 1556 shutil.copytree(os.path.join(src_root, 'src', 'js'), os.path.join(build_root, 'src', 'js')) | |
| 1557 | |
| 1558 return success | |
| 1559 | |
| 1560 | |
| 1561 def download_and_extract(archive, dest_dir, filename_prefix='', clobber=True): | |
| 1562 debug_print('download_and_extract(archive={archive}, dest_dir={dest_dir})') | |
| 1563 | |
| 1564 url = urljoin(emsdk_packages_url, archive) | |
| 1565 | |
| 1566 def try_download(url): | |
| 1567 return download_file(url, download_dir, not KEEP_DOWNLOADS, filename_prefix) | |
| 1568 | |
| 1569 # Special hack for the wasm-binaries we transitioned from `.bzip2` to | |
| 1570 # `.xz`, but we can't tell from the version/url which one to use, so | |
| 1571 # try one and then fall back to the other. | |
| 1572 success = False | |
| 1573 if 'wasm-binaries' in archive and os.path.splitext(archive)[1] == '.xz': | |
| 1574 success = try_download(url) | |
| 1575 if not success: | |
| 1576 alt_url = url.replace('.tar.xz', '.tbz2') | |
| 1577 success = try_download(alt_url) | |
| 1578 if success: | |
| 1579 url = alt_url | |
| 1580 | |
| 1581 if not success: | |
| 1582 success = try_download(url) | |
| 1583 | |
| 1584 if not success: | |
| 1585 return False | |
| 1586 | |
| 1587 # Remove the old directory, since we have some SDKs that install into the | |
| 1588 # same directory. If we didn't do this contents of the previous install | |
| 1589 # could remain. | |
| 1590 if clobber: | |
| 1591 remove_tree(dest_dir) | |
| 1592 | |
| 1593 download_target = get_download_target(url, download_dir, filename_prefix) | |
| 1594 if archive.endswith('.zip'): | |
| 1595 return unzip(download_target, dest_dir) | |
| 1596 else: | |
| 1597 return untargz(download_target, dest_dir) | |
| 1598 | |
| 1599 | |
| 1600 def to_native_path(p): | |
| 1601 if (WINDOWS and not MSYS) and not os_override: | |
| 1602 return to_unix_path(p).replace('/', '\\') | |
| 1603 else: | |
| 1604 return to_unix_path(p) | |
| 1605 | |
| 1606 | |
| 1607 # Finds and returns a list of the directories that need to be added to PATH for | |
| 1608 # the given set of tools. | |
| 1609 def get_required_path(active_tools): | |
| 1610 path_add = [to_native_path(EMSDK_PATH)] | |
| 1611 for tool in active_tools: | |
| 1612 if hasattr(tool, 'activated_path'): | |
| 1613 path = to_native_path(tool.expand_vars(tool.activated_path)) | |
| 1614 # If the tool has an activated_path_skip attribute then we don't add | |
| 1615 # the tools path to the users path if a program by that name is found | |
| 1616 # in the existing PATH. This allows us to, for example, add our version | |
| 1617 # node to the users PATH if, and only if, they don't already have a | |
| 1618 # another version of node in their PATH. | |
| 1619 if hasattr(tool, 'activated_path_skip'): | |
| 1620 current_path = which(tool.activated_path_skip) | |
| 1621 # We found an executable by this name in the current PATH, but we | |
| 1622 # ignore our own version for this purpose. | |
| 1623 if current_path and os.path.dirname(current_path) != path: | |
| 1624 continue | |
| 1625 path_add.append(path) | |
| 1626 return path_add | |
| 1627 | |
| 1628 | |
| 1629 # Returns the absolute path to the file '.emscripten' for the current user on | |
| 1630 # this system. | |
| 1631 EM_CONFIG_PATH = os.path.join(EMSDK_PATH, ".emscripten") | |
| 1632 EM_CONFIG_DICT = {} | |
| 1633 | |
| 1634 | |
| 1635 def parse_key_value(line): | |
| 1636 if not line: | |
| 1637 return ('', '') | |
| 1638 eq = line.find('=') | |
| 1639 if eq != -1: | |
| 1640 key = line[0:eq].strip() | |
| 1641 value = line[eq + 1:].strip() | |
| 1642 return (key, value) | |
| 1643 else: | |
| 1644 return (key, '') | |
| 1645 | |
| 1646 | |
| 1647 def load_em_config(): | |
| 1648 EM_CONFIG_DICT.clear() | |
| 1649 lines = [] | |
| 1650 try: | |
| 1651 lines = open(EM_CONFIG_PATH).read().split('\n') | |
| 1652 except Exception: | |
| 1653 pass | |
| 1654 for line in lines: | |
| 1655 try: | |
| 1656 key, value = parse_key_value(line) | |
| 1657 if value != '': | |
| 1658 EM_CONFIG_DICT[key] = value | |
| 1659 except Exception: | |
| 1660 pass | |
| 1661 | |
| 1662 | |
| 1663 def find_emscripten_root(active_tools): | |
| 1664 """Find the currently active emscripten root. | |
| 1665 | |
| 1666 If there is more than one tool that defines EMSCRIPTEN_ROOT (this | |
| 1667 should not happen under normal circumstances), assume the last one takes | |
| 1668 precedence. | |
| 1669 """ | |
| 1670 root = None | |
| 1671 for tool in active_tools: | |
| 1672 config = tool.activated_config() | |
| 1673 if 'EMSCRIPTEN_ROOT' in config: | |
| 1674 root = config['EMSCRIPTEN_ROOT'] | |
| 1675 return root | |
| 1676 | |
| 1677 | |
| 1678 def fetch_nightly_node_versions(): | |
| 1679 # Node.js Apple ARM64 nightly downloads are currently out of order, so pin | |
| 1680 # to recent version that does still exist. https://github.com/nodejs/node/issues/59654 | |
| 1681 if MACOS and ARCH == 'arm64': | |
| 1682 return ['v25.0.0-nightly20250715b305119844'] | |
| 1683 | |
| 1684 url = "https://nodejs.org/download/nightly/" | |
| 1685 with urlopen(url) as response: | |
| 1686 html = response.read().decode("utf-8") | |
| 1687 | |
| 1688 # Regex to capture href values like v7.0.0-nightly2016080175c6d9dd95/ | |
| 1689 pattern = re.compile(r'<a href="(v[0-9]+\.[0-9]+\.[0-9]+-nightly[0-9a-f]+)/">') | |
| 1690 matches = pattern.findall(html) | |
| 1691 return matches | |
| 1692 | |
| 1693 | |
| 1694 def dir_installed_nightly_node_versions(): | |
| 1695 path = os.path.abspath('node') | |
| 1696 try: | |
| 1697 return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name)) and name.startswith("nightly-")] | |
| 1698 except Exception: | |
| 1699 return [] | |
| 1700 | |
| 1701 | |
| 1702 def extract_newest_node_nightly_version(versions): | |
| 1703 def parse(v): | |
| 1704 # example: v7.0.0-nightly2016080175c6d9dd95 | |
| 1705 m = re.match(r'v(\d+)\.(\d+)\.(\d+)-nightly(\d+)', v) | |
| 1706 if m: | |
| 1707 major, minor, patch, nightly = m.groups() | |
| 1708 return [int(major), int(minor), int(patch), int(nightly)] | |
| 1709 else: | |
| 1710 return [] | |
| 1711 | |
| 1712 try: | |
| 1713 return max(versions, key=lambda v: parse(v)) | |
| 1714 except Exception: | |
| 1715 return None | |
| 1716 | |
| 1717 | |
| 1718 def download_node_nightly(tool): | |
| 1719 nightly_versions = fetch_nightly_node_versions() | |
| 1720 latest_nightly = extract_newest_node_nightly_version(nightly_versions) | |
| 1721 print('Latest Node.js Nightly download available is "{latest_nightly}"') | |
| 1722 | |
| 1723 output_dir = os.path.abspath('node/nightly-' + latest_nightly) | |
| 1724 # Node.js zip structure quirk: Linux and macOS archives have a /bin, | |
| 1725 # Windows does not. Unify the file structures. | |
| 1726 if WINDOWS: | |
| 1727 output_dir += '/bin' | |
| 1728 | |
| 1729 if os.path.isdir(output_dir): | |
| 1730 return True | |
| 1731 | |
| 1732 url = tool.url.replace('%version%', latest_nightly) | |
| 1733 if WINDOWS: | |
| 1734 os_ = 'win' | |
| 1735 elif LINUX: | |
| 1736 os_ = 'linux' | |
| 1737 elif MACOS: | |
| 1738 os_ = 'darwin' | |
| 1739 else: | |
| 1740 os_ = '' | |
| 1741 if platform.machine().lower() in ["x86_64", "amd64"]: | |
| 1742 arch = 'x64' | |
| 1743 elif platform.machine().lower() in ["arm64", "aarch64"]: | |
| 1744 arch = 'arm64' | |
| 1745 if WINDOWS: | |
| 1746 zip_suffix = 'zip' | |
| 1747 else: | |
| 1748 zip_suffix = 'tar.gz' | |
| 1749 url = url.replace('%os%', os_) | |
| 1750 url = url.replace('%arch%', arch) | |
| 1751 url = url.replace('%zip_suffix%', zip_suffix) | |
| 1752 download_and_extract(url, output_dir) | |
| 1753 open(tool.get_version_file_path(), 'w').write('node-nightly-64bit') | |
| 1754 return True | |
| 1755 | |
| 1756 | |
| 1757 # returns a tuple (string,string) of config files paths that need to used | |
| 1758 # to activate emsdk env depending on $SHELL, defaults to bash. | |
| 1759 def get_emsdk_shell_env_configs(): | |
| 1760 default_emsdk_env = sdk_path('emsdk_env.sh') | |
| 1761 default_shell_config_file = '$HOME/.bash_profile' | |
| 1762 shell = os.getenv('SHELL', '') | |
| 1763 if 'zsh' in shell: | |
| 1764 return (default_emsdk_env, '$HOME/.zprofile') | |
| 1765 elif 'csh' in shell: | |
| 1766 return (sdk_path('emsdk_env.csh'), '$HOME/.cshrc') | |
| 1767 elif 'fish' in shell: | |
| 1768 return (sdk_path('emsdk_env.fish'), '$HOME/.config/fish/config.fish') | |
| 1769 else: | |
| 1770 return (default_emsdk_env, default_shell_config_file) | |
| 1771 | |
| 1772 | |
| 1773 def generate_em_config(active_tools, permanently_activate, system): | |
| 1774 cfg = 'import os\n' | |
| 1775 cfg += "emsdk_path = os.path.dirname(os.getenv('EM_CONFIG')).replace('\\\\', '/')\n" | |
| 1776 | |
| 1777 # Different tools may provide the same activated configs; the latest to be | |
| 1778 # activated is the relevant one. | |
| 1779 activated_config = OrderedDict() | |
| 1780 for tool in active_tools: | |
| 1781 for name, value in tool.activated_config().items(): | |
| 1782 activated_config[name] = value | |
| 1783 | |
| 1784 if 'NODE_JS' not in activated_config: | |
| 1785 node_fallback = which('nodejs') | |
| 1786 if not node_fallback: | |
| 1787 node_fallback = 'node' | |
| 1788 activated_config['NODE_JS'] = node_fallback | |
| 1789 | |
| 1790 for name, value in activated_config.items(): | |
| 1791 if value.startswith('['): | |
| 1792 cfg += f'{name} = {value}\n' | |
| 1793 else: | |
| 1794 cfg += f"{name} = '{value}'\n" | |
| 1795 | |
| 1796 emroot = find_emscripten_root(active_tools) | |
| 1797 if emroot: | |
| 1798 version = parse_emscripten_version(emroot) | |
| 1799 # Older emscripten versions of emscripten depend on certain config | |
| 1800 # keys that are no longer used. | |
| 1801 # See https://github.com/emscripten-core/emscripten/pull/9469 | |
| 1802 if version < [1, 38, 46]: | |
| 1803 cfg += 'COMPILER_ENGINE = NODE_JS\n' | |
| 1804 # See https://github.com/emscripten-core/emscripten/pull/9542 | |
| 1805 if version < [1, 38, 48]: | |
| 1806 cfg += 'JS_ENGINES = [NODE_JS]\n' | |
| 1807 | |
| 1808 cfg = cfg.replace("'" + EMSDK_PATH, "emsdk_path + '") | |
| 1809 | |
| 1810 if os.path.exists(EM_CONFIG_PATH): | |
| 1811 backup_path = EM_CONFIG_PATH + ".old" | |
| 1812 move_with_overwrite(EM_CONFIG_PATH, backup_path) | |
| 1813 | |
| 1814 with open(EM_CONFIG_PATH, "w") as text_file: | |
| 1815 text_file.write(cfg) | |
| 1816 | |
| 1817 # Clear old emscripten content. | |
| 1818 rmfile(os.path.join(EMSDK_PATH, ".emscripten_sanity")) | |
| 1819 | |
| 1820 path_add = get_required_path(active_tools) | |
| 1821 | |
| 1822 # Give some recommended next step, depending on the platform | |
| 1823 if WINDOWS: | |
| 1824 if not permanently_activate and not system: | |
| 1825 print('Next steps:') | |
| 1826 print('- Consider running `emsdk activate` with --permanent or --system') | |
| 1827 print(' to have emsdk settings available on startup.') | |
| 1828 else: | |
| 1829 print('Next steps:') | |
| 1830 print('- To conveniently access emsdk tools from the command line,') | |
| 1831 print(' consider adding the following directories to your PATH:') | |
| 1832 for p in path_add: | |
| 1833 print(' ' + p) | |
| 1834 print('- This can be done for the current shell by running:') | |
| 1835 emsdk_env, shell_config_file = get_emsdk_shell_env_configs() | |
| 1836 print(' source "%s"' % emsdk_env) | |
| 1837 print('- Configure emsdk in your shell startup scripts by running:') | |
| 1838 print(' echo \'source "%s"\' >> %s' % (emsdk_env, shell_config_file)) | |
| 1839 | |
| 1840 | |
| 1841 def find_msbuild_dir(): | |
| 1842 program_files = os.getenv('ProgramFiles', 'C:/Program Files') | |
| 1843 program_files_x86 = os.getenv('ProgramFiles(x86)', 'C:/Program Files (x86)') | |
| 1844 MSBUILDX86_DIR = os.path.join(program_files_x86, "MSBuild/Microsoft.Cpp/v4.0/Platforms") | |
| 1845 MSBUILD_DIR = os.path.join(program_files, "MSBuild/Microsoft.Cpp/v4.0/Platforms") | |
| 1846 if os.path.exists(MSBUILDX86_DIR): | |
| 1847 return MSBUILDX86_DIR | |
| 1848 if os.path.exists(MSBUILD_DIR): | |
| 1849 return MSBUILD_DIR | |
| 1850 # No MSbuild installed. | |
| 1851 return '' | |
| 1852 | |
| 1853 | |
| 1854 class Tool: | |
| 1855 def __init__(self, data): | |
| 1856 # Convert the dictionary representation of the tool in 'data' to members of | |
| 1857 # this class for convenience. | |
| 1858 for key, value in data.items(): | |
| 1859 setattr(self, key, value) | |
| 1860 | |
| 1861 # Cache the name ID of this Tool (these are read very often) | |
| 1862 self.name = self.id | |
| 1863 if self.version: | |
| 1864 self.name += '-' + self.version | |
| 1865 if hasattr(self, 'bitness'): | |
| 1866 self.name += '-' + str(self.bitness) + 'bit' | |
| 1867 | |
| 1868 def __str__(self): | |
| 1869 return self.name | |
| 1870 | |
| 1871 def __repr__(self): | |
| 1872 return self.name | |
| 1873 | |
| 1874 def expand_vars(self, str): | |
| 1875 if WINDOWS and '%MSBuildPlatformsDir%' in str: | |
| 1876 str = str.replace('%MSBuildPlatformsDir%', find_msbuild_dir()) | |
| 1877 if '%cmake_build_type_on_win%' in str: | |
| 1878 str = str.replace('%cmake_build_type_on_win%', (decide_cmake_build_type(self) + '/') if WINDOWS else '') | |
| 1879 if '%installation_dir%' in str: | |
| 1880 str = str.replace('%installation_dir%', sdk_path(self.installation_dir())) | |
| 1881 if '%macos_app_bundle_prefix%' in str: | |
| 1882 str = str.replace('%macos_app_bundle_prefix%', 'Contents/MacOS/' if MACOS else '') | |
| 1883 if '%actual_installation_dir%' in str: | |
| 1884 actual_file = os.path.join(self.installation_dir(), 'actual.txt') | |
| 1885 if os.path.isfile(actual_file): | |
| 1886 str = str.replace('%actual_installation_dir%', sdk_path(open(actual_file).read())) | |
| 1887 else: | |
| 1888 str = str.replace('%actual_installation_dir%', '__NOT_INSTALLED__') | |
| 1889 if '%generator_prefix%' in str: | |
| 1890 str = str.replace('%generator_prefix%', cmake_generator_prefix()) | |
| 1891 str = str.replace('%.exe%', '.exe' if WINDOWS else '') | |
| 1892 if '%llvm_build_bin_dir%' in str: | |
| 1893 str = str.replace('%llvm_build_bin_dir%', llvm_build_bin_dir(self)) | |
| 1894 if '%latest_downloaded_node_nightly_dir%' in str: | |
| 1895 installed_node_nightlys = dir_installed_nightly_node_versions() | |
| 1896 latest_node_nightly = extract_newest_node_nightly_version(installed_node_nightlys) | |
| 1897 if latest_node_nightly: | |
| 1898 str = str.replace('%latest_downloaded_node_nightly_dir%', latest_node_nightly) | |
| 1899 | |
| 1900 return str | |
| 1901 | |
| 1902 # Return true if this tool requires building from source, and false if this is a precompiled tool. | |
| 1903 def needs_compilation(self): | |
| 1904 if hasattr(self, 'cmake_build_type'): | |
| 1905 return True | |
| 1906 | |
| 1907 if hasattr(self, 'uses'): | |
| 1908 for tool_name in self.uses: | |
| 1909 tool = find_tool(tool_name) | |
| 1910 if not tool: | |
| 1911 debug_print(f'Tool {self} depends on {tool_name} which does not exist!') | |
| 1912 continue | |
| 1913 if tool.needs_compilation(): | |
| 1914 return True | |
| 1915 | |
| 1916 return False | |
| 1917 | |
| 1918 # Specifies the target path where this tool will be installed to. This could | |
| 1919 # either be a directory or a filename (e.g. in case of node.js) | |
| 1920 def installation_path(self): | |
| 1921 if hasattr(self, 'install_path'): | |
| 1922 pth = self.expand_vars(self.install_path) | |
| 1923 return sdk_path(pth) | |
| 1924 p = self.version | |
| 1925 if hasattr(self, 'bitness') and (not hasattr(self, 'append_bitness') or self.append_bitness): | |
| 1926 p += '_' + str(self.bitness) + 'bit' | |
| 1927 return sdk_path(os.path.join(self.id, p)) | |
| 1928 | |
| 1929 # Specifies the target directory this tool will be installed to. | |
| 1930 def installation_dir(self): | |
| 1931 dir = self.installation_path() | |
| 1932 if path_points_to_directory(dir): | |
| 1933 return dir | |
| 1934 else: | |
| 1935 return os.path.dirname(dir) | |
| 1936 | |
| 1937 # Returns the configuration item that needs to be added to .emscripten to make | |
| 1938 # this Tool active for the current user. | |
| 1939 def activated_config(self): | |
| 1940 if hasattr(self, 'activated_cfg'): | |
| 1941 activated_cfg = self.activated_cfg | |
| 1942 else: | |
| 1943 return {} | |
| 1944 | |
| 1945 config = OrderedDict() | |
| 1946 expanded = to_unix_path(self.expand_vars(activated_cfg)) | |
| 1947 for specific_cfg in expanded.split(';'): | |
| 1948 name, value = specific_cfg.split('=') | |
| 1949 config[name] = value.strip("'") | |
| 1950 return config | |
| 1951 | |
| 1952 def activated_environment(self): | |
| 1953 if hasattr(self, 'activated_env'): | |
| 1954 activated_env = self.activated_env | |
| 1955 else: | |
| 1956 return [] | |
| 1957 | |
| 1958 return self.expand_vars(activated_env).split(';') | |
| 1959 | |
| 1960 def compatible_with_this_arch(self): | |
| 1961 if hasattr(self, 'arch'): | |
| 1962 if self.arch != ARCH: | |
| 1963 return False | |
| 1964 return True | |
| 1965 | |
| 1966 def compatible_with_this_os(self): | |
| 1967 if hasattr(self, 'os'): | |
| 1968 if self.os == 'all': | |
| 1969 return True | |
| 1970 if self.compatible_with_this_arch() and ((WINDOWS and 'win' in self.os) or (LINUX and ('linux' in self.os or 'unix' in self.os)) or (MACOS and ('macos' in self.os or 'unix' in self.os))): | |
| 1971 return True | |
| 1972 else: | |
| 1973 return False | |
| 1974 else: | |
| 1975 if not hasattr(self, 'macos_url') and not hasattr(self, 'windows_url') and not hasattr(self, 'unix_url') and not hasattr(self, 'linux_url'): | |
| 1976 return True | |
| 1977 | |
| 1978 if MACOS and hasattr(self, 'macos_url') and self.compatible_with_this_arch(): | |
| 1979 return True | |
| 1980 | |
| 1981 if LINUX and hasattr(self, 'linux_url') and self.compatible_with_this_arch(): | |
| 1982 return True | |
| 1983 | |
| 1984 if WINDOWS and hasattr(self, 'windows_url') and self.compatible_with_this_arch(): | |
| 1985 return True | |
| 1986 | |
| 1987 if UNIX and hasattr(self, 'unix_url'): | |
| 1988 return True | |
| 1989 | |
| 1990 return hasattr(self, 'url') | |
| 1991 | |
| 1992 # the "version file" is a file inside install dirs that indicates the | |
| 1993 # version installed there. this helps disambiguate when there is more than | |
| 1994 # one version that may be installed to the same directory (which is used | |
| 1995 # to avoid accumulating builds over time in some cases, with new builds | |
| 1996 # overwriting the old) | |
| 1997 def get_version_file_path(self): | |
| 1998 return os.path.join(self.installation_path(), '.emsdk_version') | |
| 1999 | |
| 2000 def is_installed_version(self): | |
| 2001 version_file_path = self.get_version_file_path() | |
| 2002 if os.path.isfile(version_file_path): | |
| 2003 with open(version_file_path) as version_file: | |
| 2004 return version_file.read().strip() == self.name | |
| 2005 return False | |
| 2006 | |
| 2007 def update_installed_version(self): | |
| 2008 with open(self.get_version_file_path(), 'w') as version_file: | |
| 2009 version_file.write(self.name + '\n') | |
| 2010 | |
| 2011 def is_installed(self, skip_version_check=False): | |
| 2012 # If this tool/sdk depends on other tools, require that all dependencies are | |
| 2013 # installed for this tool to count as being installed. | |
| 2014 if hasattr(self, 'uses'): | |
| 2015 for tool_name in self.uses: | |
| 2016 tool = find_tool(tool_name) | |
| 2017 if tool is None: | |
| 2018 errlog(f"Manifest error: No tool by name '{tool_name}' found! This may indicate an internal SDK error!") | |
| 2019 return False | |
| 2020 if not tool.is_installed(): | |
| 2021 return False | |
| 2022 | |
| 2023 if self.download_url() is None: | |
| 2024 debug_print(str(self) + ' has no files to download, so is installed by default.') | |
| 2025 return True | |
| 2026 | |
| 2027 content_exists = is_nonempty_directory(self.installation_path()) | |
| 2028 debug_print(str(self) + ' installation path is ' + self.installation_path() + ', exists: ' + str(content_exists) + '.') | |
| 2029 | |
| 2030 # For e.g. fastcomp clang from git repo, the activated PATH is the | |
| 2031 # directory where the compiler is built to, and installation_path is | |
| 2032 # the directory where the source tree exists. To distinguish between | |
| 2033 # multiple packages sharing the same source (clang-main-32bit, | |
| 2034 # clang-main-64bit, clang-main-32bit and clang-main-64bit each | |
| 2035 # share the same git repo), require that in addition to the installation | |
| 2036 # directory, each item in the activated PATH must exist. | |
| 2037 if hasattr(self, 'activated_path') and not os.path.exists(self.expand_vars(self.activated_path)): | |
| 2038 content_exists = False | |
| 2039 | |
| 2040 if hasattr(self, 'custom_is_installed_script'): | |
| 2041 if self.custom_is_installed_script == 'is_binaryen_installed': | |
| 2042 return is_binaryen_installed(self) | |
| 2043 elif self.custom_is_installed_script == 'is_firefox_installed': | |
| 2044 return is_firefox_installed(self) | |
| 2045 else: | |
| 2046 raise Exception('Unknown custom_is_installed_script directive "' + self.custom_is_installed_script + '"!') | |
| 2047 | |
| 2048 return content_exists and (skip_version_check or self.is_installed_version()) | |
| 2049 | |
| 2050 def is_active(self): | |
| 2051 if not self.is_installed(): | |
| 2052 return False | |
| 2053 | |
| 2054 # All dependencies of this tool must be active as well. | |
| 2055 deps = self.dependencies() | |
| 2056 for tool in deps: | |
| 2057 if not tool.is_active(): | |
| 2058 return False | |
| 2059 | |
| 2060 activated_cfg = self.activated_config() | |
| 2061 if not activated_cfg: | |
| 2062 return len(deps) > 0 | |
| 2063 | |
| 2064 for key, value in activated_cfg.items(): | |
| 2065 if key not in EM_CONFIG_DICT: | |
| 2066 debug_print(f'{self} is not active, because key="{key}" does not exist in .emscripten') | |
| 2067 return False | |
| 2068 | |
| 2069 # all paths are stored dynamically relative to the emsdk root, so | |
| 2070 # normalize those first. | |
| 2071 config_value = EM_CONFIG_DICT[key].replace("emsdk_path + '", "'" + EMSDK_PATH) | |
| 2072 config_value = config_value.strip("'") | |
| 2073 if config_value != value: | |
| 2074 debug_print(f'{self} is not active, because key="{key}" has value "{config_value}" but should have value "{value}"') | |
| 2075 return False | |
| 2076 return True | |
| 2077 | |
| 2078 # Returns true if the system environment variables requires by this tool are currently active. | |
| 2079 def is_env_active(self): | |
| 2080 envs = self.activated_environment() | |
| 2081 for env in envs: | |
| 2082 key, value = parse_key_value(env) | |
| 2083 if key not in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value): | |
| 2084 debug_print(f'{self} is not active, because environment variable key="{key}" has value "{os.getenv(key)}" but should have value "{value}"') | |
| 2085 return False | |
| 2086 | |
| 2087 if hasattr(self, 'activated_path'): | |
| 2088 path = to_unix_path(self.expand_vars(self.activated_path)) | |
| 2089 for p in path: | |
| 2090 path_items = os.environ['PATH'].replace('\\', '/').split(ENVPATH_SEPARATOR) | |
| 2091 if not normalized_contains(path_items, p): | |
| 2092 debug_print(f'{self} is not active, because environment variable PATH item "{p}" is not present (PATH={os.environ["PATH"]})') | |
| 2093 return False | |
| 2094 return True | |
| 2095 | |
| 2096 # If this tool can be installed on this system, this function returns True. | |
| 2097 # Otherwise, this function returns a string that describes the reason why this | |
| 2098 # tool is not available. | |
| 2099 def can_be_installed(self): | |
| 2100 if hasattr(self, 'bitness'): | |
| 2101 if self.bitness == 64 and not is_os_64bit(): | |
| 2102 return "this tool is only provided for 64-bit OSes" | |
| 2103 return True | |
| 2104 | |
| 2105 def download_url(self): | |
| 2106 if WINDOWS and hasattr(self, 'windows_url'): | |
| 2107 return self.windows_url | |
| 2108 elif MACOS and hasattr(self, 'macos_url'): | |
| 2109 return self.macos_url | |
| 2110 elif LINUX and hasattr(self, 'linux_url'): | |
| 2111 return self.linux_url | |
| 2112 elif UNIX and hasattr(self, 'unix_url'): | |
| 2113 return self.unix_url | |
| 2114 elif hasattr(self, 'url'): | |
| 2115 return self.url | |
| 2116 else: | |
| 2117 return None | |
| 2118 | |
| 2119 def install(self): | |
| 2120 """Returns True if the Tool was installed of False if was skipped due to | |
| 2121 already being installed. | |
| 2122 """ | |
| 2123 if self.can_be_installed() is not True: | |
| 2124 exit_with_error(f"The tool '{self}' is not available due to the reason: {self.can_be_installed()}") | |
| 2125 | |
| 2126 if self.id == 'sdk': | |
| 2127 return self.install_sdk() | |
| 2128 else: | |
| 2129 return self.install_tool() | |
| 2130 | |
| 2131 def install_sdk(self): | |
| 2132 """Returns True if any SDK component was installed of False all componented | |
| 2133 were already installed. | |
| 2134 """ | |
| 2135 print(f"Installing SDK '{self}'..") | |
| 2136 installed = False | |
| 2137 | |
| 2138 for tool_name in self.uses: | |
| 2139 tool = find_tool(tool_name) | |
| 2140 if tool is None: | |
| 2141 exit_with_error(f"manifest error: No tool by name '{tool_name}' found! This may indicate an internal SDK error!") | |
| 2142 installed |= tool.install() | |
| 2143 | |
| 2144 if not installed: | |
| 2145 print(f"All SDK components already installed: '{self}'.") | |
| 2146 return False | |
| 2147 | |
| 2148 if getattr(self, 'custom_install_script', None) == 'emscripten_npm_install': | |
| 2149 # upstream tools have hardcoded paths that are not stored in emsdk_manifest.json registry | |
| 2150 install_path = 'upstream' | |
| 2151 emscripten_dir = os.path.join(EMSDK_PATH, install_path, 'emscripten') | |
| 2152 # Older versions of the sdk did not include the node_modules directory | |
| 2153 # and require `npm ci` to be run post-install | |
| 2154 if not os.path.exists(os.path.join(emscripten_dir, 'node_modules')): | |
| 2155 if not emscripten_npm_install(self, emscripten_dir): | |
| 2156 exit_with_error('post-install step failed: emscripten_npm_install') | |
| 2157 | |
| 2158 print(f"Done installing SDK '{self}'.") | |
| 2159 return True | |
| 2160 | |
| 2161 def install_tool(self): | |
| 2162 """Returns True if the SDK was installed of False if was skipped due to | |
| 2163 already being installed. | |
| 2164 """ | |
| 2165 # Avoid doing a redundant reinstall of the tool, if it has already been installed. | |
| 2166 # However all tools that are sourced directly from git branches do need to be | |
| 2167 # installed every time when requested, since the install step is then used to git | |
| 2168 # pull the tool to a newer version. | |
| 2169 if self.is_installed() and not hasattr(self, 'git_branch'): | |
| 2170 print(f"Skipped installing {self.name}, already installed.") | |
| 2171 return False | |
| 2172 | |
| 2173 print(f"Installing tool '{self}'..") | |
| 2174 url = self.download_url() | |
| 2175 | |
| 2176 custom_install_scripts = { | |
| 2177 'build_llvm': build_llvm, | |
| 2178 'build_ninja': build_ninja, | |
| 2179 'build_ccache': build_ccache, | |
| 2180 'download_node_nightly': download_node_nightly, | |
| 2181 'download_firefox': download_firefox, | |
| 2182 } | |
| 2183 if hasattr(self, 'custom_install_script') and self.custom_install_script in custom_install_scripts: | |
| 2184 success = custom_install_scripts[self.custom_install_script](self) | |
| 2185 elif hasattr(self, 'git_branch'): | |
| 2186 success = git_clone_checkout_and_pull(url, self.installation_path(), self.git_branch, getattr(self, 'remote_name', 'origin')) | |
| 2187 elif url.endswith(ARCHIVE_SUFFIXES): | |
| 2188 success = download_and_extract(url, self.installation_path(), | |
| 2189 filename_prefix=getattr(self, 'download_prefix', '')) | |
| 2190 else: | |
| 2191 assert False, 'unhandled url type: ' + url | |
| 2192 | |
| 2193 if not success: | |
| 2194 exit_with_error("installation failed!") | |
| 2195 | |
| 2196 if hasattr(self, 'custom_install_script'): | |
| 2197 if self.custom_install_script == 'emscripten_npm_install': | |
| 2198 success = emscripten_npm_install(self, self.installation_path()) | |
| 2199 elif self.custom_install_script in ('build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly', 'download_firefox'): | |
| 2200 # 'build_llvm' is a special one that does the download on its | |
| 2201 # own, others do the download manually. | |
| 2202 pass | |
| 2203 elif self.custom_install_script == 'build_binaryen': | |
| 2204 success = build_binaryen_tool(self) | |
| 2205 else: | |
| 2206 raise Exception('Unknown custom_install_script command "' + self.custom_install_script + '"!') | |
| 2207 | |
| 2208 if not success: | |
| 2209 exit_with_error("installation failed!") | |
| 2210 | |
| 2211 # Install an emscripten-version.txt file if told to, and if there is one. | |
| 2212 # (If this is not an actual release, but some other build, then we do not | |
| 2213 # write anything.) | |
| 2214 if hasattr(self, 'emscripten_releases_hash'): | |
| 2215 emscripten_version_file_path = os.path.join(to_native_path(self.expand_vars(self.activated_path)), 'emscripten-version.txt') | |
| 2216 version = get_emscripten_release_version(self.emscripten_releases_hash) | |
| 2217 if version: | |
| 2218 with open(emscripten_version_file_path, 'w') as f: | |
| 2219 f.write('"%s"\n' % version) | |
| 2220 | |
| 2221 print(f"Done installing tool '{self}'.") | |
| 2222 | |
| 2223 # Sanity check that the installation succeeded, and if so, remove unneeded | |
| 2224 # leftover installation files. | |
| 2225 if not self.is_installed(skip_version_check=True): | |
| 2226 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.") | |
| 2227 | |
| 2228 self.cleanup_temp_install_files() | |
| 2229 self.update_installed_version() | |
| 2230 return True | |
| 2231 | |
| 2232 def cleanup_temp_install_files(self): | |
| 2233 if KEEP_DOWNLOADS: | |
| 2234 return | |
| 2235 url = self.download_url() | |
| 2236 if url.endswith(ARCHIVE_SUFFIXES): | |
| 2237 download_target = get_download_target(url, download_dir, getattr(self, 'download_prefix', '')) | |
| 2238 debug_print(f"Deleting temporary download: {download_target}") | |
| 2239 rmfile(download_target) | |
| 2240 | |
| 2241 def uninstall(self): | |
| 2242 if not self.is_installed(): | |
| 2243 print(f"Tool '{self}' was not installed. No need to uninstall.") | |
| 2244 return | |
| 2245 print(f"Uninstalling tool '{self}'..") | |
| 2246 if hasattr(self, 'custom_uninstall_script'): | |
| 2247 if self.custom_uninstall_script == 'uninstall_binaryen': | |
| 2248 uninstall_binaryen(self) | |
| 2249 else: | |
| 2250 raise Exception(f'Unknown custom_uninstall_script directive "{self.custom_uninstall_script}"!') | |
| 2251 print(f"Deleting path '{self.installation_path()}'") | |
| 2252 remove_tree(self.installation_path()) | |
| 2253 print(f"Done uninstalling '{self}'.") | |
| 2254 | |
| 2255 def dependencies(self): | |
| 2256 if not hasattr(self, 'uses'): | |
| 2257 return [] | |
| 2258 deps = [] | |
| 2259 | |
| 2260 for tool_name in self.uses: | |
| 2261 tool = find_tool(tool_name) | |
| 2262 if tool: | |
| 2263 deps += [tool] | |
| 2264 return deps | |
| 2265 | |
| 2266 def recursive_dependencies(self): | |
| 2267 if not hasattr(self, 'uses'): | |
| 2268 return [] | |
| 2269 deps = [] | |
| 2270 for tool_name in self.uses: | |
| 2271 tool = find_tool(tool_name) | |
| 2272 if tool: | |
| 2273 deps += [tool] | |
| 2274 deps += tool.recursive_dependencies() | |
| 2275 return deps | |
| 2276 | |
| 2277 | |
| 2278 # A global registry of all known Emscripten SDK tools available in the SDK manifest. | |
| 2279 tools = [] | |
| 2280 tools_map = {} | |
| 2281 | |
| 2282 | |
| 2283 def add_tool(tool): | |
| 2284 tool.is_sdk = False | |
| 2285 tools.append(tool) | |
| 2286 if find_tool(str(tool)): | |
| 2287 raise Exception('Duplicate tool ' + str(tool) + '! Existing:\n{' + ', '.join("%s: %s" % item for item in vars(find_tool(str(tool))).items()) + '}, New:\n{' + ', '.join("%s: %s" % item for item in vars(tool).items()) + '}') | |
| 2288 tools_map[str(tool)] = tool | |
| 2289 | |
| 2290 | |
| 2291 # A global registry of all known SDK toolsets. | |
| 2292 sdks = [] | |
| 2293 sdks_map = {} | |
| 2294 | |
| 2295 | |
| 2296 def add_sdk(sdk): | |
| 2297 sdk.is_sdk = True | |
| 2298 sdks.append(sdk) | |
| 2299 if find_sdk(str(sdk)): | |
| 2300 raise Exception('Duplicate sdk ' + str(sdk) + '! Existing:\n{' + ', '.join("%s: %s" % item for item in vars(find_sdk(str(sdk))).items()) + '}, New:\n{' + ', '.join("%s: %s" % item for item in vars(sdk).items()) + '}') | |
| 2301 sdks_map[str(sdk)] = sdk | |
| 2302 | |
| 2303 | |
| 2304 # N.B. In both tools and sdks list above, we take the convention that the newest | |
| 2305 # items are at the back of the list (ascending chronological order) | |
| 2306 | |
| 2307 def find_tool(name): | |
| 2308 return tools_map.get(name) | |
| 2309 | |
| 2310 | |
| 2311 def find_sdk(name): | |
| 2312 return sdks_map.get(name) | |
| 2313 | |
| 2314 | |
| 2315 def is_os_64bit(): | |
| 2316 return ARCH.endswith('64') | |
| 2317 | |
| 2318 | |
| 2319 def find_latest_version(): | |
| 2320 return resolve_sdk_aliases('latest') | |
| 2321 | |
| 2322 | |
| 2323 def find_latest_hash(): | |
| 2324 version = find_latest_version() | |
| 2325 releases_info = load_releases_info() | |
| 2326 return releases_info['releases'][version] | |
| 2327 | |
| 2328 | |
| 2329 def resolve_sdk_aliases(name, verbose=False): | |
| 2330 releases_info = load_releases_info() | |
| 2331 while name in releases_info['aliases']: | |
| 2332 if verbose: | |
| 2333 print("Resolving SDK alias '%s' to '%s'" % (name, releases_info['aliases'][name])) | |
| 2334 name = releases_info['aliases'][name] | |
| 2335 return name | |
| 2336 | |
| 2337 | |
| 2338 def find_latest_sdk(): | |
| 2339 return 'sdk-releases-%s-64bit' % (find_latest_hash()) | |
| 2340 | |
| 2341 | |
| 2342 def find_tot_sdk(): | |
| 2343 debug_print('Fetching emscripten-releases repository...') | |
| 2344 global extra_release_tag | |
| 2345 extra_release_tag = get_emscripten_releases_tot() | |
| 2346 return 'sdk-releases-%s-64bit' % (extra_release_tag) | |
| 2347 | |
| 2348 | |
| 2349 def parse_emscripten_version(emscripten_root): | |
| 2350 version_file = os.path.join(emscripten_root, 'emscripten-version.txt') | |
| 2351 with open(version_file) as f: | |
| 2352 version = f.read().strip() | |
| 2353 version = version.strip('"').split('-')[0].split('.') | |
| 2354 return [int(v) for v in version] | |
| 2355 | |
| 2356 | |
| 2357 # Given a git hash in emscripten-releases, find the emscripten | |
| 2358 # version for it. There may not be one if this is not the hash of | |
| 2359 # a release, in which case we return None. | |
| 2360 def get_emscripten_release_version(emscripten_releases_hash): | |
| 2361 releases_info = load_releases_info() | |
| 2362 for key, value in dict(releases_info['releases']).items(): | |
| 2363 if value == emscripten_releases_hash: | |
| 2364 return key.split('-')[0] | |
| 2365 return None | |
| 2366 | |
| 2367 | |
| 2368 # Get the tip-of-tree build identifier. | |
| 2369 def get_emscripten_releases_tot(): | |
| 2370 git_clone_checkout_and_pull(emscripten_releases_repo, sdk_path('releases'), 'main') | |
| 2371 recent_releases = git_recent_commits(sdk_path('releases')) | |
| 2372 # The recent releases are the latest hashes in the git repo. There | |
| 2373 # may not be a build for the most recent ones yet; find the last | |
| 2374 # that does. | |
| 2375 arch = '' | |
| 2376 if ARCH == 'arm64': | |
| 2377 arch = '-arm64' | |
| 2378 | |
| 2379 def make_url(ext): | |
| 2380 return emscripten_releases_download_url_template % ( | |
| 2381 os_name(), | |
| 2382 release, | |
| 2383 arch, | |
| 2384 ext, | |
| 2385 ) | |
| 2386 | |
| 2387 for release in recent_releases: | |
| 2388 make_url('tar.xz' if not WINDOWS else 'zip') | |
| 2389 try: | |
| 2390 urlopen(make_url('tar.xz' if not WINDOWS else 'zip')) | |
| 2391 except Exception: | |
| 2392 if not WINDOWS: | |
| 2393 # Try the old `.tbz2` name | |
| 2394 # TODO:remove this once tot builds are all using xz | |
| 2395 try: | |
| 2396 urlopen(make_url('tbz2')) | |
| 2397 except Exception: | |
| 2398 continue | |
| 2399 else: | |
| 2400 continue | |
| 2401 return release | |
| 2402 exit_with_error('failed to find build of any recent emsdk revision') | |
| 2403 | |
| 2404 | |
| 2405 def get_release_hash(arg, releases_info): | |
| 2406 return releases_info.get(arg, None) or releases_info.get(f'sdk-{arg}-64bit') | |
| 2407 | |
| 2408 | |
| 2409 def version_key(ver): | |
| 2410 return tuple(map(int, re.split('[._-]', ver)[:3])) | |
| 2411 | |
| 2412 | |
| 2413 def is_emsdk_sourced_from_github(): | |
| 2414 return os.path.exists(os.path.join(EMSDK_PATH, '.git')) | |
| 2415 | |
| 2416 | |
| 2417 def update_emsdk(): | |
| 2418 if is_emsdk_sourced_from_github(): | |
| 2419 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)') | |
| 2420 sys.exit(1) | |
| 2421 if not download_and_extract(emsdk_zip_download_url, EMSDK_PATH, clobber=False): | |
| 2422 sys.exit(1) | |
| 2423 | |
| 2424 | |
| 2425 # Lists all legacy (pre-emscripten-releases) tagged versions directly in the Git | |
| 2426 # repositories. These we can pull and compile from source. | |
| 2427 def load_legacy_emscripten_tags(): | |
| 2428 return open(sdk_path('legacy-emscripten-tags.txt')).read().split('\n') | |
| 2429 | |
| 2430 | |
| 2431 def load_legacy_binaryen_tags(): | |
| 2432 return open(sdk_path('legacy-binaryen-tags.txt')).read().split('\n') | |
| 2433 | |
| 2434 | |
| 2435 def remove_prefix(s, prefix): | |
| 2436 if s.startswith(prefix): | |
| 2437 return s[len(prefix):] | |
| 2438 else: | |
| 2439 return s | |
| 2440 | |
| 2441 | |
| 2442 def remove_suffix(s, suffix): | |
| 2443 if s.endswith(suffix): | |
| 2444 return s[:len(s) - len(suffix)] | |
| 2445 else: | |
| 2446 return s | |
| 2447 | |
| 2448 | |
| 2449 # filename should be one of: 'llvm-precompiled-tags-32bit.txt', 'llvm-precompiled-tags-64bit.txt' | |
| 2450 def load_file_index_list(filename): | |
| 2451 items = open(sdk_path(filename)).read().splitlines() | |
| 2452 items = [remove_suffix(remove_suffix(remove_prefix(x, 'emscripten-llvm-e'), '.tar.gz'), '.zip').strip() for x in items] | |
| 2453 items = [x for x in items if 'latest' not in x and len(x) > 0] | |
| 2454 | |
| 2455 # Sort versions from oldest to newest (the default sort would be | |
| 2456 # lexicographic, i.e. '1.37.1 < 1.37.10 < 1.37.2') | |
| 2457 return sorted(items, key=version_key) | |
| 2458 | |
| 2459 | |
| 2460 # Load the json info for emscripten-releases. | |
| 2461 def load_releases_info(): | |
| 2462 if not hasattr(load_releases_info, 'cached_info'): | |
| 2463 try: | |
| 2464 text = open(sdk_path('emscripten-releases-tags.json')).read() | |
| 2465 load_releases_info.cached_info = json.loads(text) | |
| 2466 except Exception as e: | |
| 2467 print('Error parsing emscripten-releases-tags.json!') | |
| 2468 exit_with_error(str(e)) | |
| 2469 | |
| 2470 return load_releases_info.cached_info | |
| 2471 | |
| 2472 | |
| 2473 def get_installed_sdk_version(): | |
| 2474 version_file = sdk_path(os.path.join('upstream', '.emsdk_version')) | |
| 2475 if not os.path.exists(version_file): | |
| 2476 return None | |
| 2477 with open(version_file) as f: | |
| 2478 version = f.read() | |
| 2479 return version.split('-')[1] | |
| 2480 | |
| 2481 | |
| 2482 # Get a list of tags for emscripten-releases. | |
| 2483 def load_releases_tags(): | |
| 2484 tags = [] | |
| 2485 info = load_releases_info() | |
| 2486 | |
| 2487 for _version, sha in sorted(info['releases'].items(), key=lambda x: version_key(x[0])): | |
| 2488 tags.append(sha) | |
| 2489 | |
| 2490 if extra_release_tag: | |
| 2491 tags.append(extra_release_tag) | |
| 2492 | |
| 2493 # Explicitly add the currently installed SDK version. This could be a custom | |
| 2494 # version (installed explicitly) so it might not be part of the main list | |
| 2495 # loaded above. | |
| 2496 installed = get_installed_sdk_version() | |
| 2497 if installed and installed not in tags: | |
| 2498 tags.append(installed) | |
| 2499 | |
| 2500 return tags | |
| 2501 | |
| 2502 | |
| 2503 def load_releases_versions(): | |
| 2504 info = load_releases_info() | |
| 2505 versions = list(info['releases'].keys()) | |
| 2506 return versions | |
| 2507 | |
| 2508 | |
| 2509 def load_sdk_manifest(): | |
| 2510 try: | |
| 2511 manifest = json.loads(open(sdk_path("emsdk_manifest.json")).read()) | |
| 2512 except Exception as e: | |
| 2513 print('Error parsing emsdk_manifest.json!') | |
| 2514 print(str(e)) | |
| 2515 return | |
| 2516 | |
| 2517 emscripten_tags = load_legacy_emscripten_tags() | |
| 2518 llvm_precompiled_tags_32bit = [] | |
| 2519 llvm_precompiled_tags_64bit = load_file_index_list('llvm-tags-64bit.txt') | |
| 2520 llvm_precompiled_tags = llvm_precompiled_tags_32bit + llvm_precompiled_tags_64bit | |
| 2521 binaryen_tags = load_legacy_binaryen_tags() | |
| 2522 releases_tags = load_releases_tags() | |
| 2523 | |
| 2524 def dependencies_exist(sdk): | |
| 2525 for tool_name in sdk.uses: | |
| 2526 tool = find_tool(tool_name) | |
| 2527 if not tool: | |
| 2528 debug_print('missing dependency: ' + tool_name) | |
| 2529 return False | |
| 2530 return True | |
| 2531 | |
| 2532 def cmp_version(ver, cmp_operand, reference): | |
| 2533 if cmp_operand == '<=': | |
| 2534 return version_key(ver) <= version_key(reference) | |
| 2535 if cmp_operand == '<': | |
| 2536 return version_key(ver) < version_key(reference) | |
| 2537 if cmp_operand == '>=': | |
| 2538 return version_key(ver) >= version_key(reference) | |
| 2539 if cmp_operand == '>': | |
| 2540 return version_key(ver) > version_key(reference) | |
| 2541 if cmp_operand == '==': | |
| 2542 return version_key(ver) == version_key(reference) | |
| 2543 if cmp_operand == '!=': | |
| 2544 return version_key(ver) != version_key(reference) | |
| 2545 raise Exception(f'Invalid cmp_operand "{cmp_operand}"!') | |
| 2546 | |
| 2547 def passes_filters(param, ver, filters): | |
| 2548 for v in filters: | |
| 2549 if v[0] == param and not cmp_version(ver, v[1], v[2]): | |
| 2550 return False | |
| 2551 return True | |
| 2552 | |
| 2553 # A 'category parameter' is a %foo%-encoded identifier that specifies | |
| 2554 # a class of tools instead of just one tool, e.g. %tag% | |
| 2555 def expand_category_param(param, category_list, t, is_sdk): | |
| 2556 for i, ver in enumerate(category_list): | |
| 2557 if not ver.strip(): | |
| 2558 continue | |
| 2559 t2 = copy.copy(t) | |
| 2560 found_param = False | |
| 2561 for p, v in vars(t2).items(): | |
| 2562 if isinstance(v, str) and param in v: | |
| 2563 t2.__dict__[p] = v.replace(param, ver) | |
| 2564 found_param = True | |
| 2565 if not found_param: | |
| 2566 continue | |
| 2567 t2.is_old = i < len(category_list) - 2 | |
| 2568 if hasattr(t2, 'uses'): | |
| 2569 t2.uses = [x.replace(param, ver) for x in t2.uses] | |
| 2570 | |
| 2571 # Filter out expanded tools by version requirements, such as ["tag", "<=", "1.37.22"] | |
| 2572 if hasattr(t2, 'version_filter'): | |
| 2573 passes = passes_filters(param, ver, t2.version_filter) | |
| 2574 if not passes: | |
| 2575 continue | |
| 2576 | |
| 2577 if is_sdk: | |
| 2578 if dependencies_exist(t2): | |
| 2579 if not find_sdk(t2.name): | |
| 2580 add_sdk(t2) | |
| 2581 else: | |
| 2582 debug_print('SDK ' + str(t2) + ' already existed in manifest, not adding twice') | |
| 2583 else: | |
| 2584 if not find_tool(t2.name): | |
| 2585 add_tool(t2) | |
| 2586 else: | |
| 2587 debug_print('Tool ' + str(t2) + ' already existed in manifest, not adding twice') | |
| 2588 | |
| 2589 for tool in manifest['tools']: | |
| 2590 t = Tool(tool) | |
| 2591 if t.compatible_with_this_os(): | |
| 2592 if not hasattr(t, 'is_old'): | |
| 2593 t.is_old = False | |
| 2594 | |
| 2595 # Expand the metapackages that refer to tags | |
| 2596 if '%tag%' in t.version: | |
| 2597 expand_category_param('%tag%', emscripten_tags, t, is_sdk=False) | |
| 2598 elif '%precompiled_tag%' in t.version: | |
| 2599 expand_category_param('%precompiled_tag%', llvm_precompiled_tags, t, is_sdk=False) | |
| 2600 elif '%precompiled_tag32%' in t.version: | |
| 2601 expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, t, is_sdk=False) | |
| 2602 elif '%precompiled_tag64%' in t.version: | |
| 2603 expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, t, is_sdk=False) | |
| 2604 elif '%binaryen_tag%' in t.version: | |
| 2605 expand_category_param('%binaryen_tag%', binaryen_tags, t, is_sdk=False) | |
| 2606 elif '%releases-tag%' in t.version: | |
| 2607 expand_category_param('%releases-tag%', releases_tags, t, is_sdk=False) | |
| 2608 else: | |
| 2609 add_tool(t) | |
| 2610 | |
| 2611 for sdk_str in manifest['sdks']: | |
| 2612 sdk_str['id'] = 'sdk' | |
| 2613 sdk = Tool(sdk_str) | |
| 2614 if sdk.compatible_with_this_os(): | |
| 2615 if not hasattr(sdk, 'is_old'): | |
| 2616 sdk.is_old = False | |
| 2617 | |
| 2618 if '%tag%' in sdk.version: | |
| 2619 expand_category_param('%tag%', emscripten_tags, sdk, is_sdk=True) | |
| 2620 elif '%precompiled_tag%' in sdk.version: | |
| 2621 expand_category_param('%precompiled_tag%', llvm_precompiled_tags, sdk, is_sdk=True) | |
| 2622 elif '%precompiled_tag32%' in sdk.version: | |
| 2623 expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, sdk, is_sdk=True) | |
| 2624 elif '%precompiled_tag64%' in sdk.version: | |
| 2625 expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, sdk, is_sdk=True) | |
| 2626 elif '%releases-tag%' in sdk.version: | |
| 2627 expand_category_param('%releases-tag%', releases_tags, sdk, is_sdk=True) | |
| 2628 else: | |
| 2629 add_sdk(sdk) | |
| 2630 | |
| 2631 | |
| 2632 # Tests if the two given tools can be active at the same time. | |
| 2633 # Currently only a simple check for name for same tool with different versions, | |
| 2634 # possibly adds more logic in the future. | |
| 2635 def can_simultaneously_activate(tool1, tool2): | |
| 2636 return tool1.id != tool2.id | |
| 2637 | |
| 2638 | |
| 2639 # Expands dependencies for each tool, and removes ones that don't exist. | |
| 2640 def process_tool_list(tools_to_activate): | |
| 2641 i = 0 | |
| 2642 # Gather dependencies for each tool | |
| 2643 while i < len(tools_to_activate): | |
| 2644 tool = tools_to_activate[i] | |
| 2645 deps = tool.recursive_dependencies() | |
| 2646 tools_to_activate = tools_to_activate[:i] + deps + tools_to_activate[i:] | |
| 2647 i += len(deps) + 1 | |
| 2648 | |
| 2649 for tool in tools_to_activate: | |
| 2650 if not tool.is_installed(): | |
| 2651 exit_with_error("error: tool is not installed and therefore cannot be activated: '%s'" % tool) | |
| 2652 | |
| 2653 # Remove conflicting tools | |
| 2654 i = 0 | |
| 2655 while i < len(tools_to_activate): | |
| 2656 j = 0 | |
| 2657 while j < i: | |
| 2658 secondary_tool = tools_to_activate[j] | |
| 2659 primary_tool = tools_to_activate[i] | |
| 2660 if not can_simultaneously_activate(primary_tool, secondary_tool): | |
| 2661 tools_to_activate.pop(j) | |
| 2662 j -= 1 | |
| 2663 i -= 1 | |
| 2664 j += 1 | |
| 2665 i += 1 | |
| 2666 return tools_to_activate | |
| 2667 | |
| 2668 | |
| 2669 def write_set_env_script(env_string): | |
| 2670 assert CMD or POWERSHELL | |
| 2671 open(EMSDK_SET_ENV, 'w').write(env_string) | |
| 2672 | |
| 2673 | |
| 2674 # Reconfigure .emscripten to choose the currently activated toolset, set PATH | |
| 2675 # and other environment variables. | |
| 2676 # Returns the full list of deduced tools that are now active. | |
| 2677 def set_active_tools(tools_to_activate, permanently_activate, system): | |
| 2678 tools_to_activate = process_tool_list(tools_to_activate) | |
| 2679 | |
| 2680 if tools_to_activate: | |
| 2681 tools = [x for x in tools_to_activate if not x.is_sdk] | |
| 2682 print('Setting the following tools as active:\n ' + '\n '.join([str(t) for t in tools])) | |
| 2683 print('') | |
| 2684 | |
| 2685 generate_em_config(tools_to_activate, permanently_activate, system) | |
| 2686 | |
| 2687 # Construct a .bat or .ps1 script that will be invoked to set env. vars and PATH | |
| 2688 # We only do this on cmd or powershell since emsdk.bat/ps1 is able to modify the | |
| 2689 # calling shell environment. On other shell `source emsdk_env.sh` is | |
| 2690 # required. | |
| 2691 if CMD or POWERSHELL: | |
| 2692 # always set local environment variables since permanently activating will only set the registry settings and | |
| 2693 # will not affect the current session | |
| 2694 env_vars_to_add = get_env_vars_to_add(tools_to_activate, system, user=permanently_activate) | |
| 2695 env_string = construct_env_with_vars(env_vars_to_add) | |
| 2696 write_set_env_script(env_string) | |
| 2697 | |
| 2698 if WINDOWS and permanently_activate: | |
| 2699 win_set_environment_variables(env_vars_to_add, system, user=permanently_activate) | |
| 2700 | |
| 2701 return tools_to_activate | |
| 2702 | |
| 2703 | |
| 2704 def currently_active_sdk(): | |
| 2705 for sdk in reversed(sdks): | |
| 2706 if sdk.is_active(): | |
| 2707 return sdk | |
| 2708 return None | |
| 2709 | |
| 2710 | |
| 2711 def currently_active_tools(): | |
| 2712 active_tools = [] | |
| 2713 for tool in tools: | |
| 2714 if tool.is_active(): | |
| 2715 active_tools += [tool] | |
| 2716 return active_tools | |
| 2717 | |
| 2718 | |
| 2719 # http://stackoverflow.com/questions/480214/how-do-you-remove-duplicates-from-a-list-in-python-whilst-preserving-order | |
| 2720 def unique_items(seq): | |
| 2721 seen = set() | |
| 2722 seen_add = seen.add | |
| 2723 return [x for x in seq if x not in seen and not seen_add(x)] | |
| 2724 | |
| 2725 | |
| 2726 # Tests if a path is contained in the given list, but with separators normalized. | |
| 2727 def normalized_contains(lst, elem): | |
| 2728 elem = to_unix_path(elem) | |
| 2729 for e in lst: | |
| 2730 if elem == to_unix_path(e): | |
| 2731 return True | |
| 2732 return False | |
| 2733 | |
| 2734 | |
| 2735 def to_msys_path(p): | |
| 2736 p = to_unix_path(p) | |
| 2737 new_path = re.sub(r'([a-zA-Z]):/(.*)', r'/\1/\2', p) | |
| 2738 if len(new_path) > 3 and new_path[0] == '/' and new_path[2] == '/': | |
| 2739 new_path = new_path[0] + new_path[1].lower() + new_path[2:] | |
| 2740 return new_path | |
| 2741 | |
| 2742 | |
| 2743 # Looks at the current PATH and adds and removes entries so that the PATH reflects | |
| 2744 # the set of given active tools. | |
| 2745 def adjusted_path(tools_to_activate, system=False, user=False): | |
| 2746 # These directories should be added to PATH | |
| 2747 path_add = get_required_path(tools_to_activate) | |
| 2748 # These already exist. | |
| 2749 if WINDOWS and not MSYS: | |
| 2750 existing_path = win_get_environment_variable('PATH', system=system, user=user, fallback=True).split(ENVPATH_SEPARATOR) | |
| 2751 else: | |
| 2752 existing_path = os.environ['PATH'].split(ENVPATH_SEPARATOR) | |
| 2753 | |
| 2754 existing_emsdk_tools = [] | |
| 2755 existing_nonemsdk_path = [] | |
| 2756 for entry in existing_path: | |
| 2757 if to_unix_path(entry).startswith(EMSDK_PATH): | |
| 2758 existing_emsdk_tools.append(entry) | |
| 2759 else: | |
| 2760 existing_nonemsdk_path.append(entry) | |
| 2761 | |
| 2762 new_emsdk_tools = [] | |
| 2763 kept_emsdk_tools = [] | |
| 2764 for entry in path_add: | |
| 2765 if not normalized_contains(existing_emsdk_tools, entry): | |
| 2766 new_emsdk_tools.append(entry) | |
| 2767 else: | |
| 2768 kept_emsdk_tools.append(entry) | |
| 2769 | |
| 2770 whole_path = unique_items(new_emsdk_tools + kept_emsdk_tools + existing_nonemsdk_path) | |
| 2771 | |
| 2772 if MSYS: | |
| 2773 # XXX Hack: If running native Windows Python in MSYS prompt where PATH | |
| 2774 # entries look like "/c/Windows/System32", os.environ['PATH'] | |
| 2775 # in Python will transform to show them as "C:\\Windows\\System32", so need | |
| 2776 # to reconvert path delimiter back to forward slashes. | |
| 2777 whole_path = [to_msys_path(p) for p in whole_path] | |
| 2778 new_emsdk_tools = [to_msys_path(p) for p in new_emsdk_tools] | |
| 2779 | |
| 2780 separator = ':' if MSYS else ENVPATH_SEPARATOR | |
| 2781 return (separator.join(whole_path), new_emsdk_tools) | |
| 2782 | |
| 2783 | |
| 2784 def get_env_vars_to_add(tools_to_activate, system, user): | |
| 2785 env_vars_to_add = [] | |
| 2786 | |
| 2787 newpath, added_path = adjusted_path(tools_to_activate, system, user) | |
| 2788 | |
| 2789 # Don't bother setting the path if there are no changes. | |
| 2790 if os.environ['PATH'] != newpath: | |
| 2791 env_vars_to_add += [('PATH', newpath)] | |
| 2792 | |
| 2793 if added_path: | |
| 2794 info('Adding directories to PATH:') | |
| 2795 for item in added_path: | |
| 2796 info('PATH += ' + item) | |
| 2797 info('') | |
| 2798 | |
| 2799 # A core variable EMSDK points to the root of Emscripten SDK directory. | |
| 2800 env_vars_to_add += [('EMSDK', EMSDK_PATH)] | |
| 2801 | |
| 2802 for tool in tools_to_activate: | |
| 2803 for env in tool.activated_environment(): | |
| 2804 key, value = parse_key_value(env) | |
| 2805 value = to_native_path(tool.expand_vars(value)) | |
| 2806 env_vars_to_add += [(key, value)] | |
| 2807 | |
| 2808 emroot = find_emscripten_root(tools_to_activate) | |
| 2809 if emroot: | |
| 2810 # For older emscripten versions that don't use an embedded cache by | |
| 2811 # default we need to export EM_CACHE. | |
| 2812 # | |
| 2813 # Sadly, we can't put this in the config file since those older versions | |
| 2814 # also didn't read the `CACHE` key from the config file: | |
| 2815 # | |
| 2816 # History: | |
| 2817 # - 'CACHE' config started being honored in 1.39.16 | |
| 2818 # https://github.com/emscripten-core/emscripten/pull/11091 | |
| 2819 # - Default to embedded cache also started in 1.39.16 | |
| 2820 # https://github.com/emscripten-core/emscripten/pull/11126 | |
| 2821 # - Emscripten supports automatically locating the embedded | |
| 2822 # config in 1.39.13: | |
| 2823 # https://github.com/emscripten-core/emscripten/pull/10935 | |
| 2824 # | |
| 2825 # Since setting EM_CACHE in the environment effects the entire machine | |
| 2826 # we want to avoid this except when installing these older emscripten | |
| 2827 # versions that really need it. | |
| 2828 version = parse_emscripten_version(emroot) | |
| 2829 if version < [1, 39, 16]: | |
| 2830 em_cache_dir = os.path.join(emroot, 'cache') | |
| 2831 env_vars_to_add += [('EM_CACHE', em_cache_dir)] | |
| 2832 if version < [1, 39, 13]: | |
| 2833 env_vars_to_add += [('EM_CONFIG', os.path.normpath(EM_CONFIG_PATH))] | |
| 2834 | |
| 2835 return env_vars_to_add | |
| 2836 | |
| 2837 | |
| 2838 def construct_env(tools_to_activate, system, user): | |
| 2839 info('Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)') | |
| 2840 return construct_env_with_vars(get_env_vars_to_add(tools_to_activate, system, user)) | |
| 2841 | |
| 2842 | |
| 2843 def unset_env(key): | |
| 2844 if POWERSHELL: | |
| 2845 return 'Remove-Item env:%s\n' % key | |
| 2846 if CMD: | |
| 2847 return 'set %s=\n' % key | |
| 2848 if CSH: | |
| 2849 return 'unsetenv %s;\n' % key | |
| 2850 if FISH: | |
| 2851 return 'set -e %s;\n' % key | |
| 2852 if BASH: | |
| 2853 return 'unset %s;\n' % key | |
| 2854 assert False | |
| 2855 | |
| 2856 | |
| 2857 def construct_env_with_vars(env_vars_to_add): | |
| 2858 env_string = '' | |
| 2859 if env_vars_to_add: | |
| 2860 info('Setting environment variables:') | |
| 2861 | |
| 2862 for key, value in env_vars_to_add: | |
| 2863 # Don't set env vars which are already set to the correct value. | |
| 2864 if key in os.environ and to_unix_path(os.environ[key]) == to_unix_path(value): | |
| 2865 continue | |
| 2866 info(key + ' = ' + value) | |
| 2867 if POWERSHELL: | |
| 2868 env_string += f'$env:{key}="{value}"\n' | |
| 2869 elif CMD: | |
| 2870 env_string += f'SET {key}={value}\n' | |
| 2871 elif CSH: | |
| 2872 env_string += f'setenv {key} "{value}";\n' | |
| 2873 elif FISH: | |
| 2874 env_string += f'set -gx {key} "{value}";\n' | |
| 2875 elif BASH: | |
| 2876 env_string += f'export {key}="{value}";\n' | |
| 2877 else: | |
| 2878 assert False | |
| 2879 | |
| 2880 if 'EMSDK_PYTHON' in env_vars_to_add: | |
| 2881 # When using our bundled python we never want the user's | |
| 2882 # PYTHONHOME or PYTHONPATH | |
| 2883 # See https://github.com/emscripten-core/emsdk/issues/598 | |
| 2884 env_string += unset_env('PYTHONHOME') | |
| 2885 env_string += unset_env('PYTHONPATH') | |
| 2886 | |
| 2887 # Remove any environment variables that might have been set by old or | |
| 2888 # inactive tools/sdks. For example, we set EM_CACHE for older versions | |
| 2889 # of the SDK but we want to remove that from the current environment | |
| 2890 # if no such tool is active. | |
| 2891 # Ignore certain keys that are inputs to emsdk itself. | |
| 2892 ignore_keys = {'EMSDK_POWERSHELL', 'EMSDK_CSH', 'EMSDK_CMD', 'EMSDK_BASH', 'EMSDK_FISH', | |
| 2893 'EMSDK_NUM_CORES', 'EMSDK_NOTTY', 'EMSDK_KEEP_DOWNLOADS'} | |
| 2894 env_keys_to_add = {pair[0] for pair in env_vars_to_add} | |
| 2895 for key in os.environ: | |
| 2896 if key.startswith('EMSDK_') or key in ('EM_CACHE', 'EM_CONFIG'): | |
| 2897 if key not in env_keys_to_add and key not in ignore_keys: | |
| 2898 info('Clearing existing environment variable: %s' % key) | |
| 2899 env_string += unset_env(key) | |
| 2900 | |
| 2901 return env_string | |
| 2902 | |
| 2903 | |
| 2904 def error_on_missing_tool(name): | |
| 2905 if name.endswith('-64bit') and not is_os_64bit(): | |
| 2906 exit_with_error("'%s' is only provided for 64-bit OSes" % name) | |
| 2907 else: | |
| 2908 exit_with_error("tool or SDK not found: '%s'" % name) | |
| 2909 | |
| 2910 | |
| 2911 def expand_sdk_name(name, activating): | |
| 2912 if 'upstream-master' in name: | |
| 2913 errlog('upstream-master SDK has been renamed main') | |
| 2914 name = name.replace('upstream-master', 'main') | |
| 2915 if 'fastcomp' in name: | |
| 2916 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') | |
| 2917 if name in ('tot', 'sdk-tot', 'tot-upstream'): | |
| 2918 if activating: | |
| 2919 # When we are activating a tot release, assume that the currently | |
| 2920 # installed SDK, if any, is the tot release we want to activate. | |
| 2921 # Without this `install tot && activate tot` will race with the builders | |
| 2922 # that are producing new builds. | |
| 2923 installed = get_installed_sdk_version() | |
| 2924 if installed: | |
| 2925 debug_print('activating currently installed SDK; not updating tot version') | |
| 2926 return 'sdk-releases-%s-64bit' % installed | |
| 2927 return find_tot_sdk() | |
| 2928 | |
| 2929 if '-upstream' in name: | |
| 2930 name = name.replace('-upstream', '') | |
| 2931 | |
| 2932 name = resolve_sdk_aliases(name, verbose=True) | |
| 2933 | |
| 2934 # check if it's a release handled by an emscripten-releases version, | |
| 2935 # and if so use that by using the right hash. we support a few notations, | |
| 2936 # x.y.z | |
| 2937 # sdk-x.y.z-64bit | |
| 2938 # TODO: support short notation for old builds too? | |
| 2939 fullname = name | |
| 2940 version = fullname.replace('sdk-', '').replace('releases-', '').replace('-64bit', '').replace('tag-', '') | |
| 2941 sdk = 'sdk-' if not name.startswith('releases-') else '' | |
| 2942 releases_info = load_releases_info()['releases'] | |
| 2943 release_hash = get_release_hash(version, releases_info) | |
| 2944 if release_hash: | |
| 2945 # Known release hash | |
| 2946 full_name = '%sreleases-%s-64bit' % (sdk, release_hash) | |
| 2947 print("Resolving SDK version '%s' to '%s'" % (version, full_name)) | |
| 2948 return full_name | |
| 2949 | |
| 2950 if len(version) == 40: | |
| 2951 global extra_release_tag | |
| 2952 extra_release_tag = version | |
| 2953 return '%sreleases-%s-64bit' % (sdk, version) | |
| 2954 | |
| 2955 return name | |
| 2956 | |
| 2957 | |
| 2958 def main(args): # noqa: C901, PLR0911, PLR0912, PLR0915 | |
| 2959 if not args: | |
| 2960 errlog("Missing command; Type 'emsdk help' to get a list of commands.") | |
| 2961 return 1 | |
| 2962 | |
| 2963 debug_print('emsdk.py running under `%s`' % sys.executable) | |
| 2964 cmd = args.pop(0) | |
| 2965 | |
| 2966 if cmd in ('help', '--help', '-h'): | |
| 2967 print(' emsdk: Available commands:') | |
| 2968 | |
| 2969 print(''' | |
| 2970 emsdk list [--old] [--uses] - Lists all available SDKs and tools and their | |
| 2971 current installation status. With the --old | |
| 2972 parameter, also historical versions are | |
| 2973 shown. If --uses is passed, displays the | |
| 2974 composition of different SDK packages and | |
| 2975 dependencies. | |
| 2976 | |
| 2977 emsdk update - Updates emsdk to the newest version. If you have | |
| 2978 bootstrapped emsdk via cloning directly from | |
| 2979 GitHub, call "git pull" instead to update emsdk. | |
| 2980 | |
| 2981 emsdk install [options] <tool 1> <tool 2> <tool 3> ... | |
| 2982 - Downloads and installs given tools or SDKs. | |
| 2983 Options can contain: | |
| 2984 | |
| 2985 -j<num>: Specifies the number of cores to use when | |
| 2986 building the tool. Default: use one less | |
| 2987 than the # of detected cores. | |
| 2988 | |
| 2989 --build=<type>: Controls what kind of build of LLVM to | |
| 2990 perform. Pass either 'Debug', 'Release', | |
| 2991 'MinSizeRel' or 'RelWithDebInfo'. Default: | |
| 2992 'Release'. | |
| 2993 | |
| 2994 --generator=<type>: Specifies the CMake Generator to be used | |
| 2995 during the build. Possible values are the | |
| 2996 same as what your CMake supports and whether | |
| 2997 the generator is valid depends on the tools | |
| 2998 you have installed. Defaults to 'Unix Makefiles' | |
| 2999 on *nix systems. If generator name is multiple | |
| 3000 words, enclose with single or double quotes. | |
| 3001 | |
| 3002 --shallow: When installing tools from one of the git | |
| 3003 development branches, this parameter can be | |
| 3004 passed to perform a shallow git clone instead | |
| 3005 of a full one. This reduces the amount of | |
| 3006 network transfer that is needed. This option | |
| 3007 should only be used when you are interested in | |
| 3008 downloading one of the development branches, | |
| 3009 but are not looking to develop Emscripten | |
| 3010 yourself. Default: disabled, i.e. do a full | |
| 3011 clone. | |
| 3012 | |
| 3013 --build-tests: If enabled, LLVM is built with internal tests | |
| 3014 included. Pass this to enable running test | |
| 3015 other.test_llvm_lit in the Emscripten test | |
| 3016 suite. Default: disabled. | |
| 3017 --enable-assertions: If specified, LLVM is built with assert() | |
| 3018 checks enabled. Useful for development | |
| 3019 purposes. Default: Enabled | |
| 3020 --disable-assertions: Forces assertions off during the build. | |
| 3021 | |
| 3022 --vs2019/--vs2022: If building from source, overrides to build | |
| 3023 using the specified compiler. When installing | |
| 3024 precompiled packages, this has no effect. | |
| 3025 Note: The same compiler specifier must be | |
| 3026 passed to the emsdk activate command to | |
| 3027 activate the desired version. | |
| 3028 | |
| 3029 Notes on building from source: | |
| 3030 | |
| 3031 To pass custom CMake directives when configuring | |
| 3032 LLVM build, specify the environment variable | |
| 3033 LLVM_CMAKE_ARGS="param1=value1,param2=value2" | |
| 3034 in the environment where the build is invoked. | |
| 3035 See README.md for details. | |
| 3036 | |
| 3037 --override-repository: Specifies the git URL to use for a given Tool. E.g. | |
| 3038 --override-repository emscripten-main@https://github.com/<fork>/emscripten/tree/<refspec> | |
| 3039 | |
| 3040 | |
| 3041 emsdk deactivate tool/sdk - Removes the given tool or SDK from the current set of activated tools. | |
| 3042 | |
| 3043 | |
| 3044 emsdk uninstall <tool/sdk> - Removes the given tool or SDK from disk.''') | |
| 3045 | |
| 3046 if WINDOWS: | |
| 3047 print(''' | |
| 3048 emsdk activate [--permanent] [--system] [--build=type] [--vs2019/--vs2022] <tool/sdk> | |
| 3049 | |
| 3050 - Activates the given tool or SDK in the | |
| 3051 environment of the current shell. | |
| 3052 | |
| 3053 - If the `--permanent` option is passed, then the environment | |
| 3054 variables are set permanently for the current user. | |
| 3055 | |
| 3056 - If the `--system` option is passed, the registration | |
| 3057 is done for all users of the system. | |
| 3058 This needs admin privileges | |
| 3059 (uses Machine environment variables). | |
| 3060 | |
| 3061 - If a custom compiler version was used to override | |
| 3062 the compiler to use, pass the same --vs2019/--vs2022 | |
| 3063 parameter here to choose which version to activate. | |
| 3064 | |
| 3065 emcmdprompt.bat - Spawns a new command prompt window with the | |
| 3066 Emscripten environment active.''') | |
| 3067 else: | |
| 3068 print(''' emsdk activate [--build=type] <tool/sdk> | |
| 3069 | |
| 3070 - Activates the given tool or SDK in the | |
| 3071 environment of the current shell.''') | |
| 3072 | |
| 3073 print(''' | |
| 3074 Both commands 'install' and 'activate' accept an optional parameter | |
| 3075 '--build=type', which can be used to override what kind of installation | |
| 3076 or activation to perform. Possible values for type are Debug, Release, | |
| 3077 MinSizeRel or RelWithDebInfo. Note: When overriding a custom build type, | |
| 3078 be sure to match the same --build= option to both 'install' and | |
| 3079 'activate' commands and the invocation of 'emsdk_env', or otherwise | |
| 3080 these commands will default to operating on the default build type | |
| 3081 which is RelWithDebInfo.''') | |
| 3082 | |
| 3083 print(''' | |
| 3084 | |
| 3085 Environment: | |
| 3086 EMSDK_KEEP_DOWNLOADS=1 - if you want to keep the downloaded archives. | |
| 3087 EMSDK_NOTTY=1 - override isatty() result (mainly to log progress). | |
| 3088 EMSDK_NUM_CORES=n - limit parallelism to n cores. | |
| 3089 EMSDK_VERBOSE=1 - very verbose output, useful for debugging.''') | |
| 3090 return 0 | |
| 3091 | |
| 3092 # Extracts a boolean command line argument from args and returns True if it was present | |
| 3093 def extract_bool_arg(name): | |
| 3094 if name in args: | |
| 3095 args.remove(name) | |
| 3096 return True | |
| 3097 return False | |
| 3098 | |
| 3099 def extract_string_arg(name): | |
| 3100 for i in range(len(args)): | |
| 3101 if args[i] == name: | |
| 3102 value = args[i + 1] | |
| 3103 del args[i:i + 2] | |
| 3104 return value | |
| 3105 | |
| 3106 arg_old = extract_bool_arg('--old') | |
| 3107 arg_uses = extract_bool_arg('--uses') | |
| 3108 arg_permanent = extract_bool_arg('--permanent') | |
| 3109 arg_global = extract_bool_arg('--global') | |
| 3110 arg_system = extract_bool_arg('--system') | |
| 3111 if arg_global: | |
| 3112 print('--global is deprecated. Use `--system` to set the environment variables for all users') | |
| 3113 arg_system = True | |
| 3114 if arg_system: | |
| 3115 arg_permanent = True | |
| 3116 if extract_bool_arg('--embedded'): | |
| 3117 errlog('embedded mode is now the only mode available') | |
| 3118 if extract_bool_arg('--no-embedded'): | |
| 3119 errlog('embedded mode is now the only mode available') | |
| 3120 return 1 | |
| 3121 | |
| 3122 arg_notty = extract_bool_arg('--notty') | |
| 3123 if arg_notty: | |
| 3124 global TTY_OUTPUT | |
| 3125 TTY_OUTPUT = False | |
| 3126 | |
| 3127 # Replace meta-packages with the real package names. | |
| 3128 if cmd in ('update', 'install', 'activate'): | |
| 3129 activating = cmd == 'activate' | |
| 3130 args = [expand_sdk_name(a, activating=activating) for a in args] | |
| 3131 | |
| 3132 load_em_config() | |
| 3133 load_sdk_manifest() | |
| 3134 | |
| 3135 # Apply any overrides to git branch names to clone from. | |
| 3136 forked_url = extract_string_arg('--override-repository') | |
| 3137 while forked_url: | |
| 3138 tool_name, url_and_refspec = forked_url.split('@') | |
| 3139 t = find_tool(tool_name) | |
| 3140 if not t: | |
| 3141 errlog('Failed to find tool {tool_name}!') | |
| 3142 return False | |
| 3143 else: | |
| 3144 t.url, t.git_branch, t.remote_name = parse_github_url_and_refspec(url_and_refspec) | |
| 3145 debug_print(f'Reading git repository URL "{t.url}" and git branch "{t.git_branch}" for Tool "{tool_name}".') | |
| 3146 | |
| 3147 forked_url = extract_string_arg('--override-repository') | |
| 3148 | |
| 3149 # Process global args | |
| 3150 for i in range(len(args)): | |
| 3151 if args[i].startswith('--generator='): | |
| 3152 build_generator = re.match(r'''^--generator=['"]?([^'"]+)['"]?$''', args[i]) | |
| 3153 if build_generator: | |
| 3154 global CMAKE_GENERATOR | |
| 3155 CMAKE_GENERATOR = build_generator.group(1) | |
| 3156 args[i] = '' | |
| 3157 else: | |
| 3158 errlog(f"Cannot parse CMake generator string: {args[i]}. Try wrapping generator string with quotes") | |
| 3159 return 1 | |
| 3160 elif args[i].startswith('--build='): | |
| 3161 build_type = re.match(r'^--build=(.+)$', args[i]) | |
| 3162 if build_type: | |
| 3163 global CMAKE_BUILD_TYPE_OVERRIDE | |
| 3164 build_type = build_type.group(1) | |
| 3165 build_types = ['Debug', 'MinSizeRel', 'RelWithDebInfo', 'Release'] | |
| 3166 try: | |
| 3167 build_type_index = [x.lower() for x in build_types].index(build_type.lower()) | |
| 3168 CMAKE_BUILD_TYPE_OVERRIDE = build_types[build_type_index] | |
| 3169 args[i] = '' | |
| 3170 except Exception: | |
| 3171 errlog(f'Unknown CMake build type "{build_type}" specified! Please specify one of {build_types}') | |
| 3172 return 1 | |
| 3173 else: | |
| 3174 errlog(f'Invalid command line parameter {args[i]} specified!') | |
| 3175 return 1 | |
| 3176 args = [x for x in args if x] | |
| 3177 | |
| 3178 if cmd == 'list': | |
| 3179 print('') | |
| 3180 | |
| 3181 def installed_sdk_text(name): | |
| 3182 sdk = find_sdk(name) | |
| 3183 return 'INSTALLED' if sdk and sdk.is_installed() else '' | |
| 3184 | |
| 3185 if (LINUX or MACOS or WINDOWS) and ARCH in ('x86', 'x86_64'): | |
| 3186 print('The *recommended* precompiled SDK download is %s (%s).' % (find_latest_version(), find_latest_hash())) | |
| 3187 print() | |
| 3188 print('To install/activate it use:') | |
| 3189 print(' latest') | |
| 3190 print('') | |
| 3191 print('This is equivalent to installing/activating:') | |
| 3192 print(' %s %s' % (find_latest_version(), installed_sdk_text(find_latest_sdk()))) | |
| 3193 print('') | |
| 3194 else: | |
| 3195 print('Warning: your platform does not have precompiled SDKs available.') | |
| 3196 print('You may install components from source.') | |
| 3197 print('') | |
| 3198 | |
| 3199 print('All recent (non-legacy) installable versions are:') | |
| 3200 releases_versions = sorted(load_releases_versions(), key=version_key, reverse=True) | |
| 3201 releases_info = load_releases_info()['releases'] | |
| 3202 for ver in releases_versions: | |
| 3203 print(' %s %s' % (ver, installed_sdk_text('sdk-releases-%s-64bit' % get_release_hash(ver, releases_info)))) | |
| 3204 print() | |
| 3205 | |
| 3206 # Use array to work around the lack of being able to mutate from enclosing | |
| 3207 # function. | |
| 3208 has_partially_active_tools = [False] | |
| 3209 | |
| 3210 if sdks: | |
| 3211 def find_sdks(needs_compilation): | |
| 3212 s = [] | |
| 3213 for sdk in sdks: | |
| 3214 if sdk.is_old and not arg_old: | |
| 3215 continue | |
| 3216 if sdk.needs_compilation() == needs_compilation: | |
| 3217 s += [sdk] | |
| 3218 return s | |
| 3219 | |
| 3220 def print_sdks(s): | |
| 3221 for sdk in s: | |
| 3222 installed = '\tINSTALLED' if sdk.is_installed() else '' | |
| 3223 active = '*' if sdk.is_active() else ' ' | |
| 3224 print(' ' + active + ' {0: <25}'.format(str(sdk)) + installed) | |
| 3225 if arg_uses: | |
| 3226 for dep in sdk.uses: | |
| 3227 print(' - {0: <25}'.format(dep)) | |
| 3228 print('') | |
| 3229 print('The additional following precompiled SDKs are also available for download:') | |
| 3230 print_sdks(find_sdks(False)) | |
| 3231 | |
| 3232 print('The following SDKs can be compiled from source:') | |
| 3233 print_sdks(find_sdks(True)) | |
| 3234 | |
| 3235 if tools: | |
| 3236 def find_tools(needs_compilation): | |
| 3237 t = [] | |
| 3238 for tool in tools: | |
| 3239 if tool.is_old and not arg_old: | |
| 3240 continue | |
| 3241 if tool.needs_compilation() != needs_compilation: | |
| 3242 continue | |
| 3243 t += [tool] | |
| 3244 return t | |
| 3245 | |
| 3246 def print_tools(t): | |
| 3247 for tool in t: | |
| 3248 if tool.is_old and not arg_old: | |
| 3249 continue | |
| 3250 if tool.can_be_installed() is True: | |
| 3251 installed = '\tINSTALLED' if tool.is_installed() else '' | |
| 3252 else: | |
| 3253 installed = '\tNot available: ' + tool.can_be_installed() | |
| 3254 tool_is_active = tool.is_active() | |
| 3255 tool_is_env_active = tool_is_active and tool.is_env_active() | |
| 3256 if tool_is_env_active: | |
| 3257 active = ' * ' | |
| 3258 elif tool_is_active: | |
| 3259 active = '(*)' | |
| 3260 has_partially_active_tools[0] = has_partially_active_tools[0] or True | |
| 3261 else: | |
| 3262 active = ' ' | |
| 3263 print(' ' + active + ' {0: <25}'.format(str(tool)) + installed) | |
| 3264 print('') | |
| 3265 | |
| 3266 print('The following precompiled tool packages are available for download:') | |
| 3267 print_tools(find_tools(needs_compilation=False)) | |
| 3268 print('The following tools can be compiled from source:') | |
| 3269 print_tools(find_tools(needs_compilation=True)) | |
| 3270 else: | |
| 3271 if is_emsdk_sourced_from_github(): | |
| 3272 print("There are no tools available. Run 'git pull' to fetch the latest set of tools.") | |
| 3273 else: | |
| 3274 print("There are no tools available. Run 'emsdk update' to fetch the latest set of tools.") | |
| 3275 print('') | |
| 3276 | |
| 3277 print('Items marked with * are activated for the current user.') | |
| 3278 if has_partially_active_tools[0]: | |
| 3279 env_cmd = 'emsdk_env.bat' if WINDOWS else 'source ./emsdk_env.sh' | |
| 3280 print(f'Items marked with (*) are selected for use, but your current shell environment is not configured to use them. 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 '.')) | |
| 3281 if not arg_old: | |
| 3282 print('') | |
| 3283 print("To access the historical archived versions, type 'emsdk list --old'") | |
| 3284 | |
| 3285 print('') | |
| 3286 if is_emsdk_sourced_from_github(): | |
| 3287 print('Run "git pull" to pull in the latest list.') | |
| 3288 else: | |
| 3289 print('Run "./emsdk update" to pull in the latest list.') | |
| 3290 | |
| 3291 return 0 | |
| 3292 elif cmd == 'construct_env': | |
| 3293 # Clean up old temp file up front, in case of failure later before we get | |
| 3294 # to write out the new one. | |
| 3295 tools_to_activate = currently_active_tools() | |
| 3296 tools_to_activate = process_tool_list(tools_to_activate) | |
| 3297 env_string = construct_env(tools_to_activate, arg_system, arg_permanent) | |
| 3298 if CMD or POWERSHELL: | |
| 3299 write_set_env_script(env_string) | |
| 3300 else: | |
| 3301 sys.stdout.write(env_string) | |
| 3302 return 0 | |
| 3303 elif cmd == 'update': | |
| 3304 update_emsdk() | |
| 3305 if WINDOWS: | |
| 3306 # Clean up litter after old emsdk update which may have left this temp | |
| 3307 # file around. | |
| 3308 rmfile(sdk_path(EMSDK_SET_ENV)) | |
| 3309 return 0 | |
| 3310 elif cmd == 'update-tags': | |
| 3311 errlog('`update-tags` is not longer needed. To install the latest tot release just run `install tot`') | |
| 3312 return 0 | |
| 3313 elif cmd in ('activate', 'deactivate'): | |
| 3314 if arg_permanent: | |
| 3315 print('Registering active Emscripten environment permanently') | |
| 3316 print('') | |
| 3317 | |
| 3318 tools_to_activate = currently_active_tools() | |
| 3319 for arg in args: | |
| 3320 tool = find_tool(arg) | |
| 3321 if tool is None: | |
| 3322 tool = find_sdk(arg) | |
| 3323 if tool is None: | |
| 3324 error_on_missing_tool(arg) | |
| 3325 | |
| 3326 if cmd == 'activate': | |
| 3327 tools_to_activate += [tool] | |
| 3328 elif tool in tools_to_activate: | |
| 3329 print('Deactivating tool ' + str(tool) + '.') | |
| 3330 tools_to_activate.remove(tool) | |
| 3331 else: | |
| 3332 print(f'Tool "{arg}" was not active, no need to deactivate.') | |
| 3333 if not tools_to_activate: | |
| 3334 errlog('No tools/SDKs specified to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]') | |
| 3335 return 1 | |
| 3336 active_tools = set_active_tools(tools_to_activate, permanently_activate=arg_permanent, system=arg_system) | |
| 3337 if not active_tools: | |
| 3338 errlog('No tools/SDKs found to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]') | |
| 3339 return 1 | |
| 3340 if WINDOWS and not arg_permanent: | |
| 3341 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.') | |
| 3342 return 0 | |
| 3343 elif cmd == 'install': | |
| 3344 global BUILD_FOR_TESTING, ENABLE_LLVM_ASSERTIONS, CPU_CORES, GIT_CLONE_SHALLOW | |
| 3345 | |
| 3346 # Process args | |
| 3347 for i in range(len(args)): | |
| 3348 if args[i].startswith('-j'): | |
| 3349 multicore = re.match(r'^-j(\d+)$', args[i]) | |
| 3350 if multicore: | |
| 3351 CPU_CORES = int(multicore.group(1)) | |
| 3352 args[i] = '' | |
| 3353 else: | |
| 3354 errlog(f'Invalid command line parameter {args[i]} specified!') | |
| 3355 return 1 | |
| 3356 elif args[i] == '--shallow': | |
| 3357 GIT_CLONE_SHALLOW = True | |
| 3358 args[i] = '' | |
| 3359 elif args[i] == '--build-tests': | |
| 3360 BUILD_FOR_TESTING = True | |
| 3361 args[i] = '' | |
| 3362 elif args[i] == '--enable-assertions': | |
| 3363 ENABLE_LLVM_ASSERTIONS = 'ON' | |
| 3364 args[i] = '' | |
| 3365 elif args[i] == '--disable-assertions': | |
| 3366 ENABLE_LLVM_ASSERTIONS = 'OFF' | |
| 3367 args[i] = '' | |
| 3368 args = [x for x in args if x] | |
| 3369 if not args: | |
| 3370 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.") | |
| 3371 return 1 | |
| 3372 | |
| 3373 if LINUX and ARCH == 'arm64' and args != ['latest']: | |
| 3374 errlog('WARNING: arm64-linux binaries are not available for all releases.') | |
| 3375 errlog('See https://github.com/emscripten-core/emsdk/issues/547') | |
| 3376 | |
| 3377 for t in args: | |
| 3378 tool = find_tool(t) | |
| 3379 if tool is None: | |
| 3380 tool = find_sdk(t) | |
| 3381 if tool is None: | |
| 3382 error_on_missing_tool(t) | |
| 3383 tool.install() | |
| 3384 return 0 | |
| 3385 elif cmd == 'uninstall': | |
| 3386 if not args: | |
| 3387 errlog("Syntax error. Call 'emsdk uninstall <tool name>'. Call 'emsdk list' to obtain a list of available tools.") | |
| 3388 return 1 | |
| 3389 tool = find_tool(args[0]) | |
| 3390 if tool is None: | |
| 3391 errlog(f"Error: Tool by name '{args[0]}' was not found.") | |
| 3392 return 1 | |
| 3393 tool.uninstall() | |
| 3394 return 0 | |
| 3395 | |
| 3396 errlog(f"Unknown command '{cmd}' given! Type 'emsdk help' to get a list of commands.") | |
| 3397 return 1 | |
| 3398 | |
| 3399 | |
| 3400 if __name__ == '__main__': | |
| 3401 try: | |
| 3402 sys.exit(main(sys.argv[1:])) | |
| 3403 except KeyboardInterrupt: | |
| 3404 exit_with_error('aborted by user, exiting') | |
| 3405 sys.exit(1) |