feat(job): wire use-git-clone + submodules; default in-guest clone
The job command previously discarded --use-git-clone and --submodules
(`del`) and always in-guest cloned with submodules off — making both
action inputs dead no-ops (submodule repos silently broken).
- job.py: --use-git-clone/--host-clone and --submodules/--no-submodules
are real booleans defaulting to ON. Default = in-guest clone with
submodules. --host-clone clones on the host into a temp dir
(_host_clone) and transfers a zip; temp dir cleaned in finally.
clone_submodules now honoured (was hardcoded False).
- action.yml: use-git-clone / submodules default 'true'; forward the
explicit positive/negative flag so the Python default is overridden
deterministically.
- self-test.yml: add smoke-{windows,linux}-hostclone jobs covering the
host-side transport (default jobs already cover in-guest).
- tests: default-transport, host-clone, and --no-submodules coverage.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,10 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
@@ -192,6 +195,43 @@ def _make_clone_name(job_id: str) -> str:
|
||||
return f"Clone_{job_id}_{timestamp}_{suffix}"
|
||||
|
||||
|
||||
def _host_clone(
|
||||
repo_url: str, branch: str, commit: str, submodules: bool
|
||||
) -> tuple[Path, Path]:
|
||||
"""Clone the repo on the host into a temp dir (host-side transport).
|
||||
|
||||
Returns ``(tmp_root, src_dir)``. ``src_dir`` is passed as
|
||||
``host_source_dir`` to the build (zipped and transferred to the
|
||||
guest). The caller must remove ``tmp_root`` when done. The repo URL
|
||||
may embed credentials, so git output is never echoed.
|
||||
"""
|
||||
tmp_root = Path(tempfile.mkdtemp(prefix="ci-host-clone-"))
|
||||
src_dir = tmp_root / "src"
|
||||
clone = ["git", "clone", "--depth", "1", "--branch", branch]
|
||||
if submodules:
|
||||
clone.append("--recurse-submodules")
|
||||
clone += [repo_url, str(src_dir)]
|
||||
try:
|
||||
subprocess.run(clone, check=True, capture_output=True, text=True)
|
||||
if commit:
|
||||
subprocess.run(
|
||||
["git", "-C", str(src_dir), "fetch", "--depth", "1",
|
||||
"origin", commit],
|
||||
check=True, capture_output=True, text=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["git", "-C", str(src_dir), "checkout", commit],
|
||||
check=True, capture_output=True, text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
shutil.rmtree(tmp_root, ignore_errors=True)
|
||||
# Do not surface stderr verbatim — it can contain the authed URL.
|
||||
raise click.ClickException(
|
||||
f"host-side git clone failed (exit {exc.returncode})"
|
||||
) from None
|
||||
return tmp_root, src_dir
|
||||
|
||||
|
||||
def _destroy_clone(
|
||||
backend: VmBackend | None,
|
||||
handle: VmHandle | None,
|
||||
@@ -232,8 +272,20 @@ def _destroy_clone(
|
||||
@click.option("--configuration", default="Release", show_default=True)
|
||||
@click.option("--build-command", "build_command", default="")
|
||||
@click.option("--guest-artifact-source", "guest_artifact_source", default="dist", show_default=True)
|
||||
@click.option("--submodules", is_flag=True, default=False)
|
||||
@click.option("--use-git-clone", "use_git_clone", is_flag=True, default=False)
|
||||
@click.option(
|
||||
"--submodules/--no-submodules",
|
||||
"submodules",
|
||||
default=True,
|
||||
show_default=True,
|
||||
help="Recurse submodules when cloning (default: on).",
|
||||
)
|
||||
@click.option(
|
||||
"--use-git-clone/--host-clone",
|
||||
"use_git_clone",
|
||||
default=True,
|
||||
show_default=True,
|
||||
help="In-guest git clone (default) vs host-side clone + zip transfer.",
|
||||
)
|
||||
@click.option("--use-shared-cache", "use_shared_cache", is_flag=True, default=False)
|
||||
@click.option("--skip-artifact", "skip_artifact", is_flag=True, default=False)
|
||||
@click.option(
|
||||
@@ -326,9 +378,8 @@ def job(
|
||||
Replaces ``scripts/Invoke-CIJob.ps1``. Cleanup of the ephemeral VM is
|
||||
guaranteed via ``try/finally`` even on failure or ``KeyboardInterrupt``.
|
||||
"""
|
||||
# Reserved for future host-side clone variant; the Python port always
|
||||
# provisions sources via in-guest ``git clone``.
|
||||
del submodules, use_git_clone
|
||||
# Transport: in-guest git clone (default) or host-side clone + zip
|
||||
# transfer (``--host-clone``). Both honour ``--submodules``.
|
||||
|
||||
# Inject Gitea credentials into HTTP(S) clone URL so git doesn't prompt.
|
||||
authed_repo_url = repo_url
|
||||
@@ -469,42 +520,58 @@ def job(
|
||||
|
||||
# ── Phase 4/5: build inside guest ──────────────────────────────
|
||||
click.echo("\n[job] Phase 4/5 — invoking remote build")
|
||||
if guest_os == "linux":
|
||||
_linux_build(
|
||||
ip_address=ip_address,
|
||||
ssh_user=ssh_user,
|
||||
key_path=key_path,
|
||||
known_hosts=ssh_known_hosts_file,
|
||||
workdir="/opt/ci/build",
|
||||
output_dir="/opt/ci/output",
|
||||
host_source_dir=None,
|
||||
clone_url=authed_repo_url,
|
||||
clone_branch=branch,
|
||||
clone_commit=commit,
|
||||
clone_submodules=False,
|
||||
build_command=build_command,
|
||||
artifact_source=guest_artifact_source,
|
||||
extra_env=extra_env,
|
||||
skip_artifact=skip_artifact,
|
||||
)
|
||||
host_clone_root: Path | None = None
|
||||
if use_git_clone:
|
||||
build_clone_url: str | None = authed_repo_url
|
||||
build_host_src: str | None = None
|
||||
click.echo("[job] transport: in-guest git clone")
|
||||
else:
|
||||
_windows_build(
|
||||
ip_address=ip_address,
|
||||
credential_target=target,
|
||||
workdir="C:\\CI\\build",
|
||||
output_dir="C:\\CI\\output",
|
||||
host_source_dir=None,
|
||||
clone_url=authed_repo_url,
|
||||
clone_branch=branch,
|
||||
clone_commit=commit,
|
||||
clone_submodules=False,
|
||||
build_command=build_command,
|
||||
artifact_source=guest_artifact_source,
|
||||
configuration=configuration,
|
||||
extra_env=extra_env,
|
||||
skip_artifact=skip_artifact,
|
||||
use_shared_cache=use_shared_cache,
|
||||
click.echo("[job] transport: host-side clone + zip transfer")
|
||||
host_clone_root, src_dir = _host_clone(
|
||||
authed_repo_url, branch, commit, submodules
|
||||
)
|
||||
build_clone_url = None
|
||||
build_host_src = str(src_dir)
|
||||
try:
|
||||
if guest_os == "linux":
|
||||
_linux_build(
|
||||
ip_address=ip_address,
|
||||
ssh_user=ssh_user,
|
||||
key_path=key_path,
|
||||
known_hosts=ssh_known_hosts_file,
|
||||
workdir="/opt/ci/build",
|
||||
output_dir="/opt/ci/output",
|
||||
host_source_dir=build_host_src,
|
||||
clone_url=build_clone_url,
|
||||
clone_branch=branch,
|
||||
clone_commit=commit,
|
||||
clone_submodules=submodules,
|
||||
build_command=build_command,
|
||||
artifact_source=guest_artifact_source,
|
||||
extra_env=extra_env,
|
||||
skip_artifact=skip_artifact,
|
||||
)
|
||||
else:
|
||||
_windows_build(
|
||||
ip_address=ip_address,
|
||||
credential_target=target,
|
||||
workdir="C:\\CI\\build",
|
||||
output_dir="C:\\CI\\output",
|
||||
host_source_dir=build_host_src,
|
||||
clone_url=build_clone_url,
|
||||
clone_branch=branch,
|
||||
clone_commit=commit,
|
||||
clone_submodules=submodules,
|
||||
build_command=build_command,
|
||||
artifact_source=guest_artifact_source,
|
||||
configuration=configuration,
|
||||
extra_env=extra_env,
|
||||
skip_artifact=skip_artifact,
|
||||
use_shared_cache=use_shared_cache,
|
||||
)
|
||||
finally:
|
||||
if host_clone_root is not None:
|
||||
shutil.rmtree(host_clone_root, ignore_errors=True)
|
||||
phase_timings.append(("build", _now() - phase_start))
|
||||
phase_start = _now()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user