unity-devkit 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/.github/workflows/release.yml +1 -1
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/AGENTS.md +4 -6
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/PKG-INFO +2 -1
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/pyproject.toml +3 -2
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/build_unity.py +5 -5
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/license.py +3 -3
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/license_restore.py +1 -1
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/uv.lock +16 -0
- unity_devkit-0.1.0/src/unity_devkit/cache.py +0 -89
- unity_devkit-0.1.0/src/unity_devkit/ci_step.py +0 -52
- unity_devkit-0.1.0/src/unity_devkit/git_tags.py +0 -24
- unity_devkit-0.1.0/src/unity_devkit/setup.py +0 -127
- unity_devkit-0.1.0/src/unity_devkit/setup_oras.py +0 -51
- unity_devkit-0.1.0/src/unity_devkit/third-party/README.md +0 -9
- unity_devkit-0.1.0/src/unity_devkit/third-party/dotnet-install.sh +0 -1887
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/.github/workflows/ci.yml +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/.gitignore +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/CLAUDE.md +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/LICENSE +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/NOTICE +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/README.md +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/publish-config.json +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/ruff.toml +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/__init__.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/compile_unity.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/install.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/lock_unity.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/matrix.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/projects.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/py.typed +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/test_unity.py +0 -0
- {unity_devkit-0.1.0 → unity_devkit-0.1.2}/src/unity_devkit/unity.py +0 -0
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
`unity-devkit` is the Unity build toolkit: project discovery, local builds, CI builds, license activation, the `install` command (download-or-build then install onto a device), and the ORAS cache/setup helpers those need. Consumer repositories install this package from PyPI and get the same Unity CI and install flow — paired with a reusable `unity-build.yml` GitHub Actions workflow (housed in consumer repos today) that invokes only this package's entry points.
|
|
6
6
|
|
|
7
|
-
The repo and package renamed from `unity-
|
|
7
|
+
The repo and package renamed from `unity-buildkit` to `unity-devkit` (2026-09-21, member of the `-devkit` family; before that `placeframe-unity` → `unity-buildkit` on extraction). The PyPI identity is fresh: `unity-devkit` starts its own tag ledger at `0.1.0`; the terminal `unity-buildkit` distributions (≤0.1.1) are deprecation signposts pointing here, not this package's history.
|
|
8
8
|
|
|
9
9
|
## Release flow
|
|
10
10
|
|
|
11
|
-
Publishing rides `release.yml`, triggered by a successful CI run on a `main` push: the machinery — release-
|
|
11
|
+
Publishing rides `release.yml`, triggered by a successful CI run on a `main` push: the machinery — release-devkit, invoked uvx-isolated, never a project dependency (unity-devkit sits inside its own dependency graph; a project-level release-devkit edge is a resolver cycle) — computes the plan from the tag ledger and path-diff, patches the version ephemerally, and publishes to PyPI under OIDC trusted publishing (publisher bound to `release.yml`, no environment). The committed `pyproject.toml` version is permanently the `0.0.0.dev0` sentinel; the `unity-devkit-v*` tags are the version ledger (first release `0.1.0`, patch-auto thereafter). API-breaking changes ship with a manually bumped version — patch-auto assumes additive changes.
|
|
12
12
|
|
|
13
13
|
## Shape
|
|
14
14
|
|
|
@@ -25,7 +25,7 @@ One flat module per concern under `src/unity_devkit/`:
|
|
|
25
25
|
| `unity-matrix` | `matrix.py` | Emit the CI build matrix (CI-only). |
|
|
26
26
|
| `build-unity` | `build_unity.py` | CI build with library cache restore/save and version stamping (CI-only; assumes `GITHUB_WORKSPACE`, OCI registry, runner environment). |
|
|
27
27
|
|
|
28
|
-
Supporting modules: `projects.py` (manifest schema + discovery), `unity.py` (editor lookup, platform configs, batchmode command, shared Unity runner)
|
|
28
|
+
Supporting modules: `projects.py` (manifest schema + discovery), `unity.py` (editor lookup, platform configs, batchmode command, shared Unity runner). The CI-floor modules (step wrapper, runner provisioning, ORAS artifact cache, git-tag helpers) live in [`ci-devkit`](https://github.com/outernet-foundation/ci-devkit) (a runtime dependency); unity-devkit owns only Unity concerns.
|
|
29
29
|
|
|
30
30
|
## Constraints
|
|
31
31
|
|
|
@@ -39,12 +39,10 @@ Supporting modules: `projects.py` (manifest schema + discovery), `unity.py` (edi
|
|
|
39
39
|
|
|
40
40
|
**Entry-point names are the workflow contract.** Consumer `unity-build.yml` workflows invoke `unity-matrix`, `unity-license-tag`, `activate-unity-license`, and `build-unity` by name via `uv run`. Consumers pin the workflow by ref and this package by git revision independently; the two pins stay compatible as long as the entry-point names and flags hold, so treat those as a public API.
|
|
41
41
|
|
|
42
|
-
**The environment-lookup helpers are cross-repo Python API.** `unity.find_editor_for_version` (editor ladder: `unity-editor` on PATH → `/opt/unity/<version>` → `~/Unity/Hub/Editor/<version>`; raises `ValueError` listing every searched path on a miss), `unity.editor_version` (`ProjectVersion.txt` parse; `None` for a missing file or unparseable content), and `projects.directories_containing` (+ `PRUNE_DIRECTORIES`) are imported by
|
|
42
|
+
**The environment-lookup helpers are cross-repo Python API.** `unity.find_editor_for_version` (editor ladder: `unity-editor` on PATH → `/opt/unity/<version>` → `~/Unity/Hub/Editor/<version>`; raises `ValueError` listing every searched path on a miss), `unity.editor_version` (`ProjectVersion.txt` parse; `None` for a missing file or unparseable content), and `projects.directories_containing` (+ `PRUNE_DIRECTORIES`) are imported by registry consumers — prepo's `sync` verb. Their signatures and miss behavior are contract, not internals. The `SystemExit`-raising wrappers (`find_unity_editor`, `read_editor_version`) exist for unity-devkit's own CLI paths and delegate to the core helpers.
|
|
43
43
|
|
|
44
44
|
**Version stamping is opt-in via `tag_prefix`.** `build-unity` writes `.build-version.json` (consumed by the project's build `executeMethod`) only when the project's manifest declares `tag_prefix`; the version is the latest `<tag_prefix>-v*` git tag plus a run-number suffix. Projects without the field build unversioned. Consumer repos that release keep their own name→prefix map on their side — the two must agree for projects that release.
|
|
45
45
|
|
|
46
|
-
**`dotnet-install.sh` is vendored, not downloaded.** Downloading it via curl inside CI containers is unreliable (timeouts) — the same reason `actions/setup-dotnet` bundles it. It ships as package data inside `src/unity_devkit/third-party/` and is resolved `__file__`-relative, which works in editable installs, wheels, and git checkouts alike.
|
|
47
|
-
|
|
48
46
|
**The ORAS cache media type identifies the wrapper package, not the consuming repo.** `cache.save()` pushes with `application/vnd.unity-devkit.cache.v1+zstd`. Restore doesn't filter on media type, so older manifests carrying a different vendor prefix still pull, but new pushes always carry this one — the identifier tracks the tool that wrote the cache, not the project whose bytes are inside it.
|
|
49
47
|
|
|
50
48
|
## See also
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unity-devkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Unity project discovery, build, license, and CI tooling
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
License-File: NOTICE
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
8
|
Requires-Dist: bashrun>=0.1.0
|
|
9
|
+
Requires-Dist: ci-devkit>=0.1.0
|
|
9
10
|
Requires-Dist: pydantic-settings>=2.9.1
|
|
10
11
|
Requires-Dist: pydantic>=2.11.7
|
|
11
12
|
Requires-Dist: typer>=0.17.4
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unity-devkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "Unity project discovery, build, license, and CI tooling"
|
|
5
5
|
requires-python = ">=3.13"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"bashrun>=0.1.0",
|
|
8
|
+
"ci-devkit>=0.1.0",
|
|
8
9
|
"typer>=0.17.4",
|
|
9
10
|
"pydantic>=2.11.7",
|
|
10
11
|
"pydantic-settings>=2.9.1",
|
|
@@ -29,7 +30,7 @@ build-backend = "hatchling.build"
|
|
|
29
30
|
|
|
30
31
|
[tool.hatch.build.targets.wheel]
|
|
31
32
|
packages = ["src/unity_devkit"]
|
|
32
|
-
include = ["src/unity_devkit/py.typed"
|
|
33
|
+
include = ["src/unity_devkit/py.typed"]
|
|
33
34
|
|
|
34
35
|
[tool.uv.sources]
|
|
35
36
|
|
|
@@ -8,13 +8,13 @@ from pathlib import Path
|
|
|
8
8
|
import typer
|
|
9
9
|
from pydantic_settings import BaseSettings
|
|
10
10
|
|
|
11
|
-
from .cache import restore, save
|
|
12
|
-
from .ci_step import ci_step
|
|
11
|
+
from ci_devkit.cache import restore, save
|
|
12
|
+
from ci_devkit.ci_step import ci_step
|
|
13
13
|
from .license_restore import restore_license
|
|
14
|
-
from .setup import configure_git, install_dotnet
|
|
15
|
-
from .setup_oras import install_oras
|
|
14
|
+
from ci_devkit.setup import configure_git, install_dotnet
|
|
15
|
+
from ci_devkit.setup_oras import install_oras
|
|
16
16
|
from .unity import prepare_unity_project, resolve_unity_build, run_unity_batchmode
|
|
17
|
-
from .git_tags import get_latest_tag_version
|
|
17
|
+
from ci_devkit.git_tags import get_latest_tag_version
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class Settings(BaseSettings):
|
|
@@ -6,10 +6,10 @@ import typer
|
|
|
6
6
|
from bashrun import bash
|
|
7
7
|
from pydantic_settings import BaseSettings
|
|
8
8
|
|
|
9
|
-
from .cache import restore, save
|
|
9
|
+
from ci_devkit.cache import restore, save
|
|
10
10
|
from .license_restore import license_cache_tag
|
|
11
|
-
from .setup import configure_git
|
|
12
|
-
from .setup_oras import install_oras
|
|
11
|
+
from ci_devkit.setup import configure_git
|
|
12
|
+
from ci_devkit.setup_oras import install_oras
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Settings(BaseSettings):
|
|
@@ -41,6 +41,20 @@ wheels = [
|
|
|
41
41
|
{ url = "https://files.pythonhosted.org/packages/be/9a/749dd28f3defde3690e0280131f9461e4100c43c117f9363798f6dedbb91/bashrun-0.1.0-py3-none-any.whl", hash = "sha256:80f87924f88336460890d669aa1abb6fb8c389b675fdb41ac8d2b6861d4ebddb", size = 7634, upload-time = "2026-09-21T01:47:56.233Z" },
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "ci-devkit"
|
|
46
|
+
version = "0.1.0"
|
|
47
|
+
source = { registry = "https://pypi.org/simple" }
|
|
48
|
+
dependencies = [
|
|
49
|
+
{ name = "bashrun" },
|
|
50
|
+
{ name = "pydantic" },
|
|
51
|
+
{ name = "pydantic-settings" },
|
|
52
|
+
]
|
|
53
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9c/58/e0c1f17fbcf6e7adcc4192d5d0a4300be2b0d0ffd055ff746f0116ad587a/ci_devkit-0.1.0.tar.gz", hash = "sha256:ac1a463effe9a2760ea6d18301d6938c6a47d07cee9d8e14bae0c2d20ab0f377", size = 38920, upload-time = "2026-09-21T22:54:14.99Z" }
|
|
54
|
+
wheels = [
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/c3/b3/ee5474174a4d8e9626eb0cbac234ea99908ed3988cc8b807fc8f4a2680a6/ci_devkit-0.1.0-py3-none-any.whl", hash = "sha256:3df3faf0821ffc1c32403f0af5b6b1ceb312d4912cae5b58aaf97b20c251874d", size = 26289, upload-time = "2026-09-21T22:54:13.759Z" },
|
|
56
|
+
]
|
|
57
|
+
|
|
44
58
|
[[package]]
|
|
45
59
|
name = "colorama"
|
|
46
60
|
version = "0.4.6"
|
|
@@ -279,6 +293,7 @@ version = "0.0.0.dev0"
|
|
|
279
293
|
source = { editable = "." }
|
|
280
294
|
dependencies = [
|
|
281
295
|
{ name = "bashrun" },
|
|
296
|
+
{ name = "ci-devkit" },
|
|
282
297
|
{ name = "pydantic" },
|
|
283
298
|
{ name = "pydantic-settings" },
|
|
284
299
|
{ name = "typer" },
|
|
@@ -293,6 +308,7 @@ dev = [
|
|
|
293
308
|
[package.metadata]
|
|
294
309
|
requires-dist = [
|
|
295
310
|
{ name = "bashrun", specifier = ">=0.1.0" },
|
|
311
|
+
{ name = "ci-devkit", specifier = ">=0.1.0" },
|
|
296
312
|
{ name = "pydantic", specifier = ">=2.11.7" },
|
|
297
313
|
{ name = "pydantic-settings", specifier = ">=2.9.1" },
|
|
298
314
|
{ name = "typer", specifier = ">=0.17.4" },
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import platform
|
|
4
|
-
import shutil
|
|
5
|
-
import sys
|
|
6
|
-
import tempfile
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from subprocess import CalledProcessError
|
|
9
|
-
|
|
10
|
-
from bashrun import bash, bash_check, bash_pipe
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _posix(path: Path) -> str:
|
|
14
|
-
return path.as_posix() if platform.system() == "Windows" else str(path)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def restore(
|
|
18
|
-
registry: str,
|
|
19
|
-
name: str,
|
|
20
|
-
tag: str,
|
|
21
|
-
target_directory: Path,
|
|
22
|
-
*,
|
|
23
|
-
required: bool = False,
|
|
24
|
-
fallback_tags: list[str] | None = None,
|
|
25
|
-
) -> bool:
|
|
26
|
-
# OCI repository names must be lowercase; GitHub repository names preserve case
|
|
27
|
-
registry = registry.lower()
|
|
28
|
-
staging = Path(tempfile.gettempdir()) / "cache"
|
|
29
|
-
|
|
30
|
-
for candidate_tag in [tag, *(fallback_tags or [])]:
|
|
31
|
-
if candidate_tag != tag:
|
|
32
|
-
print(f"Falling back to {name}:{candidate_tag}")
|
|
33
|
-
|
|
34
|
-
reference = f"{registry}/{name}:{candidate_tag}"
|
|
35
|
-
staging.mkdir(parents=True, exist_ok=True)
|
|
36
|
-
|
|
37
|
-
if not bash_check(f"oras pull {reference} -o {staging}"):
|
|
38
|
-
shutil.rmtree(staging, ignore_errors=True)
|
|
39
|
-
continue
|
|
40
|
-
|
|
41
|
-
archive = staging / f"{name}.tar.zst"
|
|
42
|
-
try:
|
|
43
|
-
if platform.system() == "Windows":
|
|
44
|
-
bash_pipe(f"zstd -d {_posix(archive)} --stdout", f"tar -xf - -C {_posix(target_directory)}")
|
|
45
|
-
else:
|
|
46
|
-
bash(f"tar -xf {archive} -C {target_directory}")
|
|
47
|
-
except CalledProcessError:
|
|
48
|
-
print(f"WARNING: Cache extraction failed for {name}:{candidate_tag}, trying next")
|
|
49
|
-
shutil.rmtree(staging, ignore_errors=True)
|
|
50
|
-
continue
|
|
51
|
-
|
|
52
|
-
shutil.rmtree(staging, ignore_errors=True)
|
|
53
|
-
print(f"Cache hit: {name}:{candidate_tag}")
|
|
54
|
-
return True
|
|
55
|
-
|
|
56
|
-
if required:
|
|
57
|
-
print(f"FATAL: Required cache missing: {registry}/{name}:{tag}")
|
|
58
|
-
sys.exit(1)
|
|
59
|
-
|
|
60
|
-
print(f"Cache miss: {name}")
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def save(registry: str, name: str, tag: str, source_directory: Path, paths: list[str]) -> None:
|
|
65
|
-
# OCI repository names must be lowercase; GitHub repository names preserve case
|
|
66
|
-
registry = registry.lower()
|
|
67
|
-
resolved: list[str] = []
|
|
68
|
-
for pattern in paths:
|
|
69
|
-
if any(character in pattern for character in "*?["):
|
|
70
|
-
matches = sorted(source_directory.glob(pattern))
|
|
71
|
-
resolved.extend(str(match.relative_to(source_directory)) for match in matches)
|
|
72
|
-
else:
|
|
73
|
-
resolved.append(pattern)
|
|
74
|
-
|
|
75
|
-
staging = Path(tempfile.gettempdir()) / "cache"
|
|
76
|
-
staging.mkdir(parents=True, exist_ok=True)
|
|
77
|
-
archive_name = f"{name}.tar.zst"
|
|
78
|
-
archive_path = staging / archive_name
|
|
79
|
-
|
|
80
|
-
joined = " ".join(resolved)
|
|
81
|
-
if platform.system() == "Windows":
|
|
82
|
-
bash_pipe(f"tar -cf - {joined}", f"zstd -o {_posix(archive_path)}", cwd=source_directory)
|
|
83
|
-
else:
|
|
84
|
-
bash(f"tar --zstd -cf {archive_path} {joined}", cwd=source_directory)
|
|
85
|
-
|
|
86
|
-
reference = f"{registry}/{name}:{tag}"
|
|
87
|
-
bash(f"oras push {reference} {archive_name}:application/vnd.unity-devkit.cache.v1+zstd", cwd=staging)
|
|
88
|
-
archive_path.unlink()
|
|
89
|
-
print(f"Saved cache: {reference}")
|
|
@@ -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)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
from bashrun import bash, bash_check, bash_output
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def get_latest_tag_version(prefix: str) -> str | None:
|
|
9
|
-
output = bash_output(f'git tag --list "{prefix}*" --sort=-v:refname').strip()
|
|
10
|
-
if not output:
|
|
11
|
-
return None
|
|
12
|
-
latest_tag = output.splitlines()[0]
|
|
13
|
-
return latest_tag[len(prefix) :]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def has_changes_since_tag(tag: str | None, path: Path) -> bool:
|
|
17
|
-
if tag is None:
|
|
18
|
-
return True
|
|
19
|
-
return not bash_check(f"git diff --quiet {tag} HEAD -- {path}")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def create_and_push_tag(tag: str) -> None:
|
|
23
|
-
bash(f"git tag {tag}")
|
|
24
|
-
bash(f"git push origin {tag}")
|
|
@@ -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")
|
|
@@ -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).
|