UEPyScripts 1.4.0__tar.gz → 1.4.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.
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/PKG-INFO +1 -1
- uepyscripts-1.4.2/src/uepyscripts/tools/ue/__init__.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ugs/git_utils.py +20 -1
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ugs/push.py +84 -16
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ugs/ugs_types.py +10 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/.gitignore +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/LICENSE +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/README.md +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/pyproject.toml +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/__init__.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/context/__init__.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/internal/__init__.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/internal/config.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/internal/engine.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/internal/engine_resolver.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/internal/project.py +0 -0
- /uepyscripts-1.4.0/src/uepyscripts/run/__init__.py → /uepyscripts-1.4.2/src/uepyscripts/py.typed +0 -0
- {uepyscripts-1.4.0/src/uepyscripts/tools → uepyscripts-1.4.2/src/uepyscripts/run}/__init__.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/run/buildgraph.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/run/uat.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/run/ubt.py +0 -0
- {uepyscripts-1.4.0/src/uepyscripts/tools/ue → uepyscripts-1.4.2/src/uepyscripts/tools}/__init__.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ci/buildgraph.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ci/cleanup.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/helpers.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/subprocess.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/check_engine_installation.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/close_editor.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/compile_editor.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/engine_installation/engine_destination.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/engine_installation/engine_installer.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/engine_installation/engine_source.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/generate_solution.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/run_editor.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ugs/cleanup.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ugs/pull.py +0 -0
- {uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/winreg.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: UEPyScripts
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: Collection of Python tools and helpers designed to automate common Unreal Engine project tasks (builds, packaging, editor control, CI tasks, etc.).
|
|
5
5
|
Project-URL: Changelog, https://github.com/TheEmidee/UEPyScripts/releases
|
|
6
6
|
Project-URL: Homepage, https://github.com/TheEmidee/UEPyScripts
|
|
File without changes
|
|
@@ -2,6 +2,8 @@ import os
|
|
|
2
2
|
import subprocess
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
from uepyscripts import logger
|
|
6
|
+
|
|
5
7
|
ANCESTRY_LOOKBACK = 2000
|
|
6
8
|
|
|
7
9
|
|
|
@@ -16,7 +18,24 @@ def get_current_branch(cwd: Path) -> str:
|
|
|
16
18
|
branch = os.environ.get("GIT_BRANCH") # Jenkins' Git plugin sets this, sometimes as "origin/<branch>"
|
|
17
19
|
if branch:
|
|
18
20
|
return branch.split("/", 1)[1] if "/" in branch else branch
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd, text=True).strip()
|
|
23
|
+
if branch != "HEAD":
|
|
24
|
+
return branch
|
|
25
|
+
|
|
26
|
+
# Detached HEAD (e.g. a CI checkout of an explicit ref rather than a
|
|
27
|
+
# tracked branch, with no GIT_BRANCH env var set either) — fall back to
|
|
28
|
+
# whichever local or remote-tracking ref currently points at this commit.
|
|
29
|
+
refs = subprocess.check_output(
|
|
30
|
+
["git", "for-each-ref", "--points-at=HEAD", "--format=%(refname:short)", "refs/heads/", "refs/remotes/"],
|
|
31
|
+
cwd=cwd,
|
|
32
|
+
text=True,
|
|
33
|
+
).splitlines()
|
|
34
|
+
for ref in refs:
|
|
35
|
+
return ref.split("/", 1)[1] if "/" in ref else ref
|
|
36
|
+
|
|
37
|
+
logger.warning("Could not resolve current branch: HEAD is detached and no ref points at it")
|
|
38
|
+
return branch
|
|
20
39
|
|
|
21
40
|
|
|
22
41
|
def get_local_ancestry(cwd: Path, limit: int = ANCESTRY_LOOKBACK) -> list[str]:
|
|
@@ -35,10 +35,9 @@ from uepyscripts.tools.ugs.git_utils import (
|
|
|
35
35
|
get_local_ancestry,
|
|
36
36
|
resolve_nearest_published_ancestor,
|
|
37
37
|
)
|
|
38
|
-
from uepyscripts.tools.ugs.ugs_types import Manifest, VersionManifest
|
|
38
|
+
from uepyscripts.tools.ugs.ugs_types import HashCacheEntry, HashCacheManifest, Manifest, VersionManifest
|
|
39
39
|
|
|
40
40
|
# ---- CONFIG ----------------------------------------------------------
|
|
41
|
-
LOCAL_HASH_CACHE = ".hash-cache.json"
|
|
42
41
|
|
|
43
42
|
# Extensions that go through the delta/checkpoint sync pipeline (no PDBs — see symstore below)
|
|
44
43
|
SYNC_EXT = {".dll", ".exe", ".lib", ".modules", ".target"}
|
|
@@ -72,18 +71,18 @@ class HashCacheInfos:
|
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
class HashCache:
|
|
75
|
-
def __init__(self) -> None:
|
|
74
|
+
def __init__(self, commit_sha: str) -> None:
|
|
75
|
+
self.commit_sha = commit_sha
|
|
76
76
|
self.cache: dict[str, HashCacheInfos] = dict()
|
|
77
|
-
self.local_hash_cache = Path(LOCAL_HASH_CACHE)
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
self.cache = {path: HashCacheInfos(**entry) for path, entry in raw.items()}
|
|
78
|
+
def seed_from(self, files: dict[str, HashCacheEntry]) -> None:
|
|
79
|
+
self.cache = {path: HashCacheInfos(**entry) for path, entry in files.items()}
|
|
82
80
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
def to_manifest(self) -> HashCacheManifest:
|
|
82
|
+
return {
|
|
83
|
+
"commit_sha": self.commit_sha,
|
|
84
|
+
"files": {path: cast(HashCacheEntry, asdict(entry)) for path, entry in self.cache.items()},
|
|
85
|
+
}
|
|
87
86
|
|
|
88
87
|
def get(self, path: str) -> HashCacheInfos | None:
|
|
89
88
|
return self.cache.get(path)
|
|
@@ -102,10 +101,35 @@ class Context:
|
|
|
102
101
|
)
|
|
103
102
|
self.symbol_store_path: str = args.symbol_store_path
|
|
104
103
|
self.tmp_root: Path = Path(tempfile.mkdtemp(prefix="publish-"))
|
|
105
|
-
self.hash_cache = HashCache()
|
|
106
104
|
self.root_path = root_path
|
|
107
105
|
self.current_sha = get_current_sha(root_path)
|
|
108
106
|
self.current_branch = get_current_branch(root_path)
|
|
107
|
+
self.ancestry: list[str] = get_local_ancestry(root_path)
|
|
108
|
+
self.hash_cache, self.hash_cache_index = self._load_hash_cache()
|
|
109
|
+
|
|
110
|
+
def _load_hash_cache(self) -> tuple[HashCache, list[str]]:
|
|
111
|
+
"""Resolves the nearest commit with an uploaded hash cache and seeds a
|
|
112
|
+
fresh HashCache from it. Unlike the manifest baseline used for diffing,
|
|
113
|
+
an inexact match is fine here: a fingerprint mismatch just falls back to
|
|
114
|
+
a full hash for that file, no worse than a cold-cache case, so
|
|
115
|
+
there's no correctness risk in seeding from a non-ancestor."""
|
|
116
|
+
bucket_name = self.args.s3_bucket_name
|
|
117
|
+
hash_cache_index = cast(list[str], self.s3_client.download_json(bucket_name, "hash_caches/index.json", default=[]))
|
|
118
|
+
baseline_sha, _ = resolve_nearest_published_ancestor(hash_cache_index, self.ancestry)
|
|
119
|
+
|
|
120
|
+
hash_cache = HashCache(self.current_sha)
|
|
121
|
+
if baseline_sha is None:
|
|
122
|
+
logger.info("No hash cache baseline found — starting with a cold hash cache")
|
|
123
|
+
return hash_cache, hash_cache_index
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
manifest = cast(HashCacheManifest, self.s3_client.download_json(bucket_name, f"hash_caches/{baseline_sha}.json"))
|
|
127
|
+
hash_cache.seed_from(manifest["files"])
|
|
128
|
+
logger.info(f"Seeded hash cache from {baseline_sha} ({len(manifest['files'])} entries)")
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.warning(f"Failed to load hash cache from hash_caches/{baseline_sha}.json ({e}); starting with a cold hash cache")
|
|
131
|
+
|
|
132
|
+
return hash_cache, hash_cache_index
|
|
109
133
|
|
|
110
134
|
def finalize(self) -> None:
|
|
111
135
|
if not self.args.keep_temp_directory:
|
|
@@ -327,6 +351,24 @@ def prune_old_versions(context: Context, cfg: FilesConfiguration, index: list[st
|
|
|
327
351
|
return index[cutoff_idx:], checkpoints_to_keep
|
|
328
352
|
|
|
329
353
|
|
|
354
|
+
def prune_old_hash_caches(context: Context, hash_cache_index: list[str], keep: int) -> list[str]:
|
|
355
|
+
"""Simple count-based retention for the hash-cache side-channel: unlike
|
|
356
|
+
index.json (only appended on actual content changes), hash_cache_index
|
|
357
|
+
grows once per run, so it needs its own, faster-turnover retention
|
|
358
|
+
rather than piggybacking on prune_old_versions's checkpoint-aligned
|
|
359
|
+
logic."""
|
|
360
|
+
if len(hash_cache_index) <= keep:
|
|
361
|
+
return hash_cache_index
|
|
362
|
+
|
|
363
|
+
to_delete = hash_cache_index[:-keep]
|
|
364
|
+
to_keep = hash_cache_index[-keep:]
|
|
365
|
+
|
|
366
|
+
logger.info(f"Pruning {len(to_delete)} old hash cache(s)")
|
|
367
|
+
context.s3_client.delete_keys(context.args.s3_bucket_name, [f"hash_caches/{sha}.json" for sha in to_delete])
|
|
368
|
+
|
|
369
|
+
return to_keep
|
|
370
|
+
|
|
371
|
+
|
|
330
372
|
def publish_to_symbol_store(context: Context, paths: list[str], cfg: FilesConfiguration) -> None:
|
|
331
373
|
"""Indexes PDB/DLL/EXE files into the symbol store via symstore.exe."""
|
|
332
374
|
logger.info("Publish to symbol store")
|
|
@@ -397,6 +439,12 @@ def parse_arguments() -> argparse.Namespace:
|
|
|
397
439
|
default=2,
|
|
398
440
|
help="Number of checkpoints to retain; older ones (and their deltas) get pruned (default: 2)",
|
|
399
441
|
)
|
|
442
|
+
parser.add_argument(
|
|
443
|
+
"--keep_hash_caches",
|
|
444
|
+
type=int,
|
|
445
|
+
default=100,
|
|
446
|
+
help="Number of hash cache entries to retain in hash_caches/index.json; older ones get pruned (default: 100)",
|
|
447
|
+
)
|
|
400
448
|
parser.add_argument(
|
|
401
449
|
"--checkpoint-branch",
|
|
402
450
|
type=str,
|
|
@@ -460,8 +508,7 @@ def main() -> None:
|
|
|
460
508
|
logger.info(f"Finished scan. Found {len(new_state)} files.")
|
|
461
509
|
|
|
462
510
|
logger.info("Resolve diff baseline from git ancestry")
|
|
463
|
-
|
|
464
|
-
baseline_sha, is_exact = resolve_nearest_published_ancestor(index, ancestry)
|
|
511
|
+
baseline_sha, is_exact = resolve_nearest_published_ancestor(index, context.ancestry)
|
|
465
512
|
|
|
466
513
|
old_state: dict[str, str]
|
|
467
514
|
if baseline_sha is None:
|
|
@@ -573,8 +620,29 @@ def main() -> None:
|
|
|
573
620
|
|
|
574
621
|
logger.info("=== Finished processing binary files ===")
|
|
575
622
|
|
|
576
|
-
|
|
577
|
-
|
|
623
|
+
if not context.args.skip_upload:
|
|
624
|
+
logger.info("Upload hash cache")
|
|
625
|
+
context.s3_client.upload_bytes(
|
|
626
|
+
context.args.s3_bucket_name,
|
|
627
|
+
f"hash_caches/{context.current_sha}.json",
|
|
628
|
+
json.dumps(context.hash_cache.to_manifest()).encode(),
|
|
629
|
+
content_type="application/json",
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
if context.current_sha not in context.hash_cache_index:
|
|
633
|
+
context.hash_cache_index.append(context.current_sha)
|
|
634
|
+
|
|
635
|
+
context.hash_cache_index = prune_old_hash_caches(context, context.hash_cache_index, context.args.keep_hash_caches)
|
|
636
|
+
|
|
637
|
+
logger.info("Upload hash_caches/index.json")
|
|
638
|
+
context.s3_client.upload_bytes(
|
|
639
|
+
context.args.s3_bucket_name,
|
|
640
|
+
"hash_caches/index.json",
|
|
641
|
+
json.dumps(context.hash_cache_index).encode(),
|
|
642
|
+
content_type="application/json",
|
|
643
|
+
)
|
|
644
|
+
else:
|
|
645
|
+
logger.info("Skipping hash cache upload (--skip-upload)")
|
|
578
646
|
finally:
|
|
579
647
|
context.finalize()
|
|
580
648
|
|
|
@@ -7,3 +7,13 @@ class VersionManifest(TypedDict):
|
|
|
7
7
|
files: Manifest # full state at this version: {relative_path: sha256}
|
|
8
8
|
changed: list[str] # files added or modified since the previous version
|
|
9
9
|
removed: list[str] # files removed since the previous version
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HashCacheEntry(TypedDict):
|
|
13
|
+
fingerprint: str # "mtime_ns:size"
|
|
14
|
+
hash: str # blake3 hex digest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HashCacheManifest(TypedDict):
|
|
18
|
+
commit_sha: str # commit this cache was captured at
|
|
19
|
+
files: dict[str, HashCacheEntry]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/uepyscripts-1.4.0/src/uepyscripts/run/__init__.py → /uepyscripts-1.4.2/src/uepyscripts/py.typed
RENAMED
|
File without changes
|
{uepyscripts-1.4.0/src/uepyscripts/tools → uepyscripts-1.4.2/src/uepyscripts/run}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{uepyscripts-1.4.0/src/uepyscripts/tools/ue → uepyscripts-1.4.2/src/uepyscripts/tools}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{uepyscripts-1.4.0 → uepyscripts-1.4.2}/src/uepyscripts/tools/ue/check_engine_installation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|