Skip to content

Commit 14ae1f0

Browse files
TTsangSCda-woodsErotemic
authored
FIX: building on Windows-ARM64 (#391)
* Narrow scopes where 3.12 hacks apply * Update CI to target and test ARM64 Windows * Fixed expressions and unset ${VSCMD_ARG_TGT_ARCH} * pre-include `pycore_atomic.h` to circumvent bad funcdefs * temporarily truncate job matrix to focus on Windows-ARM64 * sidestep inclusion of `pycore_atomic.h`, just mock what we need * temporarily stop building Windows-AMD64 and ABI3 wheels * Added missing #define * Try moving Python internal includes into the C file * simplify macros * roll back #392 * vendor in `_Py_atomic_int` from `pycore_atomic.h` * Skip unavailable platforms in CI * Allow Cython tests to XFAIL on Windows-ARM64 * Fixed attempted import on the failing path * Undo eda8d0b and f407d00, restoring the rest of the pipeline * Temp: truncate pipeline to debug test on Win-ARM64 This reverts commit 3d47c90. * Set MSVC arch for ARM runners * Revert soft-XFAIL of Cython test This partially reverts commit 0a16f5c but keeps the typing fixes introduced therein. * Un-truncate pipeline This reverts commit 072106d. * Streamline pipeline, added comments * changelog * Make suggested changes Co-authored-by: Jon Crall <erotemic@gmail.com> --------- Co-authored-by: da-woods <dw-git@d-woods.co.uk> Co-authored-by: Jon Crall <erotemic@gmail.com>
1 parent 1c086a4 commit 14ae1f0

5 files changed

Lines changed: 102 additions & 18 deletions

File tree

.github/workflows/tests.yml

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,20 @@ jobs:
128128
- ubuntu-latest
129129
- macOS-latest
130130
- windows-latest
131+
# Since the oldest Python available on windows-11-arm on GitHub
132+
# is 3.11.0 (see https://raw.githubusercontent.com/actions/\
133+
# python-versions/main/versions-manifest.json), older wheels
134+
# cannot be tested... so just skip pre-building them
131135
cibw_skip:
132-
- '*-win32 *-win32 cp313-musllinux_i686'
136+
- '*-win32 cp3{9,10}-win_arm64 cp313-musllinux_i686'
133137
arch:
134138
- auto
135139
steps:
136140
- name: Checkout source
137141
uses: actions/checkout@v4.2.2
138142
- name: Enable MSVC 64bit
139143
uses: ilammy/msvc-dev-cmd@v1
140-
if: matrix.os == 'windows-latest' && ${{ contains(matrix.cibw_skip, '*-win32') }}
144+
if: ${{ startsWith(matrix.os, 'windows-') }} && ${{ contains(matrix.cibw_skip, '*-win32') }}
141145
- name: Set up QEMU
142146
uses: docker/setup-qemu-action@v3.0.0
143147
if: runner.os == 'Linux' && matrix.arch != 'auto'
@@ -150,9 +154,17 @@ jobs:
150154
config-file: pyproject.toml
151155
env:
152156
CIBW_SKIP: ${{ matrix.cibw_skip }}
157+
# We're building on Windows-x64, so ARM64 wheels can't be tested
158+
# locally by `cibuildwheel` (don't worry, we're testing them
159+
# later though in `test_binpy_wheels`)
160+
CIBW_TEST_SKIP: '*-win_arm64'
153161
CIBW_ARCHS_LINUX: ${{ matrix.arch }}
154162
CIBW_ENVIRONMENT: PYTHONUTF8=1
155163
PYTHONUTF8: '1'
164+
# `msvc-dev-cmd` sets this envvar, which interferes with
165+
# cross-architecture building...
166+
# just let `cibuildwheel` handle that
167+
VSCMD_ARG_TGT_ARCH: ''
156168
- name: Show built files
157169
shell: bash
158170
run: ls -la wheelhouse
@@ -224,6 +236,7 @@ jobs:
224236
install-extras: tests-strict,runtime-strict
225237
os: windows-latest
226238
arch: auto
239+
# Note: cibuildwheel can't target 3.8 on Window ARM64
227240
- python-version: '3.13'
228241
install-extras: tests-strict,runtime-strict,optional-strict
229242
os: ubuntu-latest
@@ -236,6 +249,10 @@ jobs:
236249
install-extras: tests-strict,runtime-strict,optional-strict
237250
os: windows-latest
238251
arch: auto
252+
- python-version: '3.13'
253+
install-extras: tests-strict,runtime-strict,optional-strict
254+
os: windows-11-arm
255+
arch: auto
239256
- python-version: '3.13'
240257
install-extras: tests
241258
os: macOS-latest
@@ -244,6 +261,10 @@ jobs:
244261
install-extras: tests
245262
os: windows-latest
246263
arch: auto
264+
- python-version: '3.13'
265+
install-extras: tests
266+
os: windows-11-arm
267+
arch: auto
247268
- python-version: '3.8'
248269
install-extras: tests,optional
249270
os: ubuntu-latest
@@ -328,12 +349,35 @@ jobs:
328349
install-extras: tests,optional
329350
os: windows-latest
330351
arch: auto
352+
# Again, cibuildwheel can't target 3.8 on Window ARM64, and
353+
# GitHub doesn't have anything below Python 3.11 on their ARM64
354+
# machines, so just test the built wheels from 3.11+
355+
- python-version: '3.11'
356+
install-extras: tests,optional
357+
os: windows-11-arm
358+
arch: auto
359+
- python-version: '3.12'
360+
install-extras: tests,optional
361+
os: windows-11-arm
362+
arch: auto
363+
- python-version: '3.13'
364+
install-extras: tests,optional
365+
os: windows-11-arm
366+
arch: auto
367+
- python-version: 3.14.0-rc.1
368+
install-extras: tests,optional
369+
os: windows-11-arm
370+
arch: auto
331371
steps:
332372
- name: Checkout source
333373
uses: actions/checkout@v4.2.2
334374
- name: Enable MSVC 64bit
335375
uses: ilammy/msvc-dev-cmd@v1
336-
if: matrix.os == 'windows-latest'
376+
if: ${{ startsWith(matrix.os, 'windows-') }}
377+
# As noted in msvc-dev-cmd #90 (and the Action docs), it currently
378+
# # assumes `arch=x64`, so we have to manually set it here...
379+
with:
380+
arch: ${{ contains(matrix.os, 'arm') && 'arm64' || 'x64' }}
337381
- name: Set up QEMU
338382
uses: docker/setup-qemu-action@v3.0.0
339383
if: runner.os == 'Linux' && matrix.arch != 'auto'

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Changes
1010
* FIX: ref-count leaks #372
1111
* FIX: mitigate speed regressions introduced in 5.0.0 #376
1212
* FIX: Use import system to locate module file run by ``kernprof -m`` #389
13+
* FIX: Fixed build on Windows-ARM64 and now building wheels therefor in CI #391
1314

1415
5.0.0
1516
~~~~~

line_profiler/c_trace_callbacks.h

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
* and causes problems in 3.12 (see CPython #105268, #105350, #107348)
1515
* - Undefine the `HAVE_STD_ATOMIC` macro, which causes problems on
1616
* Linux in 3.12 (see CPython #108216)
17+
* - Set `Py_ATOMIC_H` to true to circumvent the #include of
18+
* `include/pycore_atomic.h` (in `include/pycore_interp.h`, so that
19+
* problematic function definitions therein are replaced with dummy
20+
* ones (see #390); note that we still need to vendor in parts
21+
* therefrom which are used by `pycore_interp.h`, and its dependencies
22+
* `pycore_ceval_state.h` and `pycore_gil.h` (or at least mock them)
1723
* Note in any case that we don't actually use `PyInterpreterState`
1824
* directly -- we just need its memory layout so that we can refer to
1925
* its `.last_restart_version` member
@@ -24,17 +30,45 @@
2430
# ifndef Py_BUILD_CORE
2531
# define Py_BUILD_CORE 1
2632
# endif
27-
# ifdef _PyGC_FINALIZED
33+
# if PY_VERSION_HEX < 0x030d0000 // 3.13
2834
# undef _PyGC_FINALIZED
35+
# ifdef __linux__
36+
# undef HAVE_STD_ATOMIC
37+
# endif
38+
# if (defined(_M_ARM) || defined(_M_ARM64)) && (! defined(Py_ATOMIC_H))
39+
# define Py_ATOMIC_H
40+
// Used in `pycore_interp.h`
41+
typedef struct _Py_atomic_address {
42+
volatile uintptr_t _value;
43+
} _Py_atomic_address;
44+
// Used in `pycore_gil.h` and `pycore_ceval_state.h`
45+
typedef struct _Py_atomic_int {
46+
volatile int _value;
47+
} _Py_atomic_int;
48+
/* Stub out macros in `pycore_atomic.h` used in macros in
49+
* `pycore_interp.h` (which aren't related to the
50+
* `struct _is` we need).
51+
* If any stub is referenced, fail the build with an
52+
* unresolved external.
53+
* This ensures we never ship wheels that "use" these
54+
* placeholders. */
55+
# ifdef _MSC_VER
56+
__declspec(dllimport) void lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(void);
57+
__declspec(dllimport) void lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(void);
58+
# else
59+
extern void lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(void);
60+
extern void lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(void);
61+
# endif
62+
# define _LP_ATOMIC_PANIC_LOAD_EXPR() (lp_link_error__stubbed_cpython_atomic_LOAD_relaxed_was_used_this_is_a_bug(), 0)
63+
# define _LP_ATOMIC_PANIC_STORE_STMT() do { lp_link_error__stubbed_cpython_atomic_STORE_relaxed_was_used_this_is_a_bug(); } while (0)
64+
// Panic-on-use shims (expression/statement forms)
65+
# undef _Py_atomic_load_relaxed
66+
# undef _Py_atomic_store_relaxed
67+
# define _Py_atomic_load_relaxed(obj) ((void)(obj), _LP_ATOMIC_PANIC_LOAD_EXPR())
68+
# define _Py_atomic_store_relaxed(obj, val) do { (void)(obj); (void)(val); _LP_ATOMIC_PANIC_STORE_STMT(); } while (0)
69+
# endif
2970
# endif
30-
# ifdef HAVE_STD_ATOMIC
31-
# undef HAVE_STD_ATOMIC
32-
# endif
33-
# if PY_VERSION_HEX >= 0x030900a6 // 3.9.0a6
34-
# include "internal/pycore_interp.h"
35-
# else
36-
# include "internal/pycore_pystate.h"
37-
# endif
71+
# include "internal/pycore_interp.h"
3872
#endif
3973

4074
typedef struct TraceCallback

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ test-extras = ["tests-strict", "runtime-strict"]
4040
# https://cibuildwheel.readthedocs.io/en/stable/options/#archs
4141
[tool.cibuildwheel.macos]
4242
archs = ["x86_64", "universal2", "arm64"]
43+
[tool.cibuildwheel.windows]
44+
archs = ['AMD64', 'ARM64']
4345

4446

4547
[tool.mypy]

tests/test_cython.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
from line_profiler._line_profiler import (
2020
CANNOT_LINE_TRACE_CYTHON, find_cython_source_file)
21-
from line_profiler.line_profiler import get_code_block, LineProfiler
21+
from line_profiler.line_profiler import ( # type:ignore[attr-defined]
22+
get_code_block, LineProfiler)
2223

2324

2425
def propose_name(prefix: str) -> Generator[str, None, None]:
@@ -71,7 +72,8 @@ def _install_cython_example(
7172

7273
@pytest.fixture(scope='module')
7374
def cython_example(
74-
tmp_path_factory: pytest.TempPathFactory) -> Tuple[Path, ModuleType]:
75+
tmp_path_factory: pytest.TempPathFactory,
76+
) -> Generator[Tuple[Path, ModuleType], None, None]:
7577
"""
7678
Install the example Cython module, yield the path to the Cython
7779
source file and the corresponding module, uninstall it at teardown.
@@ -83,7 +85,7 @@ def cython_example(
8385
yield (path, import_module(mod_name))
8486

8587

86-
def test_recover_cython_source(cython_example: Tuple[Path, str]) -> None:
88+
def test_recover_cython_source(cython_example: Tuple[Path, ModuleType]) -> None:
8789
"""
8890
Check that Cython sources are correctly located by
8991
`line_profiler._line_profiler.find_cython_source_file()` and
@@ -104,16 +106,17 @@ def test_recover_cython_source(cython_example: Tuple[Path, str]) -> None:
104106
CANNOT_LINE_TRACE_CYTHON,
105107
reason='Cannot line-trace Cython code in version '
106108
+ '.'.join(str(v) for v in sys.version_info[:3]))
107-
def test_profile_cython_source(cython_example: Tuple[Path, str]) -> None:
109+
def test_profile_cython_source(cython_example: Tuple[Path, ModuleType]) -> None:
108110
"""
109111
Check that calls to Cython functions (built with the appropriate
110112
compile-time options) can be profiled.
111113
"""
112114
prof_cos = LineProfiler()
113115
prof_sin = LineProfiler()
114116

115-
cos = prof_cos(cython_example[1].cos)
116-
sin = prof_sin(cython_example[1].sin)
117+
_, module = cython_example
118+
cos = prof_cos(module.cos)
119+
sin = prof_sin(module.sin)
117120
assert pytest.approx(cos(.125, 10)) == math.cos(.125)
118121
assert pytest.approx(sin(2.5, 3)) == 2.5 - 2.5 ** 3 / 6 + 2.5 ** 5 / 120
119122

0 commit comments

Comments
 (0)