From dd7a84626b2b56f41791038a216a6406affb4392 Mon Sep 17 00:00:00 2001 From: Giancarlo Cicellyn Comneno Date: Thu, 26 Feb 2026 18:31:55 +0100 Subject: [PATCH] go-framework: fail early when no Go sources are present --- rockcraft/extensions/go.py | 11 ++++++++- tests/unit/extensions/test_go.py | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/rockcraft/extensions/go.py b/rockcraft/extensions/go.py index 9c7f6d99e..4a903ffb7 100644 --- a/rockcraft/extensions/go.py +++ b/rockcraft/extensions/go.py @@ -111,7 +111,7 @@ def project_name(self) -> str: return self.yaml_data["name"] def _check_project(self) -> None: - """Check go.mod file exist in project.""" + """Check go.mod file and Go sources exist in project.""" if not (self.project_root / "go.mod").exists(): raise ExtensionError( "missing go.mod file, it should be present in the project directory", @@ -119,6 +119,15 @@ def _check_project(self) -> None: logpath_report=False, ) + go_files = list(self.project_root.rglob("*.go")) + if not go_files: + raise ExtensionError( + "No Go source files found. The go-framework extension requires " + "at least one Go source file (typically with a main package).", + doc_slug="/reference/extensions/go-framework/#project-requirements", + logpath_report=False, + ) + def _get_install_app_part(self) -> dict[str, Any]: """Generate install-app part with the Go plugin.""" install_app = self._get_nested( diff --git a/tests/unit/extensions/test_go.py b/tests/unit/extensions/test_go.py index d3cd2683b..5116d672f 100644 --- a/tests/unit/extensions/test_go.py +++ b/tests/unit/extensions/test_go.py @@ -37,6 +37,7 @@ def go_extension(mock_extensions): @pytest.mark.usefixtures("go_extension") def test_go_extension_default(tmp_path, go_input_yaml): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") applied = extensions.apply_extensions(tmp_path, go_input_yaml) assert applied == { @@ -91,6 +92,7 @@ def test_go_extension_default(tmp_path, go_input_yaml): @pytest.mark.usefixtures("go_extension") def test_go_extension_bare(tmp_path): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") go_input_yaml = { "name": "foo-bar", "extensions": ["go-framework"], @@ -122,6 +124,36 @@ def test_go_extension_no_go_mod_file_error(tmp_path, go_input_yaml): ) +@pytest.mark.usefixtures("go_extension") +def test_go_extension_no_go_sources_error(tmp_path, go_input_yaml): + (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + + with pytest.raises(ExtensionError) as exc: + extensions.apply_extensions(tmp_path, go_input_yaml) + + assert ( + str(exc.value) + == "No Go source files found. The go-framework extension requires at least one " + "Go source file (typically with a main package)." + ) + assert ( + str(exc.value.doc_slug) + == "/reference/extensions/go-framework/#project-requirements" + ) + + +@pytest.mark.usefixtures("go_extension") +def test_go_extension_detects_nested_go_sources(tmp_path, go_input_yaml): + (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + nested = tmp_path / "cmd" / "server" + nested.mkdir(parents=True) + (nested / "main.go").write_text("package main\n\nfunc main() {}\n") + + applied = extensions.apply_extensions(tmp_path, go_input_yaml) + + assert applied["services"]["go"]["command"] == "goprojectname" + + @pytest.mark.usefixtures("go_extension") @pytest.mark.parametrize("build_environment", [[], [{"OTHER_ENV_VAR": "val"}]]) def test_go_extension_base_bare(tmp_path, go_input_yaml, build_environment): @@ -132,6 +164,7 @@ def test_go_extension_base_bare(tmp_path, go_input_yaml, build_environment): "go-framework/install-app": {"build-environment": build_environment}, } (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") applied = extensions.apply_extensions(tmp_path, go_input_yaml) assert "build-environment" in applied["parts"]["go-framework/install-app"] @@ -171,6 +204,7 @@ def test_go_extension_overrides_organize( tmp_path, go_input_yaml, organize, expected_organize ): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") if organize: go_input_yaml["parts"] = { "go-framework/install-app": {"organize": organize}, @@ -197,6 +231,7 @@ def test_go_extension_override_build_snaps( tmp_path, go_input_yaml, build_packages, build_snaps, expected_build_snaps ): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") if build_snaps or build_packages: go_input_yaml["parts"] = { "go-framework/install-app": { @@ -213,6 +248,7 @@ def test_go_extension_override_build_snaps( @pytest.mark.usefixtures("go_extension") def test_go_extension_override_service_go_command(tmp_path, go_input_yaml): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") go_input_yaml["parts"] = { "go-framework/install-app": { "organize": { @@ -237,6 +273,7 @@ def test_go_extension_override_service_go_command(tmp_path, go_input_yaml): @pytest.mark.usefixtures("go_extension") def test_go_extension_extra_assets(tmp_path, go_input_yaml): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") (tmp_path / "static").mkdir() (tmp_path / "templates").mkdir() (tmp_path / "migrate").write_text("migrate") @@ -260,6 +297,7 @@ def test_go_extension_extra_assets(tmp_path, go_input_yaml): @pytest.mark.usefixtures("go_extension") def test_go_extension_extra_assets_overridden(tmp_path, go_input_yaml): (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") (tmp_path / "static").mkdir() go_input_yaml["parts"] = { "go-framework/assets": { @@ -324,6 +362,7 @@ def test_go_extension_26_04_experimental_no_env(tmp_path): def test_go_extension_default_26_04(tmp_path, monkeypatch): monkeypatch.setenv("ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "1") (tmp_path / "go.mod").write_text("module projectname\n\ngo 1.22.4") + (tmp_path / "main.go").write_text("package main\n\nfunc main() {}\n") go_input_yaml = { "name": "goprojectname", "base": "ubuntu@26.04",