-
-
Notifications
You must be signed in to change notification settings - Fork 306
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
34 changed files
with
1,552 additions
and
47 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from __future__ import annotations | ||
|
||
import re | ||
from ast import literal_eval | ||
from collections import defaultdict | ||
|
||
import httpx | ||
from utils import ROOT | ||
|
||
URL = 'https://raw.githubusercontent.com/ofek/pyapp/master/build.rs' | ||
OUTPUT_FILE = ROOT / 'src' / 'hatch' / 'python' / 'distributions.py' | ||
ARCHES = {('linux', 'x86'): 'i686', ('windows', 'x86_64'): 'amd64', ('windows', 'x86'): 'i386'} | ||
|
||
|
||
def parse_distributions(contents: str, constant: str): | ||
match = re.search(f'^const {constant}.+?^];$', contents, flags=re.DOTALL | re.MULTILINE) | ||
if not match: | ||
message = f'Could not find {constant} in {URL}' | ||
raise ValueError(message) | ||
|
||
block = match.group(0).replace('",\n', '",') | ||
for line in block.splitlines()[1:-1]: | ||
line = line.strip() | ||
if not line or line.startswith('//'): | ||
continue | ||
|
||
identifier, *data, source = literal_eval(line[:-1]) | ||
os, arch = data[:2] | ||
if arch == 'powerpc64': | ||
arch = 'ppc64le' | ||
|
||
data[1] = ARCHES.get((os, arch), arch) | ||
yield identifier, tuple(data), source | ||
|
||
|
||
def main(): | ||
response = httpx.get(URL) | ||
response.raise_for_status() | ||
|
||
contents = response.text | ||
distributions = defaultdict(list) | ||
ordering_data = defaultdict(dict) | ||
|
||
for i, distribution_type in enumerate(('DEFAULT_CPYTHON_DISTRIBUTIONS', 'DEFAULT_PYPY_DISTRIBUTIONS')): | ||
for identifier, data, source in parse_distributions(contents, distribution_type): | ||
ordering_data[i][identifier] = None | ||
distributions[identifier].append((data, source)) | ||
|
||
ordered = [identifier for identifiers in ordering_data.values() for identifier in reversed(identifiers)] | ||
output = [ | ||
'from __future__ import annotations', | ||
'', | ||
'# fmt: off', | ||
'ORDERED_DISTRIBUTIONS: tuple[str, ...] = (', | ||
] | ||
for identifier in ordered: | ||
output.append(f' {identifier!r},') | ||
output.append(')') | ||
|
||
output.append('DISTRIBUTIONS: dict[str, dict[tuple[str, ...], str]] = {') | ||
for identifier, data in distributions.items(): | ||
output.append(f' {identifier!r}: {{') | ||
|
||
for d, source in data: | ||
output.append(f' {d!r}:') | ||
output.append(f' {source!r},') | ||
|
||
output.append(' },') | ||
|
||
output.append('}') | ||
output.append('') | ||
output = '\n'.join(output) | ||
|
||
with open(OUTPUT_FILE, 'w') as f: | ||
f.write(output) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import click | ||
|
||
from hatch.cli.python.install import install | ||
from hatch.cli.python.remove import remove | ||
from hatch.cli.python.show import show | ||
from hatch.cli.python.update import update | ||
|
||
|
||
@click.group(short_help='Manage Python installations') | ||
def python(): | ||
pass | ||
|
||
|
||
python.add_command(install) | ||
python.add_command(remove) | ||
python.add_command(show) | ||
python.add_command(update) |
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,76 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
import click | ||
|
||
from hatch.cli.python.utils import DISTRIBUTION_SELECTION | ||
from hatch.python.distributions import ORDERED_DISTRIBUTIONS | ||
|
||
if TYPE_CHECKING: | ||
from hatch.cli.application import Application | ||
|
||
|
||
def ensure_path_public(path: str, shells: list[str]) -> None: | ||
import userpath | ||
|
||
if userpath.in_current_path(path) or userpath.in_new_path(path, shells): | ||
return | ||
|
||
userpath.append(path, shells=shells) | ||
|
||
|
||
@click.command(short_help='Install Python distributions') | ||
@click.argument('names', type=DISTRIBUTION_SELECTION, required=True, nargs=-1) | ||
@click.option('--private', is_flag=True, help='Do not add distributions to the user PATH') | ||
@click.option('--update', '-u', is_flag=True, help='Update existing installations') | ||
@click.option( | ||
'--dir', '-d', 'directory', help='The directory in which to install distributions, overriding configuration' | ||
) | ||
@click.pass_obj | ||
def install(app: Application, *, names: tuple[str, ...], private: bool, update: bool, directory: str | None): | ||
"""Install Python distributions.""" | ||
from hatch.errors import PythonDistributionResolutionError | ||
from hatch.python.resolve import get_distribution | ||
|
||
shells = [] | ||
if not private and not app.platform.windows: | ||
shell_name, _ = app.shell_data | ||
shells.append(shell_name) | ||
|
||
manager = app.get_python_manager(directory) | ||
installed = manager.get_installed() | ||
selection = ORDERED_DISTRIBUTIONS if 'all' in names else names | ||
compatible = [] | ||
incompatible = [] | ||
for name in selection: | ||
if name in installed: | ||
compatible.append(name) | ||
continue | ||
|
||
try: | ||
get_distribution(name) | ||
except PythonDistributionResolutionError: | ||
incompatible.append(name) | ||
else: | ||
compatible.append(name) | ||
|
||
if incompatible and 'all' not in names: | ||
app.abort(f'Incompatible distributions: {", ".join(incompatible)}') | ||
|
||
for name in compatible: | ||
needs_update = False | ||
if name in installed: | ||
needs_update = installed[name].needs_update() | ||
if not needs_update: | ||
app.display_warning(f'The latest version is already installed: {name}') | ||
continue | ||
elif not (update or app.confirm(f'Update {name}?')): | ||
app.abort(f'Distribution is already installed: {name}') | ||
|
||
with app.status(f'{"Updating" if needs_update else "Installing"} {name}'): | ||
dist = manager.install(name) | ||
if not private: | ||
ensure_path_public(str(dist.python_path.parent), shells=shells) | ||
|
||
app.display_success(f'{"Updated" if needs_update else "Installed"} {name} @ {dist.path}') |
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,29 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
import click | ||
|
||
from hatch.cli.python.utils import DISTRIBUTION_SELECTION | ||
|
||
if TYPE_CHECKING: | ||
from hatch.cli.application import Application | ||
|
||
|
||
@click.command(short_help='Remove Python distributions') | ||
@click.argument('names', type=DISTRIBUTION_SELECTION, required=True, nargs=-1) | ||
@click.option('--dir', '-d', 'directory', help='The directory in which distributions reside') | ||
@click.pass_obj | ||
def remove(app: Application, *, names: tuple[str, ...], directory: str | None): | ||
"""Remove Python distributions.""" | ||
manager = app.get_python_manager(directory) | ||
installed = manager.get_installed() | ||
selection = tuple(installed) if 'all' in names else names | ||
for name in selection: | ||
if name not in installed: | ||
app.display_warning(f'Distribution is not installed: {name}') | ||
continue | ||
|
||
dist = installed[name] | ||
with app.status(f'Removing {name}'): | ||
manager.remove(dist) |
Oops, something went wrong.