diff --git a/ChangeLog.md b/ChangeLog.md index bd1cc78513d09..3b90f176ff965 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -23,6 +23,8 @@ See docs/process.md for more on how version tagging works. - The minimum version of node required to run emscripten was bumped from v16.20 to v18. Version 4.0 was mistakenly shipped with a change that required v20, but that was reverted. (#23410) +- The version of python required to run emscripten was bumped from python3.6 to + python3.9. (#23417) 4.0.0 - 01/14/25 ---------------- diff --git a/pyproject.toml b/pyproject.toml index 5db694ed8e736..636cb0e6482f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ lint.ignore = [ "B006", "B011", "B018", + "B019", "B023", "B026", "B904", diff --git a/tools/building.py b/tools/building.py index 3d764fe82d33a..231b810ce4c60 100644 --- a/tools/building.py +++ b/tools/building.py @@ -476,17 +476,6 @@ def check_closure_compiler(cmd, args, env, allowed_to_fail): return True -# Remove this once we require python3.7 and can use std.isascii. -# See: https://docs.python.org/3/library/stdtypes.html#str.isascii -def isascii(s): - try: - s.encode('ascii') - except UnicodeEncodeError: - return False - else: - return True - - def get_closure_compiler_and_env(user_args): env = shared.env_with_node_in_path() closure_cmd = get_closure_compiler() @@ -623,7 +612,7 @@ def run_closure_cmd(cmd, filename, env): tempfiles = shared.get_temp_files() def move_to_safe_7bit_ascii_filename(filename): - if isascii(filename): + if filename.isascii(): return os.path.abspath(filename) safe_filename = tempfiles.get('.js').name # Safe 7-bit filename shutil.copyfile(filename, safe_filename) diff --git a/tools/extract_metadata.py b/tools/extract_metadata.py index a5159f24a1da8..3a7b4d59075ba 100644 --- a/tools/extract_metadata.py +++ b/tools/extract_metadata.py @@ -4,6 +4,7 @@ # found in the LICENSE file. import logging +from functools import cache from typing import List, Dict from . import webassembly, utils @@ -140,7 +141,7 @@ def parse_function_for_memory_inits(module, func_index, offset_map): parse_function_for_memory_inits(module, t, offset_map) -@webassembly.memoize +@cache def get_passive_segment_offsets(module): start_func_index = module.get_start() assert start_func_index is not None diff --git a/tools/shared.py b/tools/shared.py index 1f4f37f24b5b0..f29dcef281ad5 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -6,8 +6,8 @@ from .toolchain_profiler import ToolchainProfiler from enum import Enum, unique, auto -from functools import wraps from subprocess import PIPE +import functools import atexit import json import logging @@ -20,9 +20,9 @@ import sys import tempfile -# We depend on python 3.6 for fstring support -if sys.version_info < (3, 6): - print('error: emscripten requires python 3.6 or above', file=sys.stderr) +# We depend on python 3.9 features +if sys.version_info < (3, 9): + print(f'error: emscripten requires python 3.9 or above ({sys.executable} {sys.version})', file=sys.stderr) sys.exit(1) from . import colored_logger @@ -67,6 +67,7 @@ EMSCRIPTEN_TEMP_DIR = None logger = logging.getLogger('shared') +memoize = functools.cache # warning about absolute-paths is disabled by default, and not enabled by -Wall diagnostics.add_warning('absolute-paths', enabled=False, part_of_all=False) @@ -273,22 +274,6 @@ def get_npm_cmd(name): return cmd -# TODO(sbc): Replace with functools.cache, once we update to python 3.7 -def memoize(func): - called = False - result = None - - @wraps(func) - def helper(): - nonlocal called, result - if not called: - result = func() - called = True - return result - - return helper - - @memoize def get_clang_version(): if not os.path.exists(CLANG_CC): diff --git a/tools/system_libs.py b/tools/system_libs.py index ea06d88515a74..eac05f20e041c 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -2442,17 +2442,8 @@ def calculate(args): return ret -# Once we require python 3.8 we can use shutil.copytree with -# dirs_exist_ok=True and remove this function. def copytree_exist_ok(src, dst): - os.makedirs(dst, exist_ok=True) - for entry in os.scandir(src): - srcname = os.path.join(src, entry.name) - dstname = os.path.join(dst, entry.name) - if entry.is_dir(): - copytree_exist_ok(srcname, dstname) - else: - shared.safe_copy(srcname, dstname) + shutil.copytree(src, dst, dirs_exist_ok=True) def install_system_headers(stamp): diff --git a/tools/webassembly.py b/tools/webassembly.py index 898d33e35b997..09b26cc42c7b0 100644 --- a/tools/webassembly.py +++ b/tools/webassembly.py @@ -6,9 +6,9 @@ """Utilities for manipulating WebAssembly binaries from python. """ +from functools import cache from collections import namedtuple from enum import IntEnum -from functools import wraps import logging import os import sys @@ -55,30 +55,6 @@ def read_sleb(iobuf): return leb128.i.decode_reader(iobuf)[0] -def memoize(method): - - @wraps(method) - def wrapper(self, *args, **kwargs): - assert not kwargs - key = (method.__name__, args) - if key not in self._cache: - self._cache[key] = method(self, *args, **kwargs) - return self._cache[key] - - return wrapper - - -def once(method): - - @wraps(method) - def helper(self, *args, **kwargs): - key = method - if key not in self._cache: - self._cache[key] = method(self, *args, **kwargs) - - return helper - - class Type(IntEnum): I32 = 0x7f # -0x1 I64 = 0x7e # -0x2 @@ -280,7 +256,7 @@ def sections(self): yield Section(section_type, section_size, section_offset, name) offset = section_offset + section_size - @memoize + @cache def get_types(self): type_section = self.get_section(SecType.TYPE) if not type_section: @@ -315,7 +291,7 @@ def parse_features_section(self): feature_count -= 1 return features - @memoize + @cache def parse_dylink_section(self): dylink_section = next(self.sections()) assert dylink_section.type == SecType.CUSTOM @@ -380,7 +356,7 @@ def parse_dylink_section(self): return Dylink(mem_size, mem_align, table_size, table_align, needed, export_info, import_info) - @memoize + @cache def get_exports(self): export_section = self.get_section(SecType.EXPORT) if not export_section: @@ -397,7 +373,7 @@ def get_exports(self): return exports - @memoize + @cache def get_imports(self): import_section = self.get_section(SecType.IMPORT) if not import_section: @@ -430,7 +406,7 @@ def get_imports(self): return imports - @memoize + @cache def get_globals(self): global_section = self.get_section(SecType.GLOBAL) if not global_section: @@ -445,7 +421,7 @@ def get_globals(self): globls.append(Global(global_type, mutable, init)) return globls - @memoize + @cache def get_start(self): start_section = self.get_section(SecType.START) if not start_section: @@ -453,7 +429,7 @@ def get_start(self): self.seek(start_section.offset) return self.read_uleb() - @memoize + @cache def get_functions(self): code_section = self.get_section(SecType.CODE) if not code_section: @@ -471,14 +447,14 @@ def get_functions(self): def get_section(self, section_code): return next((s for s in self.sections() if s.type == section_code), None) - @memoize + @cache def get_custom_section(self, name): for section in self.sections(): if section.type == SecType.CUSTOM and section.name == name: return section return None - @memoize + @cache def get_segments(self): segments = [] data_section = self.get_section(SecType.DATA) @@ -496,7 +472,7 @@ def get_segments(self): self.seek(offset + size) return segments - @memoize + @cache def get_tables(self): table_section = self.get_section(SecType.TABLE) if not table_section: @@ -512,7 +488,7 @@ def get_tables(self): return tables - @memoize + @cache def get_function_types(self): function_section = self.get_section(SecType.FUNCTION) if not function_section: @@ -525,7 +501,7 @@ def get_function_types(self): def has_name_section(self): return self.get_custom_section('name') is not None - @once + @cache def _calc_indexes(self): self.imports_by_kind = {} for i in self.get_imports():