From 149e10e364a07e3134d40c460ae74998e7c7548e Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 15 Jan 2025 09:34:32 -0800 Subject: [PATCH] Update min python version to 3.9 The reason for picking 3.9 here is that it provides all the features we currently have need of, and it available in the places we care about: - debian/stable (bookworm): 3.11 - ubuntu/LTS (jammy): 3.10 - emsdk: 3.9.2 It also seems like a good idea to choose the emsdk version since that is the version we use for testing and we don't currently have any mechanism to test on anything older than that (which means we currently lack any way to confirm that we really do support 3.6). Replaces: #23378 Fixes: #23387 --- ChangeLog.md | 2 ++ pyproject.toml | 1 + tools/building.py | 13 +--------- tools/extract_metadata.py | 3 ++- tools/shared.py | 25 ++++---------------- tools/system_libs.py | 11 +-------- tools/webassembly.py | 50 ++++++++++----------------------------- 7 files changed, 25 insertions(+), 80 deletions(-) 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():