Skip to content

Commit 6e07eeb

Browse files
authored
fix: add timeout to Gradle Group ID detection (#446)
This PR sets a timeout on the `subprocess.run()` call that runs `gradlew properties` to detect the group ID of a Gradle project. The `timout` is read from `build_timeout` in `defaults.ini` for the detected build tool. Signed-off-by: behnazh-w <[email protected]>
1 parent 4cecb0c commit 6e07eeb

File tree

4 files changed

+64
-2
lines changed

4 files changed

+64
-2
lines changed

src/macaron/config/defaults.ini

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ deploy_arg =
118118
org.sonatype.plugins:nexus-staging-maven-plugin:deploy-staged-repository
119119
# See https://help.sonatype.com/repomanager3/integrations/nexus-repository-maven-plugin.
120120
nxrm3:staging-deploy
121-
122121
build_log = Apache Maven
123122
wrapper_files =
124123
.mvn/wrapper/maven-wrapper.jar
@@ -242,6 +241,10 @@ jenkins =
242241
gradle-git-publish
243242
gitPublishPush
244243

244+
[builder.gradle.runtime]
245+
# This is the timeout (in seconds) to run the build tool.
246+
build_timeout = 600
247+
245248
# This is the spec for trusted Pip packaging tools.
246249
[builder.pip]
247250
entry_conf =
@@ -271,6 +274,7 @@ build_arg =
271274
deploy_arg =
272275
publish
273276
upload
277+
274278
[builder.pip.ci.deploy]
275279
github_actions = pypa/gh-action-pypi-publish
276280

@@ -292,6 +296,7 @@ build_arg =
292296
build
293297
deploy_arg =
294298
publish
299+
295300
[builder.poetry.ci.deploy]
296301
github_actions = pypa/gh-action-pypi-publish
297302

src/macaron/slsa_analyzer/build_tool/base_build_tool.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
from abc import ABC, abstractmethod
1010
from collections.abc import Iterable
11+
from dataclasses import dataclass
1112
from pathlib import Path
1213

1314
from macaron.dependency_analyzer import DependencyAnalyzer
@@ -41,6 +42,17 @@ def file_exists(path: str, file_name: str) -> bool:
4142
return False
4243

4344

45+
@dataclass
46+
class RuntimeOptions:
47+
"""The class for build tool runtime configurations read from `defaults.ini`.
48+
49+
Note that Macaron uses the options in this class to "run" a build tool.
50+
"""
51+
52+
#: The timeout used for running the build tool commands.
53+
build_timeout: float = 600
54+
55+
4456
class BaseBuildTool(ABC):
4557
"""This abstract class is used to implement Build Tools."""
4658

@@ -79,6 +91,7 @@ def __init__(self, name: str) -> None:
7991
}
8092
self.build_log: list[str] = []
8193
self.wrapper_files: list[str] = []
94+
self.runtime_options = RuntimeOptions()
8295

8396
def __str__(self) -> str:
8497
return self.name

src/macaron/slsa_analyzer/build_tool/gradle.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ def load_defaults(self) -> None:
4545
if item in self.ci_deploy_kws:
4646
self.ci_deploy_kws[item] = defaults.get_list("builder.gradle.ci.deploy", item)
4747

48+
if "builder.gradle.runtime" in defaults:
49+
try:
50+
self.runtime_options.build_timeout = defaults.getfloat(
51+
"builder.gradle.runtime", "build_timeout", fallback=self.runtime_options.build_timeout
52+
)
53+
except ValueError as error:
54+
logger.error(
55+
"Failed to validate builder.gradle.runtime.build_timeout in defaults.ini. "
56+
"Falling back to the default build timeout %s seconds: %s",
57+
self.runtime_options.build_timeout,
58+
error,
59+
)
60+
4861
def is_detected(self, repo_path: str) -> bool:
4962
"""Return True if this build tool is used in the target repo.
5063
@@ -217,13 +230,17 @@ def get_group_id(self, gradle_exec: str, project_path: str) -> str | None:
217230
The group id of the project, if exists.
218231
"""
219232
try:
233+
logger.info(
234+
"Identifying the group ID for the artifact. This can take a while if Gradle needs to be downloaded."
235+
)
220236
result = subprocess.run( # nosec B603
221237
[gradle_exec, "properties"],
222238
capture_output=True,
223239
cwd=project_path,
224240
check=False,
241+
timeout=self.runtime_options.build_timeout,
225242
)
226-
except (subprocess.CalledProcessError, OSError) as error:
243+
except (subprocess.CalledProcessError, OSError, subprocess.TimeoutExpired) as error:
227244
logger.debug("Could not capture the group id of the Gradle project at %s", project_path)
228245
logger.debug("Error: %s", error)
229246
return None

tests/slsa_analyzer/build_tool/test_gradle.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pytest
99

10+
from macaron.config.defaults import load_defaults
1011
from macaron.slsa_analyzer.build_tool.gradle import Gradle
1112
from tests.slsa_analyzer.mock_git_utils import prepare_repo_for_testing
1213

@@ -86,3 +87,29 @@ def test_get_group_ids_separate_projects(tmp_path: Path, gradle_tool: Gradle) ->
8687
"io.micronaut.foo",
8788
"io.micronaut.bar",
8889
}
90+
91+
92+
@pytest.mark.parametrize(("timeout", "expected"), [("0", set()), ("invalid", {"io.micronaut"})])
93+
def test_get_group_ids_timeout(tmp_path: Path, gradle_tool: Gradle, timeout: str, expected: set) -> None:
94+
"""Test the timeout configuration on ``get_group_ids`` method."""
95+
repo_dir = tmp_path.joinpath("repo")
96+
repo_dir.mkdir()
97+
98+
with open(repo_dir.joinpath("build.gradle"), "w", encoding="utf-8") as file:
99+
file.write('group = "io.micronaut"')
100+
101+
user_config_path = str(tmp_path.joinpath("config.ini"))
102+
user_config_input = f"""
103+
[builder.gradle.runtime]
104+
build_timeout = {timeout}
105+
"""
106+
with open(user_config_path, "w", encoding="utf-8") as user_config_file:
107+
user_config_file.write(user_config_input)
108+
109+
# We don't have to worry about modifying the ``defaults`` object causing test
110+
# pollution here, since we reload the ``defaults`` object before every test with the
111+
# ``setup_test`` fixture.
112+
load_defaults(user_config_path)
113+
gradle_tool.load_defaults()
114+
115+
assert set(gradle_tool.get_group_ids(str(repo_dir))) == expected

0 commit comments

Comments
 (0)