mirror of
https://github.com/python/cpython.git
synced 2025-11-24 20:30:18 +00:00
[3.14] gh-136264: Fix `--relative-paths` for PEP 739's build-details.json (GH-138510) (#138638)
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if the ABI has changed (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
Some checks are pending
Tests / Windows MSI (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if the ABI has changed (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
* gh-136264: Fix ``--relative-paths`` for PEP 739's build-details.json (GH-138510)
* KeyError is not raised for defaultdict
* Fix relative paths on different drives on Windows
* Add a round-trip test
(cherry picked from commit 057ee17410)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Co-authored-by: Itamar Oren <itamarost@gmail.com>
* Update test_build_details.py
* Update Lib/test/test_build_details.py
---------
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Co-authored-by: Itamar Oren <itamarost@gmail.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
parent
829c81ae21
commit
389450399a
2 changed files with 125 additions and 20 deletions
|
|
@ -1,12 +1,34 @@
|
||||||
|
import importlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import string
|
import string
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from test.support import is_android, is_apple_mobile, is_wasm32
|
from test.support import is_android, is_apple_mobile, is_wasm32
|
||||||
|
|
||||||
|
BASE_PATH = Path(
|
||||||
|
__file__, # Lib/test/test_build_details.py
|
||||||
|
'..', # Lib/test
|
||||||
|
'..', # Lib
|
||||||
|
'..', # <src/install dir>
|
||||||
|
).resolve()
|
||||||
|
MODULE_PATH = BASE_PATH / 'Tools' / 'build' / 'generate-build-details.py'
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Import "generate-build-details.py" as "generate_build_details"
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"generate_build_details", MODULE_PATH
|
||||||
|
)
|
||||||
|
generate_build_details = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules["generate_build_details"] = generate_build_details
|
||||||
|
spec.loader.exec_module(generate_build_details)
|
||||||
|
except (FileNotFoundError, ImportError):
|
||||||
|
generate_build_details = None
|
||||||
|
|
||||||
|
|
||||||
class FormatTestsBase:
|
class FormatTestsBase:
|
||||||
@property
|
@property
|
||||||
|
|
@ -31,16 +53,15 @@ class FormatTestsBase:
|
||||||
value = value[part]
|
value = value[part]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def test_parse(self):
|
|
||||||
self.data
|
|
||||||
|
|
||||||
def test_top_level_container(self):
|
def test_top_level_container(self):
|
||||||
self.assertIsInstance(self.data, dict)
|
self.assertIsInstance(self.data, dict)
|
||||||
for key, value in self.data.items():
|
for key, value in self.data.items():
|
||||||
with self.subTest(key=key):
|
with self.subTest(key=key):
|
||||||
if key in ('schema_version', 'base_prefix', 'base_interpreter', 'platform'):
|
if key in ('schema_version', 'base_prefix', 'base_interpreter',
|
||||||
|
'platform'):
|
||||||
self.assertIsInstance(value, str)
|
self.assertIsInstance(value, str)
|
||||||
elif key in ('language', 'implementation', 'abi', 'suffixes', 'libpython', 'c_api', 'arbitrary_data'):
|
elif key in ('language', 'implementation', 'abi', 'suffixes',
|
||||||
|
'libpython', 'c_api', 'arbitrary_data'):
|
||||||
self.assertIsInstance(value, dict)
|
self.assertIsInstance(value, dict)
|
||||||
|
|
||||||
def test_base_prefix(self):
|
def test_base_prefix(self):
|
||||||
|
|
@ -71,15 +92,20 @@ class FormatTestsBase:
|
||||||
self.assertEqual(len(value), sys.version_info.n_fields)
|
self.assertEqual(len(value), sys.version_info.n_fields)
|
||||||
for part_name, part_value in value.items():
|
for part_name, part_value in value.items():
|
||||||
with self.subTest(part=part_name):
|
with self.subTest(part=part_name):
|
||||||
self.assertEqual(part_value, getattr(sys.version_info, part_name))
|
sys_version_value = getattr(sys.version_info, part_name)
|
||||||
|
self.assertEqual(part_value, sys_version_value)
|
||||||
|
|
||||||
def test_implementation(self):
|
def test_implementation(self):
|
||||||
|
impl_ver = sys.implementation.version
|
||||||
for key, value in self.key('implementation').items():
|
for key, value in self.key('implementation').items():
|
||||||
with self.subTest(part=key):
|
with self.subTest(part=key):
|
||||||
if key == 'version':
|
if key == 'version':
|
||||||
self.assertEqual(len(value), len(sys.implementation.version))
|
self.assertEqual(len(value), len(impl_ver))
|
||||||
for part_name, part_value in value.items():
|
for part_name, part_value in value.items():
|
||||||
self.assertEqual(getattr(sys.implementation.version, part_name), part_value)
|
self.assertFalse(isinstance(sys.implementation.version, dict))
|
||||||
|
getattr(sys.implementation.version, part_name)
|
||||||
|
sys_implementation_value = getattr(impl_ver, part_name)
|
||||||
|
self.assertEqual(sys_implementation_value, part_value)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(getattr(sys.implementation, key), value)
|
self.assertEqual(getattr(sys.implementation, key), value)
|
||||||
|
|
||||||
|
|
@ -99,7 +125,8 @@ class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
|
||||||
def location(self):
|
def location(self):
|
||||||
if sysconfig.is_python_build():
|
if sysconfig.is_python_build():
|
||||||
projectdir = sysconfig.get_config_var('projectbase')
|
projectdir = sysconfig.get_config_var('projectbase')
|
||||||
with open(os.path.join(projectdir, 'pybuilddir.txt')) as f:
|
pybuilddir = os.path.join(projectdir, 'pybuilddir.txt')
|
||||||
|
with open(pybuilddir, encoding='utf-8') as f:
|
||||||
dirname = os.path.join(projectdir, f.read())
|
dirname = os.path.join(projectdir, f.read())
|
||||||
else:
|
else:
|
||||||
dirname = sysconfig.get_path('stdlib')
|
dirname = sysconfig.get_path('stdlib')
|
||||||
|
|
@ -107,7 +134,7 @@ class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def contents(self):
|
def contents(self):
|
||||||
with open(self.location, 'r') as f:
|
with open(self.location, 'r', encoding='utf-8') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
@needs_installed_python
|
@needs_installed_python
|
||||||
|
|
@ -147,5 +174,64 @@ class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
|
||||||
self.assertTrue(os.path.exists(os.path.join(value['pkgconfig_path'], f'python-{version}.pc')))
|
self.assertTrue(os.path.exists(os.path.join(value['pkgconfig_path'], f'python-{version}.pc')))
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(
|
||||||
|
generate_build_details is None,
|
||||||
|
"Failed to import generate-build-details"
|
||||||
|
)
|
||||||
|
@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX right now')
|
||||||
|
@unittest.skipIf(is_wasm32, 'Feature not available on WebAssembly builds')
|
||||||
|
class BuildDetailsRelativePathsTests(unittest.TestCase):
|
||||||
|
@property
|
||||||
|
def build_details_absolute_paths(self):
|
||||||
|
data = generate_build_details.generate_data(schema_version='1.0')
|
||||||
|
return json.loads(json.dumps(data))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def build_details_relative_paths(self):
|
||||||
|
data = self.build_details_absolute_paths
|
||||||
|
generate_build_details.make_paths_relative(data, config_path=None)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def test_round_trip(self):
|
||||||
|
data_abs_path = self.build_details_absolute_paths
|
||||||
|
data_rel_path = self.build_details_relative_paths
|
||||||
|
|
||||||
|
self.assertEqual(data_abs_path['base_prefix'],
|
||||||
|
data_rel_path['base_prefix'])
|
||||||
|
|
||||||
|
base_prefix = data_abs_path['base_prefix']
|
||||||
|
|
||||||
|
top_level_keys = ('base_interpreter',)
|
||||||
|
for key in top_level_keys:
|
||||||
|
self.assertEqual(key in data_abs_path, key in data_rel_path)
|
||||||
|
if key not in data_abs_path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
abs_rel_path = os.path.join(base_prefix, data_rel_path[key])
|
||||||
|
abs_rel_path = os.path.normpath(abs_rel_path)
|
||||||
|
self.assertEqual(data_abs_path[key], abs_rel_path)
|
||||||
|
|
||||||
|
second_level_keys = (
|
||||||
|
('libpython', 'dynamic'),
|
||||||
|
('libpython', 'dynamic_stableabi'),
|
||||||
|
('libpython', 'static'),
|
||||||
|
('c_api', 'headers'),
|
||||||
|
('c_api', 'pkgconfig_path'),
|
||||||
|
|
||||||
|
)
|
||||||
|
for part, key in second_level_keys:
|
||||||
|
self.assertEqual(part in data_abs_path, part in data_rel_path)
|
||||||
|
if part not in data_abs_path:
|
||||||
|
continue
|
||||||
|
self.assertEqual(key in data_abs_path[part],
|
||||||
|
key in data_rel_path[part])
|
||||||
|
if key not in data_abs_path[part]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
abs_rel_path = os.path.join(base_prefix, data_rel_path[part][key])
|
||||||
|
abs_rel_path = os.path.normpath(abs_rel_path)
|
||||||
|
self.assertEqual(data_abs_path[part][key], abs_rel_path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
|
||||||
data['language']['version'] = sysconfig.get_python_version()
|
data['language']['version'] = sysconfig.get_python_version()
|
||||||
data['language']['version_info'] = version_info_to_dict(sys.version_info)
|
data['language']['version_info'] = version_info_to_dict(sys.version_info)
|
||||||
|
|
||||||
data['implementation'] = vars(sys.implementation)
|
data['implementation'] = vars(sys.implementation).copy()
|
||||||
data['implementation']['version'] = version_info_to_dict(sys.implementation.version)
|
data['implementation']['version'] = version_info_to_dict(sys.implementation.version)
|
||||||
# Fix cross-compilation
|
# Fix cross-compilation
|
||||||
if '_multiarch' in data['implementation']:
|
if '_multiarch' in data['implementation']:
|
||||||
|
|
@ -133,33 +133,51 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
|
||||||
def make_paths_relative(data: dict[str, Any], config_path: str | None = None) -> None:
|
def make_paths_relative(data: dict[str, Any], config_path: str | None = None) -> None:
|
||||||
# Make base_prefix relative to the config_path directory
|
# Make base_prefix relative to the config_path directory
|
||||||
if config_path:
|
if config_path:
|
||||||
data['base_prefix'] = os.path.relpath(data['base_prefix'], os.path.dirname(config_path))
|
data['base_prefix'] = relative_path(data['base_prefix'],
|
||||||
|
os.path.dirname(config_path))
|
||||||
|
base_prefix = data['base_prefix']
|
||||||
|
|
||||||
# Update path values to make them relative to base_prefix
|
# Update path values to make them relative to base_prefix
|
||||||
PATH_KEYS = [
|
PATH_KEYS = (
|
||||||
'base_interpreter',
|
'base_interpreter',
|
||||||
'libpython.dynamic',
|
'libpython.dynamic',
|
||||||
'libpython.dynamic_stableabi',
|
'libpython.dynamic_stableabi',
|
||||||
'libpython.static',
|
'libpython.static',
|
||||||
'c_api.headers',
|
'c_api.headers',
|
||||||
'c_api.pkgconfig_path',
|
'c_api.pkgconfig_path',
|
||||||
]
|
)
|
||||||
for entry in PATH_KEYS:
|
for entry in PATH_KEYS:
|
||||||
parent, _, child = entry.rpartition('.')
|
*parents, child = entry.split('.')
|
||||||
# Get the key container object
|
# Get the key container object
|
||||||
try:
|
try:
|
||||||
container = data
|
container = data
|
||||||
for part in parent.split('.'):
|
for part in parents:
|
||||||
container = container[part]
|
container = container[part]
|
||||||
|
if child not in container:
|
||||||
|
raise KeyError(child)
|
||||||
current_path = container[child]
|
current_path = container[child]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
# Get the relative path
|
# Get the relative path
|
||||||
new_path = os.path.relpath(current_path, data['base_prefix'])
|
new_path = relative_path(current_path, base_prefix)
|
||||||
# Join '.' so that the path is formated as './path' instead of 'path'
|
# Join '.' so that the path is formated as './path' instead of 'path'
|
||||||
new_path = os.path.join('.', new_path)
|
new_path = os.path.join('.', new_path)
|
||||||
container[child] = new_path
|
container[child] = new_path
|
||||||
|
|
||||||
|
|
||||||
|
def relative_path(path: str, base: str) -> str:
|
||||||
|
if os.name != 'nt':
|
||||||
|
return os.path.relpath(path, base)
|
||||||
|
|
||||||
|
# There are no relative paths between drives on Windows.
|
||||||
|
path_drv, _ = os.path.splitdrive(path)
|
||||||
|
base_drv, _ = os.path.splitdrive(base)
|
||||||
|
if path_drv.lower() == base_drv.lower():
|
||||||
|
return os.path.relpath(path, base)
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser(exit_on_error=False)
|
parser = argparse.ArgumentParser(exit_on_error=False)
|
||||||
parser.add_argument('location')
|
parser.add_argument('location')
|
||||||
|
|
@ -186,8 +204,9 @@ def main() -> None:
|
||||||
make_paths_relative(data, args.config_file_path)
|
make_paths_relative(data, args.config_file_path)
|
||||||
|
|
||||||
json_output = json.dumps(data, indent=2)
|
json_output = json.dumps(data, indent=2)
|
||||||
with open(args.location, 'w') as f:
|
with open(args.location, 'w', encoding='utf-8') as f:
|
||||||
print(json_output, file=f)
|
f.write(json_output)
|
||||||
|
f.write('\n')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue