Skip to content

Commit fa93760

Browse files
authored
Add --elide-unused-requires-dist lock option. (#2613)
This cuts down lock file size without changing any observable result of using the lock file. It should cut down on lock subset times since there is both less to parse and less dependencies to rule out after the parse, but this effect is unmeasured at present.
1 parent 50d84a9 commit fa93760

16 files changed

+623
-99
lines changed

CHANGES.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Release Notes
22

3+
## 2.25.0
4+
5+
This release adds support for
6+
`pex3 lock {create,sync} --elide-unused-requires-dist`. This new lock
7+
option causes any dependencies of a locked requirement that can never
8+
be activated to be elided from the lock file. This leads to no material
9+
difference in lock file use, but it does cut down on the lock file size.
10+
11+
* Add `--elide-unused-requires-dist` lock option. (#2613)
12+
313
## 2.24.3
414

515
This release fixes a long-standing bug in resolve checking. Previously,

package/pex-scie.lock

+9-31
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"allow_wheels": true,
55
"build_isolation": true,
66
"constraints": [],
7+
"elide_unused_requires_dist": true,
78
"excluded": [],
89
"locked_resolves": [
910
{
@@ -17,13 +18,7 @@
1718
}
1819
],
1920
"project_name": "psutil",
20-
"requires_dists": [
21-
"enum34; python_version <= \"3.4\" and extra == \"test\"",
22-
"ipaddress; python_version < \"3.0\" and extra == \"test\"",
23-
"mock; python_version < \"3.0\" and extra == \"test\"",
24-
"pywin32; sys_platform == \"win32\" and extra == \"test\"",
25-
"wmi; sys_platform == \"win32\" and extra == \"test\""
26-
],
21+
"requires_dists": [],
2722
"requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7",
2823
"version": "6.0.0"
2924
}
@@ -45,13 +40,7 @@
4540
}
4641
],
4742
"project_name": "psutil",
48-
"requires_dists": [
49-
"enum34; python_version <= \"3.4\" and extra == \"test\"",
50-
"ipaddress; python_version < \"3.0\" and extra == \"test\"",
51-
"mock; python_version < \"3.0\" and extra == \"test\"",
52-
"pywin32; sys_platform == \"win32\" and extra == \"test\"",
53-
"wmi; sys_platform == \"win32\" and extra == \"test\""
54-
],
43+
"requires_dists": [],
5544
"requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7",
5645
"version": "6.0.0"
5746
}
@@ -73,13 +62,7 @@
7362
}
7463
],
7564
"project_name": "psutil",
76-
"requires_dists": [
77-
"enum34; python_version <= \"3.4\" and extra == \"test\"",
78-
"ipaddress; python_version < \"3.0\" and extra == \"test\"",
79-
"mock; python_version < \"3.0\" and extra == \"test\"",
80-
"pywin32; sys_platform == \"win32\" and extra == \"test\"",
81-
"wmi; sys_platform == \"win32\" and extra == \"test\""
82-
],
65+
"requires_dists": [],
8366
"requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7",
8467
"version": "6.0.0"
8568
}
@@ -101,13 +84,7 @@
10184
}
10285
],
10386
"project_name": "psutil",
104-
"requires_dists": [
105-
"enum34; python_version <= \"3.4\" and extra == \"test\"",
106-
"ipaddress; python_version < \"3.0\" and extra == \"test\"",
107-
"mock; python_version < \"3.0\" and extra == \"test\"",
108-
"pywin32; sys_platform == \"win32\" and extra == \"test\"",
109-
"wmi; sys_platform == \"win32\" and extra == \"test\""
110-
],
87+
"requires_dists": [],
11188
"requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7",
11289
"version": "6.0.0"
11390
}
@@ -123,8 +100,8 @@
123100
"only_wheels": [],
124101
"overridden": [],
125102
"path_mappings": {},
126-
"pex_version": "2.17.0",
127-
"pip_version": "24.2",
103+
"pex_version": "2.24.3",
104+
"pip_version": "24.3.1",
128105
"prefer_older_binary": false,
129106
"requirements": [
130107
"psutil>=5.3"
@@ -134,5 +111,6 @@
134111
"style": "strict",
135112
"target_systems": [],
136113
"transitive": true,
137-
"use_pep517": null
114+
"use_pep517": null,
115+
"use_system_time": false
138116
}

pex/cli/commands/lock.py

+47-5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
from typing import IO, Dict, Iterable, List, Mapping, Optional, Set, Text, Tuple, Union
7777

7878
import attr # vendor:skip
79+
80+
from pex.resolve.lockfile.updater import Update
7981
else:
8082
from pex.third_party import attr
8183

@@ -526,6 +528,20 @@ def add_create_lock_options(cls, create_parser):
526528
)
527529
),
528530
)
531+
create_parser.add_argument(
532+
"--elide-unused-requires-dist",
533+
"--no-elide-unused-requires-dist",
534+
dest="elide_unused_requires_dist",
535+
type=bool,
536+
default=False,
537+
action=HandleBoolAction,
538+
help=(
539+
"When creating the lock, elide dependencies from the 'requires_dists' lists that "
540+
"can never be active due to markers. This does not change the reachable content of "
541+
"the lock, but it does cut down on lock file size. This currently only elides "
542+
"extras deps that are never activated, but may trim more in the future."
543+
),
544+
)
529545
cls._add_lock_options(create_parser)
530546
cls._add_resolve_options(create_parser)
531547
cls.add_json_options(create_parser, entity="lock", include_switch=False)
@@ -899,6 +915,7 @@ def _create(self):
899915
for interpreter_constraint in target_configuration.interpreter_constraints
900916
),
901917
target_systems=tuple(self.options.target_systems),
918+
elide_unused_requires_dist=self.options.elide_unused_requires_dist,
902919
)
903920
elif self.options.target_systems:
904921
return Error(
@@ -907,7 +924,10 @@ def _create(self):
907924
)
908925
)
909926
else:
910-
lock_configuration = LockConfiguration(style=self.options.style)
927+
lock_configuration = LockConfiguration(
928+
style=self.options.style,
929+
elide_unused_requires_dist=self.options.elide_unused_requires_dist,
930+
)
911931

912932
targets = try_(
913933
self._resolve_targets(
@@ -1242,7 +1262,7 @@ def _process_lock_update(
12421262
dry_run = self.options.dry_run
12431263
path_mappings = self._get_path_mappings()
12441264
output = sys.stdout if dry_run is DryRunStyle.DISPLAY else sys.stderr
1245-
updates = [] # type: List[Union[DeleteUpdate, VersionUpdate, ArtifactsUpdate]]
1265+
updates = [] # type: List[Update]
12461266
warnings = [] # type: List[str]
12471267
for resolve_update in lock_update.resolves:
12481268
platform = resolve_update.updated_resolve.target_platform
@@ -1317,7 +1337,7 @@ def _process_lock_update(
13171337
)
13181338
if update_req:
13191339
requirements_by_project_name[project_name] = update_req
1320-
else:
1340+
elif isinstance(update, ArtifactsUpdate):
13211341
message_lines = [
13221342
" {lead_in} {project_name} {version} artifacts:".format(
13231343
lead_in="Would update" if dry_run else "Updated",
@@ -1351,8 +1371,25 @@ def _process_lock_update(
13511371
)
13521372
for artifact in update.removed
13531373
)
1354-
13551374
print("\n".join(message_lines), file=output)
1375+
else:
1376+
message_lines = [
1377+
" {lead_in} {project_name} {version} requirements:".format(
1378+
lead_in="Would update" if dry_run else "Updated",
1379+
project_name=project_name,
1380+
version=update.version,
1381+
)
1382+
]
1383+
if update.added:
1384+
message_lines.extend(
1385+
" + {added}".format(added=req) for req in update.added
1386+
)
1387+
if update.removed:
1388+
message_lines.extend(
1389+
" - {removed}".format(removed=req) for req in update.removed
1390+
)
1391+
print("\n".join(message_lines), file=output)
1392+
13561393
if fingerprint_updates:
13571394
warnings.append(
13581395
"Detected fingerprint changes in the following locked {projects} for lock "
@@ -1547,6 +1584,7 @@ def _sync(self):
15471584
for interpreter_constraint in target_configuration.interpreter_constraints
15481585
),
15491586
target_systems=tuple(self.options.target_systems),
1587+
elide_unused_requires_dist=self.options.elide_unused_requires_dist,
15501588
)
15511589
elif self.options.target_systems:
15521590
return Error(
@@ -1555,7 +1593,10 @@ def _sync(self):
15551593
)
15561594
)
15571595
else:
1558-
lock_configuration = LockConfiguration(style=self.options.style)
1596+
lock_configuration = LockConfiguration(
1597+
style=self.options.style,
1598+
elide_unused_requires_dist=self.options.elide_unused_requires_dist,
1599+
)
15591600

15601601
lock_file_path = self.options.lock
15611602
if os.path.exists(lock_file_path):
@@ -1566,6 +1607,7 @@ def _sync(self):
15661607
style=lock_configuration.style,
15671608
requires_python=SortedTuple(lock_configuration.requires_python),
15681609
target_systems=SortedTuple(lock_configuration.target_systems),
1610+
elide_unused_requires_dist=lock_configuration.elide_unused_requires_dist,
15691611
pip_version=pip_configuration.version,
15701612
resolver_version=pip_configuration.resolver_version,
15711613
allow_prereleases=pip_configuration.allow_prereleases,

pex/resolve/lock_downloader.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
DownloadableArtifact,
2626
FileArtifact,
2727
LocalProjectArtifact,
28-
LockConfiguration,
2928
VCSArtifact,
3029
)
3130
from pex.resolve.lockfile.download_manager import DownloadedArtifact, DownloadManager
@@ -233,11 +232,7 @@ def create(
233232
file_lock_style=file_lock_style,
234233
downloader=ArtifactDownloader(
235234
resolver=resolver,
236-
lock_configuration=LockConfiguration(
237-
style=lock.style,
238-
requires_python=lock.requires_python,
239-
target_systems=lock.target_systems,
240-
),
235+
lock_configuration=lock.lock_configuration(),
241236
target=target,
242237
package_index_configuration=PackageIndexConfiguration.create(
243238
pip_version=pip_version,

pex/resolve/locked_resolve.py

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class LockConfiguration(object):
8787
style = attr.ib() # type: LockStyle.Value
8888
requires_python = attr.ib(default=()) # type: Tuple[str, ...]
8989
target_systems = attr.ib(default=()) # type: Tuple[TargetSystem.Value, ...]
90+
elide_unused_requires_dist = attr.ib(default=False) # type: bool
9091

9192
@requires_python.validator
9293
@target_systems.validator

pex/resolve/lockfile/create.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,11 @@ def lock(self, downloaded):
341341
package_index_configuration=self.package_index_configuration,
342342
max_parallel_jobs=self.max_parallel_jobs,
343343
),
344-
platform_tag=None
345-
if self.lock_configuration.style == LockStyle.UNIVERSAL
346-
else target.platform.tag,
344+
platform_tag=(
345+
None
346+
if self.lock_configuration.style == LockStyle.UNIVERSAL
347+
else target.platform.tag
348+
),
347349
)
348350
for target, resolved_requirements in resolved_requirements_by_target.items()
349351
)
@@ -456,6 +458,7 @@ def create(
456458
excluded=dependency_configuration.excluded,
457459
overridden=dependency_configuration.all_overrides(),
458460
locked_resolves=locked_resolves,
461+
elide_unused_requires_dist=lock_configuration.elide_unused_requires_dist,
459462
)
460463

461464
if lock_configuration.style is LockStyle.UNIVERSAL and (

pex/resolve/lockfile/json_codec.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ def parse_version_specifier(
209209
for index, target_system in enumerate(get("target_systems", list, optional=True) or ())
210210
]
211211

212+
elide_unused_requires_dist = get("elide_unused_requires_dist", bool, optional=True) or False
213+
212214
only_wheels = [
213215
parse_project_name(project_name, path=".only_wheels[{index}]".format(index=index))
214216
for index, project_name in enumerate(get("only_wheels", list, optional=True) or ())
@@ -231,7 +233,7 @@ def parse_version_specifier(
231233
for index, constraint in enumerate(get("constraints", list))
232234
]
233235

234-
use_system_time = get("use_system_time", bool, optional=True)
236+
use_system_time = get("use_system_time", bool, optional=True) or False
235237

236238
excluded = [
237239
parse_requirement(req, path=".excluded[{index}]".format(index=index))
@@ -337,6 +339,7 @@ def assemble_tag(
337339
style=get_enum_value(LockStyle, "style"),
338340
requires_python=get("requires_python", list),
339341
target_systems=target_systems,
342+
elide_unused_requires_dist=elide_unused_requires_dist,
340343
pip_version=get_enum_value(
341344
PipVersion,
342345
"pip_version",
@@ -354,10 +357,7 @@ def assemble_tag(
354357
prefer_older_binary=get("prefer_older_binary", bool),
355358
use_pep517=get("use_pep517", bool, optional=True),
356359
build_isolation=get("build_isolation", bool),
357-
# N.B.: Although locks are now always generated under SOURCE_DATE_EPOCH=fixed and
358-
# PYTHONHASHSEED=0 (aka: `use_system_time=False`), that did not use to be the case. In
359-
# those old locks there was no "use_system_time" field.
360-
use_system_time=use_system_time if use_system_time is not None else True,
360+
use_system_time=use_system_time,
361361
),
362362
transitive=get("transitive", bool),
363363
excluded=excluded,
@@ -391,6 +391,7 @@ def as_json_data(
391391
"style": str(lockfile.style),
392392
"requires_python": list(lockfile.requires_python),
393393
"target_systems": [str(target_system) for target_system in lockfile.target_systems],
394+
"elide_unused_requires_dist": lockfile.elide_unused_requires_dist,
394395
"pip_version": str(lockfile.pip_version),
395396
"resolver_version": str(lockfile.resolver_version),
396397
"requirements": [

pex/resolve/lockfile/model.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
from pex.pep_503 import ProjectName
1212
from pex.pip.version import PipVersion, PipVersionValue
1313
from pex.requirements import LocalProjectRequirement
14-
from pex.resolve.locked_resolve import LocalProjectArtifact, LockedResolve, LockStyle, TargetSystem
14+
from pex.resolve.locked_resolve import (
15+
LocalProjectArtifact,
16+
LockConfiguration,
17+
LockedResolve,
18+
LockStyle,
19+
TargetSystem,
20+
)
21+
from pex.resolve.lockfile import requires_dist
1522
from pex.resolve.resolved_requirement import Pin
1623
from pex.resolve.resolver_configuration import BuildConfiguration, ResolverVersion
1724
from pex.sorted_tuple import SortedTuple
@@ -47,6 +54,7 @@ def create(
4754
source=None, # type: Optional[str]
4855
pip_version=None, # type: Optional[PipVersionValue]
4956
resolver_version=None, # type: Optional[ResolverVersion.Value]
57+
elide_unused_requires_dist=False, # type: bool
5058
):
5159
# type: (...) -> Lockfile
5260

@@ -94,6 +102,7 @@ def extract_requirement(req):
94102
style=style,
95103
requires_python=SortedTuple(requires_python),
96104
target_systems=SortedTuple(target_systems),
105+
elide_unused_requires_dist=elide_unused_requires_dist,
97106
pip_version=pip_ver,
98107
resolver_version=resolver_version or ResolverVersion.default(pip_ver),
99108
requirements=SortedTuple(resolve_requirements, key=str),
@@ -110,7 +119,14 @@ def extract_requirement(req):
110119
transitive=transitive,
111120
excluded=SortedTuple(excluded),
112121
overridden=SortedTuple(overridden),
113-
locked_resolves=SortedTuple(locked_resolves),
122+
locked_resolves=SortedTuple(
123+
(
124+
requires_dist.remove_unused_requires_dist(resolve_requirements, locked_resolve)
125+
if elide_unused_requires_dist
126+
else locked_resolve
127+
)
128+
for locked_resolve in locked_resolves
129+
),
114130
local_project_requirement_mapping=requirement_by_local_project_directory,
115131
source=source,
116132
)
@@ -119,6 +135,7 @@ def extract_requirement(req):
119135
style = attr.ib() # type: LockStyle.Value
120136
requires_python = attr.ib() # type: SortedTuple[str]
121137
target_systems = attr.ib() # type: SortedTuple[TargetSystem.Value]
138+
elide_unused_requires_dist = attr.ib() # type: bool
122139
pip_version = attr.ib() # type: PipVersionValue
123140
resolver_version = attr.ib() # type: ResolverVersion.Value
124141
requirements = attr.ib() # type: SortedTuple[Requirement]
@@ -139,6 +156,15 @@ def extract_requirement(req):
139156
local_project_requirement_mapping = attr.ib(eq=False) # type: Mapping[str, Requirement]
140157
source = attr.ib(default=None, eq=False) # type: Optional[str]
141158

159+
def lock_configuration(self):
160+
# type: () -> LockConfiguration
161+
return LockConfiguration(
162+
style=self.style,
163+
requires_python=self.requires_python,
164+
target_systems=self.target_systems,
165+
elide_unused_requires_dist=self.elide_unused_requires_dist,
166+
)
167+
142168
def build_configuration(self):
143169
# type: () -> BuildConfiguration
144170
return BuildConfiguration.create(

0 commit comments

Comments
 (0)