-
-
Notifications
You must be signed in to change notification settings - Fork 307
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into 1423-better-pyapp-integration
- Loading branch information
Showing
21 changed files
with
275 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = '1.24.2' | ||
__version__ = '1.25.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
import platform | ||
import re | ||
|
||
__all__ = ['process_macos_plat_tag'] | ||
|
||
|
||
def process_macos_plat_tag(plat: str, /, *, compat: bool) -> str: | ||
""" | ||
Process the macOS platform tag. This will normalize the macOS version to | ||
10.16 if compat=True. If the MACOSX_DEPLOYMENT_TARGET environment variable | ||
is set, then it will be used instead for the target version. If archflags | ||
is set, then the archs will be respected, including a universal build. | ||
""" | ||
# Default to a native build | ||
current_arch = platform.machine() | ||
arm = current_arch == 'arm64' | ||
|
||
# Look for cross-compiles | ||
archflags = os.environ.get('ARCHFLAGS', '') | ||
if archflags and (archs := re.findall(r'-arch (\S+)', archflags)): | ||
new_arch = 'universal2' if set(archs) == {'x86_64', 'arm64'} else archs[0] | ||
arm = archs == ['arm64'] | ||
plat = f'{plat[: plat.rfind(current_arch)]}{new_arch}' | ||
|
||
# Process macOS version | ||
if sdk_match := re.search(r'macosx_(\d+_\d+)', plat): | ||
macos_version = sdk_match.group(1) | ||
target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', None) | ||
|
||
try: | ||
new_version = normalize_macos_version(target or macos_version, arm=arm, compat=compat) | ||
except ValueError: | ||
new_version = normalize_macos_version(macos_version, arm=arm, compat=compat) | ||
|
||
return plat.replace(macos_version, new_version, 1) | ||
|
||
return plat | ||
|
||
|
||
def normalize_macos_version(version: str, *, arm: bool, compat: bool) -> str: | ||
""" | ||
Set minor version to 0 if major is 11+. Enforces 11+ if arm=True. 11+ is | ||
converted to 10.16 if compat=True. Version is always returned in | ||
"major_minor" format. | ||
""" | ||
version = version.replace('.', '_') | ||
if '_' not in version: | ||
version = f'{version}_0' | ||
major, minor = (int(d) for d in version.split('_')[:2]) | ||
major = max(major, 11) if arm else major | ||
minor = 0 if major >= 11 else minor # noqa: PLR2004 | ||
if compat and major >= 11: # noqa: PLR2004 | ||
major = 10 | ||
minor = 16 | ||
return f'{major}_{minor}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
from __future__ import annotations | ||
|
||
import re | ||
import sys | ||
from importlib.metadata import Distribution, DistributionFinder | ||
|
||
from packaging.markers import default_environment | ||
from packaging.requirements import Requirement | ||
|
||
|
||
class DistributionCache: | ||
def __init__(self, sys_path: list[str]) -> None: | ||
self._resolver = Distribution.discover(context=DistributionFinder.Context(path=sys_path)) | ||
self._distributions: dict[str, Distribution] = {} | ||
self._search_exhausted = False | ||
self._canonical_regex = re.compile(r'[-_.]+') | ||
|
||
def __getitem__(self, item: str) -> Distribution | None: | ||
item = self._canonical_regex.sub('-', item).lower() | ||
possible_distribution = self._distributions.get(item) | ||
if possible_distribution is not None: | ||
return possible_distribution | ||
|
||
# Be safe even though the code as-is will never reach this since | ||
# the first unknown distribution will fail fast | ||
if self._search_exhausted: # no cov | ||
return None | ||
|
||
for distribution in self._resolver: | ||
name = distribution.metadata['Name'] | ||
if name is None: | ||
continue | ||
|
||
name = self._canonical_regex.sub('-', name).lower() | ||
self._distributions[name] = distribution | ||
if name == item: | ||
return distribution | ||
|
||
self._search_exhausted = True | ||
|
||
return None | ||
|
||
|
||
def dependency_in_sync( | ||
requirement: Requirement, environment: dict[str, str], installed_distributions: DistributionCache | ||
) -> bool: | ||
if requirement.marker and not requirement.marker.evaluate(environment): | ||
return True | ||
|
||
distribution = installed_distributions[requirement.name] | ||
if distribution is None: | ||
return False | ||
|
||
extras = requirement.extras | ||
if extras: | ||
transitive_requirements: list[str] = distribution.metadata.get_all('Requires-Dist', []) | ||
if not transitive_requirements: | ||
return False | ||
|
||
available_extras: list[str] = distribution.metadata.get_all('Provides-Extra', []) | ||
|
||
for requirement_string in transitive_requirements: | ||
transitive_requirement = Requirement(requirement_string) | ||
if not transitive_requirement.marker: | ||
continue | ||
|
||
for extra in extras: | ||
# FIXME: This may cause a build to never be ready if newer versions do not provide the desired | ||
# extra and it's just a user error/typo. See: https://github.com/pypa/pip/issues/7122 | ||
if extra not in available_extras: | ||
return False | ||
|
||
extra_environment = dict(environment) | ||
extra_environment['extra'] = extra | ||
if not dependency_in_sync(transitive_requirement, extra_environment, installed_distributions): | ||
return False | ||
|
||
if requirement.specifier and not requirement.specifier.contains(distribution.version): | ||
return False | ||
|
||
# TODO: handle https://discuss.python.org/t/11938 | ||
if requirement.url: | ||
direct_url_file = distribution.read_text('direct_url.json') | ||
if direct_url_file is not None: | ||
import json | ||
|
||
# https://packaging.python.org/specifications/direct-url/ | ||
direct_url_data = json.loads(direct_url_file) | ||
if 'vcs_info' in direct_url_data: | ||
url = direct_url_data['url'] | ||
vcs_info = direct_url_data['vcs_info'] | ||
vcs = vcs_info['vcs'] | ||
commit_id = vcs_info['commit_id'] | ||
requested_revision = vcs_info.get('requested_revision') | ||
|
||
# Try a few variations, see https://peps.python.org/pep-0440/#direct-references | ||
if ( | ||
requested_revision and requirement.url == f'{vcs}+{url}@{requested_revision}#{commit_id}' | ||
) or requirement.url == f'{vcs}+{url}@{commit_id}': | ||
return True | ||
|
||
if requirement.url in {f'{vcs}+{url}', f'{vcs}+{url}@{requested_revision}'}: | ||
import subprocess | ||
|
||
if vcs == 'git': | ||
vcs_cmd = [vcs, 'ls-remote', url] | ||
if requested_revision: | ||
vcs_cmd.append(requested_revision) | ||
# TODO: add elifs for hg, svn, and bzr https://github.com/pypa/hatch/issues/760 | ||
else: | ||
return False | ||
result = subprocess.run(vcs_cmd, capture_output=True, text=True) # noqa: PLW1510 | ||
if result.returncode or not result.stdout.strip(): | ||
return False | ||
latest_commit_id, *_ = result.stdout.split() | ||
return commit_id == latest_commit_id | ||
|
||
return False | ||
|
||
return True | ||
|
||
|
||
def dependencies_in_sync( | ||
requirements: list[Requirement], sys_path: list[str] | None = None, environment: dict[str, str] | None = None | ||
) -> bool: | ||
if sys_path is None: | ||
sys_path = sys.path | ||
if environment is None: | ||
environment = default_environment() # type: ignore | ||
|
||
installed_distributions = DistributionCache(sys_path) | ||
return all(dependency_in_sync(requirement, environment, installed_distributions) for requirement in requirements) # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.