Skip to content

Commit 82a5f68

Browse files
authored
Merge branch 'main' into pyrepl-new
2 parents bb602ec + e11315d commit 82a5f68

File tree

288 files changed

+5843
-2697
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

288 files changed

+5843
-2697
lines changed

.github/CODEOWNERS

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@ Lib/test/test_build_details.py @FFY00
100100
InternalDocs/ @AA-Turner
101101

102102
# Tools, Configuration, etc
103-
Doc/Makefile @AA-Turner @hugovk
104-
Doc/_static/ @AA-Turner @hugovk
105-
Doc/conf.py @AA-Turner @hugovk
106-
Doc/make.bat @AA-Turner @hugovk
107-
Doc/requirements.txt @AA-Turner @hugovk
108-
Doc/tools/ @AA-Turner @hugovk
103+
Doc/Makefile @AA-Turner @hugovk @StanFromIreland
104+
Doc/_static/ @AA-Turner @hugovk @StanFromIreland
105+
Doc/conf.py @AA-Turner @hugovk @StanFromIreland
106+
Doc/make.bat @AA-Turner @hugovk @StanFromIreland
107+
Doc/requirements.txt @AA-Turner @hugovk @StanFromIreland
108+
Doc/tools/ @AA-Turner @hugovk @StanFromIreland
109109

110110
# PR Previews
111111
.readthedocs.yml @AA-Turner

.github/workflows/stale.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jobs:
1111
if: github.repository_owner == 'python'
1212
runs-on: ubuntu-latest
1313
permissions:
14+
actions: write
1415
pull-requests: write
1516
timeout-minutes: 10
1617

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: a27a2e47c7751b639d2b5badf0ef6ff11fee893f # frozen: v0.15.4
3+
rev: e05c5c0818279e5ac248ac9e954431ba58865e61 # frozen: v0.15.7
44
hooks:
55
- id: ruff-check
66
name: Run Ruff (lint) on Platforms/Apple/

Android/README.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,6 @@ require adding your user to a group, or changing your udev rules. On GitHub
103103
Actions, the test script will do this automatically using the commands shown
104104
[here](https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/).
105105

106-
The test suite can usually be run on a device with 2 GB of RAM, but this is
107-
borderline, so you may need to increase it to 4 GB. As of Android
108-
Studio Koala, 2 GB is the default for all emulators, although the user interface
109-
may indicate otherwise. Locate the emulator's directory under `~/.android/avd`,
110-
and find `hw.ramSize` in both config.ini and hardware-qemu.ini. Either set these
111-
manually to the same value, or use the Android Studio Device Manager, which will
112-
update both files.
113-
114106
You can run the test suite either:
115107

116108
* Within the CPython repository, after doing a build as described above. On

Android/testbed/app/build.gradle.kts

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ val KNOWN_ABIS = mapOf(
2020
"x86_64-linux-android" to "x86_64",
2121
)
2222

23+
val osArch = System.getProperty("os.arch")
24+
val NATIVE_ABI = mapOf(
25+
"aarch64" to "arm64-v8a",
26+
"amd64" to "x86_64",
27+
"arm64" to "arm64-v8a",
28+
"x86_64" to "x86_64",
29+
)[osArch] ?: throw GradleException("Unknown os.arch '$osArch'")
30+
2331
// Discover prefixes.
2432
val prefixes = ArrayList<File>()
2533
if (inSourceTree) {
@@ -151,6 +159,9 @@ android {
151159
testOptions {
152160
managedDevices {
153161
localDevices {
162+
// systemImageSource should use what its documentation calls an
163+
// "explicit source", i.e. the sdkmanager package name format, because
164+
// that will be required in CreateEmulatorTask below.
154165
create("minVersion") {
155166
device = "Small Phone"
156167

@@ -159,13 +170,13 @@ android {
159170

160171
// ATD devices are smaller and faster, but have a minimum
161172
// API level of 30.
162-
systemImageSource = if (apiLevel >= 30) "aosp-atd" else "aosp"
173+
systemImageSource = if (apiLevel >= 30) "aosp_atd" else "default"
163174
}
164175

165176
create("maxVersion") {
166177
device = "Small Phone"
167178
apiLevel = defaultConfig.targetSdk!!
168-
systemImageSource = "aosp-atd"
179+
systemImageSource = "aosp_atd"
169180
}
170181
}
171182

@@ -191,6 +202,138 @@ dependencies {
191202
}
192203

193204

205+
afterEvaluate {
206+
// Every new emulator has a maximum of 2 GB RAM, regardless of its hardware profile
207+
// (https://cs.android.com/android-studio/platform/tools/base/+/refs/tags/studio-2025.3.2:sdklib/src/main/java/com/android/sdklib/internal/avd/EmulatedProperties.java;l=68).
208+
// This is barely enough to test Python, and not enough to test Pandas
209+
// (https://github.com/python/cpython/pull/137186#issuecomment-3136301023,
210+
// https://github.com/pandas-dev/pandas/pull/63405#issuecomment-3667846159).
211+
// So we'll increase it by editing the emulator configuration files.
212+
//
213+
// If the emulator doesn't exist yet, we want to edit it after it's created, but
214+
// before it starts for the first time. Otherwise it'll need to be cold-booted
215+
// again, which would slow down the first run, which is likely the only run in CI
216+
// environments. But the Setup task both creates and starts the emulator if it
217+
// doesn't already exist. So we create it ourselves before the Setup task runs.
218+
for (device in android.testOptions.managedDevices.localDevices) {
219+
val createTask = tasks.register<CreateEmulatorTask>("${device.name}Create") {
220+
this.device = device.device
221+
apiLevel = device.apiLevel
222+
systemImageSource = device.systemImageSource
223+
abi = NATIVE_ABI
224+
}
225+
tasks.named("${device.name}Setup") {
226+
dependsOn(createTask)
227+
}
228+
}
229+
}
230+
231+
abstract class CreateEmulatorTask : DefaultTask() {
232+
@get:Input abstract val device: Property<String>
233+
@get:Input abstract val apiLevel: Property<Int>
234+
@get:Input abstract val systemImageSource: Property<String>
235+
@get:Input abstract val abi: Property<String>
236+
@get:Inject abstract val execOps: ExecOperations
237+
238+
private val avdName by lazy {
239+
listOf(
240+
"dev${apiLevel.get()}",
241+
systemImageSource.get(),
242+
abi.get(),
243+
device.get().replace(' ', '_'),
244+
).joinToString("_")
245+
}
246+
247+
private val avdDir by lazy {
248+
// XDG_CONFIG_HOME is respected by both avdmanager and Gradle.
249+
val userHome = System.getenv("ANDROID_USER_HOME") ?: (
250+
(System.getenv("XDG_CONFIG_HOME") ?: System.getProperty("user.home")!!)
251+
+ "/.android"
252+
)
253+
File("$userHome/avd/gradle-managed", "$avdName.avd")
254+
}
255+
256+
@TaskAction
257+
fun run() {
258+
if (!avdDir.exists()) {
259+
createAvd()
260+
}
261+
updateAvd()
262+
}
263+
264+
fun createAvd() {
265+
val systemImage = listOf(
266+
"system-images",
267+
"android-${apiLevel.get()}",
268+
systemImageSource.get(),
269+
abi.get(),
270+
).joinToString(";")
271+
272+
runCmdlineTool("sdkmanager", systemImage)
273+
runCmdlineTool(
274+
"avdmanager", "create", "avd",
275+
"--name", avdName,
276+
"--path", avdDir,
277+
"--device", device.get().lowercase().replace(" ", "_"),
278+
"--package", systemImage,
279+
)
280+
281+
val iniName = "$avdName.ini"
282+
if (!File(avdDir.parentFile.parentFile, iniName).renameTo(
283+
File(avdDir.parentFile, iniName)
284+
)) {
285+
throw GradleException("Failed to rename $iniName")
286+
}
287+
}
288+
289+
fun updateAvd() {
290+
for (filename in listOf(
291+
"config.ini", // Created by avdmanager; always exists
292+
"hardware-qemu.ini", // Created on first run; might not exist
293+
)) {
294+
val iniFile = File(avdDir, filename)
295+
if (!iniFile.exists()) {
296+
if (filename == "config.ini") {
297+
throw GradleException("$iniFile does not exist")
298+
}
299+
continue
300+
}
301+
302+
val iniText = iniFile.readText()
303+
val pattern = Regex(
304+
"""^\s*hw.ramSize\s*=\s*(.+?)\s*$""", RegexOption.MULTILINE
305+
)
306+
val matches = pattern.findAll(iniText).toList()
307+
if (matches.size != 1) {
308+
throw GradleException(
309+
"Found ${matches.size} instances of $pattern in $iniFile; expected 1"
310+
)
311+
}
312+
313+
val expectedRam = "4096"
314+
if (matches[0].groupValues[1] != expectedRam) {
315+
iniFile.writeText(
316+
iniText.replace(pattern, "hw.ramSize = $expectedRam")
317+
)
318+
}
319+
}
320+
}
321+
322+
fun runCmdlineTool(tool: String, vararg args: Any) {
323+
val androidHome = System.getenv("ANDROID_HOME")!!
324+
val exeSuffix =
325+
if (System.getProperty("os.name").lowercase().startsWith("win")) ".exe"
326+
else ""
327+
val command =
328+
listOf("$androidHome/cmdline-tools/latest/bin/$tool$exeSuffix", *args)
329+
println(command.joinToString(" "))
330+
execOps.exec {
331+
commandLine(command)
332+
}
333+
}
334+
}
335+
336+
194337
// Create some custom tasks to copy Python and its standard library from
195338
// elsewhere in the repository.
196339
androidComponents.onVariants { variant ->

Doc/c-api/dict.rst

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Dictionary objects
4545
The first argument can be a :class:`dict`, a :class:`frozendict`, or a
4646
mapping.
4747
48-
.. versionchanged:: next
48+
.. versionchanged:: 3.15
4949
Also accept :class:`frozendict`.
5050
5151
@@ -76,7 +76,7 @@ Dictionary objects
7676
7777
The first argument can be a :class:`dict` or a :class:`frozendict`.
7878
79-
.. versionchanged:: next
79+
.. versionchanged:: 3.15
8080
Also accept :class:`frozendict`.
8181
8282
@@ -90,7 +90,7 @@ Dictionary objects
9090
9191
.. versionadded:: 3.13
9292
93-
.. versionchanged:: next
93+
.. versionchanged:: 3.15
9494
Also accept :class:`frozendict`.
9595
9696
@@ -142,7 +142,7 @@ Dictionary objects
142142
143143
.. versionadded:: 3.13
144144
145-
.. versionchanged:: next
145+
.. versionchanged:: 3.15
146146
Also accept :class:`frozendict`.
147147
148148
See also the :c:func:`PyObject_GetItem` function.
@@ -166,7 +166,7 @@ Dictionary objects
166166
Calling this API without an :term:`attached thread state` had been allowed for historical
167167
reason. It is no longer allowed.
168168
169-
.. versionchanged:: next
169+
.. versionchanged:: 3.15
170170
Also accept :class:`frozendict`.
171171
172172
@@ -177,7 +177,7 @@ Dictionary objects
177177
occurred. Return ``NULL`` **without** an exception set if the key
178178
wasn't present.
179179
180-
.. versionchanged:: next
180+
.. versionchanged:: 3.15
181181
Also accept :class:`frozendict`.
182182
183183
@@ -195,7 +195,7 @@ Dictionary objects
195195
Prefer using the :c:func:`PyDict_GetItemWithError` function with your own
196196
:c:func:`PyUnicode_FromString` *key* instead.
197197
198-
.. versionchanged:: next
198+
.. versionchanged:: 3.15
199199
Also accept :class:`frozendict`.
200200
201201
@@ -207,7 +207,7 @@ Dictionary objects
207207
208208
.. versionadded:: 3.13
209209
210-
.. versionchanged:: next
210+
.. versionchanged:: 3.15
211211
Also accept :class:`frozendict`.
212212
213213
@@ -275,7 +275,7 @@ Dictionary objects
275275
276276
The first argument can be a :class:`dict` or a :class:`frozendict`.
277277
278-
.. versionchanged:: next
278+
.. versionchanged:: 3.15
279279
Also accept :class:`frozendict`.
280280
281281
@@ -285,7 +285,7 @@ Dictionary objects
285285
286286
The first argument can be a :class:`dict` or a :class:`frozendict`.
287287
288-
.. versionchanged:: next
288+
.. versionchanged:: 3.15
289289
Also accept :class:`frozendict`.
290290
291291
@@ -296,7 +296,7 @@ Dictionary objects
296296
297297
The first argument can be a :class:`dict` or a :class:`frozendict`.
298298
299-
.. versionchanged:: next
299+
.. versionchanged:: 3.15
300300
Also accept :class:`frozendict`.
301301
302302
@@ -309,15 +309,15 @@ Dictionary objects
309309
310310
The argument can be a :class:`dict` or a :class:`frozendict`.
311311
312-
.. versionchanged:: next
312+
.. versionchanged:: 3.15
313313
Also accept :class:`frozendict`.
314314
315315
316316
.. c:function:: Py_ssize_t PyDict_GET_SIZE(PyObject *p)
317317
318318
Similar to :c:func:`PyDict_Size`, but without error checking.
319319
320-
.. versionchanged:: next
320+
.. versionchanged:: 3.15
321321
Also accept :class:`frozendict`.
322322
323323
@@ -391,7 +391,7 @@ Dictionary objects
391391
:term:`strong reference <strong reference>` (for example, using
392392
:c:func:`Py_NewRef`).
393393
394-
.. versionchanged:: next
394+
.. versionchanged:: 3.15
395395
Also accept :class:`frozendict`.
396396
397397
.. c:function:: int PyDict_Merge(PyObject *a, PyObject *b, int override)

0 commit comments

Comments
 (0)