fix(extension): 容器入口可写工作目录——代码复制进 run_dir 执行+产物收割输出
18 用例脚本在 cwd 写 outputs/ 相对目录,代码挂载只读致 trial 失败。
等价做法:代码复制进 output/work 执行,{code_dir} 指向可写副本,
成功后 work 产物合并进 output_dir,输出契约校验不变。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -266,6 +266,30 @@ def _find_code_dir(ext_dir: Path) -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
def _copy_code_tree(src: Path, dst: Path) -> None:
|
||||
"""把代码树复制进可写目录(shutil.copytree,目标已存在)。"""
|
||||
import shutil
|
||||
if not src.is_dir():
|
||||
return
|
||||
for item in src.iterdir():
|
||||
dest = dst / item.name
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dest, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(item, dest)
|
||||
|
||||
|
||||
def _merge_tree(src: Path, dst: Path) -> None:
|
||||
"""把 src 全部内容合并进 dst(含子目录;同名覆盖)。"""
|
||||
import shutil
|
||||
for item in src.iterdir():
|
||||
dest = dst / item.name
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dest, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(item, dest)
|
||||
|
||||
|
||||
def _kill_group(pid: int, sig: int) -> None:
|
||||
try:
|
||||
os.killpg(pid, sig)
|
||||
@@ -362,13 +386,15 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
input_dir = Path(args.input_dir)
|
||||
output_dir = Path(args.output_dir)
|
||||
# 2026-09-15 修复:默认工作目录 = 代码目录(而非 output_dir)。
|
||||
# 用户脚本语义(18 用例)是「cd 代码目录后 bash run.sh」——run.sh 内
|
||||
# 的相对脚本路径(python check_inputs.py)依赖 cwd=代码目录;
|
||||
# 显式 --work-dir 参数仍可覆盖。
|
||||
work_dir = Path(args.work_dir) if args.work_dir else code_dir
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
# 2026-09-15 修复:代码挂载只读,但 18 用例语义是「cd 代码目录后脚本写
|
||||
# outputs/ 相对目录」。等价做法 = 把代码复制进可写 run_dir 执行,
|
||||
# 结束后把 run_dir 产物收割进 output_dir。显式 --work-dir 可覆盖。
|
||||
work_dir = Path(args.work_dir) if args.work_dir else (output_dir / "work")
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
_copy_code_tree(code_dir, work_dir)
|
||||
# 模板中 {code_dir} 指向可写副本(而非只读挂载)
|
||||
effective_code_dir = work_dir
|
||||
|
||||
try:
|
||||
parameters = _parse_params(args.param)
|
||||
@@ -386,7 +412,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
input_files=input_files,
|
||||
parameters=parameters,
|
||||
output_dir=output_dir,
|
||||
code_dir=code_dir,
|
||||
code_dir=effective_code_dir,
|
||||
)
|
||||
except ExtensionRenderError as exc:
|
||||
print(f"[run_extension] 模板渲染失败: {exc}", file=sys.stderr)
|
||||
@@ -413,6 +439,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if code != EXIT_OK:
|
||||
print(f"[run_extension] command exited with code {code}", file=sys.stderr)
|
||||
return code
|
||||
# 收割:work_dir 内全部产物(含 outputs/ 子目录与中间文件)合并进 output_dir
|
||||
try:
|
||||
_merge_tree(work_dir, output_dir)
|
||||
except OSError as exc:
|
||||
print(f"[run_extension] 产物收割失败: {exc}", file=sys.stderr)
|
||||
return EXIT_SETUP
|
||||
if not _output_contract_ok(output_dir):
|
||||
print(f"[run_extension] 输出契约校验失败:输出目录为空 {output_dir} (exit {EXIT_OUTPUT_CONTRACT})",
|
||||
file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user