Skip to content

Commit f893e08

Browse files
authored
CM-44581 - Add support for running Gradle restore command on all subprojects (#278)
1 parent a0c04d3 commit f893e08

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

cycode/cli/commands/scan/scan_command.py

+12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from cycode.cli.consts import (
1414
ISSUE_DETECTED_STATUS_CODE,
1515
NO_ISSUES_STATUS_CODE,
16+
SCA_GRADLE_ALL_SUB_PROJECTS_FLAG,
1617
SCA_SKIP_RESTORE_DEPENDENCIES_FLAG,
1718
)
1819
from cycode.cli.models import Severity
@@ -110,6 +111,15 @@
110111
type=bool,
111112
required=False,
112113
)
114+
@click.option(
115+
f'--{SCA_GRADLE_ALL_SUB_PROJECTS_FLAG}',
116+
is_flag=True,
117+
default=False,
118+
help='When specified, Cycode will run gradle restore command for all sub projects. '
119+
'Should run from root project directory ONLY!',
120+
type=bool,
121+
required=False,
122+
)
113123
@click.pass_context
114124
def scan_command(
115125
context: click.Context,
@@ -124,6 +134,7 @@ def scan_command(
124134
report: bool,
125135
no_restore: bool,
126136
sync: bool,
137+
gradle_all_sub_projects: bool,
127138
) -> int:
128139
"""Scans for Secrets, IaC, SCA or SAST violations."""
129140
add_breadcrumb('scan')
@@ -145,6 +156,7 @@ def scan_command(
145156
context.obj['monitor'] = monitor
146157
context.obj['report'] = report
147158
context.obj[SCA_SKIP_RESTORE_DEPENDENCIES_FLAG] = no_restore
159+
context.obj[SCA_GRADLE_ALL_SUB_PROJECTS_FLAG] = gradle_all_sub_projects
148160

149161
_sca_scan_to_context(context, sca_scan)
150162

cycode/cli/consts.py

+2
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,5 @@
224224
SCA_SHORTCUT_DEPENDENCY_PATHS = 2
225225

226226
SCA_SKIP_RESTORE_DEPENDENCIES_FLAG = 'no-restore'
227+
228+
SCA_GRADLE_ALL_SUB_PROJECTS_FLAG = 'gradle-all-sub-projects'
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,70 @@
11
import os
2-
from typing import List
2+
import re
3+
from typing import List, Optional, Set
34

45
import click
56

7+
from cycode.cli.consts import SCA_GRADLE_ALL_SUB_PROJECTS_FLAG
68
from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
79
from cycode.cli.models import Document
10+
from cycode.cli.utils.path_utils import get_path_from_context
11+
from cycode.cli.utils.shell_executor import shell
812

913
BUILD_GRADLE_FILE_NAME = 'build.gradle'
1014
BUILD_GRADLE_KTS_FILE_NAME = 'build.gradle.kts'
1115
BUILD_GRADLE_DEP_TREE_FILE_NAME = 'gradle-dependencies-generated.txt'
16+
BUILD_GRADLE_ALL_PROJECTS_TIMEOUT = 180
17+
BUILD_GRADLE_ALL_PROJECTS_COMMAND = ['gradle', 'projects']
18+
ALL_PROJECTS_REGEX = r"[+-]{3} Project '(.*?)'"
1219

1320

1421
class RestoreGradleDependencies(BaseRestoreDependencies):
15-
def __init__(self, context: click.Context, is_git_diff: bool, command_timeout: int) -> None:
22+
def __init__(
23+
self, context: click.Context, is_git_diff: bool, command_timeout: int, projects: Optional[Set[str]] = None
24+
) -> None:
1625
super().__init__(context, is_git_diff, command_timeout, create_output_file_manually=True)
26+
if projects is None:
27+
projects = set()
28+
self.projects = self.get_all_projects() if self.is_gradle_sub_projects() else projects
29+
30+
def is_gradle_sub_projects(self) -> bool:
31+
return self.context.obj.get(SCA_GRADLE_ALL_SUB_PROJECTS_FLAG)
1732

1833
def is_project(self, document: Document) -> bool:
1934
return document.path.endswith(BUILD_GRADLE_FILE_NAME) or document.path.endswith(BUILD_GRADLE_KTS_FILE_NAME)
2035

2136
def get_commands(self, manifest_file_path: str) -> List[List[str]]:
22-
return [['gradle', 'dependencies', '-b', manifest_file_path, '-q', '--console', 'plain']]
37+
return (
38+
self.get_commands_for_sub_projects(manifest_file_path)
39+
if self.is_gradle_sub_projects()
40+
else [['gradle', 'dependencies', '-b', manifest_file_path, '-q', '--console', 'plain']]
41+
)
2342

2443
def get_lock_file_name(self) -> str:
2544
return BUILD_GRADLE_DEP_TREE_FILE_NAME
2645

2746
def verify_restore_file_already_exist(self, restore_file_path: str) -> bool:
2847
return os.path.isfile(restore_file_path)
48+
49+
def get_working_directory(self, document: Document) -> Optional[str]:
50+
return get_path_from_context(self.context) if self.is_gradle_sub_projects() else None
51+
52+
def get_all_projects(self) -> Set[str]:
53+
projects_output = shell(
54+
command=BUILD_GRADLE_ALL_PROJECTS_COMMAND,
55+
timeout=BUILD_GRADLE_ALL_PROJECTS_TIMEOUT,
56+
working_directory=get_path_from_context(self.context),
57+
)
58+
59+
projects = re.findall(ALL_PROJECTS_REGEX, projects_output)
60+
61+
return set(projects)
62+
63+
def get_commands_for_sub_projects(self, manifest_file_path: str) -> List[List[str]]:
64+
project_name = os.path.basename(os.path.dirname(manifest_file_path))
65+
project_name = f':{project_name}'
66+
return (
67+
[['gradle', f'{project_name}:dependencies', '-q', '--console', 'plain']]
68+
if project_name in self.projects
69+
else []
70+
)

cycode/cli/utils/scan_batch.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def run_parallel_batched_scan(
5050
progress_bar: 'BaseProgressBar',
5151
) -> Tuple[Dict[str, 'CliError'], List['LocalScanResult']]:
5252
max_size = consts.SCAN_BATCH_MAX_SIZE_IN_BYTES.get(scan_type, consts.DEFAULT_SCAN_BATCH_MAX_SIZE_IN_BYTES)
53-
batches = split_documents_into_batches(documents, max_size)
53+
54+
batches = [documents] if scan_type == consts.SCA_SCAN_TYPE else split_documents_into_batches(documents, max_size)
5455

5556
progress_bar.set_section_length(ScanProgressBarSection.SCAN, len(batches)) # * 3
5657
# TODO(MarshalX): we should multiply the count of batches in SCAN section because each batch has 3 steps:

0 commit comments

Comments
 (0)