|
8 | 8 |
|
9 | 9 | import logging |
10 | 10 | import os |
| 11 | +import subprocess # nosec B404 |
11 | 12 |
|
| 13 | +import macaron |
12 | 14 | from macaron.config.defaults import defaults |
13 | 15 | from macaron.config.global_config import global_config |
14 | 16 | from macaron.dependency_analyzer import DependencyAnalyzer, DependencyAnalyzerError, DependencyTools |
@@ -135,3 +137,107 @@ def get_dep_analyzer(self, repo_path: str) -> CycloneDxGradle: |
135 | 137 | ) |
136 | 138 |
|
137 | 139 | raise DependencyAnalyzerError(f"Unsupported SBOM generator for Gradle: {tool_name}.") |
| 140 | + |
| 141 | + def get_gradle_exec(self, repo_path: str) -> str: |
| 142 | + """Get the Gradle executable for the repo. |
| 143 | +
|
| 144 | + Parameters |
| 145 | + ---------- |
| 146 | + repo_path: str |
| 147 | + The absolute path to a repository containing Gradle projects. |
| 148 | +
|
| 149 | + Returns |
| 150 | + ------- |
| 151 | + str |
| 152 | + The absolute path to the Gradle executable. |
| 153 | + """ |
| 154 | + # We try to use the gradlew that comes with the repository first. |
| 155 | + repo_gradlew = os.path.join(repo_path, "gradlew") |
| 156 | + if os.path.isfile(repo_gradlew) and os.access(repo_gradlew, os.X_OK): |
| 157 | + return repo_gradlew |
| 158 | + |
| 159 | + # We use Macaron's built-in gradlew as a fallback option. |
| 160 | + return os.path.join(os.path.join(macaron.MACARON_PATH, "resources"), "gradlew") |
| 161 | + |
| 162 | + def get_group_ids(self, repo_path: str) -> set[str]: |
| 163 | + """Get the group ids of all Gradle projects in a repository. |
| 164 | +
|
| 165 | + A Gradle project is a directory containing a ``build.gradle`` file. |
| 166 | + According to the Gradle's documentation, there is a one-to-one mapping between |
| 167 | + a "project" and a ``build.gradle`` file. |
| 168 | + See: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html. |
| 169 | +
|
| 170 | + Note: This method makes the assumption that projects nested in a parent project |
| 171 | + directory has the same group id with the parent. This behavior is consistent with |
| 172 | + the behavior of the ``get_build_dirs`` method. |
| 173 | +
|
| 174 | + Parameters |
| 175 | + ---------- |
| 176 | + repo_path: str |
| 177 | + The absolute path to a repository containing Gradle projects. |
| 178 | +
|
| 179 | + Returns |
| 180 | + ------- |
| 181 | + set[str] |
| 182 | + The set of group ids of all Gradle projects in the repository. |
| 183 | + """ |
| 184 | + gradle_exec = self.get_gradle_exec(repo_path) |
| 185 | + group_ids = set() |
| 186 | + |
| 187 | + for gradle_project_relpath in self.get_build_dirs(repo_path): |
| 188 | + gradle_project_path = os.path.join(repo_path, gradle_project_relpath) |
| 189 | + group_id = self.get_group_id( |
| 190 | + gradle_exec=gradle_exec, |
| 191 | + project_path=gradle_project_path, |
| 192 | + ) |
| 193 | + if group_id: |
| 194 | + group_ids.add(group_id) |
| 195 | + |
| 196 | + return group_ids |
| 197 | + |
| 198 | + def get_group_id(self, gradle_exec: str, project_path: str) -> str | None: |
| 199 | + """Get the group id of a Gradle project. |
| 200 | +
|
| 201 | + A Gradle project is a directory containing a ``build.gradle`` file. |
| 202 | + According to the Gradle's documentation, there is a one-to-one mapping between |
| 203 | + a "project" and a ``build.gradle`` file. |
| 204 | + See: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html. |
| 205 | +
|
| 206 | + Parameters |
| 207 | + ---------- |
| 208 | + gradle_exec: str |
| 209 | + The absolute path to the Gradle executable. |
| 210 | +
|
| 211 | + project_path : str |
| 212 | + The absolute path to the Gradle project. |
| 213 | +
|
| 214 | + Returns |
| 215 | + ------- |
| 216 | + str | None |
| 217 | + The group id of the project, if exists. |
| 218 | + """ |
| 219 | + try: |
| 220 | + result = subprocess.run( # nosec B603 |
| 221 | + [gradle_exec, "properties"], |
| 222 | + capture_output=True, |
| 223 | + cwd=project_path, |
| 224 | + check=False, |
| 225 | + ) |
| 226 | + except (subprocess.CalledProcessError, OSError) as error: |
| 227 | + logger.debug("Could not capture the group id of the Gradle project at %s", project_path) |
| 228 | + logger.debug("Error: %s", error) |
| 229 | + return None |
| 230 | + |
| 231 | + if result.returncode == 0: |
| 232 | + lines = result.stdout.decode().split("\n") |
| 233 | + for line in lines: |
| 234 | + if line.startswith("group: "): |
| 235 | + group = line.replace("group: ", "") |
| 236 | + # The value of group here can be an empty string. |
| 237 | + if group: |
| 238 | + return group |
| 239 | + break |
| 240 | + |
| 241 | + logger.debug("Could not capture the group id of the repo at %s", project_path) |
| 242 | + logger.debug("Stderr:\n%s", result.stderr) |
| 243 | + return None |
0 commit comments