Skip to content
58 changes: 29 additions & 29 deletions Lib/sysconfig/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,34 +666,34 @@ def get_platform():

# Set for cross builds explicitly
if "_PYTHON_HOST_PLATFORM" in os.environ:
return os.environ["_PYTHON_HOST_PLATFORM"]

# Try to distinguish various flavours of Unix
osname, host, release, version, machine = os.uname()

# Convert the OS name to lowercase, remove '/' characters, and translate
# spaces (for "Power Macintosh")
osname = osname.lower().replace('/', '')
machine = machine.replace(' ', '_')
machine = machine.replace('/', '-')

if osname[:5] == "linux":
if sys.platform == "android":
osname = "android"
release = get_config_var("ANDROID_API_LEVEL")

# Wheel tags use the ABI names from Android's own tools.
machine = {
"x86_64": "x86_64",
"i686": "x86",
"aarch64": "arm64_v8a",
"armv7l": "armeabi_v7a",
}[machine]
else:
# At least on Linux/Intel, 'machine' is the processor --
# i386, etc.
# XXX what about Alpha, SPARC, etc?
return f"{osname}-{machine}"
osname, _, machine = os.environ["_PYTHON_HOST_PLATFORM"].partition('-')
release = None
else:
# Try to distinguish various flavours of Unix
osname, host, release, version, machine = os.uname()

# Convert the OS name to lowercase, remove '/' characters, and translate
# spaces (for "Power Macintosh")
osname = osname.lower().replace('/', '')
machine = machine.replace(' ', '_')
machine = machine.replace('/', '-')

if osname == "android" or sys.platform == "android":
osname = "android"
release = get_config_var("ANDROID_API_LEVEL")

# Wheel tags use the ABI names from Android's own tools.
machine = {
"x86_64": "x86_64",
"i686": "x86",
"aarch64": "arm64_v8a",
"armv7l": "armeabi_v7a",
}[machine]
elif osname == "linux":
# At least on Linux/Intel, 'machine' is the processor --
# i386, etc.
# XXX what about Alpha, SPARC, etc?
return f"{osname}-{machine}"
elif osname[:5] == "sunos":
if release[0] >= "5": # SunOS 5 == Solaris 2
osname = "solaris"
Expand Down Expand Up @@ -725,7 +725,7 @@ def get_platform():
get_config_vars(),
osname, release, machine)

return f"{osname}-{release}-{machine}"
return '-'.join(map(str, filter(None, (osname, release, machine))))


def get_python_version():
Expand Down
128 changes: 128 additions & 0 deletions Lib/test/test_build_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import json
import os
import sys
import sysconfig
import string
import unittest

from test.support import is_android, is_apple_mobile, is_emscripten, is_wasi


class FormatTestsBase:
@property
def contents(self):
"""Install details file contents. Should be overriden by subclasses."""
raise NotImplementedError

@property
def data(self):
"""Parsed install details file data, as a Python object."""
return json.loads(self.contents)

def key(self, name):
"""Helper to fetch subsection entries.

It takes the entry name, allowing the usage of a dot to separate the
different subsection names (eg. specifying 'a.b.c' as the key will
return the value of self.data['a']['b']['c']).
"""
value = self.data
for part in name.split('.'):
value = value[part]
return value

def test_parse(self):
self.data

def test_top_level_container(self):
self.assertIsInstance(self.data, dict)
for key, value in self.data.items():
with self.subTest(key=key):
if key in ('schema_version', 'base_prefix', 'base_interpreter', 'platform'):
self.assertIsInstance(value, str)
elif key in ('language', 'implementation', 'abi', 'suffixes', 'libpython', 'c_api', 'arbitrary_data'):
self.assertIsInstance(value, dict)

def test_base_prefix(self):
self.assertIsInstance(self.key('base_prefix'), str)

def test_base_interpreter(self):
"""Test the base_interpreter entry.

The generic test wants the key to be missing. If your implementation
provides a value for it, you should override this test.
"""
with self.assertRaises(KeyError):
self.key('base_interpreter')

def test_platform(self):
self.assertEqual(self.key('platform'), sysconfig.get_platform())

def test_language_version(self):
allowed_characters = string.digits + string.ascii_letters + '.'
value = self.key('language.version')

self.assertLessEqual(set(value), set(allowed_characters))
self.assertTrue(sys.version.startswith(value))

def test_language_version_info(self):
value = self.key('language.version_info')

self.assertEqual(len(value), sys.version_info.n_fields)
for part_name, part_value in value.items():
with self.subTest(part=part_name):
self.assertEqual(part_value, getattr(sys.version_info, part_name))

def test_implementation(self):
for key, value in self.key('implementation').items():
with self.subTest(part=key):
if key == 'version':
self.assertEqual(len(value), len(sys.implementation.version))
for part_name, part_value in value.items():
self.assertEqual(getattr(sys.implementation.version, part_name), part_value)
else:
self.assertEqual(getattr(sys.implementation, key), value)


needs_installed_python = unittest.skipIf(
sysconfig.is_python_build(),
'This test can only run in an installed Python',
)


@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX right now')
@unittest.skipIf(is_wasi or is_emscripten, 'Feature not available on WebAssembly builds')
class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
"""Test CPython's install details file implementation."""

@property
def location(self):
if sysconfig.is_python_build():
projectdir = sysconfig.get_config_var('projectbase')
with open(os.path.join(projectdir, 'pybuilddir.txt')) as f:
dirname = os.path.join(projectdir, f.read())
else:
dirname = sysconfig.get_path('stdlib')
return os.path.join(dirname, 'build-details.json')

@property
def contents(self):
with open(self.location, 'r') as f:
return f.read()

@needs_installed_python
def test_location(self):
self.assertTrue(os.path.isfile(self.location))

# Override generic format tests with tests for our specific implemenation.

@needs_installed_python
@unittest.skipIf(is_android or is_apple_mobile, 'Android and iOS run tests via a custom testbed method that changes sys.executable')
def test_base_interpreter(self):
value = self.key('base_interpreter')

self.assertEqual(os.path.realpath(value), os.path.realpath(sys.executable))


if __name__ == '__main__':
unittest.main()
6 changes: 5 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ list-targets:

.PHONY: build_all
build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \
gdbhooks Programs/_testembed scripts checksharedmods rundsymutil
gdbhooks Programs/_testembed scripts checksharedmods rundsymutil build-details.json

.PHONY: build_wasm
build_wasm: check-clean-src $(BUILDPYTHON) platform sharedmods \
Expand Down Expand Up @@ -934,6 +934,9 @@ pybuilddir.txt: $(PYTHON_FOR_BUILD_DEPS)
exit 1 ; \
fi

build-details.json: pybuilddir.txt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The target path here is wrong so this gets re-run for every invocation of make.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FFY00 was this addressed since?

$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/generate-build-details.py `cat pybuilddir.txt`/build-details.json

# Build static library
$(LIBRARY): $(LIBRARY_OBJS)
-rm -f $@
Expand Down Expand Up @@ -2644,6 +2647,7 @@ libinstall: all $(srcdir)/Modules/xxmodule.c
done
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py $(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfig_vars_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json $(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) `cat pybuilddir.txt`/build-details.json $(DESTDIR)$(LIBDEST); \
$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
@ # If app store compliance has been configured, apply the patch to the
@ # installed library code. The patch has been previously validated against
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
A ``build-details.json`` file is now install in the platform-independent
standard library directory (:pep:`739` implementation).
Loading
Loading