| |
| |
| |
| |
| |
|
|
| import copy |
| import errno |
| import json |
| import multiprocessing |
| import os |
| import os.path |
| import platform |
| import re |
| import shutil |
| import stat |
| import subprocess |
| import sys |
| import sysconfig |
| import tarfile |
| import zipfile |
| from collections import OrderedDict |
|
|
| if os.name == 'nt': |
| import ctypes.wintypes |
| import winreg |
|
|
| from urllib.parse import urljoin |
| from urllib.request import urlopen |
|
|
| if sys.version_info < (3, 2): |
| print(f'error: emsdk requires python 3.2 or above ({sys.executable} {sys.version})', file=sys.stderr) |
| sys.exit(1) |
|
|
| emsdk_packages_url = 'https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/' |
|
|
| emscripten_releases_repo = 'https://chromium.googlesource.com/emscripten-releases' |
|
|
| emscripten_releases_download_url_template = "https://storage.googleapis.com/webassembly/emscripten-releases-builds/%s/%s/wasm-binaries%s.%s" |
|
|
| |
| |
| |
| emsdk_zip_download_url = 'https://github.com/emscripten-core/emsdk/archive/HEAD.zip' |
|
|
| download_dir = 'downloads/' |
|
|
| extra_release_tag = None |
|
|
|
|
| def get_env_boolean(name): |
| env_var = os.getenv(name) |
| assert env_var in {None, '1', '0'}, f'invalid environment variable setting ${env_var} for ${name}' |
| return env_var == '1' |
|
|
|
|
| |
| |
| VERBOSE = get_env_boolean('EMSDK_VERBOSE') |
| QUIET = get_env_boolean('EMSDK_QUIET') |
| if get_env_boolean('EMSDK_NOTTY'): |
| TTY_OUTPUT = False |
| else: |
| TTY_OUTPUT = sys.stdout.isatty() |
|
|
|
|
| def info(msg): |
| if not QUIET: |
| print(msg, file=sys.stderr) |
|
|
|
|
| def errlog(msg): |
| print(msg, file=sys.stderr) |
|
|
|
|
| def exit_with_error(msg): |
| errlog('error: %s' % msg) |
| sys.exit(1) |
|
|
|
|
| WINDOWS = False |
| MINGW = False |
| MSYS = False |
| MACOS = False |
| LINUX = False |
|
|
| os_override = os.environ.get('EMSDK_OS') |
| if os_override: |
| if os_override == 'windows': |
| WINDOWS = True |
| elif os_override == 'linux': |
| LINUX = True |
| elif os_override == 'macos': |
| MACOS = True |
| else: |
| assert False, 'EMSDK_OS must be one of: windows, linux, macos' |
| else: |
| if os.name == 'nt' or ('windows' in os.getenv('SYSTEMROOT', '').lower()) or ('windows' in os.getenv('COMSPEC', '').lower()): |
| WINDOWS = True |
|
|
| if os.getenv('MSYSTEM'): |
| MSYS = True |
| |
| |
| |
| if sysconfig.get_platform() == 'mingw': |
| MINGW = True |
| if os.getenv('MSYSTEM') != 'MSYS' and os.getenv('MSYSTEM') != 'MINGW64': |
| |
| 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.') |
|
|
| if platform.mac_ver()[0]: |
| MACOS = True |
|
|
| if not MACOS and (platform.system() == 'Linux'): |
| LINUX = True |
|
|
| UNIX = (MACOS or LINUX) |
|
|
|
|
| |
| POWERSHELL = get_env_boolean('EMSDK_POWERSHELL') |
| CSH = get_env_boolean('EMSDK_CSH') |
| CMD = get_env_boolean('EMSDK_CMD') |
| BASH = get_env_boolean('EMSDK_BASH') |
| FISH = get_env_boolean('EMSDK_FISH') |
|
|
| if WINDOWS and BASH: |
| MSYS = True |
|
|
| if not CSH and not POWERSHELL and not BASH and not CMD and not FISH: |
| |
| if WINDOWS and not MSYS: |
| CMD = True |
| else: |
| BASH = True |
|
|
| if WINDOWS: |
| ENVPATH_SEPARATOR = ';' |
| else: |
| ENVPATH_SEPARATOR = ':' |
|
|
| |
| machine = os.getenv('EMSDK_ARCH', platform.machine().lower()) |
| if machine.startswith(('x64', 'amd64', 'x86_64')): |
| ARCH = 'x86_64' |
| elif machine.endswith('86'): |
| ARCH = 'x86' |
| elif machine.startswith('aarch64') or machine.lower().startswith('arm64'): |
| ARCH = 'arm64' |
| elif machine.startswith('arm'): |
| ARCH = 'arm' |
| else: |
| exit_with_error('unknown machine architecture: ' + machine) |
|
|
|
|
| |
| CPU_CORES = int(os.getenv('EMSDK_NUM_CORES', max(multiprocessing.cpu_count() - 1, 1))) |
|
|
| CMAKE_BUILD_TYPE_OVERRIDE = None |
|
|
| |
| GIT_CLONE_SHALLOW = False |
|
|
| |
| |
| BUILD_FOR_TESTING = False |
|
|
| |
| |
| |
| ENABLE_LLVM_ASSERTIONS = 'auto' |
|
|
| |
| KEEP_DOWNLOADS = get_env_boolean('EMSDK_KEEP_DOWNLOADS') |
|
|
|
|
| def os_name(): |
| if WINDOWS: |
| return 'win' |
| elif LINUX: |
| return 'linux' |
| elif MACOS: |
| return 'mac' |
| else: |
| raise Exception('unknown OS') |
|
|
|
|
| def debug_print(msg): |
| if VERBOSE: |
| errlog(msg) |
|
|
|
|
| def to_unix_path(p): |
| return p.replace('\\', '/') |
|
|
|
|
| EMSDK_PATH = to_unix_path(os.path.dirname(os.path.realpath(__file__))) |
|
|
| EMSDK_SET_ENV = "" |
| if POWERSHELL: |
| EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.ps1') |
| else: |
| EMSDK_SET_ENV = os.path.join(EMSDK_PATH, 'emsdk_set_env.bat') |
|
|
|
|
| |
| |
| |
| |
| def parse_github_url_and_refspec(url): |
| if not url: |
| return ('', '', None) |
|
|
| if url.endswith(('/tree/', '/tree', '/commit/', '/commit')): |
| raise Exception(f'Malformed git URL and refspec {url}!') |
|
|
| if '/tree/' in url: |
| if url.endswith('/'): |
| raise Exception(f'Malformed git URL and refspec {url}!') |
| url, refspec = url.split('/tree/') |
| remote_name = url.split('/')[-2] |
| return (url, refspec, remote_name) |
| elif '/commit/' in url: |
| if url.endswith('/'): |
| raise Exception(f'Malformed git URL and refspec {url}!') |
| url, refspec = url.split('/commit/') |
| remote_name = url.split('/')[-2] |
| return (url, refspec, remote_name) |
| else: |
| return (url, 'main', None) |
|
|
|
|
| ARCHIVE_SUFFIXES = ('zip', '.tar', '.gz', '.xz', '.tbz2', '.bz2') |
|
|
|
|
| def vswhere(version): |
| try: |
| program_files = os.getenv('ProgramFiles(x86)') |
| if not program_files: |
| program_files = os.environ['ProgramFiles'] |
| vswhere_path = os.path.join(program_files, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe') |
| |
| tools_arch = 'ARM64' if ARCH == 'arm64' else 'x86.x64' |
| |
| |
| 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'])) |
| return str(output[0]['installationPath']) |
| except Exception: |
| return '' |
|
|
|
|
| CMAKE_GENERATOR = 'Unix Makefiles' |
| if WINDOWS: |
| |
| if '--mingw' in sys.argv: |
| CMAKE_GENERATOR = 'MinGW Makefiles' |
| elif '--vs2022' in sys.argv: |
| CMAKE_GENERATOR = 'Visual Studio 17' |
| elif '--vs2019' in sys.argv: |
| CMAKE_GENERATOR = 'Visual Studio 16' |
| elif len(vswhere(17)) > 0: |
| CMAKE_GENERATOR = 'Visual Studio 17' |
| elif len(vswhere(16)) > 0: |
| CMAKE_GENERATOR = 'Visual Studio 16' |
| elif shutil.which('mingw32-make') is not None and shutil.which('g++') is not None: |
| CMAKE_GENERATOR = 'MinGW Makefiles' |
| else: |
| |
| CMAKE_GENERATOR = '' |
|
|
|
|
| sys.argv = [a for a in sys.argv if a not in {'--mingw', '--vs2019', '--vs2022'}] |
|
|
|
|
| |
| def cmake_generator_prefix(): |
| if CMAKE_GENERATOR == 'Visual Studio 17': |
| return '_vs2022' |
| if CMAKE_GENERATOR == 'Visual Studio 16': |
| return '_vs2019' |
| elif CMAKE_GENERATOR == 'MinGW Makefiles': |
| return '_mingw' |
| |
| return '' |
|
|
|
|
| |
| |
| def remove_tree(d): |
| debug_print('remove_tree(' + str(d) + ')') |
| if not os.path.exists(d): |
| return |
| try: |
| def remove_readonly_and_try_again(func, path, exc_info): |
| if not (os.stat(path).st_mode & stat.S_IWRITE): |
| os.chmod(path, stat.S_IWRITE) |
| func(path) |
| else: |
| raise exc_info[1] |
| shutil.rmtree(d, onerror=remove_readonly_and_try_again) |
| except Exception as e: |
| debug_print('remove_tree threw an exception, ignoring: ' + str(e)) |
|
|
|
|
| def win_set_environment_variable_direct(key, value, system=True): |
| folder = None |
| try: |
| if system: |
| |
| folder = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 0, winreg.KEY_ALL_ACCESS) |
| else: |
| |
| folder = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_ALL_ACCESS) |
| winreg.SetValueEx(folder, key, 0, winreg.REG_EXPAND_SZ, value) |
| debug_print(f'Set key={key} with value {value} in registry.') |
| return True |
| except Exception as e: |
| |
| if e.args[3] == 5: |
| exit_with_error(f'failed to set the environment variable \'{key}\'! Setting environment variables permanently requires administrator access. Please rerun this command with administrative privileges. This can be done for example by holding down the Ctrl and Shift keys while opening a command prompt in start menu.') |
| errlog(f'Failed to write environment variable {key}:') |
| errlog(str(e)) |
| return False |
| finally: |
| if folder is not None: |
| folder.Close() |
|
|
|
|
| def win_get_environment_variable(key, system=True, user=True, fallback=True): |
| if (not system and not user and fallback): |
| |
| return os.environ[key] |
| try: |
| folder = None |
| try: |
| if system: |
| |
| folder = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment') |
| else: |
| |
| folder = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment') |
| value = str(winreg.QueryValueEx(folder, key)[0]) |
| except Exception: |
| |
| |
| |
| |
| if fallback: |
| return os.environ[key] |
| return None |
| finally: |
| if folder is not None: |
| folder.Close() |
|
|
| except Exception as e: |
| |
| if e.args[0] != 2: |
| |
| errlog(f'Failed to read environment variable {key}:') |
| errlog(str(e)) |
| return None |
| return value |
|
|
|
|
| def win_set_environment_variable(key, value, system, user): |
| debug_print('set ' + str(key) + '=' + str(value) + ', in system=' + str(system)) |
| previous_value = win_get_environment_variable(key, system=system, user=user) |
| if previous_value == value: |
| debug_print(' no need to set, since same value already exists.') |
| |
| return False |
|
|
| if not value: |
| try: |
| if system: |
| cmd = ['REG', 'DELETE', 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', '/V', key, '/f'] |
| else: |
| cmd = ['REG', 'DELETE', 'HKCU\\Environment', '/V', key, '/f'] |
| debug_print(str(cmd)) |
| value = subprocess.call(cmd, stdout=subprocess.PIPE) |
| except Exception: |
| return False |
| return True |
|
|
| try: |
| if win_set_environment_variable_direct(key, value, system): |
| return True |
| |
| value = value.replace('%', '^%') |
| if len(value) >= 1024: |
| exit_with_error(f'the new environment variable {key} is more than 1024 characters long! A value this long cannot be set via command line: please add the environment variable specified above to system environment manually via Control Panel.') |
| cmd = ['SETX', key, value] |
| debug_print(str(cmd)) |
| retcode = subprocess.call(cmd, stdout=subprocess.PIPE) |
| if retcode != 0: |
| errlog(f'ERROR! Failed to set environment variable {key}={value}. You may need to set it manually.') |
| else: |
| return True |
| except Exception as e: |
| errlog(f'ERROR! Failed to set environment variable {key}={value}:') |
| errlog(str(e)) |
| errlog('You may need to set it manually.') |
|
|
| return False |
|
|
|
|
| def win_set_environment_variables(env_vars_to_add, system, user): |
| if not env_vars_to_add: |
| return |
|
|
| changed = False |
|
|
| for key, value in env_vars_to_add: |
| if win_set_environment_variable(key, value, system, user): |
| if not changed: |
| changed = True |
| print('Setting global environment variables:') |
|
|
| print(key + ' = ' + value) |
|
|
| if not changed: |
| print('Global environment variables up to date') |
| return |
|
|
| |
| try: |
| HWND_BROADCAST = ctypes.wintypes.HWND(0xFFFF) |
| WM_SETTINGCHANGE = 0x001A |
| SMTO_BLOCK = 0x0001 |
| ctypes.windll.user32.SendMessageTimeoutA( |
| HWND_BROADCAST, |
| WM_SETTINGCHANGE, |
| 0, |
| 'Environment', |
| SMTO_BLOCK, |
| 100) |
| except Exception as e: |
| errlog('SendMessageTimeout failed with error: ' + str(e)) |
|
|
|
|
| def win_delete_environment_variable(key, system=True, user=True): |
| debug_print(f'win_delete_environment_variable(key={key}, system={system})') |
| return win_set_environment_variable(key, None, system, user) |
|
|
|
|
| |
| def sdk_path(path): |
| if os.path.isabs(path): |
| return path |
|
|
| return to_unix_path(os.path.join(EMSDK_PATH, path)) |
|
|
|
|
| |
| def rmfile(filename): |
| debug_print(f'rmfile({filename})') |
| if os.path.lexists(filename): |
| os.remove(filename) |
|
|
|
|
| def mkdir_p(path): |
| debug_print(f'mkdir_p({path})') |
| os.makedirs(path, exist_ok=True) |
|
|
|
|
| def is_nonempty_directory(path): |
| if not os.path.isdir(path): |
| return False |
| return len(os.listdir(path)) != 0 |
|
|
|
|
| def run(cmd, cwd=None, quiet=False): |
| debug_print('run(cmd=' + str(cmd) + ', cwd=' + str(cwd) + ')') |
| process = subprocess.Popen(cmd, cwd=cwd, env=os.environ.copy()) |
| process.communicate() |
| if process.returncode != 0 and not quiet: |
| errlog(str(cmd) + ' failed with error code ' + str(process.returncode) + '!') |
| return process.returncode |
|
|
|
|
| |
| def untargz(source_filename, dest_dir): |
| print(f"Unpacking '{source_filename}' to '{dest_dir}'") |
| mkdir_p(dest_dir) |
| returncode = run(['tar', '-xvf' if VERBOSE else '-xf', sdk_path(source_filename), '--strip', '1'], cwd=dest_dir) |
| |
| |
| return returncode == 0 |
|
|
|
|
| |
| |
| |
| |
| def fix_potentially_long_windows_pathname(pathname): |
| if (not WINDOWS or MSYS) or os_override: |
| return pathname |
| |
| |
| if not os.path.isabs(pathname) and len(pathname) > 200: |
| errlog(f'Warning: Seeing a relative path "{pathname}" which is dangerously long for being referenced as a short Windows path name. Refactor emsdk to be able to handle this!') |
| if pathname.startswith('\\\\?\\'): |
| return pathname |
| pathname = os.path.normpath(pathname.replace('/', '\\')) |
| if MINGW: |
| |
| |
| |
| return '//?/' + pathname |
| return '\\\\?\\' + pathname |
|
|
|
|
| |
| |
| |
| def move_with_overwrite(src, dest): |
| if os.path.exists(dest): |
| os.remove(dest) |
| os.rename(src, dest) |
|
|
|
|
| |
| def unzip(source_filename, dest_dir): |
| print(f"Unpacking '{source_filename}' to '{dest_dir}'") |
| mkdir_p(dest_dir) |
| common_subdir = None |
| try: |
| with zipfile.ZipFile(source_filename) as zf: |
| |
| |
| |
| for member in zf.infolist(): |
| words = member.filename.split('/') |
| if len(words) > 1: |
| if common_subdir is None: |
| common_subdir = words[0] |
| elif common_subdir != words[0]: |
| common_subdir = None |
| break |
| else: |
| common_subdir = None |
| break |
|
|
| unzip_to_dir = dest_dir |
| if common_subdir: |
| unzip_to_dir = os.path.join(os.path.dirname(dest_dir), 'unzip_temp') |
|
|
| |
| unzip_to_dir = fix_potentially_long_windows_pathname(unzip_to_dir) |
| for member in zf.infolist(): |
| zf.extract(member, unzip_to_dir) |
| dst_filename = os.path.join(unzip_to_dir, os.path.normpath(member.filename)) |
|
|
| |
| unix_attributes = member.external_attr >> 16 |
| if unix_attributes: |
| os.chmod(dst_filename, unix_attributes) |
|
|
| |
| |
| if common_subdir: |
| assert member.filename.startswith(common_subdir), f'unexpected filename {member.filename}' |
| stripped_filename = '.' + member.filename[len(common_subdir):] |
| final_dst_filename = os.path.join(dest_dir, stripped_filename) |
| |
| if stripped_filename.endswith('/'): |
| d = fix_potentially_long_windows_pathname(final_dst_filename) |
| if not os.path.isdir(d): |
| os.mkdir(d) |
| else: |
| parent_dir = os.path.dirname(fix_potentially_long_windows_pathname(final_dst_filename)) |
| if parent_dir and not os.path.exists(parent_dir): |
| os.makedirs(parent_dir) |
| move_with_overwrite(fix_potentially_long_windows_pathname(dst_filename), fix_potentially_long_windows_pathname(final_dst_filename)) |
|
|
| if common_subdir: |
| remove_tree(unzip_to_dir) |
| except zipfile.BadZipfile as e: |
| errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}! Removing the corrupted zip file.") |
| rmfile(source_filename) |
| return False |
| except Exception as e: |
| errlog(f"Unzipping file '{source_filename}' failed due to reason: {e}") |
| return False |
|
|
| return True |
|
|
|
|
| |
| |
| |
| |
| def path_points_to_directory(path): |
| if path == '.': |
| return True |
| last_slash = max(path.rfind('/'), path.rfind('\\')) |
| last_dot = path.rfind('.') |
| no_suffix = last_dot < last_slash or last_dot == -1 |
| if no_suffix: |
| return True |
| suffix = path[last_dot:] |
| |
| |
| if suffix in {'.exe', '.zip', '.txt'}: |
| return False |
| else: |
| return True |
|
|
|
|
| def get_content_length(download): |
| try: |
| meta = download.info() |
| if hasattr(meta, "getheaders") and hasattr(meta.getheaders, "Content-Length"): |
| return int(meta.getheaders("Content-Length")[0]) |
| elif hasattr(download, "getheader") and download.getheader('Content-Length'): |
| return int(download.getheader('Content-Length')) |
| elif hasattr(meta, "getheader") and meta.getheader('Content-Length'): |
| return int(meta.getheader('Content-Length')) |
| except Exception: |
| pass |
|
|
| return 0 |
|
|
|
|
| def get_download_target(url, dstpath, filename_prefix=''): |
| file_name = filename_prefix + url.split('/')[-1] |
| if path_points_to_directory(dstpath): |
| file_name = os.path.join(dstpath, file_name) |
| else: |
| file_name = dstpath |
|
|
| |
| |
| file_name = sdk_path(file_name) |
|
|
| return file_name |
|
|
|
|
| def download_with_curl(url, file_name): |
| print("Downloading: %s from %s" % (file_name, url)) |
| if not shutil.which('curl'): |
| exit_with_error('curl not found in PATH') |
| |
| |
| |
| subprocess.check_call(['curl', '-#', '-f', '-L', '-o', file_name, url]) |
|
|
|
|
| def download_with_urllib(url, file_name): |
| u = urlopen(url) |
| file_size = get_content_length(u) |
| if file_size > 0: |
| print("Downloading: %s from %s, %s Bytes" % (file_name, url, file_size)) |
| else: |
| print("Downloading: %s from %s" % (file_name, url)) |
|
|
| file_size_dl = 0 |
| |
| progress_max = 80 - 4 |
| progress_shown = 0 |
| block_sz = 256 * 1024 |
| if not TTY_OUTPUT: |
| print(' [', end='') |
|
|
| with open(file_name, 'wb') as f: |
| while True: |
| buffer = u.read(block_sz) |
| if not buffer: |
| break |
|
|
| file_size_dl += len(buffer) |
| f.write(buffer) |
| if file_size: |
| percent = file_size_dl * 100.0 / file_size |
| if TTY_OUTPUT: |
| status = r" %10d [%3.02f%%]" % (file_size_dl, percent) |
| print(status, end='\r') |
| else: |
| while progress_shown < progress_max * percent / 100: |
| print('-', end='') |
| sys.stdout.flush() |
| progress_shown += 1 |
|
|
| if not TTY_OUTPUT: |
| print(']') |
| sys.stdout.flush() |
|
|
| debug_print('finished downloading (%d bytes)' % file_size_dl) |
|
|
|
|
| |
| |
| def download_file(url, dstpath, download_even_if_exists=False, |
| filename_prefix=''): |
| debug_print(f'download_file(url={url}, dstpath={dstpath})') |
| file_name = get_download_target(url, dstpath, filename_prefix) |
|
|
| if os.path.exists(file_name) and not download_even_if_exists: |
| print(f"File '{file_name}' already downloaded, skipping.") |
| return file_name |
|
|
| mkdir_p(os.path.dirname(file_name)) |
|
|
| try: |
| |
| |
| |
| |
| if MACOS or 'EMSDK_USE_CURL' in os.environ: |
| download_with_curl(url, file_name) |
| else: |
| download_with_urllib(url, file_name) |
| except Exception as e: |
| errlog(f"Error: Downloading URL '{url}': {e}") |
| return None |
| except KeyboardInterrupt: |
| rmfile(file_name) |
| raise |
|
|
| return file_name |
|
|
|
|
| def run_get_output(cmd, cwd=None): |
| debug_print(f'run_get_output(cmd={cmd}, cwd={cwd})') |
| process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, env=os.environ.copy(), universal_newlines=True) |
| stdout, stderr = process.communicate() |
| return (process.returncode, stdout, stderr) |
|
|
|
|
| cached_git_executable = None |
|
|
|
|
| |
| |
| |
| |
| def GIT(must_succeed=True): |
| global cached_git_executable |
| if cached_git_executable is not None: |
| return cached_git_executable |
| |
| |
| |
| gits = ['git/1.9.4/bin/git.exe', shutil.which('git')] |
| for git in gits: |
| try: |
| ret, stdout, stderr = run_get_output([git, '--version']) |
| if ret == 0: |
| cached_git_executable = git |
| return git |
| except Exception: |
| pass |
| if must_succeed: |
| if WINDOWS: |
| 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" |
| elif MACOS: |
| 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 )" |
| elif LINUX: |
| 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" |
| else: |
| msg = "git executable was not found. Please install git for this operation!" |
| exit_with_error(msg) |
| |
| return '' |
|
|
|
|
| def git_repo_version(repo_path): |
| returncode, stdout, stderr = run_get_output([GIT(), 'log', '-n', '1', '--pretty="%aD %H"'], cwd=repo_path) |
| if returncode == 0: |
| return stdout.strip() |
| else: |
| return "" |
|
|
|
|
| def git_recent_commits(repo_path, n=20): |
| returncode, stdout, stderr = run_get_output([GIT(), 'log', '-n', str(n), '--pretty="%H"'], cwd=repo_path) |
| if returncode == 0: |
| return stdout.strip().replace('\r', '').replace('"', '').split('\n') |
| else: |
| return [] |
|
|
|
|
| def get_git_remotes(repo_path): |
| remotes = [] |
| output = subprocess.check_output([GIT(), 'remote', '-v'], stderr=subprocess.STDOUT, text=True, cwd=repo_path) |
| for line in output.splitlines(): |
| remotes += [line.split()[0]] |
| return remotes |
|
|
|
|
| def git_clone(url, dstpath, branch, remote_name='origin'): |
| debug_print(f'git_clone(url={url}, dstpath={dstpath})') |
| if os.path.isdir(os.path.join(dstpath, '.git')): |
| remotes = get_git_remotes(dstpath) |
| if remote_name in remotes: |
| debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, skipping.') |
| return True |
| else: |
| debug_print(f'Repository {url} with remote "{remote_name}" already cloned to directory {dstpath}, but remote has not yet been added. Creating.') |
| return run([GIT(), 'remote', 'add', remote_name, url], cwd=dstpath) == 0 |
|
|
| mkdir_p(dstpath) |
| git_clone_args = ['--recurse-submodules', '--branch', branch] |
| if GIT_CLONE_SHALLOW: |
| git_clone_args += ['--depth', '1'] |
| print(f'Cloning from {url}...') |
| return run([GIT(), 'clone', '-o', remote_name] + git_clone_args + [url, dstpath]) == 0 |
|
|
|
|
| def git_pull(repo_path, branch_or_tag, remote_name='origin'): |
| debug_print(f'git_pull(repo_path={repo_path}, branch/tag={branch_or_tag}, remote_name={remote_name})') |
| ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path) |
| if ret != 0: |
| return False |
| try: |
| print(f"Fetching latest changes to the branch/tag '{branch_or_tag}' for '{repo_path}'...") |
| ret = run([GIT(), 'fetch', '--quiet', remote_name], repo_path) |
| if ret != 0: |
| return False |
| |
| target_is_tag = run([GIT(), 'symbolic-ref', '-q', 'HEAD'], repo_path, quiet=True) |
|
|
| if target_is_tag: |
| ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', branch_or_tag], repo_path) |
| else: |
| local_branch_prefix = (remote_name + '_') if remote_name != 'origin' else '' |
| ret = run([GIT(), 'checkout', '--recurse-submodules', '--quiet', '-B', local_branch_prefix + branch_or_tag, |
| '--track', remote_name + '/' + branch_or_tag], repo_path) |
| if ret != 0: |
| return False |
| if not target_is_tag: |
| |
| |
| ret = run([GIT(), 'merge', '--ff-only', remote_name + '/' + branch_or_tag], repo_path) |
| if ret != 0: |
| return False |
| run([GIT(), 'submodule', 'update', '--init'], repo_path, quiet=True) |
| except Exception: |
| errlog('git operation failed!') |
| return False |
| print(f"Successfully updated and checked out branch/tag '{branch_or_tag}' on repository '{repo_path}'") |
| print("Current repository version: " + git_repo_version(repo_path)) |
| return True |
|
|
|
|
| def git_clone_checkout_and_pull(url, dstpath, branch, override_remote_name='origin'): |
| debug_print(f'git_clone_checkout_and_pull(url={url}, dstpath={dstpath}, branch={branch}, override_remote_name={override_remote_name})') |
|
|
| |
| success = git_clone(url, dstpath, branch, override_remote_name) |
| if not success: |
| return False |
|
|
| |
| return git_pull(dstpath, branch, override_remote_name) |
|
|
|
|
| |
| |
| def decide_cmake_build_type(tool): |
| if CMAKE_BUILD_TYPE_OVERRIDE: |
| return CMAKE_BUILD_TYPE_OVERRIDE |
| else: |
| return tool.cmake_build_type |
|
|
|
|
| |
| def llvm_build_dir(tool): |
| generator_suffix = cmake_generator_prefix() |
| bitness_suffix = '_32' if tool.bitness == 32 else '_64' |
|
|
| if hasattr(tool, 'git_branch'): |
| build_dir = 'build_' + tool.git_branch.replace(os.sep, '-') + generator_suffix + bitness_suffix |
| else: |
| build_dir = 'build_' + tool.version + generator_suffix + bitness_suffix |
| return build_dir |
|
|
|
|
| def exe_suffix(filename): |
| if WINDOWS and not filename.endswith('.exe'): |
| filename += '.exe' |
| return filename |
|
|
|
|
| |
| |
| def llvm_build_bin_dir(tool): |
| build_dir = llvm_build_dir(tool) |
| if WINDOWS and 'Visual Studio' in CMAKE_GENERATOR: |
| old_llvm_bin_dir = os.path.join(build_dir, 'bin', decide_cmake_build_type(tool)) |
|
|
| new_llvm_bin_dir = None |
| default_cmake_build_type = decide_cmake_build_type(tool) |
| cmake_build_types = [default_cmake_build_type, 'Release', 'RelWithDebInfo', 'MinSizeRel', 'Debug'] |
| for build_type in cmake_build_types: |
| d = os.path.join(build_dir, build_type, 'bin') |
| if os.path.isfile(os.path.join(tool.installation_path(), d, exe_suffix('clang'))): |
| new_llvm_bin_dir = d |
| break |
|
|
| if new_llvm_bin_dir and os.path.exists(os.path.join(tool.installation_path(), new_llvm_bin_dir)): |
| return new_llvm_bin_dir |
| elif os.path.exists(os.path.join(tool.installation_path(), old_llvm_bin_dir)): |
| return old_llvm_bin_dir |
| return os.path.join(build_dir, default_cmake_build_type, 'bin') |
| else: |
| return os.path.join(build_dir, 'bin') |
|
|
|
|
| def build_env(): |
| env = os.environ.copy() |
|
|
| |
| |
| if MACOS: |
| env['CXXFLAGS'] = ((env['CXXFLAGS'] + ' ') if hasattr(env, 'CXXFLAGS') else '') + '-stdlib=libc++' |
| if WINDOWS: |
| |
| |
| env['UseMultiToolTask'] = 'true' |
| env['EnforceProcessCountAcrossBuilds'] = 'true' |
| return env |
|
|
|
|
| |
| def find_cmake(): |
| def locate_cmake_from_tool(tool): |
| tool_path = get_required_path([tool]) |
| tool_path = tool_path[-1] |
| cmake_exe = 'cmake.exe' if WINDOWS else 'cmake' |
| cmake_exe = os.path.join(tool_path, cmake_exe) |
| if os.path.isfile(cmake_exe): |
| return cmake_exe |
|
|
| |
| for tool in reversed(tools): |
| if tool.id == 'cmake' and tool.is_active(): |
| cmake_exe = locate_cmake_from_tool(tool) |
| if cmake_exe: |
| info(f'Found installed+activated CMake tool at "{cmake_exe}"') |
| return cmake_exe |
|
|
| |
| cmake_exe = shutil.which('cmake') |
| if cmake_exe: |
| info(f'Found CMake from PATH at "{cmake_exe}"') |
| return cmake_exe |
|
|
| |
| |
| |
| |
| for tool in reversed(tools): |
| if tool.id == 'cmake' and tool.is_installed(): |
| cmake_exe = locate_cmake_from_tool(tool) |
| if cmake_exe: |
| info(f'Found installed CMake tool at "{cmake_exe}"') |
| return cmake_exe |
|
|
| exit_with_error('Unable to find "cmake" in PATH, or as installed/activated tool! Please install CMake first') |
|
|
|
|
| def make_build(build_root, build_type): |
| debug_print(f'make_build(build_root={build_root}, build_type={build_type})') |
| if CPU_CORES > 1: |
| print('Performing a parallel build with ' + str(CPU_CORES) + ' cores.') |
| else: |
| print('Performing a singlethreaded build.') |
|
|
| make = [find_cmake(), '--build', '.', '--config', build_type] |
| if 'Visual Studio' in CMAKE_GENERATOR: |
| |
| |
| |
| |
| |
| |
| make += ['-j', str(CPU_CORES), '--', '/p:CL_MPCount=' + str(CPU_CORES)] |
| else: |
| |
| make += ['--', '-j', str(CPU_CORES)] |
|
|
| |
| try: |
| print('Running build: ' + str(make)) |
| ret = subprocess.check_call(make, cwd=build_root, env=build_env()) |
| if ret != 0: |
| errlog('Build failed with exit code {ret}!') |
| errlog('Working directory: ' + build_root) |
| return False |
| except Exception as e: |
| errlog('Build failed due to exception!') |
| errlog('Working directory: ' + build_root) |
| errlog(str(e)) |
| return False |
|
|
| return True |
|
|
|
|
| def cmake_configure(generator, build_root, src_root, build_type, extra_cmake_args): |
| 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) + ')') |
| |
| if not os.path.isdir(build_root): |
| |
| os.mkdir(build_root) |
| try: |
| cmdline = [find_cmake(), '-DCMAKE_BUILD_TYPE=' + build_type, '-DPYTHON_EXECUTABLE=' + sys.executable] |
| if generator: |
| cmdline += ['-G', generator] |
| |
| |
| cmdline += ['-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0'] |
| cmdline += extra_cmake_args + [src_root] |
|
|
| print('Running CMake: ' + str(cmdline)) |
|
|
| |
| |
| os.environ['MACOSX_DEPLOYMENT_TARGET'] = '11.0' |
|
|
| def quote_parens(x): |
| if ' ' in x: |
| return '"' + x.replace('"', '\\"') + '"' |
| else: |
| return x |
|
|
| |
| |
| open(os.path.join(build_root, 'recmake.' + ('bat' if WINDOWS else 'sh')), 'w').write(' '.join(map(quote_parens, cmdline))) |
| ret = subprocess.check_call(cmdline, cwd=build_root, env=build_env()) |
| if ret != 0: |
| errlog('CMake invocation failed with exit code {ret}!') |
| errlog('Working directory: ' + build_root) |
| return False |
| except OSError as e: |
| if e.errno == errno.ENOENT: |
| errlog(str(e)) |
| errlog('Could not run CMake, perhaps it has not been installed?') |
| if WINDOWS: |
| errlog('Installing this package requires CMake. Get it from http://www.cmake.org/') |
| elif LINUX: |
| 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/') |
| elif MACOS: |
| 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/') |
| return False |
| raise |
| except Exception as e: |
| errlog('CMake invocation failed due to exception!') |
| errlog('Working directory: ' + build_root) |
| errlog(str(e)) |
| return False |
|
|
| return True |
|
|
|
|
| def xcode_sdk_version(): |
| try: |
| output = subprocess.check_output(['xcrun', '--show-sdk-version'], universal_newlines=True) |
| return output.strip().split('.') |
| except Exception: |
| return subprocess.checkplatform.mac_ver()[0].split('.') |
|
|
|
|
| def cmake_target_platform(tool): |
| |
| if hasattr(tool, 'arch'): |
| if tool.arch == 'arm64': |
| return 'ARM64' |
| elif tool.arch == 'x86_64': |
| return 'x64' |
| elif tool.arch == 'x86': |
| return 'Win32' |
| if ARCH == 'arm64': |
| return 'ARM64' |
| else: |
| return 'x64' if tool.bitness == 64 else 'Win32' |
|
|
|
|
| def cmake_host_platform(): |
| |
| arch_to_cmake_host_platform = { |
| 'arm64': 'ARM64', |
| 'arm': 'ARM', |
| 'x86_64': 'x64', |
| 'x86': 'x86', |
| } |
| return arch_to_cmake_host_platform[ARCH] |
|
|
|
|
| def get_generator_and_config_args(tool): |
| args = [] |
| cmake_generator = CMAKE_GENERATOR |
| if 'Visual Studio 16' in CMAKE_GENERATOR or 'Visual Studio 17' in CMAKE_GENERATOR: |
| |
| |
| |
| args += ['-A', cmake_target_platform(tool)] |
| args += ['-Thost=' + cmake_host_platform()] |
| elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64: |
| cmake_generator += ' Win64' |
| args += ['-Thost=x64'] |
| return (cmake_generator, args) |
|
|
|
|
| def cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args): |
| success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args) |
| if success: |
| success = make_build(build_root, build_type) |
|
|
| if not success and get_env_boolean('EMSDK_RETRY_CLEAN_BUILD'): |
| |
| shutil.rmtree(build_root) |
|
|
| |
| success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args) |
| if success: |
| success = make_build(build_root, build_type) |
|
|
| return success |
|
|
|
|
| def build_llvm(tool): |
| debug_print('build_llvm(' + str(tool) + ')') |
| llvm_root = tool.installation_path() |
| llvm_src_root = os.path.join(llvm_root, 'src') |
| success = git_clone_checkout_and_pull(tool.download_url(), llvm_src_root, tool.git_branch) |
| if not success: |
| return False |
|
|
| build_dir = llvm_build_dir(tool) |
| build_root = os.path.join(llvm_root, build_dir) |
|
|
| build_type = decide_cmake_build_type(tool) |
|
|
| |
| tests_arg = 'ON' if BUILD_FOR_TESTING else 'OFF' |
|
|
| enable_assertions = ENABLE_LLVM_ASSERTIONS.lower() == 'on' or (ENABLE_LLVM_ASSERTIONS == 'auto' and build_type.lower() != 'release' and build_type.lower() != 'minsizerel') |
|
|
| if ARCH in {'x86', 'x86_64'}: |
| targets_to_build = 'WebAssembly;X86' |
| elif ARCH == 'arm': |
| targets_to_build = 'WebAssembly;ARM' |
| elif ARCH == 'arm64': |
| targets_to_build = 'WebAssembly;AArch64' |
| else: |
| targets_to_build = 'WebAssembly' |
| cmake_generator, args = get_generator_and_config_args(tool) |
| args += ['-DLLVM_TARGETS_TO_BUILD=' + targets_to_build, |
| '-DLLVM_INCLUDE_EXAMPLES=OFF', |
| '-DLLVM_INCLUDE_TESTS=' + tests_arg, |
| '-DCLANG_INCLUDE_TESTS=' + tests_arg, |
| '-DLLVM_ENABLE_ASSERTIONS=' + ('ON' if enable_assertions else 'OFF'), |
| |
| |
| '-DLLVM_ENABLE_LIBXML2=OFF', '-DLLVM_ENABLE_TERMINFO=OFF', '-DLLDB_ENABLE_LIBEDIT=OFF', |
| '-DLLVM_ENABLE_LIBEDIT=OFF', '-DLLVM_ENABLE_LIBPFM=OFF'] |
| |
| |
| |
| |
| |
| |
| args += ['-DLLVM_ENABLE_PROJECTS=clang;lld'] |
| |
| |
| |
| args += ['-DLLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN=ON'] |
|
|
| if os.getenv('LLVM_CMAKE_ARGS'): |
| extra_args = os.environ['LLVM_CMAKE_ARGS'].split(',') |
| print('Passing the following extra arguments to LLVM CMake configuration: ' + str(extra_args)) |
| args += extra_args |
|
|
| cmakelists_dir = os.path.join(llvm_src_root, 'llvm') |
|
|
| success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args) |
|
|
| return success |
|
|
|
|
| def build_ninja(tool): |
| debug_print('build_ninja(' + str(tool) + ')') |
| root = os.path.normpath(tool.installation_path()) |
| src_root = os.path.join(root, 'src') |
| success = git_clone_checkout_and_pull(tool.download_url(), src_root, tool.git_branch) |
| if not success: |
| return False |
|
|
| build_dir = llvm_build_dir(tool) |
| build_root = os.path.join(root, build_dir) |
|
|
| build_type = decide_cmake_build_type(tool) |
|
|
| |
| cmake_generator, args = get_generator_and_config_args(tool) |
|
|
| cmakelists_dir = os.path.join(src_root) |
|
|
| success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args) |
|
|
| if success: |
| bin_dir = os.path.join(root, 'bin') |
| mkdir_p(bin_dir) |
| exe_paths = [os.path.join(build_root, 'Release', 'ninja'), os.path.join(build_root, 'ninja')] |
| for e in exe_paths: |
| for s in ['.exe', '']: |
| ninja = e + s |
| if os.path.isfile(ninja): |
| dst = os.path.join(bin_dir, 'ninja' + s) |
| shutil.copyfile(ninja, dst) |
| os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC) |
|
|
| return success |
|
|
|
|
| def build_ccache(tool): |
| debug_print('build_ccache(' + str(tool) + ')') |
| root = os.path.normpath(tool.installation_path()) |
| src_root = os.path.join(root, 'src') |
| success = git_clone_checkout_and_pull(tool.download_url(), src_root, tool.git_branch) |
| if not success: |
| return False |
|
|
| build_dir = llvm_build_dir(tool) |
| build_root = os.path.join(root, build_dir) |
|
|
| build_type = decide_cmake_build_type(tool) |
|
|
| |
| cmake_generator, args = get_generator_and_config_args(tool) |
| args += ['-DZSTD_FROM_INTERNET=ON'] |
|
|
| cmakelists_dir = os.path.join(src_root) |
|
|
| success = cmake_configure_and_build(cmake_generator, build_root, cmakelists_dir, build_type, args) |
|
|
| if success: |
| bin_dir = os.path.join(root, 'bin') |
| mkdir_p(bin_dir) |
| exe_paths = [os.path.join(build_root, 'Release', 'ccache'), os.path.join(build_root, 'ccache')] |
| for e in exe_paths: |
| for s in ['.exe', '']: |
| ccache = e + s |
| if os.path.isfile(ccache): |
| dst = os.path.join(bin_dir, 'ccache' + s) |
| shutil.copyfile(ccache, dst) |
| os.chmod(dst, os.stat(dst).st_mode | stat.S_IEXEC) |
|
|
| cache_dir = os.path.join(root, 'cache') |
| open(os.path.join(root, 'emcc_ccache.conf'), 'w').write('''# Set maximum cache size to 10 GB: |
| max_size = 10G |
| cache_dir = %s |
| ''' % cache_dir) |
| mkdir_p(cache_dir) |
|
|
| return success |
|
|
|
|
| def download_firefox(tool): |
| debug_print('download_firefox(' + str(tool) + ')') |
|
|
| |
| try: |
| from mozdownload import FactoryScraper |
| except ImportError: |
| |
| subprocess.check_call([sys.executable, "-m", "pip", "install", "mozdownload"]) |
| from mozdownload import FactoryScraper |
|
|
| if WINDOWS: |
| extension = 'exe' |
| elif MACOS: |
| extension = 'dmg' |
| else: |
| |
| |
| |
| extension = 'tar.xz' |
|
|
| platform = None |
| if LINUX and 'arm' in ARCH: |
| platform = 'linux-arm64' |
| if WINDOWS and 'arm' in ARCH: |
| platform = 'win64-aarch64' |
|
|
| if tool.version == 'nightly': |
| scraper = FactoryScraper('daily', extension=extension, locale='en-US', platform=platform) |
| else: |
| scraper = FactoryScraper('release', extension=extension, locale='en-US', platform=platform, version=tool.version) |
|
|
| if tool.version == 'nightly': |
| firefox_version = os.path.basename(scraper.filename).split(".en-US")[0] |
| else: |
| firefox_version = os.path.basename(scraper.filename).split("firefox-")[1].split(".en-US")[0] |
|
|
| print('Target Firefox version: ' + firefox_version) |
| if tool.version in {'latest', 'latest-esr', 'latest-beta', 'nightly'}: |
| pretend_version_dir = os.path.normpath(tool.installation_path()) |
| orig_version = tool.version |
| tool.version = firefox_version |
| root = os.path.normpath(tool.installation_path()) |
| tool.version = orig_version |
| else: |
| pretend_version_dir = None |
| root = os.path.normpath(tool.installation_path()) |
|
|
| |
| |
| def save_actual_version(): |
| if os.path.isfile(firefox_exe) and pretend_version_dir: |
| print(pretend_version_dir) |
| os.makedirs(pretend_version_dir, exist_ok=True) |
| open(os.path.join(pretend_version_dir, 'actual.txt'), 'w').write(os.path.relpath(root, EMSDK_PATH)) |
|
|
| |
| print('Firefox installation root directory: ' + root) |
| exe_dir = os.path.join(root, 'Contents', 'MacOS') if MACOS else root |
| firefox_exe = os.path.join(exe_dir, exe_suffix('firefox')) |
| if os.path.isfile(firefox_exe): |
| print(firefox_exe + ' is already installed, skipping..') |
| save_actual_version() |
| return True |
|
|
| print('Downloading Firefox from ' + scraper.url) |
| filename = scraper.download() |
| print('Finished downloading ' + filename) |
|
|
| if not MACOS: |
| os.makedirs(root, exist_ok=True) |
|
|
| if extension == 'exe': |
| |
| run(['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y', filename, '-o' + root]) |
|
|
| if '.tar.' in filename: |
| if filename.endswith('.tar.bz2'): |
| tar_type = 'r:bz2' |
| elif filename.endswith('.tar.xz'): |
| tar_type = 'r:xz' |
| else: |
| raise Exception('Unknown archive type!') |
|
|
| with tarfile.open(filename, tar_type) as tar: |
| tar.extractall(path=root) |
| collapse_subdir = os.path.join(root, 'firefox') |
|
|
| elif filename.endswith('.dmg'): |
| mount_point = '/Volumes/Firefox Nightly' if tool.version == 'nightly' else '/Volumes/Firefox' |
| app_name = 'Firefox Nightly.app' if tool.version == 'nightly' else 'Firefox.app' |
|
|
| |
| if os.path.exists(mount_point): |
| run(['hdiutil', 'detach', mount_point]) |
|
|
| |
| if os.path.exists(mount_point): |
| raise Exception(f'Previous mount of Firefox already exists at "{mount_point}", unable to proceed.') |
|
|
| |
| run(['hdiutil', 'attach', filename]) |
| firefox_dir = os.path.join(mount_point, app_name) |
| if not os.path.isdir(firefox_dir): |
| raise Exception(f'Unable to find Firefox directory "{firefox_dir}" inside app image.') |
|
|
| |
| shutil.copytree(firefox_dir, root) |
| run(['hdiutil', 'detach', mount_point]) |
| collapse_subdir = None |
|
|
| elif filename.endswith('.exe'): |
| |
| collapse_subdir = os.path.join(root, 'core') |
|
|
| |
| if collapse_subdir and os.path.isdir(collapse_subdir): |
| |
| collapse = collapse_subdir + '_temp_renamed' |
| os.rename(collapse_subdir, collapse) |
|
|
| |
| for f in os.listdir(collapse): |
| shutil.move(os.path.join(collapse, f), os.path.dirname(collapse)) |
|
|
| |
| os.rmdir(collapse) |
|
|
| |
| os.remove(filename) |
|
|
| |
| if MACOS: |
| distribution_path = os.path.join(root, 'Contents', 'Resources', 'distribution') |
| else: |
| distribution_path = os.path.join(root, 'distribution') |
| os.makedirs(distribution_path, exist_ok=True) |
| open(os.path.join(distribution_path, 'policies.json'), 'w').write('''{ |
| "policies": { |
| "AppAutoUpdate": false, |
| "DisableAppUpdate": true |
| } |
| }''') |
|
|
| if MACOS: |
| |
| |
| |
| |
| |
| |
| run(['defaults', 'write', '-app', root, 'ApplePersistenceIgnoreState', 'YES']) |
| run(['defaults', 'write', '-app', root, 'NSQuitAlwaysKeepsWindows', '-bool', 'false']) |
|
|
| save_actual_version() |
|
|
| |
| return os.path.isfile(firefox_exe) |
|
|
|
|
| def is_firefox_installed(tool): |
| actual_file = os.path.join(tool.installation_dir(), 'actual.txt') |
| if not os.path.isfile(actual_file): |
| return False |
|
|
| actual_installation_dir = sdk_path(open(actual_file).read()) |
| exe_dir = os.path.join(actual_installation_dir, 'Contents', 'MacOS') if MACOS else actual_installation_dir |
| firefox_exe = os.path.join(exe_dir, exe_suffix('firefox')) |
| return os.path.isfile(firefox_exe) |
|
|
|
|
| |
| def find_latest_installed_tool(name): |
| for t in reversed(tools): |
| if t.id == name and t.is_installed(): |
| return t |
|
|
|
|
| |
| def emscripten_npm_install(tool, directory): |
| node_tool = find_latest_installed_tool('node') |
| if not node_tool: |
| npm_fallback = shutil.which('npm') |
| if not npm_fallback: |
| errlog('Failed to find npm command!') |
| errlog('Running "npm ci" in installed Emscripten root directory ' + tool.installation_path() + ' is required!') |
| errlog('Please install node.js first!') |
| return False |
| node_path = os.path.dirname(npm_fallback) |
| else: |
| node_path = os.path.join(node_tool.installation_path(), 'bin') |
|
|
| npm = os.path.join(node_path, 'npm' + ('.cmd' if WINDOWS else '')) |
| env = os.environ.copy() |
| env["PATH"] = node_path + os.pathsep + env["PATH"] |
| print('Running post-install step: npm ci ...') |
| try: |
| subprocess.check_output( |
| [npm, 'ci', '--production'], |
| cwd=directory, stderr=subprocess.STDOUT, env=env, |
| universal_newlines=True) |
| except subprocess.CalledProcessError as e: |
| errlog('Error running %s:\n%s' % (e.cmd, e.output)) |
| return False |
|
|
| print('Done running: npm ci') |
|
|
| if os.path.isfile(os.path.join(directory, 'bootstrap.py')): |
| try: |
| subprocess.check_output([sys.executable, os.path.join(directory, 'bootstrap.py')], |
| cwd=directory, stderr=subprocess.STDOUT, env=env, |
| universal_newlines=True) |
| except subprocess.CalledProcessError as e: |
| errlog('Error running %s:\n%s' % (e.cmd, e.output)) |
| return False |
|
|
| print('Done running: Emscripten bootstrap') |
| return True |
|
|
|
|
| |
| def binaryen_build_root(tool): |
| build_root = tool.installation_path().strip() |
| if build_root.endswith(('/', '\\')): |
| build_root = build_root[:-1] |
| generator_prefix = cmake_generator_prefix() |
| build_root = build_root + generator_prefix + '_' + str(tool.bitness) + 'bit_binaryen' |
| return build_root |
|
|
|
|
| def uninstall_binaryen(tool): |
| debug_print('uninstall_binaryen(' + str(tool) + ')') |
| build_root = binaryen_build_root(tool) |
| print(f"Deleting path '{build_root}'") |
| remove_tree(build_root) |
|
|
|
|
| def is_binaryen_installed(tool): |
| build_root = binaryen_build_root(tool) |
| return os.path.exists(build_root) |
|
|
|
|
| def build_binaryen_tool(tool): |
| debug_print('build_binaryen_tool(' + str(tool) + ')') |
| src_root = tool.installation_path() |
| build_root = binaryen_build_root(tool) |
| build_type = decide_cmake_build_type(tool) |
|
|
| |
| cmake_generator, args = get_generator_and_config_args(tool) |
| args += ['-DENABLE_WERROR=0'] |
| args += ['-DBUILD_TESTS=0'] |
|
|
| if 'Visual Studio' in CMAKE_GENERATOR: |
| if BUILD_FOR_TESTING: |
| args += ['-DRUN_STATIC_ANALYZER=1'] |
|
|
| success = cmake_configure_and_build(cmake_generator, build_root, src_root, build_type, args) |
|
|
| if success: |
| |
| remove_tree(os.path.join(build_root, 'scripts')) |
| shutil.copytree(os.path.join(src_root, 'scripts'), os.path.join(build_root, 'scripts')) |
| remove_tree(os.path.join(build_root, 'src', 'js')) |
| shutil.copytree(os.path.join(src_root, 'src', 'js'), os.path.join(build_root, 'src', 'js')) |
|
|
| return success |
|
|
|
|
| def download_and_extract(archive, dest_dir, filename_prefix='', clobber=True): |
| debug_print('download_and_extract(archive={archive}, dest_dir={dest_dir})') |
|
|
| url = urljoin(emsdk_packages_url, archive) |
|
|
| def try_download(url): |
| return download_file(url, download_dir, not KEEP_DOWNLOADS, filename_prefix) |
|
|
| |
| |
| |
| success = False |
| if 'wasm-binaries' in archive and os.path.splitext(archive)[1] == '.xz': |
| success = try_download(url) |
| if not success: |
| alt_url = url.replace('.tar.xz', '.tbz2') |
| success = try_download(alt_url) |
| if success: |
| url = alt_url |
|
|
| if not success: |
| success = try_download(url) |
|
|
| if not success: |
| return False |
|
|
| |
| |
| |
| if clobber: |
| remove_tree(dest_dir) |
|
|
| download_target = get_download_target(url, download_dir, filename_prefix) |
| if archive.endswith('.zip'): |
| return unzip(download_target, dest_dir) |
| else: |
| return untargz(download_target, dest_dir) |
|
|
|
|
| def to_native_path(p): |
| if (WINDOWS and not MSYS) and not os_override: |
| return to_unix_path(p).replace('/', '\\') |
| else: |
| return to_unix_path(p) |
|
|
|
|
| |
| |
| def get_required_path(active_tools): |
| path_add = [to_native_path(EMSDK_PATH)] |
| for tool in active_tools: |
| if hasattr(tool, 'activated_path'): |
| path = to_native_path(tool.expand_vars(tool.activated_path)) |
| |
| |
| |
| |
| |
| if hasattr(tool, 'activated_path_skip'): |
| current_path = shutil.which(tool.activated_path_skip) |
| |
| |
| if current_path and os.path.dirname(current_path) != path: |
| continue |
| path_add.append(path) |
| return path_add |
|
|
|
|
| |
| |
| EM_CONFIG_PATH = os.path.join(EMSDK_PATH, ".emscripten") |
| EM_CONFIG_DICT = {} |
|
|
|
|
| def parse_key_value(line): |
| if not line: |
| return ('', '') |
| eq = line.find('=') |
| if eq != -1: |
| key = line[0:eq].strip() |
| value = line[eq + 1:].strip() |
| return (key, value) |
| else: |
| return (key, '') |
|
|
|
|
| def load_em_config(): |
| EM_CONFIG_DICT.clear() |
| lines = [] |
| try: |
| lines = open(EM_CONFIG_PATH).read().split('\n') |
| except Exception: |
| pass |
| for line in lines: |
| try: |
| key, value = parse_key_value(line) |
| if value: |
| EM_CONFIG_DICT[key] = value |
| except Exception: |
| pass |
|
|
|
|
| def find_emscripten_root(active_tools): |
| """Find the currently active emscripten root. |
| |
| If there is more than one tool that defines EMSCRIPTEN_ROOT (this |
| should not happen under normal circumstances), assume the last one takes |
| precedence. |
| """ |
| root = None |
| for tool in active_tools: |
| config = tool.activated_config() |
| if 'EMSCRIPTEN_ROOT' in config: |
| root = config['EMSCRIPTEN_ROOT'] |
| return root |
|
|
|
|
| def fetch_nightly_node_versions(): |
| url = "https://nodejs.org/download/nightly/" |
| with urlopen(url) as response: |
| html = response.read().decode("utf-8") |
|
|
| |
| pattern = re.compile(r'<a href="(v[0-9]+\.[0-9]+\.[0-9]+-nightly[0-9a-f]+)/">') |
| matches = pattern.findall(html) |
| return matches |
|
|
|
|
| def dir_installed_nightly_node_versions(): |
| path = os.path.abspath('node') |
| try: |
| return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name)) and name.startswith("nightly-")] |
| except Exception: |
| return [] |
|
|
|
|
| def extract_newest_node_nightly_version(versions): |
| def parse(v): |
| |
| m = re.search(r'v(\d+)\.(\d+)\.(\d+)-nightly(\d{8})', v) |
| if m: |
| major, minor, patch, nightly = m.groups() |
| return [int(major), int(minor), int(patch), int(nightly)] |
| else: |
| return [] |
|
|
| try: |
| return max(versions, key=parse) |
| except Exception: |
| return None |
|
|
|
|
| def download_node_nightly(tool): |
| nightly_versions = fetch_nightly_node_versions() |
| latest_nightly = extract_newest_node_nightly_version(nightly_versions) |
| print(f'Latest Node.js Nightly download available is "{latest_nightly}"') |
|
|
| output_dir = os.path.abspath('node/nightly-' + latest_nightly) |
| |
| |
| if WINDOWS: |
| output_dir += '/bin' |
|
|
| if os.path.isdir(output_dir): |
| return True |
|
|
| url = tool.url.replace('%version%', latest_nightly) |
| if WINDOWS: |
| os_ = 'win' |
| elif LINUX: |
| os_ = 'linux' |
| elif MACOS: |
| os_ = 'darwin' |
| else: |
| os_ = '' |
| if platform.machine().lower() in {'x86_64', 'amd64'}: |
| arch = 'x64' |
| elif platform.machine().lower() in {'arm64', 'aarch64'}: |
| arch = 'arm64' |
| if WINDOWS: |
| zip_suffix = 'zip' |
| else: |
| zip_suffix = 'tar.gz' |
| url = url.replace('%os%', os_) |
| url = url.replace('%arch%', arch) |
| url = url.replace('%zip_suffix%', zip_suffix) |
| download_and_extract(url, output_dir) |
| open(tool.get_version_file_path(), 'w').write('node-nightly-64bit') |
| return True |
|
|
|
|
| |
| |
| def get_emsdk_shell_env_configs(): |
| default_emsdk_env = sdk_path('emsdk_env.sh') |
| default_shell_config_file = '$HOME/.bash_profile' |
| shell = os.getenv('SHELL', '') |
| if 'zsh' in shell: |
| return (default_emsdk_env, '$HOME/.zprofile') |
| elif 'csh' in shell: |
| return (sdk_path('emsdk_env.csh'), '$HOME/.cshrc') |
| elif 'fish' in shell: |
| return (sdk_path('emsdk_env.fish'), '$HOME/.config/fish/config.fish') |
| else: |
| return (default_emsdk_env, default_shell_config_file) |
|
|
|
|
| def generate_em_config(active_tools, permanently_activate, system): |
| cfg = 'import os\n' |
| cfg += "emsdk_path = os.path.dirname(os.getenv('EM_CONFIG')).replace('\\\\', '/')\n" |
|
|
| |
| |
| activated_config = OrderedDict() |
| for tool in active_tools: |
| for name, value in tool.activated_config().items(): |
| activated_config[name] = value |
|
|
| if 'NODE_JS' not in activated_config: |
| node_fallback = shutil.which('nodejs') |
| if not node_fallback: |
| node_fallback = 'node' |
| activated_config['NODE_JS'] = node_fallback |
|
|
| for name, value in activated_config.items(): |
| if value.startswith('['): |
| cfg += f'{name} = {value}\n' |
| else: |
| cfg += f"{name} = '{value}'\n" |
|
|
| emroot = find_emscripten_root(active_tools) |
| if emroot: |
| version = parse_emscripten_version(emroot) |
| |
| |
| |
| if version < [1, 38, 46]: |
| cfg += 'COMPILER_ENGINE = NODE_JS\n' |
| |
| if version < [1, 38, 48]: |
| cfg += 'JS_ENGINES = [NODE_JS]\n' |
|
|
| cfg = cfg.replace("'" + EMSDK_PATH, "emsdk_path + '") |
|
|
| if os.path.exists(EM_CONFIG_PATH): |
| backup_path = EM_CONFIG_PATH + ".old" |
| move_with_overwrite(EM_CONFIG_PATH, backup_path) |
|
|
| with open(EM_CONFIG_PATH, "w") as text_file: |
| text_file.write(cfg) |
|
|
| |
| rmfile(os.path.join(EMSDK_PATH, ".emscripten_sanity")) |
|
|
| path_add = get_required_path(active_tools) |
|
|
| |
| if WINDOWS: |
| if not permanently_activate and not system: |
| print('Next steps:') |
| print('- Consider running `emsdk activate` with --permanent or --system') |
| print(' to have emsdk settings available on startup.') |
| else: |
| print('Next steps:') |
| print('- To conveniently access emsdk tools from the command line,') |
| print(' consider adding the following directories to your PATH:') |
| for p in path_add: |
| print(' ' + p) |
| print('- This can be done for the current shell by running:') |
| emsdk_env, shell_config_file = get_emsdk_shell_env_configs() |
| print(' source "%s"' % emsdk_env) |
| print('- Configure emsdk in your shell startup scripts by running:') |
| print(' echo \'source "%s"\' >> %s' % (emsdk_env, shell_config_file)) |
|
|
|
|
| class Tool: |
| def __init__(self, data): |
| |
| |
| for key, value in data.items(): |
| setattr(self, key, value) |
|
|
| |
| self.name = self.id |
| if self.version: |
| self.name += '-' + self.version |
| if hasattr(self, 'bitness'): |
| self.name += '-' + str(self.bitness) + 'bit' |
|
|
| def __str__(self): |
| return self.name |
|
|
| def __repr__(self): |
| return self.name |
|
|
| def expand_vars(self, str): |
| if '%installation_dir%' in str: |
| str = str.replace('%installation_dir%', sdk_path(self.installation_dir())) |
| if '%macos_app_bundle_prefix%' in str: |
| str = str.replace('%macos_app_bundle_prefix%', 'Contents/MacOS/' if MACOS else '') |
| if '%actual_installation_dir%' in str: |
| actual_file = os.path.join(self.installation_dir(), 'actual.txt') |
| if os.path.isfile(actual_file): |
| str = str.replace('%actual_installation_dir%', sdk_path(open(actual_file).read())) |
| else: |
| str = str.replace('%actual_installation_dir%', '__NOT_INSTALLED__') |
| if '%generator_prefix%' in str: |
| str = str.replace('%generator_prefix%', cmake_generator_prefix()) |
| str = str.replace('%.exe%', '.exe' if WINDOWS else '') |
| if '%llvm_build_bin_dir%' in str: |
| str = str.replace('%llvm_build_bin_dir%', llvm_build_bin_dir(self)) |
| if '%latest_downloaded_node_nightly_dir%' in str: |
| installed_node_nightlys = dir_installed_nightly_node_versions() |
| latest_node_nightly = extract_newest_node_nightly_version(installed_node_nightlys) |
| if latest_node_nightly: |
| str = str.replace('%latest_downloaded_node_nightly_dir%', latest_node_nightly) |
|
|
| return str |
|
|
| |
| def needs_compilation(self): |
| if hasattr(self, 'cmake_build_type'): |
| return True |
|
|
| if hasattr(self, 'uses'): |
| for tool_name in self.uses: |
| tool = find_tool(tool_name) |
| if not tool: |
| debug_print(f'Tool {self} depends on {tool_name} which does not exist!') |
| continue |
| if tool.needs_compilation(): |
| return True |
|
|
| return False |
|
|
| |
| |
| def installation_path(self): |
| if hasattr(self, 'install_path'): |
| pth = self.expand_vars(self.install_path) |
| return sdk_path(pth) |
| p = self.version |
| if hasattr(self, 'bitness') and (not hasattr(self, 'append_bitness') or self.append_bitness): |
| p += '_' + str(self.bitness) + 'bit' |
| return sdk_path(os.path.join(self.id, p)) |
|
|
| |
| def installation_dir(self): |
| dir = self.installation_path() |
| if path_points_to_directory(dir): |
| return dir |
| else: |
| return os.path.dirname(dir) |
|
|
| |
| |
| def activated_config(self): |
| if hasattr(self, 'activated_cfg'): |
| activated_cfg = self.activated_cfg |
| else: |
| return {} |
|
|
| config = OrderedDict() |
| expanded = to_unix_path(self.expand_vars(activated_cfg)) |
| for specific_cfg in expanded.split(';'): |
| name, value = specific_cfg.split('=') |
| config[name] = value.strip("'") |
| return config |
|
|
| def activated_environment(self): |
| if hasattr(self, 'activated_env'): |
| activated_env = self.activated_env |
| else: |
| return [] |
|
|
| return self.expand_vars(activated_env).split(';') |
|
|
| def compatible_with_this_arch(self): |
| if hasattr(self, 'arch'): |
| if self.arch != ARCH: |
| return False |
| return True |
|
|
| def compatible_with_this_os(self): |
| if hasattr(self, 'os'): |
| if self.os == 'all': |
| return True |
| 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))): |
| return True |
| else: |
| return False |
| else: |
| if not any(hasattr(self, a) for a in ('macos_url', 'windows_url', 'unix_url', 'linux_url')): |
| return True |
|
|
| if MACOS and hasattr(self, 'macos_url') and self.compatible_with_this_arch(): |
| return True |
|
|
| if LINUX and hasattr(self, 'linux_url') and self.compatible_with_this_arch(): |
| return True |
|
|
| if WINDOWS and hasattr(self, 'windows_url') and self.compatible_with_this_arch(): |
| return True |
|
|
| if UNIX and hasattr(self, 'unix_url'): |
| return True |
|
|
| return hasattr(self, 'url') |
|
|
| |
| |
| |
| |
| |
| def get_version_file_path(self): |
| return os.path.join(self.installation_path(), '.emsdk_version') |
|
|
| def is_installed_version(self): |
| version_file_path = self.get_version_file_path() |
| if os.path.isfile(version_file_path): |
| with open(version_file_path) as version_file: |
| return version_file.read().strip() == self.name |
| return False |
|
|
| def update_installed_version(self): |
| with open(self.get_version_file_path(), 'w') as version_file: |
| version_file.write(self.name + '\n') |
|
|
| def is_installed(self, skip_version_check=False): |
| |
| |
| if hasattr(self, 'uses'): |
| for tool_name in self.uses: |
| tool = find_tool(tool_name) |
| if tool is None: |
| errlog(f"Manifest error: No tool by name '{tool_name}' found! This may indicate an internal SDK error!") |
| return False |
| if not tool.is_installed(): |
| return False |
|
|
| if self.download_url() is None: |
| debug_print(str(self) + ' has no files to download, so is installed by default.') |
| return True |
|
|
| content_exists = is_nonempty_directory(self.installation_path()) |
| debug_print(str(self) + ' installation path is ' + self.installation_path() + ', exists: ' + str(content_exists) + '.') |
|
|
| |
| |
| |
| |
| |
| |
| |
| if hasattr(self, 'activated_path') and not os.path.exists(self.expand_vars(self.activated_path)): |
| content_exists = False |
|
|
| if hasattr(self, 'custom_is_installed_script'): |
| if self.custom_is_installed_script == 'is_binaryen_installed': |
| return is_binaryen_installed(self) |
| elif self.custom_is_installed_script == 'is_firefox_installed': |
| return is_firefox_installed(self) |
| else: |
| raise Exception('Unknown custom_is_installed_script directive "' + self.custom_is_installed_script + '"!') |
|
|
| return content_exists and (skip_version_check or self.is_installed_version()) |
|
|
| def is_active(self): |
| if not self.is_installed(): |
| return False |
|
|
| |
| deps = self.dependencies() |
| for tool in deps: |
| if not tool.is_active(): |
| return False |
|
|
| activated_cfg = self.activated_config() |
| if not activated_cfg: |
| return len(deps) > 0 |
|
|
| for key, value in activated_cfg.items(): |
| if key not in EM_CONFIG_DICT: |
| debug_print(f'{self} is not active, because key="{key}" does not exist in .emscripten') |
| return False |
|
|
| |
| |
| config_value = EM_CONFIG_DICT[key].replace("emsdk_path + '", "'" + EMSDK_PATH) |
| config_value = config_value.strip("'") |
| if config_value != value: |
| debug_print(f'{self} is not active, because key="{key}" has value "{config_value}" but should have value "{value}"') |
| return False |
| return True |
|
|
| |
| def is_env_active(self): |
| envs = self.activated_environment() |
| for env in envs: |
| key, value = parse_key_value(env) |
| if key not in os.environ or to_unix_path(os.environ[key]) != to_unix_path(value): |
| debug_print(f'{self} is not active, because environment variable key="{key}" has value "{os.getenv(key)}" but should have value "{value}"') |
| return False |
|
|
| if hasattr(self, 'activated_path'): |
| path = to_unix_path(self.expand_vars(self.activated_path)) |
| for p in path: |
| path_items = os.environ['PATH'].replace('\\', '/').split(ENVPATH_SEPARATOR) |
| if not normalized_contains(path_items, p): |
| debug_print(f'{self} is not active, because environment variable PATH item "{p}" is not present (PATH={os.environ["PATH"]})') |
| return False |
| return True |
|
|
| |
| |
| |
| def can_be_installed(self): |
| if hasattr(self, 'bitness'): |
| if self.bitness == 64 and not is_os_64bit(): |
| return "this tool is only provided for 64-bit OSes" |
| return True |
|
|
| def download_url(self): |
| if WINDOWS and hasattr(self, 'windows_url'): |
| return self.windows_url |
| elif MACOS and hasattr(self, 'macos_url'): |
| return self.macos_url |
| elif LINUX and hasattr(self, 'linux_url'): |
| return self.linux_url |
| elif UNIX and hasattr(self, 'unix_url'): |
| return self.unix_url |
| elif hasattr(self, 'url'): |
| return self.url |
| else: |
| return None |
|
|
| def install(self): |
| """Returns True if the Tool was installed of False if was skipped due to |
| already being installed. |
| """ |
| if self.can_be_installed() is not True: |
| exit_with_error(f"The tool '{self}' is not available due to the reason: {self.can_be_installed()}") |
|
|
| if self.id == 'sdk': |
| return self.install_sdk() |
| else: |
| return self.install_tool() |
|
|
| def install_sdk(self): |
| """Returns True if any SDK component was installed of False all componented |
| were already installed. |
| """ |
| print(f"Installing SDK '{self}'..") |
| installed = False |
|
|
| for tool_name in self.uses: |
| tool = find_tool(tool_name) |
| if tool is None: |
| exit_with_error(f"manifest error: No tool by name '{tool_name}' found! This may indicate an internal SDK error!") |
| installed |= tool.install() |
|
|
| if not installed: |
| print(f"All SDK components already installed: '{self}'.") |
| return False |
|
|
| if getattr(self, 'custom_install_script', None) == 'emscripten_npm_install': |
| |
| install_path = 'upstream' |
| emscripten_dir = os.path.join(EMSDK_PATH, install_path, 'emscripten') |
| |
| |
| if not os.path.exists(os.path.join(emscripten_dir, 'node_modules')): |
| if not emscripten_npm_install(self, emscripten_dir): |
| exit_with_error('post-install step failed: emscripten_npm_install') |
|
|
| print(f"Done installing SDK '{self}'.") |
| return True |
|
|
| def install_tool(self): |
| """Returns True if the SDK was installed of False if was skipped due to |
| already being installed. |
| """ |
| |
| |
| |
| |
| if self.is_installed() and not hasattr(self, 'git_branch'): |
| print(f"Skipped installing {self.name}, already installed.") |
| return False |
|
|
| print(f"Installing tool '{self}'..") |
| url = self.download_url() |
|
|
| custom_install_scripts = { |
| 'build_llvm': build_llvm, |
| 'build_ninja': build_ninja, |
| 'build_ccache': build_ccache, |
| 'download_node_nightly': download_node_nightly, |
| 'download_firefox': download_firefox, |
| } |
| if hasattr(self, 'custom_install_script') and self.custom_install_script in custom_install_scripts: |
| success = custom_install_scripts[self.custom_install_script](self) |
| elif hasattr(self, 'git_branch'): |
| success = git_clone_checkout_and_pull(url, self.installation_path(), self.git_branch, getattr(self, 'remote_name', 'origin')) |
| elif url.endswith(ARCHIVE_SUFFIXES): |
| success = download_and_extract(url, self.installation_path(), |
| filename_prefix=getattr(self, 'download_prefix', '')) |
| else: |
| assert False, 'unhandled url type: ' + url |
|
|
| if not success: |
| exit_with_error("installation failed!") |
|
|
| if hasattr(self, 'custom_install_script'): |
| if self.custom_install_script == 'emscripten_npm_install': |
| success = emscripten_npm_install(self, self.installation_path()) |
| elif self.custom_install_script in {'build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly', 'download_firefox'}: |
| |
| |
| pass |
| elif self.custom_install_script == 'build_binaryen': |
| success = build_binaryen_tool(self) |
| else: |
| raise Exception('Unknown custom_install_script command "' + self.custom_install_script + '"!') |
|
|
| if not success: |
| exit_with_error("installation failed!") |
|
|
| |
| |
| |
| if hasattr(self, 'emscripten_releases_hash'): |
| emscripten_version_file_path = os.path.join(to_native_path(self.expand_vars(self.activated_path)), 'emscripten-version.txt') |
| version = get_emscripten_release_version(self.emscripten_releases_hash) |
| if version: |
| with open(emscripten_version_file_path, 'w') as f: |
| f.write('"%s"\n' % version) |
|
|
| print(f"Done installing tool '{self}'.") |
|
|
| |
| |
| if not self.is_installed(skip_version_check=True): |
| exit_with_error(f"installation of '{self}' failed, but no error was detected. Either something went wrong with the installation, or this may indicate an internal emsdk error.") |
|
|
| self.cleanup_temp_install_files() |
| self.update_installed_version() |
| return True |
|
|
| def cleanup_temp_install_files(self): |
| if KEEP_DOWNLOADS: |
| return |
| url = self.download_url() |
| if url.endswith(ARCHIVE_SUFFIXES): |
| download_target = get_download_target(url, download_dir, getattr(self, 'download_prefix', '')) |
| debug_print(f"Deleting temporary download: {download_target}") |
| rmfile(download_target) |
|
|
| def uninstall(self): |
| if not self.is_installed(): |
| print(f"Tool '{self}' was not installed. No need to uninstall.") |
| return |
| print(f"Uninstalling tool '{self}'..") |
| if hasattr(self, 'custom_uninstall_script'): |
| if self.custom_uninstall_script == 'uninstall_binaryen': |
| uninstall_binaryen(self) |
| else: |
| raise Exception(f'Unknown custom_uninstall_script directive "{self.custom_uninstall_script}"!') |
| print(f"Deleting path '{self.installation_path()}'") |
| remove_tree(self.installation_path()) |
| print(f"Done uninstalling '{self}'.") |
|
|
| def dependencies(self): |
| if not hasattr(self, 'uses'): |
| return [] |
| deps = [] |
|
|
| for tool_name in self.uses: |
| tool = find_tool(tool_name) |
| if tool: |
| deps += [tool] |
| return deps |
|
|
| def recursive_dependencies(self): |
| if not hasattr(self, 'uses'): |
| return [] |
| deps = [] |
| for tool_name in self.uses: |
| tool = find_tool(tool_name) |
| if tool: |
| deps += [tool] |
| deps += tool.recursive_dependencies() |
| return deps |
|
|
|
|
| |
| tools = [] |
| tools_map = {} |
|
|
|
|
| def add_tool(tool): |
| tool.is_sdk = False |
| tools.append(tool) |
| if find_tool(str(tool)): |
| 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()) + '}') |
| tools_map[str(tool)] = tool |
|
|
|
|
| |
| sdks = [] |
| sdks_map = {} |
|
|
|
|
| def add_sdk(sdk): |
| sdk.is_sdk = True |
| sdks.append(sdk) |
| if find_sdk(str(sdk)): |
| 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()) + '}') |
| sdks_map[str(sdk)] = sdk |
|
|
|
|
| |
| |
|
|
| def find_tool(name): |
| return tools_map.get(name) |
|
|
|
|
| def find_sdk(name): |
| return sdks_map.get(name) |
|
|
|
|
| def is_os_64bit(): |
| return ARCH.endswith('64') |
|
|
|
|
| def find_latest_version(): |
| return resolve_sdk_aliases('latest') |
|
|
|
|
| def find_latest_hash(): |
| version = find_latest_version() |
| releases_info = load_releases_info() |
| return releases_info['releases'][version] |
|
|
|
|
| def resolve_sdk_aliases(name, verbose=False): |
| releases_info = load_releases_info() |
| while name in releases_info['aliases']: |
| if verbose: |
| print("Resolving SDK alias '%s' to '%s'" % (name, releases_info['aliases'][name])) |
| name = releases_info['aliases'][name] |
| return name |
|
|
|
|
| def find_latest_sdk(): |
| return 'sdk-releases-%s-64bit' % (find_latest_hash()) |
|
|
|
|
| def find_tot_sdk(): |
| debug_print('Fetching emscripten-releases repository...') |
| global extra_release_tag |
| extra_release_tag = get_emscripten_releases_tot() |
| return 'sdk-releases-%s-64bit' % (extra_release_tag) |
|
|
|
|
| def parse_emscripten_version(emscripten_root): |
| version_file = os.path.join(emscripten_root, 'emscripten-version.txt') |
| with open(version_file) as f: |
| version = f.read().strip() |
| version = version.strip('"').split('-')[0].split('.') |
| return [int(v) for v in version] |
|
|
|
|
| |
| |
| |
| def get_emscripten_release_version(emscripten_releases_hash): |
| releases_info = load_releases_info() |
| for key, value in dict(releases_info['releases']).items(): |
| if value == emscripten_releases_hash: |
| return key.split('-')[0] |
| return None |
|
|
|
|
| |
| def get_emscripten_releases_tot(): |
| git_clone_checkout_and_pull(emscripten_releases_repo, sdk_path('releases'), 'main') |
| recent_releases = git_recent_commits(sdk_path('releases')) |
| |
| |
| |
| arch = '' |
| if ARCH == 'arm64': |
| arch = '-arm64' |
|
|
| def make_url(ext): |
| return emscripten_releases_download_url_template % ( |
| os_name(), |
| release, |
| arch, |
| ext, |
| ) |
|
|
| for release in recent_releases: |
| make_url('tar.xz' if not WINDOWS else 'zip') |
| try: |
| urlopen(make_url('tar.xz' if not WINDOWS else 'zip')) |
| except Exception: |
| if not WINDOWS: |
| |
| |
| try: |
| urlopen(make_url('tbz2')) |
| except Exception: |
| continue |
| else: |
| continue |
| return release |
| exit_with_error('failed to find build of any recent emsdk revision') |
|
|
|
|
| def get_release_hash(arg, releases_info): |
| return releases_info.get(arg, None) or releases_info.get(f'sdk-{arg}-64bit') |
|
|
|
|
| def version_key(ver): |
| return tuple(map(int, re.split('[._-]', ver)[:3])) |
|
|
|
|
| def is_emsdk_sourced_from_github(): |
| return os.path.exists(os.path.join(EMSDK_PATH, '.git')) |
|
|
|
|
| def update_emsdk(): |
| if is_emsdk_sourced_from_github(): |
| errlog('You seem to have bootstrapped Emscripten SDK by cloning from GitHub. In this case, use "git pull" instead of "emsdk update" to update emsdk. (Not doing that automatically in case you have local changes)') |
| sys.exit(1) |
| if not download_and_extract(emsdk_zip_download_url, EMSDK_PATH, clobber=False): |
| sys.exit(1) |
|
|
|
|
| |
| |
| def load_legacy_emscripten_tags(): |
| return open(sdk_path('legacy-emscripten-tags.txt')).read().split('\n') |
|
|
|
|
| def load_legacy_binaryen_tags(): |
| return open(sdk_path('legacy-binaryen-tags.txt')).read().split('\n') |
|
|
|
|
| def remove_prefix(s, prefix): |
| if s.startswith(prefix): |
| return s[len(prefix):] |
| else: |
| return s |
|
|
|
|
| def remove_suffix(s, suffix): |
| if s.endswith(suffix): |
| return s[:len(s) - len(suffix)] |
| else: |
| return s |
|
|
|
|
| |
| def load_file_index_list(filename): |
| items = open(sdk_path(filename)).read().splitlines() |
| items = [remove_suffix(remove_suffix(remove_prefix(x, 'emscripten-llvm-e'), '.tar.gz'), '.zip').strip() for x in items] |
| items = [x for x in items if 'latest' not in x and len(x) > 0] |
|
|
| |
| |
| return sorted(items, key=version_key) |
|
|
|
|
| |
| def load_releases_info(): |
| if not hasattr(load_releases_info, 'cached_info'): |
| try: |
| text = open(sdk_path('emscripten-releases-tags.json')).read() |
| load_releases_info.cached_info = json.loads(text) |
| except Exception as e: |
| print('Error parsing emscripten-releases-tags.json!') |
| exit_with_error(str(e)) |
|
|
| return load_releases_info.cached_info |
|
|
|
|
| def get_installed_sdk_version(): |
| version_file = sdk_path(os.path.join('upstream', '.emsdk_version')) |
| if not os.path.exists(version_file): |
| return None |
| with open(version_file) as f: |
| version = f.read() |
| return version.split('-')[1] |
|
|
|
|
| |
| def load_releases_tags(): |
| tags = [] |
| info = load_releases_info() |
|
|
| for _version, sha in sorted(info['releases'].items(), key=lambda x: version_key(x[0])): |
| tags.append(sha) |
|
|
| if extra_release_tag: |
| tags.append(extra_release_tag) |
|
|
| |
| |
| |
| installed = get_installed_sdk_version() |
| if installed and installed not in tags: |
| tags.append(installed) |
|
|
| return tags |
|
|
|
|
| def load_releases_versions(): |
| info = load_releases_info() |
| versions = list(info['releases'].keys()) |
| return versions |
|
|
|
|
| def load_sdk_manifest(): |
| try: |
| manifest = json.loads(open(sdk_path("emsdk_manifest.json")).read()) |
| except Exception as e: |
| print('Error parsing emsdk_manifest.json!') |
| print(str(e)) |
| return |
|
|
| emscripten_tags = load_legacy_emscripten_tags() |
| llvm_precompiled_tags_32bit = [] |
| llvm_precompiled_tags_64bit = load_file_index_list('llvm-tags-64bit.txt') |
| llvm_precompiled_tags = llvm_precompiled_tags_32bit + llvm_precompiled_tags_64bit |
| binaryen_tags = load_legacy_binaryen_tags() |
| releases_tags = load_releases_tags() |
|
|
| def dependencies_exist(sdk): |
| for tool_name in sdk.uses: |
| tool = find_tool(tool_name) |
| if not tool: |
| debug_print('missing dependency: ' + tool_name) |
| return False |
| return True |
|
|
| def cmp_version(ver, cmp_operand, reference): |
| if cmp_operand == '<=': |
| return version_key(ver) <= version_key(reference) |
| if cmp_operand == '<': |
| return version_key(ver) < version_key(reference) |
| if cmp_operand == '>=': |
| return version_key(ver) >= version_key(reference) |
| if cmp_operand == '>': |
| return version_key(ver) > version_key(reference) |
| if cmp_operand == '==': |
| return version_key(ver) == version_key(reference) |
| if cmp_operand == '!=': |
| return version_key(ver) != version_key(reference) |
| raise Exception(f'Invalid cmp_operand "{cmp_operand}"!') |
|
|
| def passes_filters(param, ver, filters): |
| for v in filters: |
| if v[0] == param and not cmp_version(ver, v[1], v[2]): |
| return False |
| return True |
|
|
| |
| |
| def expand_category_param(param, category_list, t, is_sdk): |
| for i, ver in enumerate(category_list): |
| if not ver.strip(): |
| continue |
| t2 = copy.copy(t) |
| found_param = False |
| for p, v in vars(t2).items(): |
| if isinstance(v, str) and param in v: |
| t2.__dict__[p] = v.replace(param, ver) |
| found_param = True |
| if not found_param: |
| continue |
| t2.is_old = i < len(category_list) - 2 |
| if hasattr(t2, 'uses'): |
| t2.uses = [x.replace(param, ver) for x in t2.uses] |
|
|
| |
| if hasattr(t2, 'version_filter'): |
| passes = passes_filters(param, ver, t2.version_filter) |
| if not passes: |
| continue |
|
|
| if is_sdk: |
| if dependencies_exist(t2): |
| if not find_sdk(t2.name): |
| add_sdk(t2) |
| else: |
| debug_print('SDK ' + str(t2) + ' already existed in manifest, not adding twice') |
| else: |
| if not find_tool(t2.name): |
| add_tool(t2) |
| else: |
| debug_print('Tool ' + str(t2) + ' already existed in manifest, not adding twice') |
|
|
| for tool in manifest['tools']: |
| t = Tool(tool) |
| if t.compatible_with_this_os(): |
| if not hasattr(t, 'is_old'): |
| t.is_old = False |
|
|
| |
| if '%tag%' in t.version: |
| expand_category_param('%tag%', emscripten_tags, t, is_sdk=False) |
| elif '%precompiled_tag%' in t.version: |
| expand_category_param('%precompiled_tag%', llvm_precompiled_tags, t, is_sdk=False) |
| elif '%precompiled_tag32%' in t.version: |
| expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, t, is_sdk=False) |
| elif '%precompiled_tag64%' in t.version: |
| expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, t, is_sdk=False) |
| elif '%binaryen_tag%' in t.version: |
| expand_category_param('%binaryen_tag%', binaryen_tags, t, is_sdk=False) |
| elif '%releases-tag%' in t.version: |
| expand_category_param('%releases-tag%', releases_tags, t, is_sdk=False) |
| else: |
| add_tool(t) |
|
|
| for sdk_str in manifest['sdks']: |
| sdk_str['id'] = 'sdk' |
| sdk = Tool(sdk_str) |
| if sdk.compatible_with_this_os(): |
| if not hasattr(sdk, 'is_old'): |
| sdk.is_old = False |
|
|
| if '%tag%' in sdk.version: |
| expand_category_param('%tag%', emscripten_tags, sdk, is_sdk=True) |
| elif '%precompiled_tag%' in sdk.version: |
| expand_category_param('%precompiled_tag%', llvm_precompiled_tags, sdk, is_sdk=True) |
| elif '%precompiled_tag32%' in sdk.version: |
| expand_category_param('%precompiled_tag32%', llvm_precompiled_tags_32bit, sdk, is_sdk=True) |
| elif '%precompiled_tag64%' in sdk.version: |
| expand_category_param('%precompiled_tag64%', llvm_precompiled_tags_64bit, sdk, is_sdk=True) |
| elif '%releases-tag%' in sdk.version: |
| expand_category_param('%releases-tag%', releases_tags, sdk, is_sdk=True) |
| else: |
| add_sdk(sdk) |
|
|
|
|
| |
| |
| |
| def can_simultaneously_activate(tool1, tool2): |
| return tool1.id != tool2.id |
|
|
|
|
| |
| def process_tool_list(tools_to_activate): |
| i = 0 |
| |
| while i < len(tools_to_activate): |
| tool = tools_to_activate[i] |
| deps = tool.recursive_dependencies() |
| tools_to_activate = tools_to_activate[:i] + deps + tools_to_activate[i:] |
| i += len(deps) + 1 |
|
|
| for tool in tools_to_activate: |
| if not tool.is_installed(): |
| exit_with_error("error: tool is not installed and therefore cannot be activated: '%s'" % tool) |
|
|
| |
| i = 0 |
| while i < len(tools_to_activate): |
| j = 0 |
| while j < i: |
| secondary_tool = tools_to_activate[j] |
| primary_tool = tools_to_activate[i] |
| if not can_simultaneously_activate(primary_tool, secondary_tool): |
| tools_to_activate.pop(j) |
| j -= 1 |
| i -= 1 |
| j += 1 |
| i += 1 |
| return tools_to_activate |
|
|
|
|
| def write_set_env_script(env_string): |
| assert CMD or POWERSHELL |
| open(EMSDK_SET_ENV, 'w').write(env_string) |
|
|
|
|
| |
| |
| |
| def set_active_tools(tools_to_activate, permanently_activate, system): |
| tools_to_activate = process_tool_list(tools_to_activate) |
|
|
| if tools_to_activate: |
| tools = [x for x in tools_to_activate if not x.is_sdk] |
| print('Setting the following tools as active:\n ' + '\n '.join([str(t) for t in tools])) |
| print('') |
|
|
| generate_em_config(tools_to_activate, permanently_activate, system) |
|
|
| |
| |
| |
| |
| if CMD or POWERSHELL: |
| |
| |
| env_vars_to_add = get_env_vars_to_add(tools_to_activate, system, user=permanently_activate) |
| env_string = construct_env_with_vars(env_vars_to_add) |
| write_set_env_script(env_string) |
|
|
| if WINDOWS and permanently_activate: |
| win_set_environment_variables(env_vars_to_add, system, user=permanently_activate) |
|
|
| return tools_to_activate |
|
|
|
|
| def currently_active_sdk(): |
| for sdk in reversed(sdks): |
| if sdk.is_active(): |
| return sdk |
| return None |
|
|
|
|
| def currently_active_tools(): |
| active_tools = [] |
| for tool in tools: |
| if tool.is_active(): |
| active_tools += [tool] |
| return active_tools |
|
|
|
|
| |
| def unique_items(seq): |
| seen = set() |
| seen_add = seen.add |
| return [x for x in seq if x not in seen and not seen_add(x)] |
|
|
|
|
| |
| def normalized_contains(lst, elem): |
| elem = to_unix_path(elem) |
| for e in lst: |
| if elem == to_unix_path(e): |
| return True |
| return False |
|
|
|
|
| def to_msys_path(p): |
| p = to_unix_path(p) |
| new_path = re.sub(r'([a-zA-Z]):/(.*)', r'/\1/\2', p) |
| if len(new_path) > 3 and new_path[0] == '/' and new_path[2] == '/': |
| new_path = new_path[0] + new_path[1].lower() + new_path[2:] |
| return new_path |
|
|
|
|
| |
| |
| def adjusted_path(tools_to_activate, system=False, user=False): |
| |
| path_add = get_required_path(tools_to_activate) |
| |
| if WINDOWS and not MSYS: |
| existing_path = win_get_environment_variable('PATH', system=system, user=user, fallback=True).split(ENVPATH_SEPARATOR) |
| else: |
| existing_path = os.environ['PATH'].split(ENVPATH_SEPARATOR) |
|
|
| existing_emsdk_tools = [] |
| existing_nonemsdk_path = [] |
| for entry in existing_path: |
| if to_unix_path(entry).startswith(EMSDK_PATH): |
| existing_emsdk_tools.append(entry) |
| else: |
| existing_nonemsdk_path.append(entry) |
|
|
| new_emsdk_tools = [] |
| kept_emsdk_tools = [] |
| for entry in path_add: |
| if not normalized_contains(existing_emsdk_tools, entry): |
| new_emsdk_tools.append(entry) |
| else: |
| kept_emsdk_tools.append(entry) |
|
|
| whole_path = unique_items(new_emsdk_tools + kept_emsdk_tools + existing_nonemsdk_path) |
|
|
| if MSYS: |
| |
| |
| |
| |
| whole_path = [to_msys_path(p) for p in whole_path] |
| new_emsdk_tools = [to_msys_path(p) for p in new_emsdk_tools] |
|
|
| separator = ':' if MSYS else ENVPATH_SEPARATOR |
| return (separator.join(whole_path), new_emsdk_tools) |
|
|
|
|
| def get_env_vars_to_add(tools_to_activate, system, user): |
| env_vars_to_add = [] |
|
|
| newpath, added_path = adjusted_path(tools_to_activate, system, user) |
|
|
| |
| if os.environ['PATH'] != newpath: |
| env_vars_to_add += [('PATH', newpath)] |
|
|
| if added_path: |
| info('Adding directories to PATH:') |
| for item in added_path: |
| info('PATH += ' + item) |
| info('') |
|
|
| |
| env_vars_to_add += [('EMSDK', EMSDK_PATH)] |
|
|
| for tool in tools_to_activate: |
| for env in tool.activated_environment(): |
| key, value = parse_key_value(env) |
| value = to_native_path(tool.expand_vars(value)) |
| env_vars_to_add += [(key, value)] |
|
|
| emroot = find_emscripten_root(tools_to_activate) |
| if emroot: |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| version = parse_emscripten_version(emroot) |
| if version < [1, 39, 16]: |
| em_cache_dir = os.path.join(emroot, 'cache') |
| env_vars_to_add += [('EM_CACHE', em_cache_dir)] |
| if version < [1, 39, 13]: |
| env_vars_to_add += [('EM_CONFIG', os.path.normpath(EM_CONFIG_PATH))] |
|
|
| return env_vars_to_add |
|
|
|
|
| def construct_env(tools_to_activate, system, user): |
| info('Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)') |
| return construct_env_with_vars(get_env_vars_to_add(tools_to_activate, system, user)) |
|
|
|
|
| def unset_env(key): |
| if POWERSHELL: |
| return 'Remove-Item env:%s\n' % key |
| if CMD: |
| return 'set %s=\n' % key |
| if CSH: |
| return 'unsetenv %s;\n' % key |
| if FISH: |
| return 'set -e %s;\n' % key |
| if BASH: |
| return 'unset %s;\n' % key |
| assert False |
|
|
|
|
| def construct_env_with_vars(env_vars_to_add): |
| env_string = '' |
| if env_vars_to_add: |
| info('Setting environment variables:') |
|
|
| for key, value in env_vars_to_add: |
| |
| if key in os.environ and to_unix_path(os.environ[key]) == to_unix_path(value): |
| continue |
| info(key + ' = ' + value) |
| if POWERSHELL: |
| env_string += f'$env:{key}="{value}"\n' |
| elif CMD: |
| env_string += f'SET {key}={value}\n' |
| elif CSH: |
| env_string += f'setenv {key} "{value}";\n' |
| elif FISH: |
| env_string += f'set -gx {key} "{value}";\n' |
| elif BASH: |
| env_string += f'export {key}="{value}";\n' |
| else: |
| assert False |
|
|
| if 'EMSDK_PYTHON' in env_vars_to_add: |
| |
| |
| |
| env_string += unset_env('PYTHONHOME') |
| env_string += unset_env('PYTHONPATH') |
|
|
| |
| |
| |
| |
| |
| ignore_keys = {'EMSDK_POWERSHELL', 'EMSDK_CSH', 'EMSDK_CMD', 'EMSDK_BASH', 'EMSDK_FISH', |
| 'EMSDK_NUM_CORES', 'EMSDK_NOTTY', 'EMSDK_KEEP_DOWNLOADS'} |
| env_keys_to_add = {pair[0] for pair in env_vars_to_add} |
| for key in os.environ: |
| if key.startswith('EMSDK_') or key in {'EM_CACHE', 'EM_CONFIG'}: |
| if key not in env_keys_to_add and key not in ignore_keys: |
| info('Clearing existing environment variable: %s' % key) |
| env_string += unset_env(key) |
|
|
| return env_string |
|
|
|
|
| def error_on_missing_tool(name): |
| if name.endswith('-64bit') and not is_os_64bit(): |
| exit_with_error("'%s' is only provided for 64-bit OSes" % name) |
| else: |
| exit_with_error("tool or SDK not found: '%s'" % name) |
|
|
|
|
| def expand_sdk_name(name, activating): |
| if 'upstream-master' in name: |
| errlog('upstream-master SDK has been renamed main') |
| name = name.replace('upstream-master', 'main') |
| if 'fastcomp' in name: |
| exit_with_error('the fastcomp backend is no longer supported. Please use an older version of emsdk (for example 3.1.29) if you want to install the old fastcomp-based SDK') |
| if name in {'tot', 'sdk-tot', 'tot-upstream'}: |
| if activating: |
| |
| |
| |
| |
| installed = get_installed_sdk_version() |
| if installed: |
| debug_print('activating currently installed SDK; not updating tot version') |
| return 'sdk-releases-%s-64bit' % installed |
| return find_tot_sdk() |
|
|
| if '-upstream' in name: |
| name = name.replace('-upstream', '') |
|
|
| name = resolve_sdk_aliases(name, verbose=True) |
|
|
| |
| |
| |
| |
| |
| fullname = name |
| version = fullname.replace('sdk-', '').replace('releases-', '').replace('-64bit', '').replace('tag-', '') |
| sdk = 'sdk-' if not name.startswith('releases-') else '' |
| releases_info = load_releases_info()['releases'] |
| release_hash = get_release_hash(version, releases_info) |
| if release_hash: |
| |
| full_name = '%sreleases-%s-64bit' % (sdk, release_hash) |
| print("Resolving SDK version '%s' to '%s'" % (version, full_name)) |
| return full_name |
|
|
| if len(version) == 40: |
| global extra_release_tag |
| extra_release_tag = version |
| return '%sreleases-%s-64bit' % (sdk, version) |
|
|
| return name |
|
|
|
|
| def main(args): |
| if not args: |
| errlog("Missing command; Type 'emsdk help' to get a list of commands.") |
| return 1 |
|
|
| debug_print('emsdk.py running under `%s`' % sys.executable) |
| cmd = args.pop(0) |
|
|
| if cmd in {'help', '--help', '-h'}: |
| print(' emsdk: Available commands:') |
|
|
| print(''' |
| emsdk list [--old] [--uses] - Lists all available SDKs and tools and their |
| current installation status. With the --old |
| parameter, also historical versions are |
| shown. If --uses is passed, displays the |
| composition of different SDK packages and |
| dependencies. |
| |
| emsdk update - Updates emsdk to the newest version. If you have |
| bootstrapped emsdk via cloning directly from |
| GitHub, call "git pull" instead to update emsdk. |
| |
| emsdk install [options] <tool 1> <tool 2> <tool 3> ... |
| - Downloads and installs given tools or SDKs. |
| Options can contain: |
| |
| -j<num>: Specifies the number of cores to use when |
| building the tool. Default: use one less |
| than the # of detected cores. |
| |
| --build=<type>: Controls what kind of build of LLVM to |
| perform. Pass either 'Debug', 'Release', |
| 'MinSizeRel' or 'RelWithDebInfo'. Default: |
| 'Release'. |
| |
| --generator=<type>: Specifies the CMake Generator to be used |
| during the build. Possible values are the |
| same as what your CMake supports and whether |
| the generator is valid depends on the tools |
| you have installed. Defaults to 'Unix Makefiles' |
| on *nix systems. If generator name is multiple |
| words, enclose with single or double quotes. |
| |
| --shallow: When installing tools from one of the git |
| development branches, this parameter can be |
| passed to perform a shallow git clone instead |
| of a full one. This reduces the amount of |
| network transfer that is needed. This option |
| should only be used when you are interested in |
| downloading one of the development branches, |
| but are not looking to develop Emscripten |
| yourself. Default: disabled, i.e. do a full |
| clone. |
| |
| --build-tests: If enabled, LLVM is built with internal tests |
| included. Pass this to enable running test |
| other.test_llvm_lit in the Emscripten test |
| suite. Default: disabled. |
| --enable-assertions: If specified, LLVM is built with assert() |
| checks enabled. Useful for development |
| purposes. Default: Enabled |
| --disable-assertions: Forces assertions off during the build. |
| |
| --vs2019/--vs2022: If building from source, overrides to build |
| using the specified compiler. When installing |
| precompiled packages, this has no effect. |
| Note: The same compiler specifier must be |
| passed to the emsdk activate command to |
| activate the desired version. |
| |
| Notes on building from source: |
| |
| To pass custom CMake directives when configuring |
| LLVM build, specify the environment variable |
| LLVM_CMAKE_ARGS="param1=value1,param2=value2" |
| in the environment where the build is invoked. |
| See README.md for details. |
| |
| --override-repository: Specifies the git URL to use for a given Tool. E.g. |
| --override-repository emscripten-main@https://github.com/<fork>/emscripten/tree/<refspec> |
| |
| |
| emsdk deactivate tool/sdk - Removes the given tool or SDK from the current set of activated tools. |
| |
| |
| emsdk uninstall <tool/sdk> - Removes the given tool or SDK from disk.''') |
|
|
| if WINDOWS: |
| print(''' |
| emsdk activate [--permanent] [--system] [--build=type] [--vs2019/--vs2022] <tool/sdk> |
| |
| - Activates the given tool or SDK in the |
| environment of the current shell. |
| |
| - If the `--permanent` option is passed, then the environment |
| variables are set permanently for the current user. |
| |
| - If the `--system` option is passed, the registration |
| is done for all users of the system. |
| This needs admin privileges |
| (uses Machine environment variables). |
| |
| - If a custom compiler version was used to override |
| the compiler to use, pass the same --vs2019/--vs2022 |
| parameter here to choose which version to activate. |
| |
| emcmdprompt.bat - Spawns a new command prompt window with the |
| Emscripten environment active.''') |
| else: |
| print(''' emsdk activate [--build=type] <tool/sdk> |
| |
| - Activates the given tool or SDK in the |
| environment of the current shell.''') |
|
|
| print(''' |
| Both commands 'install' and 'activate' accept an optional parameter |
| '--build=type', which can be used to override what kind of installation |
| or activation to perform. Possible values for type are Debug, Release, |
| MinSizeRel or RelWithDebInfo. Note: When overriding a custom build type, |
| be sure to match the same --build= option to both 'install' and |
| 'activate' commands and the invocation of 'emsdk_env', or otherwise |
| these commands will default to operating on the default build type |
| which is RelWithDebInfo.''') |
|
|
| print(''' |
| |
| Environment: |
| EMSDK_KEEP_DOWNLOADS=1 - if you want to keep the downloaded archives. |
| EMSDK_NOTTY=1 - override isatty() result (mainly to log progress). |
| EMSDK_NUM_CORES=n - limit parallelism to n cores. |
| EMSDK_VERBOSE=1 - very verbose output, useful for debugging. |
| EMSDK_RETRY_CLEAN_BUILD=1 - performs a clean rebuild of compiled tools if |
| incremental build fails. Useful on CI.''') |
| return 0 |
|
|
| |
| def extract_bool_arg(name): |
| if name in args: |
| args.remove(name) |
| return True |
| return False |
|
|
| def extract_string_arg(name): |
| for i in range(len(args)): |
| if args[i] == name: |
| value = args[i + 1] |
| del args[i:i + 2] |
| return value |
|
|
| arg_old = extract_bool_arg('--old') |
| arg_uses = extract_bool_arg('--uses') |
| arg_permanent = extract_bool_arg('--permanent') |
| arg_global = extract_bool_arg('--global') |
| arg_system = extract_bool_arg('--system') |
| if arg_global: |
| print('--global is deprecated. Use `--system` to set the environment variables for all users') |
| arg_system = True |
| if arg_system: |
| arg_permanent = True |
| if extract_bool_arg('--embedded'): |
| errlog('embedded mode is now the only mode available') |
| if extract_bool_arg('--no-embedded'): |
| errlog('embedded mode is now the only mode available') |
| return 1 |
|
|
| arg_notty = extract_bool_arg('--notty') |
| if arg_notty: |
| global TTY_OUTPUT |
| TTY_OUTPUT = False |
|
|
| |
| if cmd in {'update', 'install', 'activate'}: |
| activating = cmd == 'activate' |
| args = [expand_sdk_name(a, activating=activating) for a in args] |
|
|
| load_em_config() |
| load_sdk_manifest() |
|
|
| |
| forked_url = extract_string_arg('--override-repository') |
| while forked_url: |
| tool_name, url_and_refspec = forked_url.split('@') |
| t = find_tool(tool_name) |
| if not t: |
| errlog('Failed to find tool {tool_name}!') |
| return False |
| else: |
| t.url, t.git_branch, t.remote_name = parse_github_url_and_refspec(url_and_refspec) |
| debug_print(f'Reading git repository URL "{t.url}" and git branch "{t.git_branch}" for Tool "{tool_name}".') |
|
|
| forked_url = extract_string_arg('--override-repository') |
|
|
| |
| for i in range(len(args)): |
| if args[i].startswith('--generator='): |
| build_generator = re.match(r'''^--generator=['"]?([^'"]+)['"]?$''', args[i]) |
| if build_generator: |
| global CMAKE_GENERATOR |
| CMAKE_GENERATOR = build_generator.group(1) |
| args[i] = '' |
| else: |
| errlog(f"Cannot parse CMake generator string: {args[i]}. Try wrapping generator string with quotes") |
| return 1 |
| elif args[i].startswith('--build='): |
| build_type = re.match(r'^--build=(.+)$', args[i]) |
| if build_type: |
| global CMAKE_BUILD_TYPE_OVERRIDE |
| build_type = build_type.group(1) |
| build_types = ['Debug', 'MinSizeRel', 'RelWithDebInfo', 'Release'] |
| try: |
| build_type_index = [x.lower() for x in build_types].index(build_type.lower()) |
| CMAKE_BUILD_TYPE_OVERRIDE = build_types[build_type_index] |
| args[i] = '' |
| except Exception: |
| errlog(f'Unknown CMake build type "{build_type}" specified! Please specify one of {build_types}') |
| return 1 |
| else: |
| errlog(f'Invalid command line parameter {args[i]} specified!') |
| return 1 |
| args = [x for x in args if x] |
|
|
| if cmd == 'list': |
| print('') |
|
|
| def installed_sdk_text(name): |
| sdk = find_sdk(name) |
| return 'INSTALLED' if sdk and sdk.is_installed() else '' |
|
|
| if (LINUX or MACOS or WINDOWS) and ARCH in {'x86', 'x86_64'}: |
| print('The *recommended* precompiled SDK download is %s (%s).' % (find_latest_version(), find_latest_hash())) |
| print() |
| print('To install/activate it use:') |
| print(' latest') |
| print('') |
| print('This is equivalent to installing/activating:') |
| print(' %s %s' % (find_latest_version(), installed_sdk_text(find_latest_sdk()))) |
| print('') |
| else: |
| print('Warning: your platform does not have precompiled SDKs available.') |
| print('You may install components from source.') |
| print('') |
|
|
| print('All recent (non-legacy) installable versions are:') |
| releases_versions = sorted(load_releases_versions(), key=version_key, reverse=True) |
| releases_info = load_releases_info()['releases'] |
| for ver in releases_versions: |
| print(' %s %s' % (ver, installed_sdk_text('sdk-releases-%s-64bit' % get_release_hash(ver, releases_info)))) |
| print() |
|
|
| |
| |
| has_partially_active_tools = [False] |
|
|
| if sdks: |
| def find_sdks(needs_compilation): |
| s = [] |
| for sdk in sdks: |
| if sdk.is_old and not arg_old: |
| continue |
| if sdk.needs_compilation() == needs_compilation: |
| s += [sdk] |
| return s |
|
|
| def print_sdks(s): |
| for sdk in s: |
| installed = '\tINSTALLED' if sdk.is_installed() else '' |
| active = '*' if sdk.is_active() else ' ' |
| print(' ' + active + ' {0: <25}'.format(str(sdk)) + installed) |
| if arg_uses: |
| for dep in sdk.uses: |
| print(' - {0: <25}'.format(dep)) |
| print('') |
| print('The additional following precompiled SDKs are also available for download:') |
| print_sdks(find_sdks(False)) |
|
|
| print('The following SDKs can be compiled from source:') |
| print_sdks(find_sdks(True)) |
|
|
| if tools: |
| def find_tools(needs_compilation): |
| t = [] |
| for tool in tools: |
| if tool.is_old and not arg_old: |
| continue |
| if tool.needs_compilation() != needs_compilation: |
| continue |
| t += [tool] |
| return t |
|
|
| def print_tools(t): |
| for tool in t: |
| if tool.is_old and not arg_old: |
| continue |
| if tool.can_be_installed() is True: |
| installed = '\tINSTALLED' if tool.is_installed() else '' |
| else: |
| installed = '\tNot available: ' + tool.can_be_installed() |
| tool_is_active = tool.is_active() |
| tool_is_env_active = tool_is_active and tool.is_env_active() |
| if tool_is_env_active: |
| active = ' * ' |
| elif tool_is_active: |
| active = '(*)' |
| has_partially_active_tools[0] = has_partially_active_tools[0] or True |
| else: |
| active = ' ' |
| print(' ' + active + ' {0: <25}'.format(str(tool)) + installed) |
| print('') |
|
|
| print('The following precompiled tool packages are available for download:') |
| print_tools(find_tools(needs_compilation=False)) |
| print('The following tools can be compiled from source:') |
| print_tools(find_tools(needs_compilation=True)) |
| else: |
| if is_emsdk_sourced_from_github(): |
| print("There are no tools available. Run 'git pull' to fetch the latest set of tools.") |
| else: |
| print("There are no tools available. Run 'emsdk update' to fetch the latest set of tools.") |
| print('') |
|
|
| print('Items marked with * are activated for the current user.') |
| if has_partially_active_tools[0]: |
| env_cmd = 'emsdk_env.bat' if WINDOWS else 'source ./emsdk_env.sh' |
| print(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 '.')) |
| if not arg_old: |
| print('') |
| print("To access the historical archived versions, type 'emsdk list --old'") |
|
|
| print('') |
| if is_emsdk_sourced_from_github(): |
| print('Run "git pull" to pull in the latest list.') |
| else: |
| print('Run "./emsdk update" to pull in the latest list.') |
|
|
| return 0 |
| elif cmd == 'construct_env': |
| |
| |
| tools_to_activate = currently_active_tools() |
| tools_to_activate = process_tool_list(tools_to_activate) |
| env_string = construct_env(tools_to_activate, arg_system, arg_permanent) |
| if CMD or POWERSHELL: |
| write_set_env_script(env_string) |
| else: |
| sys.stdout.write(env_string) |
| return 0 |
| elif cmd == 'update': |
| update_emsdk() |
| if WINDOWS: |
| |
| |
| rmfile(sdk_path(EMSDK_SET_ENV)) |
| return 0 |
| elif cmd == 'update-tags': |
| errlog('`update-tags` is not longer needed. To install the latest tot release just run `install tot`') |
| return 0 |
| elif cmd in {'activate', 'deactivate'}: |
| if arg_permanent: |
| print('Registering active Emscripten environment permanently') |
| print('') |
|
|
| tools_to_activate = currently_active_tools() |
| for arg in args: |
| tool = find_tool(arg) |
| if tool is None: |
| tool = find_sdk(arg) |
| if tool is None: |
| error_on_missing_tool(arg) |
|
|
| if cmd == 'activate': |
| tools_to_activate += [tool] |
| elif tool in tools_to_activate: |
| print('Deactivating tool ' + str(tool) + '.') |
| tools_to_activate.remove(tool) |
| else: |
| print(f'Tool "{arg}" was not active, no need to deactivate.') |
| if not tools_to_activate: |
| errlog('No tools/SDKs specified to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]') |
| return 1 |
| active_tools = set_active_tools(tools_to_activate, permanently_activate=arg_permanent, system=arg_system) |
| if not active_tools: |
| errlog('No tools/SDKs found to activate! Usage:\n emsdk activate tool/sdk1 [tool/sdk2] [...]') |
| return 1 |
| if WINDOWS and not arg_permanent: |
| errlog('The changes made to environment variables only apply to the currently running shell instance. Use the \'emsdk_env.bat\' to re-enter this environment later, or if you\'d like to register this environment permanently, rerun this command with the option --permanent.') |
| return 0 |
| elif cmd == 'install': |
| global BUILD_FOR_TESTING, ENABLE_LLVM_ASSERTIONS, CPU_CORES, GIT_CLONE_SHALLOW |
|
|
| |
| for i in range(len(args)): |
| if args[i].startswith('-j'): |
| multicore = re.match(r'^-j(\d+)$', args[i]) |
| if multicore: |
| CPU_CORES = int(multicore.group(1)) |
| args[i] = '' |
| else: |
| errlog(f'Invalid command line parameter {args[i]} specified!') |
| return 1 |
| elif args[i] == '--shallow': |
| GIT_CLONE_SHALLOW = True |
| args[i] = '' |
| elif args[i] == '--build-tests': |
| BUILD_FOR_TESTING = True |
| args[i] = '' |
| elif args[i] == '--enable-assertions': |
| ENABLE_LLVM_ASSERTIONS = 'ON' |
| args[i] = '' |
| elif args[i] == '--disable-assertions': |
| ENABLE_LLVM_ASSERTIONS = 'OFF' |
| args[i] = '' |
| args = [x for x in args if x] |
| if not args: |
| errlog("Missing parameter. Type 'emsdk install <tool name>' to install a tool or an SDK. Type 'emsdk list' to obtain a list of available tools. Type 'emsdk install latest' to automatically install the newest version of the SDK.") |
| return 1 |
|
|
| if LINUX and ARCH == 'arm64' and args != ['latest']: |
| errlog('WARNING: arm64-linux binaries are not available for all releases.') |
| errlog('See https://github.com/emscripten-core/emsdk/issues/547') |
|
|
| for t in args: |
| tool = find_tool(t) |
| if tool is None: |
| tool = find_sdk(t) |
| if tool is None: |
| error_on_missing_tool(t) |
| tool.install() |
| return 0 |
| elif cmd == 'uninstall': |
| if not args: |
| errlog("Syntax error. Call 'emsdk uninstall <tool name>'. Call 'emsdk list' to obtain a list of available tools.") |
| return 1 |
| tool = find_tool(args[0]) |
| if tool is None: |
| errlog(f"Error: Tool by name '{args[0]}' was not found.") |
| return 1 |
| tool.uninstall() |
| return 0 |
|
|
| errlog(f"Unknown command '{cmd}' given! Type 'emsdk help' to get a list of commands.") |
| return 1 |
|
|
|
|
| if __name__ == '__main__': |
| try: |
| sys.exit(main(sys.argv[1:])) |
| except KeyboardInterrupt: |
| exit_with_error('aborted by user, exiting') |
| sys.exit(1) |
|
|