Skip to content

Commit 5b30ef4

Browse files
authored
Add aarch64 manylinux wheels (#2425)
1 parent 1d092e7 commit 5b30ef4

File tree

12 files changed

+95
-24
lines changed

12 files changed

+95
-24
lines changed

.github/workflows/build.yml

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,20 @@ concurrency:
1818
jobs:
1919
# Linux + macOS + Windows Python 3
2020
py3:
21-
name: py3-${{ matrix.os }}-${{ startsWith(matrix.os, 'ubuntu') && 'all' || matrix.archs }}
21+
name: "py3-${{ matrix.os }}-${{ matrix.arch }}"
2222
runs-on: ${{ matrix.os }}
2323
timeout-minutes: 30
2424
strategy:
2525
fail-fast: false
2626
matrix:
2727
include:
28-
- os: ubuntu-latest
29-
archs: "x86_64 i686"
30-
- os: macos-12
31-
archs: "x86_64"
32-
- os: macos-14
33-
archs: "arm64"
34-
- os: windows-2019
35-
archs: "AMD64"
36-
- os: windows-2019
37-
archs: "x86"
28+
- {os: ubuntu-latest, arch: x86_64}
29+
- {os: ubuntu-latest, arch: i686}
30+
- {os: ubuntu-latest, arch: aarch64}
31+
- {os: macos-12, arch: x86_64}
32+
- {os: macos-14, arch: arm64}
33+
- {os: windows-2019, arch: AMD64}
34+
- {os: windows-2019, arch: x86}
3835
steps:
3936
- uses: actions/checkout@v4
4037

@@ -51,16 +48,20 @@ jobs:
5148
with:
5249
python-version: 3.11
5350

51+
- name: Set up QEMU
52+
uses: docker/setup-qemu-action@v3
53+
if: matrix.arch == 'aarch64'
54+
5455
- name: Create wheels + run tests
55-
uses: pypa/cibuildwheel@v2.18.0
56+
uses: pypa/cibuildwheel@v2.19.1
5657
env:
57-
CIBW_ARCHS: "${{ matrix.archs }}"
58+
CIBW_ARCHS: "${{ matrix.arch }}"
5859
CIBW_PRERELEASE_PYTHONS: True
5960

6061
- name: Upload wheels
6162
uses: actions/upload-artifact@v4
6263
with:
63-
name: wheels-py3-${{ matrix.os }}-${{ startsWith(matrix.os, 'ubuntu') && 'all' || matrix.archs }}
64+
name: wheels-py3-${{ matrix.os }}-${{ matrix.arch }}
6465
path: wheelhouse
6566

6667
- name: Generate .tar.gz

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- 2407_: `Process.connections()`_ was renamed to `Process.net_connections()`_.
1919
The old name is still available, but it's deprecated (triggers a
2020
``DeprecationWarning``) and will be removed in the future.
21+
- 2425_: [Linux]: provide aarch64 wheels. (patch by Matthieu Darbois / Ben Raz)
2122

2223
**Bug fixes**
2324

psutil/tests/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT",
8888
"HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS",
8989
"HAS_SENSORS_TEMPERATURES", "HAS_NET_CONNECTIONS_UNIX", "MACOS_11PLUS",
90-
"MACOS_12PLUS", "COVERAGE",
90+
"MACOS_12PLUS", "COVERAGE", 'AARCH64', "QEMU_USER",
9191
# subprocesses
9292
'pyrun', 'terminate', 'reap_children', 'spawn_testproc', 'spawn_zombie',
9393
'spawn_children_pair',
@@ -128,8 +128,14 @@
128128
GITHUB_ACTIONS = 'GITHUB_ACTIONS' in os.environ or 'CIBUILDWHEEL' in os.environ
129129
CI_TESTING = APPVEYOR or GITHUB_ACTIONS
130130
COVERAGE = 'COVERAGE_RUN' in os.environ
131+
if LINUX and GITHUB_ACTIONS:
132+
with open('/proc/1/cmdline') as f:
133+
QEMU_USER = "/bin/qemu-" in f.read()
134+
else:
135+
QEMU_USER = False
131136
# are we a 64 bit process?
132137
IS_64BIT = sys.maxsize > 2**32
138+
AARCH64 = platform.machine() == "aarch64"
133139

134140

135141
@memoize

psutil/tests/test_contracts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from psutil.tests import HAS_SENSORS_FANS
3131
from psutil.tests import HAS_SENSORS_TEMPERATURES
3232
from psutil.tests import PYPY
33+
from psutil.tests import QEMU_USER
3334
from psutil.tests import SKIP_SYSCONS
3435
from psutil.tests import PsutilTestCase
3536
from psutil.tests import create_sockets
@@ -277,6 +278,7 @@ def test_net_if_addrs(self):
277278
self.assertIsInstance(addr.netmask, (str, type(None)))
278279
self.assertIsInstance(addr.broadcast, (str, type(None)))
279280

281+
@unittest.skipIf(QEMU_USER, 'QEMU user not supported')
280282
def test_net_if_stats(self):
281283
# Duplicate of test_system.py. Keep it anyway.
282284
for ifname, info in psutil.net_if_stats().items():

psutil/tests/test_linux.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
from psutil._compat import PY3
2929
from psutil._compat import FileNotFoundError
3030
from psutil._compat import basestring
31+
from psutil.tests import AARCH64
3132
from psutil.tests import GITHUB_ACTIONS
3233
from psutil.tests import GLOBAL_TIMEOUT
3334
from psutil.tests import HAS_BATTERY
3435
from psutil.tests import HAS_CPU_FREQ
3536
from psutil.tests import HAS_GETLOADAVG
3637
from psutil.tests import HAS_RLIMIT
3738
from psutil.tests import PYPY
39+
from psutil.tests import QEMU_USER
3840
from psutil.tests import TOLERANCE_DISK_USAGE
3941
from psutil.tests import TOLERANCE_SYS_MEM
4042
from psutil.tests import PsutilTestCase
@@ -277,8 +279,14 @@ def test_used(self):
277279
# This got changed in:
278280
# https://gitlab.com/procps-ng/procps/commit/
279281
# 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
282+
# Newer versions of procps are using yet another way to compute used
283+
# memory.
284+
# https://gitlab.com/procps-ng/procps/commit/
285+
# 2184e90d2e7cdb582f9a5b706b47015e56707e4d
280286
if get_free_version_info() < (3, 3, 12):
281-
raise unittest.SkipTest("old free version")
287+
raise unittest.SkipTest("free version too old")
288+
if get_free_version_info() >= (4, 0, 0):
289+
raise unittest.SkipTest("free version too recent")
282290
cli_value = free_physmem().used
283291
psutil_value = psutil.virtual_memory().used
284292
self.assertAlmostEqual(
@@ -341,8 +349,14 @@ def test_used(self):
341349
# This got changed in:
342350
# https://gitlab.com/procps-ng/procps/commit/
343351
# 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
352+
# Newer versions of procps are using yet another way to compute used
353+
# memory.
354+
# https://gitlab.com/procps-ng/procps/commit/
355+
# 2184e90d2e7cdb582f9a5b706b47015e56707e4d
344356
if get_free_version_info() < (3, 3, 12):
345-
raise unittest.SkipTest("old free version")
357+
raise unittest.SkipTest("free version too old")
358+
if get_free_version_info() >= (4, 0, 0):
359+
raise unittest.SkipTest("free version too recent")
346360
vmstat_value = vmstat('used memory') * 1024
347361
psutil_value = psutil.virtual_memory().used
348362
self.assertAlmostEqual(
@@ -830,6 +844,7 @@ def path_exists_mock(path):
830844
assert psutil.cpu_freq()
831845

832846
@unittest.skipIf(not HAS_CPU_FREQ, "not supported")
847+
@unittest.skipIf(AARCH64, "aarch64 does not report mhz in /proc/cpuinfo")
833848
def test_emulate_use_cpuinfo(self):
834849
# Emulate a case where /sys/devices/system/cpu/cpufreq* does not
835850
# exist and /proc/cpuinfo is used instead.
@@ -1037,6 +1052,7 @@ def test_ips(self):
10371052

10381053

10391054
@unittest.skipIf(not LINUX, "LINUX only")
1055+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
10401056
class TestSystemNetIfStats(PsutilTestCase):
10411057
@unittest.skipIf(not which("ifconfig"), "ifconfig utility not available")
10421058
def test_against_ifconfig(self):
@@ -1596,7 +1612,7 @@ def test_issue_687(self):
15961612
with ThreadTask():
15971613
p = psutil.Process()
15981614
threads = p.threads()
1599-
self.assertEqual(len(threads), 2)
1615+
self.assertEqual(len(threads), 3 if QEMU_USER else 2)
16001616
tid = sorted(threads, key=lambda x: x.id)[1].id
16011617
self.assertNotEqual(p.pid, tid)
16021618
pt = psutil.Process(tid)
@@ -2276,6 +2292,7 @@ def test_name(self):
22762292
value = self.read_status_file("Name:")
22772293
self.assertEqual(self.proc.name(), value)
22782294

2295+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
22792296
def test_status(self):
22802297
value = self.read_status_file("State:")
22812298
value = value[value.find('(') + 1 : value.rfind(')')]

psutil/tests/test_memleaks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import functools
2020
import os
2121
import platform
22+
import sys
2223
import unittest
2324

2425
import psutil
@@ -43,6 +44,7 @@
4344
from psutil.tests import HAS_SENSORS_BATTERY
4445
from psutil.tests import HAS_SENSORS_FANS
4546
from psutil.tests import HAS_SENSORS_TEMPERATURES
47+
from psutil.tests import QEMU_USER
4648
from psutil.tests import TestMemoryLeak
4749
from psutil.tests import create_sockets
4850
from psutil.tests import get_testfn
@@ -398,6 +400,7 @@ def test_disk_usage(self):
398400
times = FEW_TIMES if POSIX else self.times
399401
self.execute(lambda: psutil.disk_usage('.'), times=times)
400402

403+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
401404
def test_disk_partitions(self):
402405
self.execute(psutil.disk_partitions)
403406

@@ -435,6 +438,7 @@ def test_net_if_addrs(self):
435438
tolerance = 80 * 1024 if WINDOWS else self.tolerance
436439
self.execute(psutil.net_if_addrs, tolerance=tolerance)
437440

441+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
438442
def test_net_if_stats(self):
439443
self.execute(psutil.net_if_stats)
440444

@@ -491,6 +495,11 @@ def test_win_service_get_description(self):
491495

492496

493497
if __name__ == '__main__':
498+
from psutil.tests.runner import cprint
494499
from psutil.tests.runner import run_from_name
495500

501+
if QEMU_USER:
502+
cprint("skipping %s tests under QEMU_USER" % __file__, "brown")
503+
sys.exit(0)
504+
496505
run_from_name(__file__)

psutil/tests/test_misc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from psutil.tests import HAS_SENSORS_TEMPERATURES
4444
from psutil.tests import PYTHON_EXE
4545
from psutil.tests import PYTHON_EXE_ENV
46+
from psutil.tests import QEMU_USER
4647
from psutil.tests import SCRIPTS_DIR
4748
from psutil.tests import PsutilTestCase
4849
from psutil.tests import mock
@@ -288,6 +289,9 @@ def check(ret):
288289
for fun, name in ns.iter(ns.getters):
289290
if name in {"win_service_iter", "win_service_get"}:
290291
continue
292+
if QEMU_USER and name == "net_if_stats":
293+
# OSError: [Errno 38] ioctl(SIOCETHTOOL) not implemented
294+
continue
291295
with self.subTest(name=name):
292296
try:
293297
ret = fun()
@@ -1008,6 +1012,7 @@ def test_pstree(self):
10081012
def test_netstat(self):
10091013
self.assert_stdout('netstat.py')
10101014

1015+
@unittest.skipIf(QEMU_USER, 'QEMU user not supported')
10111016
def test_ifconfig(self):
10121017
self.assert_stdout('ifconfig.py')
10131018

psutil/tests/test_posix.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from psutil import SUNOS
2626
from psutil.tests import HAS_NET_IO_COUNTERS
2727
from psutil.tests import PYTHON_EXE
28+
from psutil.tests import QEMU_USER
2829
from psutil.tests import PsutilTestCase
2930
from psutil.tests import mock
3031
from psutil.tests import retry_on_failure
@@ -102,7 +103,11 @@ def ps_name(pid):
102103
field = "command"
103104
if SUNOS:
104105
field = "comm"
105-
return ps(field, pid).split()[0]
106+
command = ps(field, pid).split()
107+
if QEMU_USER:
108+
assert "/bin/qemu-" in command[0]
109+
return command[1]
110+
return command[0]
106111

107112

108113
def ps_args(pid):

psutil/tests/test_process.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
from psutil.tests import PYPY
5555
from psutil.tests import PYTHON_EXE
5656
from psutil.tests import PYTHON_EXE_ENV
57+
from psutil.tests import QEMU_USER
5758
from psutil.tests import PsutilTestCase
5859
from psutil.tests import ThreadTask
5960
from psutil.tests import call_until
@@ -253,6 +254,7 @@ def test_cpu_percent_numcpus_none(self):
253254
psutil.Process().cpu_percent()
254255
assert m.called
255256

257+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
256258
def test_cpu_times(self):
257259
times = psutil.Process().cpu_times()
258260
assert times.user >= 0.0, times
@@ -265,6 +267,7 @@ def test_cpu_times(self):
265267
for name in times._fields:
266268
time.strftime("%H:%M:%S", time.localtime(getattr(times, name)))
267269

270+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
268271
def test_cpu_times_2(self):
269272
user_time, kernel_time = psutil.Process().cpu_times()[:2]
270273
utime, ktime = os.times()[:2]
@@ -633,6 +636,8 @@ def test_memory_maps(self):
633636

634637
for nt in maps:
635638
if not nt.path.startswith('['):
639+
if QEMU_USER and "/bin/qemu-" in nt.path:
640+
continue
636641
assert os.path.isabs(nt.path), nt.path
637642
if POSIX:
638643
try:
@@ -698,6 +703,7 @@ def test_is_running(self):
698703
assert not p.is_running()
699704
assert not p.is_running()
700705

706+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
701707
def test_exe(self):
702708
p = self.spawn_psproc()
703709
exe = p.exe()
@@ -754,6 +760,9 @@ def test_cmdline(self):
754760
' '.join(p.cmdline()[1:]), ' '.join(cmdline[1:])
755761
)
756762
return
763+
if QEMU_USER:
764+
self.assertEqual(' '.join(p.cmdline()[2:]), ' '.join(cmdline))
765+
return
757766
self.assertEqual(' '.join(p.cmdline()), ' '.join(cmdline))
758767

759768
@unittest.skipIf(PYPY, "broken on PYPY")
@@ -771,21 +780,23 @@ def test_long_cmdline(self):
771780
self.assertEqual(p.cmdline(), cmdline)
772781
except psutil.ZombieProcess:
773782
raise unittest.SkipTest("OPENBSD: process turned into zombie")
774-
elif NETBSD:
783+
elif QEMU_USER:
784+
self.assertEqual(p.cmdline()[2:], cmdline)
785+
else:
775786
ret = p.cmdline()
776-
if ret == []:
787+
if NETBSD and ret == []:
777788
# https://github.com/giampaolo/psutil/issues/2250
778789
raise unittest.SkipTest("OPENBSD: returned EBUSY")
779-
780-
self.assertEqual(p.cmdline(), cmdline)
790+
self.assertEqual(ret, cmdline)
781791

782792
def test_name(self):
783793
p = self.spawn_psproc()
784794
name = p.name().lower()
785795
pyexe = os.path.basename(os.path.realpath(sys.executable)).lower()
786796
assert pyexe.startswith(name), (pyexe, name)
787797

788-
@unittest.skipIf(PYPY, "unreliable on PYPY")
798+
@unittest.skipIf(PYPY or QEMU_USER, "unreliable on PYPY")
799+
@unittest.skipIf(QEMU_USER, "unreliable on QEMU user")
789800
def test_long_name(self):
790801
pyexe = create_py_exe(self.get_testfn(suffix="0123456789" * 2))
791802
cmdline = [
@@ -816,6 +827,7 @@ def test_long_name(self):
816827
@unittest.skipIf(SUNOS, "broken on SUNOS")
817828
@unittest.skipIf(AIX, "broken on AIX")
818829
@unittest.skipIf(PYPY, "broken on PYPY")
830+
@unittest.skipIf(QEMU_USER, "broken on QEMU user")
819831
def test_prog_w_funky_name(self):
820832
# Test that name(), exe() and cmdline() correctly handle programs
821833
# with funky chars such as spaces and ")", see:
@@ -922,6 +934,7 @@ def cleanup(init):
922934
except psutil.AccessDenied:
923935
pass
924936

937+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
925938
def test_status(self):
926939
p = psutil.Process()
927940
self.assertEqual(p.status(), psutil.STATUS_RUNNING)
@@ -1149,6 +1162,7 @@ def test_parent_multi(self):
11491162
self.assertEqual(grandchild.parent(), child)
11501163
self.assertEqual(child.parent(), parent)
11511164

1165+
@unittest.skipIf(QEMU_USER, "QEMU user not supported")
11521166
@retry_on_failure()
11531167
def test_parents(self):
11541168
parent = psutil.Process()

psutil/tests/test_process_all.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from psutil._compat import long
3333
from psutil._compat import unicode
3434
from psutil.tests import CI_TESTING
35+
from psutil.tests import QEMU_USER
3536
from psutil.tests import VALID_PROC_STATUSES
3637
from psutil.tests import PsutilTestCase
3738
from psutil.tests import check_connection_ntuple
@@ -235,6 +236,9 @@ def username(self, ret, info):
235236
def status(self, ret, info):
236237
self.assertIsInstance(ret, str)
237238
assert ret, ret
239+
if QEMU_USER:
240+
# status does not work under qemu user
241+
return
238242
self.assertNotEqual(ret, '?') # XXX
239243
self.assertIn(ret, VALID_PROC_STATUSES)
240244

0 commit comments

Comments
 (0)