unity-devkit 0.1.0__py3-none-any.whl → 0.1.2__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.
@@ -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):
unity_devkit/license.py CHANGED
@@ -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):
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  import typer
8
8
  from pydantic_settings import BaseSettings
9
9
 
10
- from .cache import restore
10
+ from ci_devkit.cache import restore
11
11
 
12
12
 
13
13
  # LICENSE_CACHE_TAG env pins the tag for a whole CI run; without it, each call
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unity-devkit
3
- Version: 0.1.0
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
@@ -0,0 +1,18 @@
1
+ unity_devkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ unity_devkit/build_unity.py,sha256=qrgZ0NqGrq2sZZU_5K_bHxBIw_4eXEixtQwh8QuewVU,4085
3
+ unity_devkit/compile_unity.py,sha256=ktcZIdjUIODyJbXG4iHbRNmiIkY4bwF4jujnT-oIUBs,1789
4
+ unity_devkit/install.py,sha256=1pgtRo3AMOJf-Zpe--AocXr_QkONrf4iSd6WgWXkE0A,6988
5
+ unity_devkit/license.py,sha256=O4d66mWLcmxsHXsiv3coUWlu38-ZAJaDsAsmARY9G1Y,1544
6
+ unity_devkit/license_restore.py,sha256=M-YTBcSLIw9WM2ksQI5_mSqCcoKD5oYpnlhVnRuK1Aw,1077
7
+ unity_devkit/lock_unity.py,sha256=9UNMTj5ekFqqyXpRZ4fSru6SeyTda23jqtJ48rb0HOs,1009
8
+ unity_devkit/matrix.py,sha256=wb8hkEx6f8wnoEjSZszM9lpPofh5a5LspCTtsKqKKQ0,1158
9
+ unity_devkit/projects.py,sha256=pWk6klQJ5_cdT8gcwQTkB1dK7y7UuNTk5NHYUCLvBuI,1941
10
+ unity_devkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ unity_devkit/test_unity.py,sha256=Zdr-e2Qx_OgC1huAT_ZgL_YgKnu4FjCRJEhTW7pXMyQ,1068
12
+ unity_devkit/unity.py,sha256=ST5c224jhGdLZJNXI1A7kPj3cxbcfY2j_5b96rpzyJs,6775
13
+ unity_devkit-0.1.2.dist-info/METADATA,sha256=RhTld6Ja8YUh8exj0_KUOOWWg9JwmYxggG7g11MWPLI,351
14
+ unity_devkit-0.1.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
15
+ unity_devkit-0.1.2.dist-info/entry_points.txt,sha256=RKjFLiz02NSodLcgVDp8pnYX6zEa7VhrH_1GXnnt9d8,381
16
+ unity_devkit-0.1.2.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
17
+ unity_devkit-0.1.2.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
18
+ unity_devkit-0.1.2.dist-info/RECORD,,
unity_devkit/cache.py DELETED
@@ -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}")
unity_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)
unity_devkit/git_tags.py DELETED
@@ -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}")
unity_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")
@@ -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).