Skip to content

Commit 95e654c

Browse files
committed
Adapt pip and tests to merged metadata
1 parent 63dd594 commit 95e654c

File tree

5 files changed

+75
-93
lines changed

5 files changed

+75
-93
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_patched_pip.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -97,7 +97,7 @@ def setUp(self):
9797
shutil.copytree(self.venv_template_dir, self.venv_dir, symlinks=True)
9898
self.patch_dir = Path(tempfile.mkdtemp()).resolve()
9999
self.pip_env = os.environ.copy()
100-
self.pip_env['PIPLOADER_PATCHES_BASE_DIRS'] = str(self.patch_dir)
100+
self.pip_env['PIP_GRAALPY_PATCHES_URL'] = str(self.patch_dir)
101101
self.index_dir = Path(tempfile.mkdtemp()).resolve()
102102

103103
def tearDown(self):
@@ -106,18 +106,16 @@ def tearDown(self):
106106
shutil.rmtree(self.index_dir, ignore_errors=True)
107107

108108
def prepare_config(self, name, rules):
109-
package_dir = self.patch_dir / name
110-
package_dir.mkdir(exist_ok=True)
111109
toml_lines = []
112110
for rule in rules:
113-
toml_lines.append('[[rules]]')
111+
toml_lines.append(f'[[{name}.rules]]')
114112
for k, v in rule.items():
115113
if not k.startswith('$'):
116114
toml_lines.append(f'{k} = {v!r}')
117115
if patch := rule.get('patch'):
118-
with open(package_dir / patch, 'w') as f:
116+
with open(self.patch_dir / patch, 'w') as f:
119117
f.write(PATCH_TEMPLATE.format(rule.get('$patch-text', 'Patched')))
120-
with open(package_dir / 'metadata.toml', 'w') as f:
118+
with open(self.patch_dir / 'metadata.toml', 'w') as f:
121119
f.write('\n'.join(toml_lines))
122120

123121
def build_package(self, name, version):

graalpython/lib-graalpython/patches/README.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
This directory contains patches applied by pip when installing packages. There is a directory for each package that
2-
contains patches and optionally a configuration file that can specify rules for matching patches to package versions and
3-
can also influence pip version selection mechanism.
4-
5-
Configuration files are named `metadata.toml` and can contain the following:
1+
This directory contains patches applied by pip when installing packages. There is a `metadata.toml` configuration file that specifies rules for patching and
2+
can also influence pip version selection mechanism. It can contain the following:
63

74
```toml
8-
# The file defines an array of tables (dicts) named `patches`. The patch selection process iterates it and picks the
9-
# first patch one that matches in version and dist type.
5+
# The file defines a dict whose keys are package names. The sub-key 'rules' specifies a list of patching rules.
6+
# The patch selection process iterates it and picks the first patch one that matches in version and dist type.
107
# The next entry will apply to a wheel foo-1.0.0
11-
[[rules]]
12-
# Optional. Relative path to a patch file. May be omitted when the entry just specifies `install-priority`
8+
[[foo.rules]]
9+
# Optional. Relative path to a patch file. May be omitted
1310
patch = 'foo-1.0.0.patch'
1411
# Required if 'patch' is specified. SPDX license expression for the package (allows parentheses, 'AND', 'OR', 'WITH').
1512
# Allowed licenses are enumerated in mx.graalpython/verify_patches.py
@@ -33,8 +30,8 @@ subdir = 'src'
3330
# priority to 0, the version will not be shown in the suggestion list we display when we didn't find an applicable patch
3431
install-priority = 1
3532

36-
# The next entry will apply to all other artifacts of foo
37-
[[patches]]
33+
# The next entry will apply to all other versions of foo that didn't get matched by the previous rule
34+
[[foo.rules]]
3835
patch = 'foo.patch'
3936
license = 'MIT'
4037
```

graalpython/lib-graalpython/patches/pip-23.2.1.patch

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ index a8cd133..20dd1e6 100644
102102
# file in .data maps to same location as file in wheel root).
103103
diff --git a/pip/_internal/utils/graalpy.py b/pip/_internal/utils/graalpy.py
104104
new file mode 100644
105-
index 0000000..b456846
105+
index 0000000..2a67f4b
106106
--- /dev/null
107107
+++ b/pip/_internal/utils/graalpy.py
108-
@@ -0,0 +1,214 @@
108+
@@ -0,0 +1,209 @@
109109
+# ATTENTION: GraalPy uses existence of this module to verify that it is
110110
+# running a patched pip in pip_hook.py
111111
+import os
@@ -120,10 +120,10 @@ index 0000000..b456846
120120
+from pip._vendor.packaging.specifiers import SpecifierSet
121121
+from pip._vendor.packaging.version import VERSION_PATTERN
122122
+
123-
+PATCHES_BASE_DIRS = [os.path.join(__graalpython__.core_home, "patches")]
124-
+if hasattr(__graalpython__, "tdebug"):
125-
+ PATCHES_BASE_DIRS += os.environ.get('PIPLOADER_PATCHES_BASE_DIRS', "").split(",")
126-
+
123+
+METADATA_PATH = os.environ.get(
124+
+ 'PIP_GRAALPY_METADATA',
125+
+ os.path.join(__graalpython__.core_home, 'patches', 'metadata.toml'),
126+
+)
127127
+DISABLE_PATCHING = os.environ.get('PIP_GRAALPY_DISABLE_PATCHING', '').lower() in ('true', '1')
128128
+DISABLE_VERSION_SELECTION = os.environ.get('PIP_GRAALPY_DISABLE_VERSION_SELECTION', '').lower() in ('true', '1')
129129
+
@@ -133,25 +133,16 @@ index 0000000..b456846
133133
+
134134
+
135135
+class PatchRepository:
136-
+ def __init__(self, base_dirs):
136+
+ def __init__(self, metadata_path):
137137
+ self._repository = {}
138-
+ for base_dir in base_dirs:
139-
+ for package_dir in Path(base_dir).iterdir():
140-
+ denormalized_name = package_dir.name
141-
+ normalized_name = normalize_name(denormalized_name)
142-
+ metadata = {}
143-
+ if (metadata_path := package_dir / 'metadata.toml').is_file():
144-
+ with open(metadata_path, 'rb') as f:
145-
+ metadata = tomli.load(f)
146-
+ metadata.setdefault('rules', [])
147-
+ for rule in metadata['rules']:
148-
+ if 'patch' in rule:
149-
+ rule['patch'] = package_dir / rule['patch']
150-
+ self._repository[normalized_name] = metadata
138+
+ self.metadata_path = Path(metadata_path)
139+
+ if self.metadata_path.exists():
140+
+ with open(self.metadata_path, 'rb') as f:
141+
+ self._repository = {normalize_name(name): metadata for name, metadata in tomli.load(f).items()}
151142
+
152143
+ def get_rules(self, name):
153144
+ if metadata := self._repository.get(normalize_name(name)):
154-
+ return metadata['rules']
145+
+ return metadata.get('rules')
155146
+
156147
+ def get_add_sources(self, name):
157148
+ if metadata := self._repository.get(normalize_name(name)):
@@ -185,14 +176,17 @@ index 0000000..b456846
185176
+ continue
186177
+ return rule
187178
+
179+
+ def resolve_patch(self, patch_path):
180+
+ return self.metadata_path.parent / patch_path
181+
+
188182
+
189183
+__PATCH_REPOSITORY = None
190184
+
191185
+
192186
+def get_patch_repository():
193187
+ global __PATCH_REPOSITORY
194188
+ if not __PATCH_REPOSITORY:
195-
+ __PATCH_REPOSITORY = PatchRepository(PATCHES_BASE_DIRS)
189+
+ __PATCH_REPOSITORY = PatchRepository(METADATA_PATH)
196190
+ return __PATCH_REPOSITORY
197191
+
198192
+
@@ -246,6 +240,7 @@ index 0000000..b456846
246240
+ location = os.path.join(location, subdir)
247241
+ if rule:
248242
+ if patch := rule.get('patch'):
243+
+ patch = repository.resolve_patch(patch)
249244
+ print(f"Patching package {name} using {patch}")
250245
+ exe = '.exe' if os.name == 'nt' else ''
251246
+ try:

mx.graalpython/verify_patches.py

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
# SOFTWARE.
3939
import argparse
4040
import re
41-
import sys
4241
from pathlib import Path
4342

4443
from pip._vendor import tomli # pylint: disable=no-name-in-module
@@ -63,44 +62,50 @@
6362
RULE_KEYS = frozenset({'version', 'patch', 'license', 'subdir', 'dist-type', 'install-priority'})
6463

6564

66-
def validate_metadata(package_dir, metadata):
67-
if unexpected_keys := set(metadata) - SECTIONS:
68-
assert False, f"Unexpected top-level metadata keys: {unexpected_keys}"
65+
def validate_metadata(patches_dir):
66+
with open(patches_dir / 'metadata.toml', 'rb') as f:
67+
all_metadata = tomli.load(f)
6968
patches = set()
70-
if rules := metadata.get('rules'):
71-
for rule in rules:
72-
if unexpected_keys := set(rule) - RULE_KEYS:
73-
assert False, f"Unexpected rule keys: {unexpected_keys}"
74-
if patch := rule.get('patch'):
75-
patch_path = package_dir / patch
76-
assert patch_path.is_file(), f"Patch file does not exists: {patch_path}"
77-
patches.add(patch_path)
78-
license_id = rule.get('license')
79-
assert license_id, f"'license' not specified for patch {patch}"
80-
license_id = re.sub(r'[()]', ' ', license_id)
81-
for part in re.split(f'AND|OR', license_id):
82-
part = part.strip()
83-
if ' WITH ' in part:
84-
part, exception = re.split(r'\s+WITH\s+', part, 1)
85-
assert exception in ALLOWED_WITH_CLAUSES, \
86-
f"License WITH clause {exception} not in allowed list of clauses: {', '.join(ALLOWED_WITH_CLAUSES)}"
87-
assert part in ALLOWED_LICENSES, \
88-
f"License {part} not in allowed list of licenses: {', '.join(ALLOWED_LICENSES)}"
89-
if install_priority := rule.get('install-priority'):
90-
assert isinstance(install_priority, int), "'rules.install_priority' must be an int"
91-
if dist_type := rule.get('dist-type'):
92-
assert dist_type in ('wheel', 'sdist'), "'rules.dist_type' must be on of 'wheel', 'sdist'"
93-
if version := rule.get('version'):
94-
# Just try that it doesn't raise
95-
SpecifierSet(version)
96-
for file in package_dir.iterdir():
97-
assert file.name == 'metadata.toml' or file in patches, f"Dangling file in patch directory: {file}"
98-
if add_sources := metadata.get('add-sources'):
99-
for add_source in add_sources:
100-
if unexpected_keys := set(add_source) - {'version', 'url'}:
101-
assert False, f"Unexpected add_source keys: {unexpected_keys}"
102-
assert add_source.get('version'), f"Missing 'add_sources.version' key in {package_dir}"
103-
assert add_source.get('url'), f"Missing 'add_sources.url' key in {package_dir}"
69+
for package, metadata in all_metadata.items():
70+
try:
71+
if unexpected_keys := set(metadata) - SECTIONS:
72+
assert False, f"Unexpected top-level metadata keys: {unexpected_keys}"
73+
if rules := metadata.get('rules'):
74+
for rule in rules:
75+
if unexpected_keys := set(rule) - RULE_KEYS:
76+
assert False, f"Unexpected rule keys: {unexpected_keys}"
77+
if patch := rule.get('patch'):
78+
patch_path = patches_dir / patch
79+
assert patch_path.is_file(), f"Patch file does not exists: {patch_path}"
80+
patches.add(patch_path)
81+
license_id = rule.get('license')
82+
assert license_id, f"'license' not specified for patch {patch}"
83+
license_id = re.sub(r'[()]', ' ', license_id)
84+
for part in re.split(f'AND|OR', license_id):
85+
part = part.strip()
86+
if ' WITH ' in part:
87+
part, exception = re.split(r'\s+WITH\s+', part, 1)
88+
assert exception in ALLOWED_WITH_CLAUSES, \
89+
f"License WITH clause {exception} not in allowed list of clauses: {', '.join(ALLOWED_WITH_CLAUSES)}"
90+
assert part in ALLOWED_LICENSES, \
91+
f"License {part} not in allowed list of licenses: {', '.join(ALLOWED_LICENSES)}"
92+
if install_priority := rule.get('install-priority'):
93+
assert isinstance(install_priority, int), "'rules.install_priority' must be an int"
94+
if dist_type := rule.get('dist-type'):
95+
assert dist_type in ('wheel', 'sdist'), "'rules.dist_type' must be on of 'wheel', 'sdist'"
96+
if version := rule.get('version'):
97+
# Just try that it doesn't raise
98+
SpecifierSet(version)
99+
if add_sources := metadata.get('add-sources'):
100+
for add_source in add_sources:
101+
if unexpected_keys := set(add_source) - {'version', 'url'}:
102+
assert False, f"Unexpected add_source keys: {unexpected_keys}"
103+
assert add_source.get('version'), "Missing 'add_sources.version' key"
104+
assert add_source.get('url'), "Missing 'add_sources.url' key"
105+
except Exception as e:
106+
raise AssertionError(f"{package}: {e}")
107+
for file in patches_dir.iterdir():
108+
assert not file.name.endswith('patch') or file in patches, f"Dangling patch file: {file}"
104109

105110

106111
def main():
@@ -109,20 +114,7 @@ def main():
109114

110115
args = parser.parse_args()
111116

112-
errors = []
113-
for package_dir in args.patches_dir.iterdir():
114-
if package_dir.is_dir():
115-
try:
116-
if (metadata_path := package_dir / 'metadata.toml').is_file():
117-
with open(metadata_path, 'rb') as f:
118-
metadata = tomli.load(f)
119-
validate_metadata(package_dir, metadata)
120-
else:
121-
assert False, f"Patch directory without metadata: {package_dir}"
122-
except AssertionError as e:
123-
errors.append(f"\t{package_dir.name}: {e}")
124-
if errors:
125-
sys.exit("Patch metadata validation failed:\n" + '\n'.join(errors))
117+
validate_metadata(args.patches_dir)
126118

127119

128120
if __name__ == '__main__':

scripts/repack-bundled-wheels.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,4 @@ patch_wheel() {
7777
rm -rf "$tmpdir"
7878
}
7979

80-
patch_wheel pip graalpython/lib-graalpython/patches/pip/pip-23.2.1.patch
80+
patch_wheel pip graalpython/lib-graalpython/patches/pip-23.2.1.patch

0 commit comments

Comments
 (0)