| |
| |
| |
| |
|
|
| """General purpose utility functions. |
| |
| The code in this file should mostly be not emscripten-specific, but general |
| purpose enough to be useful in any command line utility. |
| """ |
|
|
| import functools |
| import logging |
| import os |
| import shlex |
| import shutil |
| import stat |
| import subprocess |
| import sys |
| from pathlib import Path |
|
|
| from . import diagnostics |
|
|
| __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) |
| WINDOWS = sys.platform.startswith('win') |
| MACOS = sys.platform == 'darwin' |
| LINUX = sys.platform.startswith('linux') |
|
|
| logger = logging.getLogger('utils') |
|
|
|
|
| def run_process(cmd, check=True, input=None, *args, **kw): |
| """Run a subprocess returning the exit code. |
| |
| By default this function will raise an exception on failure. Therefore this should only be |
| used if you want to handle such failures. For most subprocesses, failures are not recoverable |
| and should be fatal. In those cases the `check_call` wrapper should be preferred. |
| """ |
| |
| |
| sys.stdout.flush() |
| sys.stderr.flush() |
| kw.setdefault('text', True) |
| kw.setdefault('encoding', 'utf-8') |
| ret = subprocess.run(cmd, check=check, input=input, *args, **kw) |
| debug_text = '%sexecuted %s' % ('successfully ' if check else '', shlex.join(cmd)) |
| logger.debug(debug_text) |
| return ret |
|
|
|
|
| def exec(cmd): |
| if WINDOWS: |
| rtn = run_process(cmd, stdin=sys.stdin, check=False).returncode |
| sys.exit(rtn) |
| else: |
| sys.stdout.flush() |
| sys.stderr.flush() |
| os.execvp(cmd[0], cmd) |
|
|
|
|
| def exit_with_error(msg, *args): |
| diagnostics.error(msg, *args) |
|
|
|
|
| def path_from_root(*pathelems): |
| return str(Path(__rootpath__, *pathelems)) |
|
|
|
|
| def exe_path_from_root(*pathelems): |
| return find_exe(path_from_root(*pathelems)) |
|
|
|
|
| def suffix(name): |
| """Return the file extension.""" |
| return os.path.splitext(name)[1] |
|
|
|
|
| def find_exe(*pathelems): |
| path = os.path.join(*pathelems) |
|
|
| if WINDOWS: |
| |
| |
| for ext in ['.exe', '.bat']: |
| if os.path.isfile(path + ext): |
| return path + ext |
|
|
| return path |
|
|
|
|
| def replace_suffix(filename, new_suffix): |
| assert new_suffix[0] == '.' |
| return os.path.splitext(filename)[0] + new_suffix |
|
|
|
|
| def unsuffixed(name): |
| """Return the filename without the extension. |
| |
| If there are multiple extensions this strips only the final one. |
| """ |
| return os.path.splitext(name)[0] |
|
|
|
|
| def unsuffixed_basename(name): |
| return os.path.basename(unsuffixed(name)) |
|
|
|
|
| def get_file_suffix(filename): |
| """Return the essential suffix of a filename, discarding Unix-style version numbers. |
| |
| For example for 'libz.so.1.2.8' returns '.so' |
| """ |
| while filename: |
| filename, suffix = os.path.splitext(filename) |
| if not suffix[1:].isdigit(): |
| return suffix |
| return '' |
|
|
|
|
| def normalize_path(path): |
| """Normalize path separators to UNIX-style forward slashes. |
| |
| This can be useful when converting paths to URLs or JS strings, |
| or when trying to generate consistent output file contents |
| across all platforms. In most cases UNIX-style separators work |
| fine on windows. |
| """ |
| return path.replace('\\', '/').replace('//', '/') |
|
|
|
|
| def safe_ensure_dirs(dirname): |
| os.makedirs(dirname, exist_ok=True) |
|
|
|
|
| def make_writable(filename): |
| assert os.path.exists(filename) |
| old_mode = stat.S_IMODE(os.stat(filename).st_mode) |
| os.chmod(filename, old_mode | stat.S_IWUSR) |
|
|
|
|
| def safe_copy(src, dst): |
| logger.debug('copy: %s -> %s', src, dst) |
| src = os.path.abspath(src) |
| dst = os.path.abspath(dst) |
| if os.path.isdir(dst): |
| dst = os.path.join(dst, os.path.basename(src)) |
| if src == dst: |
| return |
| if dst == os.devnull: |
| return |
| |
| shutil.copy(src, dst) |
| |
| |
| make_writable(dst) |
|
|
|
|
| def read_file(file_path): |
| """Read from a file opened in text mode.""" |
| with open(file_path, encoding='utf-8') as fh: |
| return fh.read() |
|
|
|
|
| def read_binary(file_path): |
| """Read from a file opened in binary mode.""" |
| with open(file_path, 'rb') as fh: |
| return fh.read() |
|
|
|
|
| def write_file(file_path, text, line_endings=None): |
| """Write to a file opened in text mode.""" |
| if line_endings and line_endings != os.linesep: |
| text = text.replace('\n', line_endings) |
| write_binary(file_path, text.encode('utf-8')) |
| else: |
| with open(file_path, 'w', encoding='utf-8') as fh: |
| fh.write(text) |
|
|
|
|
| def write_binary(file_path, contents): |
| """Write to a file opened in binary mode.""" |
| with open(file_path, 'wb') as fh: |
| fh.write(contents) |
|
|
|
|
| def delete_file(filename): |
| """Delete a file (if it exists).""" |
| if os.path.lexists(filename): |
| os.remove(filename) |
|
|
|
|
| def delete_dir(dirname): |
| """Delete a directory (if it exists).""" |
| if not os.path.exists(dirname): |
| return |
| shutil.rmtree(dirname) |
|
|
|
|
| def delete_contents(dirname, exclude=None): |
| """Delete the contents of a directory without removing the directory itself.""" |
| if not os.path.exists(dirname): |
| return |
| for entry in os.listdir(dirname): |
| if exclude and entry in exclude: |
| continue |
| entry = os.path.join(dirname, entry) |
| if os.path.isdir(entry): |
| delete_dir(entry) |
| else: |
| delete_file(entry) |
|
|
|
|
| def get_num_cores(): |
| |
| |
| |
| |
| if hasattr(os, 'process_cpu_count'): |
| cpu_count = os.process_cpu_count() |
| elif hasattr(os, 'sched_getaffinity'): |
| cpu_count = len(os.sched_getaffinity(0)) |
| else: |
| cpu_count = os.cpu_count() |
| return int(os.environ.get('EMCC_CORES', cpu_count)) |
|
|
|
|
| memoize = functools.cache |
|
|
|
|
| |
| def set_version_globals(): |
| global EMSCRIPTEN_VERSION, EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY |
| filename = path_from_root('emscripten-version.txt') |
| EMSCRIPTEN_VERSION = read_file(filename).strip().strip('"') |
| parts = [int(x) for x in EMSCRIPTEN_VERSION.split('-')[0].split('.')] |
| EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY = parts |
|
|