feat(builder): declare multi-arch builds with ARCHES
Project architecture lists belong in makefile.builder, not .env.builder. deb/push-deb fan out when ARCH is omitted; Docker platforms derive from ARCHES. Legacy makefiles without ARCHES keep the old single-arch behavior.
This commit is contained in:
@@ -102,6 +102,13 @@ class BuilderCheckTests(unittest.TestCase):
|
||||
self.assertIn("makefile.builder", contract)
|
||||
self.assertIn(".env.builder", contract)
|
||||
self.assertIn("不要读取或改写用户 `.env`", contract)
|
||||
self.assertIn("ARCHES", contract)
|
||||
readme = (REPO_ROOT / "skills" / "builder" / "README.md").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
self.assertIn("ARCHES", readme)
|
||||
self.assertIn("amd64 arm64", readme)
|
||||
self.assertIn("多架构", readme)
|
||||
|
||||
def test_no_makefile_without_ready_is_usage_error(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
@@ -233,6 +240,172 @@ class BuilderCheckTests(unittest.TestCase):
|
||||
self.assertNotIn("hijacked", text)
|
||||
self.assertNotIn(":latest", text)
|
||||
|
||||
def test_default_template_rejects_arch_outside_arches(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
project = Path(temp)
|
||||
init_repo(project)
|
||||
write_contract_makefile(project)
|
||||
result = subprocess.run(
|
||||
["make", "-C", str(project), "-f", "makefile.builder", "-n", "build", "ARCH=arm64"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("amd64", result.stderr)
|
||||
|
||||
def test_multi_arches_deb_and_push_fan_out(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
project = Path(temp)
|
||||
init_repo(project)
|
||||
write_contract_makefile(project)
|
||||
makefile = project / "makefile.builder"
|
||||
makefile.write_text(
|
||||
makefile.read_text(encoding="utf-8").replace(
|
||||
"ARCHES ?= amd64", "ARCHES ?= amd64 arm64"
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
code, text = run_check(
|
||||
project, "--ready", which={"dpkg-deb": "/usr/bin/dpkg-deb"}
|
||||
)
|
||||
self.assertEqual(code, 0, text)
|
||||
self.assertIn("ARCHES: 'amd64 arm64'", text)
|
||||
deb = subprocess.run(
|
||||
["make", "-C", str(project), "-f", "makefile.builder", "-n", "deb"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
self.assertEqual(deb.returncode, 0, deb.stderr)
|
||||
combined = deb.stdout + deb.stderr
|
||||
self.assertIn("ARCH=amd64", combined)
|
||||
self.assertIn("ARCH=arm64", combined)
|
||||
pinned = subprocess.run(
|
||||
[
|
||||
"make",
|
||||
"-C",
|
||||
str(project),
|
||||
"-f",
|
||||
"makefile.builder",
|
||||
"-n",
|
||||
"deb",
|
||||
"ARCH=arm64",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
self.assertEqual(pinned.returncode, 0, pinned.stderr)
|
||||
self.assertIn("_arm64.deb", pinned.stdout)
|
||||
self.assertNotIn("_amd64.deb", pinned.stdout)
|
||||
|
||||
def test_legacy_makefile_without_arches_still_passes(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
project = Path(temp)
|
||||
init_repo(project)
|
||||
(project / "makefile.builder").write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"ARCH ?= amd64",
|
||||
"ifneq ($(filter $(ARCH),amd64 arm64),$(ARCH))",
|
||||
"$(error ARCH must be amd64 or arm64)",
|
||||
"endif",
|
||||
"PROJECT_NAME ?= $(notdir $(CURDIR))",
|
||||
"DIST_DIR ?= dist",
|
||||
f"include {SCRIPTS / 'version.mk'}",
|
||||
f"BUILDER_SCRIPTS := {SCRIPTS}",
|
||||
".PHONY: help version clean build deb push",
|
||||
"help:",
|
||||
"\t@echo $(PROJECT_NAME) $(VERSION)",
|
||||
"version:",
|
||||
"\t@echo $(VERSION)",
|
||||
"clean:",
|
||||
"\trm -rf $(DIST_DIR)",
|
||||
"build:",
|
||||
"\tmkdir -p $(DIST_DIR)",
|
||||
"\t@echo build linux/$(ARCH)",
|
||||
"deb: build",
|
||||
"\tmkdir -p $(DIST_DIR)",
|
||||
"\tdpkg-deb --build packaging $(DIST_DIR)/$(PROJECT_NAME)_$(VERSION)_$(ARCH).deb",
|
||||
"push:",
|
||||
"\t$(BUILDER_SCRIPTS)/upload_deb.sh $(DIST_DIR)/$(PROJECT_NAME)_$(VERSION)_$(ARCH).deb",
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
code, text = run_check(
|
||||
project, "--ready", which={"dpkg-deb": "/usr/bin/dpkg-deb"}
|
||||
)
|
||||
self.assertEqual(code, 0, text)
|
||||
self.assertIn("arches target: absent", text)
|
||||
result = subprocess.run(
|
||||
["make", "-C", str(project), "-f", "makefile.builder", "-n", "build", "ARCH=arm64"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
def test_publish_docker_derives_platforms_from_arches(self) -> None:
|
||||
script = SCRIPTS / "publish_docker.sh"
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
project = Path(temp)
|
||||
init_repo(project)
|
||||
write_contract_makefile(project)
|
||||
makefile = project / "makefile.builder"
|
||||
makefile.write_text(
|
||||
makefile.read_text(encoding="utf-8").replace(
|
||||
"ARCHES ?= amd64", "ARCHES ?= amd64 arm64"
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(project / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
|
||||
env = os.environ.copy()
|
||||
env["BUILDER_SKILL_DIR"] = str(SCRIPTS.parent)
|
||||
env.pop("PLATFORMS", None)
|
||||
derived = subprocess.run(
|
||||
[
|
||||
str(script),
|
||||
"--registry",
|
||||
"example.com",
|
||||
"--repository",
|
||||
"ns/foo",
|
||||
"--tag",
|
||||
"dry-run-tag",
|
||||
"--dry-run",
|
||||
],
|
||||
cwd=project,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=env,
|
||||
)
|
||||
self.assertEqual(derived.returncode, 0, derived.stdout + derived.stderr)
|
||||
self.assertIn("Platform: linux/amd64,linux/arm64", derived.stdout)
|
||||
env["PLATFORMS"] = "linux/amd64"
|
||||
overridden = subprocess.run(
|
||||
[
|
||||
str(script),
|
||||
"--registry",
|
||||
"example.com",
|
||||
"--repository",
|
||||
"ns/foo",
|
||||
"--tag",
|
||||
"dry-run-tag",
|
||||
"--dry-run",
|
||||
],
|
||||
cwd=project,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=env,
|
||||
)
|
||||
self.assertEqual(overridden.returncode, 0, overridden.stdout + overridden.stderr)
|
||||
self.assertIn("Platform: linux/amd64", overridden.stdout)
|
||||
self.assertNotIn("linux/arm64", overridden.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user