python-devkit 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_devkit/lock_python.py +130 -0
- python_devkit/preflight_runner.py +43 -0
- python_devkit-0.1.1.dist-info/METADATA +11 -0
- python_devkit-0.1.1.dist-info/RECORD +10 -0
- python_devkit-0.1.1.dist-info/entry_points.txt +2 -0
- python_devkit/ci_step.py +0 -52
- python_devkit/setup.py +0 -127
- python_devkit/setup_oras.py +0 -51
- python_devkit/third-party/README.md +0 -9
- python_devkit/third-party/dotnet-install.sh +0 -1887
- python_devkit-0.1.0.dist-info/METADATA +0 -10
- python_devkit-0.1.0.dist-info/RECORD +0 -12
- {python_devkit-0.1.0.dist-info → python_devkit-0.1.1.dist-info}/WHEEL +0 -0
- {python_devkit-0.1.0.dist-info → python_devkit-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {python_devkit-0.1.0.dist-info → python_devkit-0.1.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fnmatch import fnmatch
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from tomllib import load
|
|
6
|
+
from typing import Annotated, Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from bashrun import bash, bash_check, bash_output
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LockPythonConfig(BaseModel):
|
|
16
|
+
model_config = ConfigDict(extra="forbid")
|
|
17
|
+
|
|
18
|
+
group_export_dirs: dict[str, Path] = Field(default_factory=dict, alias="group-export-dirs")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WorkspaceConfig(BaseModel):
|
|
22
|
+
members: list[str] = Field(default_factory=list)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProjectConfig(BaseModel):
|
|
26
|
+
name: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PackageConfig(BaseModel):
|
|
30
|
+
project: ProjectConfig
|
|
31
|
+
dependency_groups: dict[str, Any] = Field(default_factory=dict, alias="dependency-groups")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command()
|
|
35
|
+
def lock_python(
|
|
36
|
+
check: Annotated[
|
|
37
|
+
bool, typer.Option("--check", help="Validate lock files without writing; exit non-zero if stale.")
|
|
38
|
+
] = False,
|
|
39
|
+
root: Annotated[Path, typer.Option(help="uv workspace root; defaults to the current directory.")] = Path(),
|
|
40
|
+
) -> None:
|
|
41
|
+
root = root.resolve()
|
|
42
|
+
stale = False
|
|
43
|
+
|
|
44
|
+
if check:
|
|
45
|
+
print("Checking uv.lock...")
|
|
46
|
+
if not bash_check("uv lock --check", cwd=root):
|
|
47
|
+
print(" STALE: uv.lock is out of date. Run 'uv run lock-python' to update.")
|
|
48
|
+
stale = True
|
|
49
|
+
else:
|
|
50
|
+
print(" OK")
|
|
51
|
+
else:
|
|
52
|
+
bash("uv lock", cwd=root)
|
|
53
|
+
|
|
54
|
+
with (root / "pyproject.toml").open("rb") as file:
|
|
55
|
+
workspace_toml = load(file)
|
|
56
|
+
|
|
57
|
+
tool = workspace_toml.get("tool", {})
|
|
58
|
+
workspace = WorkspaceConfig.model_validate(tool.get("uv", {}).get("workspace", {}))
|
|
59
|
+
config = LockPythonConfig.model_validate(tool.get("python-devkit", {}).get("lock-python", {}))
|
|
60
|
+
|
|
61
|
+
seen_redirected: set[str] = set()
|
|
62
|
+
|
|
63
|
+
for member in workspace.members:
|
|
64
|
+
member_dir = root / member
|
|
65
|
+
|
|
66
|
+
if not (member_dir / "Dockerfile").exists():
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
with (member_dir / "pyproject.toml").open("rb") as file:
|
|
70
|
+
package = PackageConfig.model_validate(load(file))
|
|
71
|
+
|
|
72
|
+
stale |= _export_pylock(check, root, member_dir, package.project.name, group=None)
|
|
73
|
+
|
|
74
|
+
for group in package.dependency_groups:
|
|
75
|
+
if group == "dev":
|
|
76
|
+
continue
|
|
77
|
+
redirect = _redirect_dir(group, config, root)
|
|
78
|
+
if redirect is None:
|
|
79
|
+
stale |= _export_pylock(check, root, member_dir, package.project.name, group=group)
|
|
80
|
+
continue
|
|
81
|
+
if group in seen_redirected:
|
|
82
|
+
continue
|
|
83
|
+
seen_redirected.add(group)
|
|
84
|
+
stale |= _export_pylock(check, root, redirect, package.project.name, group=group)
|
|
85
|
+
|
|
86
|
+
if check and stale:
|
|
87
|
+
raise SystemExit(1)
|
|
88
|
+
|
|
89
|
+
if check:
|
|
90
|
+
print("\nAll Python lock files are up to date.")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _export_pylock(check: bool, root: Path, export_dir: Path, package_name: str, group: str | None) -> bool:
|
|
94
|
+
if group:
|
|
95
|
+
pylock = export_dir / f"pylock.{group}.toml"
|
|
96
|
+
group_flags = f"--only-group {group} "
|
|
97
|
+
else:
|
|
98
|
+
pylock = export_dir / "pylock.toml"
|
|
99
|
+
group_flags = "--no-default-groups "
|
|
100
|
+
|
|
101
|
+
export_command = (
|
|
102
|
+
f"uv export --format pylock.toml --no-header --package {package_name} {group_flags}--no-emit-local --frozen "
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if check:
|
|
106
|
+
print(f"Checking {pylock}...")
|
|
107
|
+
exported = _normalize_line_endings(bash_output(export_command, cwd=root))
|
|
108
|
+
committed = _normalize_line_endings(pylock.read_text(encoding="utf-8")) if pylock.exists() else ""
|
|
109
|
+
if exported != committed:
|
|
110
|
+
print(f" STALE: {pylock} is out of date.")
|
|
111
|
+
return True
|
|
112
|
+
print(" OK")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
bash(export_command + f"--output-file {pylock} ", cwd=root)
|
|
116
|
+
text = pylock.read_text(encoding="utf-8")
|
|
117
|
+
with pylock.open("w", encoding="utf-8", newline="\n") as file:
|
|
118
|
+
file.write(text)
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _normalize_line_endings(text: str) -> str:
|
|
123
|
+
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _redirect_dir(group: str, config: LockPythonConfig, root: Path) -> Path | None:
|
|
127
|
+
for pattern, directory in config.group_export_dirs.items():
|
|
128
|
+
if fnmatch(group, pattern):
|
|
129
|
+
return root / directory
|
|
130
|
+
return None
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from bashrun import bash, bash_output
|
|
7
|
+
from ci_devkit.ci_step import ci_step
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CommandCheck(BaseModel):
|
|
12
|
+
label: str
|
|
13
|
+
command: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GeneratedCheck(BaseModel):
|
|
17
|
+
label: str
|
|
18
|
+
generate_command: str
|
|
19
|
+
paths: list[Path]
|
|
20
|
+
fix_command: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run_checks(checks: Sequence[CommandCheck | GeneratedCheck], *, cwd: Path | None = None) -> None:
|
|
24
|
+
for check in checks:
|
|
25
|
+
if isinstance(check, CommandCheck):
|
|
26
|
+
_run_command_check(check, cwd)
|
|
27
|
+
else:
|
|
28
|
+
_run_generated_check(check, cwd)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _run_command_check(check: CommandCheck, cwd: Path | None) -> None:
|
|
32
|
+
with ci_step(check.label):
|
|
33
|
+
bash(check.command, cwd=cwd)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _run_generated_check(check: GeneratedCheck, cwd: Path | None) -> None:
|
|
37
|
+
with ci_step(check.label):
|
|
38
|
+
bash(check.generate_command, cwd=cwd)
|
|
39
|
+
pathspec = " ".join(str(path) for path in check.paths)
|
|
40
|
+
staleness_output = bash_output(f"git status --porcelain -- {pathspec}", cwd=cwd)
|
|
41
|
+
if staleness_output.strip():
|
|
42
|
+
bash(f"git diff -- {pathspec}", cwd=cwd)
|
|
43
|
+
raise SystemExit(f"{check.label} output is stale. Run '{check.fix_command}' locally and commit the result.")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: python-devkit
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python repo-lifecycle tooling: workspace locking, preflight checks, canonical ruff configuration
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
License-File: NOTICE
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Requires-Dist: bashrun>=0.1.0
|
|
9
|
+
Requires-Dist: ci-devkit>=0.1.0
|
|
10
|
+
Requires-Dist: pydantic>=2.11.7
|
|
11
|
+
Requires-Dist: typer>=0.17.4
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
python_devkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
python_devkit/lock_python.py,sha256=szUEkmHjqRbyWb76nn45WBiGRT0kfd6A9ihTO1KQWqE,4277
|
|
3
|
+
python_devkit/preflight_runner.py,sha256=e-UI_ukHjR6NDMhEqmv4jNMApv_lNSg6twIzUo-Qd1M,1322
|
|
4
|
+
python_devkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
python_devkit-0.1.1.dist-info/METADATA,sha256=DIIJ8MbhovJnXwLOcTy85D2GxmyvqNf90NfrDDOHDLo,353
|
|
6
|
+
python_devkit-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
7
|
+
python_devkit-0.1.1.dist-info/entry_points.txt,sha256=OSRLpsw_0BVXkayN9QWWYOFPUxZ4G29i8JtxKBO9gz8,62
|
|
8
|
+
python_devkit-0.1.1.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
|
|
9
|
+
python_devkit-0.1.1.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
|
|
10
|
+
python_devkit-0.1.1.dist-info/RECORD,,
|
python_devkit/ci_step.py
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import time
|
|
4
|
-
from collections.abc import Generator
|
|
5
|
-
from contextlib import contextmanager
|
|
6
|
-
|
|
7
|
-
from pydantic_settings import BaseSettings
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Settings(BaseSettings):
|
|
11
|
-
github_step_summary: str | None = None
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
settings = Settings.model_validate({})
|
|
15
|
-
_summary_initialized = False
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _format_duration(seconds: float) -> str:
|
|
19
|
-
if seconds < 60:
|
|
20
|
-
return f"{seconds:.1f}s"
|
|
21
|
-
minutes = int(seconds // 60)
|
|
22
|
-
remaining = seconds % 60
|
|
23
|
-
return f"{minutes}m {remaining:.0f}s"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _write_summary(label: str, duration: float, *, failed: bool) -> None:
|
|
27
|
-
global _summary_initialized
|
|
28
|
-
summary_path = settings.github_step_summary
|
|
29
|
-
if not summary_path:
|
|
30
|
-
return
|
|
31
|
-
with open(summary_path, "a") as file:
|
|
32
|
-
if not _summary_initialized:
|
|
33
|
-
file.write("| Step | Duration |\n|---|---|\n")
|
|
34
|
-
_summary_initialized = True
|
|
35
|
-
status = " :x:" if failed else ""
|
|
36
|
-
file.write(f"| {label}{status} | {_format_duration(duration)} |\n")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@contextmanager
|
|
40
|
-
def ci_step(label: str) -> Generator[None]:
|
|
41
|
-
print(f"::group::{label}", flush=True)
|
|
42
|
-
start = time.monotonic()
|
|
43
|
-
failed = False
|
|
44
|
-
try:
|
|
45
|
-
yield
|
|
46
|
-
except BaseException:
|
|
47
|
-
failed = True
|
|
48
|
-
raise
|
|
49
|
-
finally:
|
|
50
|
-
duration = time.monotonic() - start
|
|
51
|
-
print("::endgroup::", flush=True)
|
|
52
|
-
_write_summary(label, duration, failed=failed)
|
python_devkit/setup.py
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import platform
|
|
5
|
-
import re
|
|
6
|
-
import shlex
|
|
7
|
-
import shutil
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
from bashrun import bash, bash_no_raise, bash_output
|
|
11
|
-
from pydantic import Field
|
|
12
|
-
from pydantic_settings import BaseSettings
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Settings(BaseSettings):
|
|
16
|
-
github_path: str | None = None
|
|
17
|
-
system_drive: str = Field("C:", validation_alias="SystemDrive")
|
|
18
|
-
agent_tools_directory: str | None = Field(None, validation_alias="AGENT_TOOLSDIRECTORY")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
settings = Settings.model_validate({})
|
|
22
|
-
|
|
23
|
-
CONTAINER_PATHS = ["/to_clean/android", "/to_clean/dotnet", "/to_clean/ghcup", "/to_clean/swift"]
|
|
24
|
-
|
|
25
|
-
BARE_LINUX_PATHS = ["/usr/local/lib/android", "/usr/share/dotnet", "/usr/local/.ghcup", "/usr/share/swift", "/opt/ghc"]
|
|
26
|
-
|
|
27
|
-
LARGE_PACKAGE_PATTERNS = [
|
|
28
|
-
"^aspnetcore-.*",
|
|
29
|
-
"^dotnet-.*",
|
|
30
|
-
"^llvm-.*",
|
|
31
|
-
"php.*",
|
|
32
|
-
"^mongodb-.*",
|
|
33
|
-
"^mysql-.*",
|
|
34
|
-
"azure-cli",
|
|
35
|
-
"google-chrome-stable",
|
|
36
|
-
"firefox",
|
|
37
|
-
"powershell",
|
|
38
|
-
"mono-devel",
|
|
39
|
-
"libgl1-mesa-dri",
|
|
40
|
-
"google-cloud-sdk",
|
|
41
|
-
"google-cloud-cli",
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _remove_paths(paths: list[str], sudo: bool = False) -> None:
|
|
46
|
-
existing = [path for path in paths if Path(path).exists()]
|
|
47
|
-
if not existing:
|
|
48
|
-
return
|
|
49
|
-
if sudo:
|
|
50
|
-
bash_no_raise(f"sudo rm -rf {shlex.join(existing)}")
|
|
51
|
-
elif platform.system() == "Windows":
|
|
52
|
-
for path in existing:
|
|
53
|
-
shutil.rmtree(path, ignore_errors=True)
|
|
54
|
-
else:
|
|
55
|
-
bash_no_raise(f"rm -rf {shlex.join(existing)}")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# actions/checkout sets safe.directory in a temporary HOME that's cleaned up
|
|
59
|
-
# after the step finishes (actions/checkout#766). Container jobs that run
|
|
60
|
-
# git later need it re-set in the real HOME.
|
|
61
|
-
def configure_git(workspace: str) -> None:
|
|
62
|
-
bash(f"git config --global --add safe.directory {shlex.quote(workspace)}")
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def free_disk_space(*, large_packages: bool = False, docker_images: bool = False, swap_storage: bool = False) -> None:
|
|
66
|
-
system = platform.system()
|
|
67
|
-
in_container = Path("/to_clean").is_dir()
|
|
68
|
-
|
|
69
|
-
if system == "Windows":
|
|
70
|
-
paths = [os.path.join(settings.system_drive, "Program Files", "dotnet")]
|
|
71
|
-
if settings.agent_tools_directory:
|
|
72
|
-
paths.append(settings.agent_tools_directory)
|
|
73
|
-
print(f"Removing: {', '.join(paths)}")
|
|
74
|
-
_remove_paths(paths)
|
|
75
|
-
elif in_container:
|
|
76
|
-
print("Removing pre-installed toolchains (container)")
|
|
77
|
-
_remove_paths(CONTAINER_PATHS)
|
|
78
|
-
else:
|
|
79
|
-
print("Removing pre-installed toolchains")
|
|
80
|
-
_remove_paths(BARE_LINUX_PATHS, sudo=True)
|
|
81
|
-
|
|
82
|
-
if large_packages:
|
|
83
|
-
print("Removing large apt packages")
|
|
84
|
-
bash_no_raise(f"sudo apt-get remove -y --fix-missing {shlex.join(LARGE_PACKAGE_PATTERNS)}")
|
|
85
|
-
bash_no_raise("sudo apt-get autoremove -y")
|
|
86
|
-
bash_no_raise("sudo apt-get clean")
|
|
87
|
-
|
|
88
|
-
if docker_images:
|
|
89
|
-
print("Pruning Docker images")
|
|
90
|
-
bash_no_raise("sudo docker image prune --all --force")
|
|
91
|
-
|
|
92
|
-
if swap_storage:
|
|
93
|
-
print("Removing swap")
|
|
94
|
-
bash_no_raise("sudo swapoff -a")
|
|
95
|
-
bash_no_raise("sudo rm -f /mnt/swapfile")
|
|
96
|
-
|
|
97
|
-
bash("df -h")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def install_dotnet(channel: str) -> None:
|
|
101
|
-
print(f"Installing .NET SDK {channel}")
|
|
102
|
-
script = Path(__file__).parent / "third-party" / "dotnet-install.sh"
|
|
103
|
-
bash(f"bash {script} --channel {channel}")
|
|
104
|
-
dotnet_path = str(Path.home() / ".dotnet")
|
|
105
|
-
os.environ["PATH"] = f"{dotnet_path}{os.pathsep}{os.environ['PATH']}"
|
|
106
|
-
if settings.github_path:
|
|
107
|
-
with open(settings.github_path, "a") as file:
|
|
108
|
-
file.write(f"{dotnet_path}\n")
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def install_node(version: str, registry_url: str | None = None) -> None:
|
|
112
|
-
system = platform.system()
|
|
113
|
-
sudo = system == "Linux" and os.geteuid() != 0
|
|
114
|
-
|
|
115
|
-
print(f"Installing Node.js {version}")
|
|
116
|
-
shasums = bash_output(f"curl -fsSL https://nodejs.org/dist/latest-v{version}.x/SHASUMS256.txt")
|
|
117
|
-
match = re.search(r"(node-v[\d.]+-linux-x64\.tar\.xz)", shasums)
|
|
118
|
-
if not match:
|
|
119
|
-
raise SystemExit(f"Could not find Node.js v{version} linux-x64 binary")
|
|
120
|
-
filename = match.group(1)
|
|
121
|
-
prefix = "sudo " if sudo else ""
|
|
122
|
-
bash(f"curl -fsSLO https://nodejs.org/dist/latest-v{version}.x/{filename}")
|
|
123
|
-
bash(f"{prefix}rm -rf /usr/local/lib/node_modules/npm")
|
|
124
|
-
bash(f"{prefix}tar -xJf {filename} -C /usr/local --strip-components=1")
|
|
125
|
-
Path(filename).unlink()
|
|
126
|
-
if registry_url:
|
|
127
|
-
(Path.home() / ".npmrc").write_text(f"registry={registry_url}\n")
|
python_devkit/setup_oras.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import platform
|
|
5
|
-
import shlex
|
|
6
|
-
import shutil
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from bashrun import bash
|
|
10
|
-
from pydantic_settings import BaseSettings
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Settings(BaseSettings):
|
|
14
|
-
github_token: str
|
|
15
|
-
github_actor: str = ""
|
|
16
|
-
github_path: str | None = None
|
|
17
|
-
runner_temp: str = "."
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def install_oras(version: str = "1.2.2") -> None:
|
|
21
|
-
settings = Settings.model_validate({})
|
|
22
|
-
system = platform.system()
|
|
23
|
-
sudo = system == "Linux" and os.geteuid() != 0
|
|
24
|
-
prefix = "sudo " if sudo else ""
|
|
25
|
-
|
|
26
|
-
if system == "Linux":
|
|
27
|
-
bash(f"{prefix}apt-get update -qq")
|
|
28
|
-
bash(f"{prefix}apt-get install -y -qq zstd")
|
|
29
|
-
elif system == "Windows":
|
|
30
|
-
bash("choco install zstandard -y --no-progress")
|
|
31
|
-
|
|
32
|
-
if system == "Linux":
|
|
33
|
-
archive = f"oras_{version}_linux_amd64.tar.gz"
|
|
34
|
-
bash(f"curl -fsSLO https://github.com/oras-project/oras/releases/download/v{version}/{archive}")
|
|
35
|
-
bash(f"{prefix}tar -xzf {archive} -C /usr/local/bin/ oras")
|
|
36
|
-
Path(archive).unlink()
|
|
37
|
-
elif system == "Windows":
|
|
38
|
-
archive = f"oras_{version}_windows_amd64.zip"
|
|
39
|
-
oras_directory = Path(settings.runner_temp) / "oras"
|
|
40
|
-
bash(f"curl -fsSLO https://github.com/oras-project/oras/releases/download/v{version}/{archive}")
|
|
41
|
-
shutil.unpack_archive(archive, oras_directory)
|
|
42
|
-
Path(archive).unlink()
|
|
43
|
-
os.environ["PATH"] = f"{oras_directory}{os.pathsep}{os.environ['PATH']}"
|
|
44
|
-
if settings.github_path:
|
|
45
|
-
with open(settings.github_path, "a") as file:
|
|
46
|
-
file.write(f"{oras_directory}\n")
|
|
47
|
-
|
|
48
|
-
bash(
|
|
49
|
-
f"oras login ghcr.io --username {shlex.quote(settings.github_actor)} --password-stdin",
|
|
50
|
-
stdin_text=settings.github_token,
|
|
51
|
-
)
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Third-party vendored files
|
|
2
|
-
|
|
3
|
-
## dotnet-install.sh
|
|
4
|
-
|
|
5
|
-
- **Source**: https://dot.net/v1/dotnet-install.sh
|
|
6
|
-
- **Upstream repo**: https://github.com/dotnet/install-scripts
|
|
7
|
-
- **License**: MIT (see header in script)
|
|
8
|
-
- **Vendored**: 2026-03-16
|
|
9
|
-
- **Why**: The `actions/setup-dotnet` GitHub Action bundles this script locally to avoid downloading it at runtime. We vendor it for the same reason — downloading it via curl inside CI containers is unreliable (timeouts).
|