diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index d3e842d9f31d01..7490dd947e1504 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -5,7 +5,7 @@ jobs: displayName: Pre-build checks pool: - vmImage: ubuntu-22.04 + vmImage: ubuntu-24.04 steps: - template: ./prebuild-checks.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 083b07156674df..f63c4606220494 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,7 +88,7 @@ jobs: name: 'Check if generated files are up to date' # Don't use ubuntu-latest but a specific version to make the job # reproducible: to get the same tools versions (autoconf, aclocal, ...) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' @@ -237,7 +237,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] openssl_ver: [3.0.15, 3.1.7, 3.2.3, 3.3.2] env: OPENSSL_VER: ${{ matrix.openssl_ver }} @@ -297,7 +297,7 @@ jobs: test_hypothesis: name: "Hypothesis tests on Ubuntu" - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true' @@ -417,7 +417,7 @@ jobs: if: needs.check_source.outputs.run_tests == 'true' strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] env: OPENSSL_VER: 3.0.15 PYTHONSTRICTEXTENSIONBUILD: 1 diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 48f05818a38f96..897c692118e9a4 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -52,7 +52,7 @@ jobs: - x86_64-pc-windows-msvc/msvc - aarch64-pc-windows-msvc/msvc - x86_64-apple-darwin/clang - - aarch64-apple-darwin/clang + # - aarch64-apple-darwin/clang - x86_64-unknown-linux-gnu/gcc - x86_64-unknown-linux-gnu/clang - aarch64-unknown-linux-gnu/gcc @@ -79,10 +79,11 @@ jobs: architecture: x86_64 runner: macos-13 compiler: clang - - target: aarch64-apple-darwin/clang - architecture: aarch64 - runner: macos-14 - compiler: clang + # GH-126464: A recent change to either GHA or LLVM broke this job: + # - target: aarch64-apple-darwin/clang + # architecture: aarch64 + # runner: macos-14 + # compiler: clang - target: x86_64-unknown-linux-gnu/gcc architecture: x86_64 runner: ubuntu-22.04 diff --git a/.github/workflows/posix-deps-apt.sh b/.github/workflows/posix-deps-apt.sh index bfc5a0874281bd..d5538cd9367ec6 100755 --- a/.github/workflows/posix-deps-apt.sh +++ b/.github/workflows/posix-deps-apt.sh @@ -13,7 +13,6 @@ apt-get -yq install \ libgdbm-dev \ libgdbm-compat-dev \ liblzma-dev \ - libmpdec-dev \ libncurses5-dev \ libreadline6-dev \ libsqlite3-dev \ diff --git a/.github/workflows/reusable-tsan.yml b/.github/workflows/reusable-tsan.yml index 65072efa8e9023..7a4d81f0bdcad1 100644 --- a/.github/workflows/reusable-tsan.yml +++ b/.github/workflows/reusable-tsan.yml @@ -21,7 +21,7 @@ on: jobs: build_tsan_reusable: name: 'Thread sanitizer' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index f0ca6a9e7ed793..ec39025504efd1 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] env: FORCE_COLOR: 1 OPENSSL_VER: 3.0.15 diff --git a/.github/workflows/reusable-wasi.yml b/.github/workflows/reusable-wasi.yml index abc617a317cc0f..85af793c342c51 100644 --- a/.github/workflows/reusable-wasi.yml +++ b/.github/workflows/reusable-wasi.yml @@ -11,7 +11,7 @@ jobs: build_wasi_reusable: name: 'build and test' timeout-minutes: 60 - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: WASMTIME_VERSION: 22.0.0 WASI_SDK_VERSION: 24 diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index b6fb36554f7cec..a42ac1f8bcdf71 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1592,6 +1592,11 @@ Copying, moving and deleting This argument has no effect when copying files on Windows (where metadata is always preserved). + .. note:: + Where supported by the operating system and file system, this method + performs a lightweight copy, where data blocks are only copied when + modified. This is known as copy-on-write. + .. versionadded:: 3.14 diff --git a/Lib/inspect.py b/Lib/inspect.py index 08718d82e91582..e3f74e9f047eaf 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1507,11 +1507,15 @@ def getclosurevars(func): global_vars = {} builtin_vars = {} unbound_names = set() - for name in code.co_names: - if name in ("None", "True", "False"): - # Because these used to be builtins instead of keywords, they - # may still show up as name references. We ignore them. - continue + global_names = set() + for instruction in dis.get_instructions(code): + opname = instruction.opname + name = instruction.argval + if opname == "LOAD_ATTR": + unbound_names.add(name) + elif opname == "LOAD_GLOBAL": + global_names.add(name) + for name in global_names: try: global_vars[name] = global_ns[name] except KeyError: diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index f5eed6f025c250..43e6624934b045 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -99,7 +99,7 @@ class PathGlobber(_GlobberBase): @staticmethod def concat_path(path, text): """Appends text to the given path.""" - return path.with_segments(path._raw_path + text) + return path.with_segments(str(path) + text) class PurePathBase: @@ -112,9 +112,9 @@ class PurePathBase: """ __slots__ = ( - # The `_raw_path` slot store a joined string path. This is set in the - # `__init__()` method. - '_raw_path', + # The `_raw_paths` slot stores unjoined string paths. This is set in + # the `__init__()` method. + '_raw_paths', # The '_resolving' slot stores a boolean indicating whether the path # is being processed by `PathBase.resolve()`. This prevents duplicate @@ -124,11 +124,14 @@ class PurePathBase: parser = ParserBase() _globber = PathGlobber - def __init__(self, path, *paths): - self._raw_path = self.parser.join(path, *paths) if paths else path - if not isinstance(self._raw_path, str): - raise TypeError( - f"path should be a str, not {type(self._raw_path).__name__!r}") + def __init__(self, arg, *args): + paths = [arg] + paths.extend(args) + for path in paths: + if not isinstance(path, str): + raise TypeError( + f"path should be a str, not {type(path).__name__!r}") + self._raw_paths = paths self._resolving = False def with_segments(self, *pathsegments): @@ -141,7 +144,19 @@ def with_segments(self, *pathsegments): def __str__(self): """Return the string representation of the path, suitable for passing to system calls.""" - return self._raw_path + paths = self._raw_paths + if len(paths) == 1: + return paths[0] + elif paths: + # Join path segments from the initializer. + path = self.parser.join(*paths) + # Cache the joined path. + paths.clear() + paths.append(path) + return path + else: + paths.append('') + return '' def as_posix(self): """Return the string representation of the path with forward (/) @@ -166,7 +181,7 @@ def anchor(self): @property def name(self): """The final path component, if any.""" - return self.parser.split(self._raw_path)[1] + return self.parser.split(str(self))[1] @property def suffix(self): @@ -202,7 +217,7 @@ def with_name(self, name): split = self.parser.split if split(name)[0]: raise ValueError(f"Invalid name {name!r}") - return self.with_segments(split(self._raw_path)[0], name) + return self.with_segments(split(str(self))[0], name) def with_stem(self, stem): """Return a new path with the stem changed.""" @@ -242,7 +257,7 @@ def relative_to(self, other, *, walk_up=False): anchor0, parts0 = self._stack anchor1, parts1 = other._stack if anchor0 != anchor1: - raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors") + raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors") while parts0 and parts1 and parts0[-1] == parts1[-1]: parts0.pop() parts1.pop() @@ -250,9 +265,9 @@ def relative_to(self, other, *, walk_up=False): if not part or part == '.': pass elif not walk_up: - raise ValueError(f"{self._raw_path!r} is not in the subpath of {other._raw_path!r}") + raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}") elif part == '..': - raise ValueError(f"'..' segment in {other._raw_path!r} cannot be walked") + raise ValueError(f"'..' segment in {str(other)!r} cannot be walked") else: parts0.append('..') return self.with_segments('', *reversed(parts0)) @@ -289,17 +304,17 @@ def joinpath(self, *pathsegments): paths) or a totally different path (if one of the arguments is anchored). """ - return self.with_segments(self._raw_path, *pathsegments) + return self.with_segments(*self._raw_paths, *pathsegments) def __truediv__(self, key): try: - return self.with_segments(self._raw_path, key) + return self.with_segments(*self._raw_paths, key) except TypeError: return NotImplemented def __rtruediv__(self, key): try: - return self.with_segments(key, self._raw_path) + return self.with_segments(key, *self._raw_paths) except TypeError: return NotImplemented @@ -311,7 +326,7 @@ def _stack(self): *parts* is a reversed list of parts following the anchor. """ split = self.parser.split - path = self._raw_path + path = str(self) parent, name = split(path) names = [] while path != parent: @@ -323,7 +338,7 @@ def _stack(self): @property def parent(self): """The logical parent of the path.""" - path = self._raw_path + path = str(self) parent = self.parser.split(path)[0] if path != parent: parent = self.with_segments(parent) @@ -335,7 +350,7 @@ def parent(self): def parents(self): """A sequence of this path's logical parents.""" split = self.parser.split - path = self._raw_path + path = str(self) parent = split(path)[0] parents = [] while path != parent: @@ -347,7 +362,7 @@ def parents(self): def is_absolute(self): """True if the path is absolute (has both a root and, if applicable, a drive).""" - return self.parser.isabs(self._raw_path) + return self.parser.isabs(str(self)) @property def _pattern_str(self): diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index 99474e1f71a307..b27f456d375225 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -68,10 +68,6 @@ class PurePath(PurePathBase): """ __slots__ = ( - # The `_raw_paths` slot stores unnormalized string paths. This is set - # in the `__init__()` method. - '_raw_paths', - # The `_drv`, `_root` and `_tail_cached` slots store parsed and # normalized parts of the path. They are set when any of the `drive`, # `root` or `_tail` properties are accessed for the first time. The @@ -299,25 +295,14 @@ def _parse_pattern(cls, pattern): parts.append('') return parts - @property - def _raw_path(self): - """The joined but unnormalized path.""" - paths = self._raw_paths - if len(paths) == 0: - path = '' - elif len(paths) == 1: - path = paths[0] - else: - path = self.parser.join(*paths) - return path - @property def drive(self): """The drive prefix (letter or UNC path), if any.""" try: return self._drv except AttributeError: - self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + raw_path = PurePathBase.__str__(self) + self._drv, self._root, self._tail_cached = self._parse_path(raw_path) return self._drv @property @@ -326,7 +311,8 @@ def root(self): try: return self._root except AttributeError: - self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + raw_path = PurePathBase.__str__(self) + self._drv, self._root, self._tail_cached = self._parse_path(raw_path) return self._root @property @@ -334,7 +320,8 @@ def _tail(self): try: return self._tail_cached except AttributeError: - self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + raw_path = PurePathBase.__str__(self) + self._drv, self._root, self._tail_cached = self._parse_path(raw_path) return self._tail_cached @property diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index a4430a868676e2..a92627a4d60f68 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1960,6 +1960,19 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(C().f(_arg)), expected) + def test_attribute_same_name_as_global_var(self): + class C: + _global_ref = object() + def f(): + print(C._global_ref, _global_ref) + nonlocal_vars = {"C": C} + global_vars = {"_global_ref": _global_ref} + builtin_vars = {"print": print} + unbound_names = {"_global_ref"} + expected = inspect.ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + self.assertEqual(inspect.getclosurevars(f), expected) + def test_nonlocal_vars(self): # More complex tests of nonlocal resolution def _nonlocal_vars(f): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 4ab804850e9c3e..d155e7c5bb9935 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -86,11 +86,6 @@ def test_unsupported_operation_pure(self): p.suffix with self.assertRaises(e): p.suffixes - with self.assertRaises(e): - p / 'bar' - with self.assertRaises(e): - 'bar' / p - self.assertRaises(e, p.joinpath, 'bar') self.assertRaises(e, p.with_name, 'bar') self.assertRaises(e, p.with_stem, 'bar') self.assertRaises(e, p.with_suffix, '.txt') diff --git a/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst b/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst new file mode 100644 index 00000000000000..4cfb66a6ccc6ee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst @@ -0,0 +1 @@ +Fixed an issue where :func:`inspect.getclosurevars` would incorrectly classify an attribute name as a global variable when the name exists both as an attribute name and a global variable. diff --git a/Misc/NEWS.d/next/Windows/2024-10-29-20-09-52.gh-issue-126074.83ZzZs.rst b/Misc/NEWS.d/next/Windows/2024-10-29-20-09-52.gh-issue-126074.83ZzZs.rst new file mode 100644 index 00000000000000..d4d06b090b5922 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-10-29-20-09-52.gh-issue-126074.83ZzZs.rst @@ -0,0 +1 @@ +Removed unnecessary DLLs from Windows embeddable package diff --git a/PC/layout/main.py b/PC/layout/main.py index 0350ed7af3f9b5..8bd435456c635a 100644 --- a/PC/layout/main.py +++ b/PC/layout/main.py @@ -31,11 +31,13 @@ from .support.nuspec import * TEST_PYDS_ONLY = FileStemSet("xxlimited", "xxlimited_35", "_ctypes_test", "_test*") +TEST_DLLS_ONLY = set() TEST_DIRS_ONLY = FileNameSet("test", "tests") IDLE_DIRS_ONLY = FileNameSet("idlelib") -TCLTK_PYDS_ONLY = FileStemSet("tcl*", "tk*", "_tkinter", "zlib1") +TCLTK_PYDS_ONLY = FileStemSet("_tkinter") +TCLTK_DLLS_ONLY = FileStemSet("tcl*", "tk*", "zlib1") TCLTK_DIRS_ONLY = FileNameSet("tkinter", "turtledemo") TCLTK_FILES_ONLY = FileNameSet("turtle.py") @@ -226,6 +228,10 @@ def in_build(f, dest="", new_name=None, no_lib=False): continue if src in EXCLUDE_FROM_DLLS: continue + if src in TEST_DLLS_ONLY and not ns.include_tests: + continue + if src in TCLTK_DLLS_ONLY and not ns.include_tcltk: + continue yield from in_build(src.name, dest=dest, no_lib=True) if ns.zip_lib: diff --git a/Python/compile.c b/Python/compile.c index 4dcb9a1b5acdb3..ecca9b0b06ecf7 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -901,7 +901,7 @@ _PyCompile_LookupArg(compiler *c, PyCodeObject *co, PyObject *name) c->u->u_metadata.u_name, co->co_name, freevars); - Py_DECREF(freevars); + Py_XDECREF(freevars); return ERROR; } return arg; diff --git a/Python/jit.c b/Python/jit.c index 135daeb1b1da80..90f693dfb7c41b 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -470,7 +470,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz size_t code_size = 0; size_t data_size = 0; jit_state state = {0}; - group = &trampoline; + group = &shim; code_size += group->code_size; data_size += group->data_size; combine_symbol_mask(group->trampoline_mask, state.trampolines.mask); @@ -507,12 +507,10 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz unsigned char *code = memory; unsigned char *data = memory + code_size; state.trampolines.mem = memory + code_size + data_size; - // Compile the trampoline, which handles converting between the native + // Compile the shim, which handles converting between the native // calling convention and the calling convention used by jitted code - // (which may be different for efficiency reasons). On platforms where - // we don't change calling conventions, the trampoline is empty and - // nothing is emitted here: - group = &trampoline; + // (which may be different for efficiency reasons). + group = &shim; group->emit(code, data, executor, NULL, &state); code += group->code_size; data += group->data_size; @@ -536,7 +534,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz return -1; } executor->jit_code = memory; - executor->jit_side_entry = memory + trampoline.code_size; + executor->jit_side_entry = memory + shim.code_size; executor->jit_size = total_size; return 0; } diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index 634208da3c8157..d8dce0a905c0f8 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -154,8 +154,8 @@ async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]: with tempfile.TemporaryDirectory() as tempdir: work = pathlib.Path(tempdir).resolve() async with asyncio.TaskGroup() as group: - coro = self._compile("trampoline", TOOLS_JIT / "trampoline.c", work) - tasks.append(group.create_task(coro, name="trampoline")) + coro = self._compile("shim", TOOLS_JIT / "shim.c", work) + tasks.append(group.create_task(coro, name="shim")) template = TOOLS_JIT_TEMPLATE_C.read_text() for case, opname in cases_and_opnames: # Write out a copy of the template with *only* this case diff --git a/Tools/jit/_writer.py b/Tools/jit/_writer.py index f33d8ef322f073..81a9f08db31703 100644 --- a/Tools/jit/_writer.py +++ b/Tools/jit/_writer.py @@ -22,11 +22,11 @@ def _dump_footer( yield " symbol_mask trampoline_mask;" yield "} StencilGroup;" yield "" - yield f"static const StencilGroup trampoline = {groups['trampoline'].as_c('trampoline')};" + yield f"static const StencilGroup shim = {groups['shim'].as_c('shim')};" yield "" yield "static const StencilGroup stencil_groups[MAX_UOP_ID + 1] = {" for opname, group in sorted(groups.items()): - if opname == "trampoline": + if opname == "shim": continue yield f" [{opname}] = {group.as_c(opname)}," yield "};" diff --git a/Tools/jit/trampoline.c b/Tools/jit/shim.c similarity index 100% rename from Tools/jit/trampoline.c rename to Tools/jit/shim.c