Remove Probe build jobs and release paths. Transfer Gitea publishing credentials to both Agent architecture jobs, publish each platform archive once, and run slow tests after development images are published.
111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Stage source and restore files for a Linux Agent image build."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXED_MTIME = 946684800 # 2000-01-01T00:00:00Z
|
|
BACKEND = ROOT / "backend"
|
|
STATIC_MSQUIC = ROOT / "packaging/native/msquic"
|
|
IGNORED_PARTS = {".git", ".release", "artifacts", "bin", "obj", "TestResults", "tests"}
|
|
GLOBAL_BACKEND_FILES = {
|
|
"Directory.Build.props",
|
|
"Directory.Build.targets",
|
|
"Directory.Packages.props",
|
|
"global.json",
|
|
"NuGet.Config",
|
|
"NuGet.config",
|
|
}
|
|
# The container stages backend as a standalone tree. Copy the repository-wide
|
|
# SDK and package versions into it so restore/publish use the same configuration.
|
|
ROOT_BUILD_FILES = {"Directory.Packages.props", "global.json"}
|
|
CONTAINERFILES = {
|
|
"agent": ROOT / "packaging/container/Agent.NativeBuild.Containerfile",
|
|
}
|
|
|
|
|
|
def copy_for_restore(path: Path, relative: Path, target: Path) -> None:
|
|
"""The restore layer must not change when only the build version does: CI rewrites <Version> in the workspace
|
|
Directory.Build.props (ci/version.sh), which used to invalidate the cached dependency restore on every commit.
|
|
Restore only needs the committed props (package versions); the source copy still carries the real version."""
|
|
if path.name == "Directory.Build.props":
|
|
committed = subprocess.run(
|
|
["git", "show", f"HEAD:backend/{relative.as_posix()}"], cwd=ROOT, capture_output=True, check=False
|
|
)
|
|
if committed.returncode == 0:
|
|
target.write_bytes(committed.stdout)
|
|
return
|
|
shutil.copy2(path, target)
|
|
|
|
|
|
def stage(component: str, runtime_identifier: str, destination: Path) -> None:
|
|
if destination.exists():
|
|
shutil.rmtree(destination)
|
|
restore = destination / "restore/backend"
|
|
source = destination / "source/backend"
|
|
restore.mkdir(parents=True)
|
|
source.mkdir(parents=True)
|
|
shutil.copy2(CONTAINERFILES[component], destination / "Containerfile")
|
|
shutil.copy2(ROOT / "packaging/container/entrypoint.sh", destination / "entrypoint.sh")
|
|
|
|
for path in BACKEND.rglob("*"):
|
|
if not path.is_file() or IGNORED_PARTS.intersection(path.parts):
|
|
continue
|
|
relative = path.relative_to(BACKEND)
|
|
if (
|
|
path.suffix in {".csproj", ".props", ".targets", ".slnx"}
|
|
or path.name in GLOBAL_BACKEND_FILES
|
|
or path.name == "packages.lock.json"
|
|
):
|
|
target = restore / relative
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
copy_for_restore(path, relative, target)
|
|
target = source / relative
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(path, target)
|
|
|
|
for name in ROOT_BUILD_FILES:
|
|
for directory in (restore, source):
|
|
shutil.copy2(ROOT / name, directory / name)
|
|
|
|
static_library = STATIC_MSQUIC / runtime_identifier / "libmsquic.a"
|
|
if not static_library.is_file():
|
|
raise FileNotFoundError(f"static MsQuic archive is missing for {runtime_identifier}: {static_library}")
|
|
staged_library = destination / "source/packaging/native/msquic" / runtime_identifier / "libmsquic.a"
|
|
staged_library.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(static_library, staged_library)
|
|
|
|
config = destination / "config"
|
|
config.mkdir()
|
|
(config / ".keep").write_text("", encoding="utf-8")
|
|
# 更新清单和请求放在这里;空的命名卷第一次挂载时会带上这个目录
|
|
(config / "update").mkdir()
|
|
(config / "update" / ".keep").write_text("", encoding="utf-8")
|
|
normalize_mtimes(destination / "restore")
|
|
|
|
|
|
def normalize_mtimes(directory: Path) -> None:
|
|
"""The Buildah layer cache keys COPY layers on file metadata as well as content. Files copied from a fresh checkout carry a new
|
|
mtime every run, so the restore layer (and the ~70 s of NuGet downloads under it) never hit the cache. Pin the mtimes."""
|
|
for path in [directory, *directory.rglob("*")]:
|
|
os.utime(path, (FIXED_MTIME, FIXED_MTIME))
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("component", choices=tuple(CONTAINERFILES))
|
|
parser.add_argument("runtime_identifier", choices=("linux-musl-x64", "linux-musl-arm64"))
|
|
parser.add_argument("destination", type=Path)
|
|
args = parser.parse_args()
|
|
stage(args.component, args.runtime_identifier, args.destination)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|