Skip to content

Commit 18505c8

Browse files
committed
SCANPY-178: The scanner should respect the SONAR_SCANNER_OPTS environment variable
1 parent 33f72bb commit 18505c8

File tree

5 files changed

+99
-3
lines changed

5 files changed

+99
-3
lines changed

src/pysonar_scanner/configuration/environment_variables.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,6 @@ def load_properties_env_variables():
6363
env_var_name = prop.env_variable_name()
6464
if env_var_name in os.environ:
6565
properties[prop.name] = os.environ[env_var_name]
66+
if prop.deprecated:
67+
logging.warning(prop.deprecation_message)
6668
return properties

src/pysonar_scanner/configuration/properties.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@
101101
SONAR_PYTHON_RUFF_REPORT_PATHS = "sonar.python.ruff.reportPaths"
102102
TOML_PATH: Key = "toml-path"
103103

104+
# ============ DEPRECATED ==============
105+
SONAR_SCANNER_OPTS = "sonar.scanner.opts"
106+
104107

105108
@dataclass
106109
class Property:
@@ -112,6 +115,10 @@ class Property:
112115

113116
cli_getter: Optional[Callable[[argparse.Namespace], Any]] = None
114117
"""function to get the value from the CLI arguments namespace. If None, the property is not settable via CLI"""
118+
119+
deprecated: bool = False
120+
121+
deprecation_message: Optional[str] = None
115122

116123
def python_name(self) -> str:
117124
"""Convert Java-style camel case name to Python-style kebab-case name."""
@@ -524,6 +531,13 @@ def env_variable_name(self) -> str:
524531
name=SONAR_MODULES,
525532
default_value=None,
526533
cli_getter=lambda args: args.sonar_modules
534+
),
535+
Property(
536+
name=SONAR_SCANNER_OPTS,
537+
default_value=None,
538+
cli_getter=None,
539+
deprecated=True,
540+
deprecation_message="SONAR_SCANNER_OPTS is deprecated, please use SONAR_SCANNER_JAVA_OPTS instead."
527541
)
528542
]
529543
# fmt: on

src/pysonar_scanner/scannerengine.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from pysonar_scanner.api import EngineInfo, SonarQubeApi
3030
from pysonar_scanner.cache import Cache, CacheFile
31-
from pysonar_scanner.configuration.properties import SONAR_SCANNER_JAVA_OPTS
31+
from pysonar_scanner.configuration.properties import SONAR_SCANNER_JAVA_OPTS, SONAR_SCANNER_OPTS
3232
from pysonar_scanner.exceptions import ChecksumException
3333
from pysonar_scanner.jre import JREResolvedPath
3434

@@ -148,6 +148,7 @@ def __init__(self, jre_path: JREResolvedPath, scanner_engine_path: pathlib.Path)
148148
def run(self, config: dict[str, Any]):
149149
# Extract Java options if present; they must influence the JVM invocation, not the scanner engine itself
150150
java_opts = config.get(SONAR_SCANNER_JAVA_OPTS)
151+
java_opts = config.get(SONAR_SCANNER_OPTS) if not java_opts else java_opts
151152

152153
cmd = self.__build_command(self.jre_path, self.scanner_engine_path, java_opts)
153154
logging.debug(f"Command: {cmd}")
@@ -173,7 +174,7 @@ def __build_command(
173174

174175
def __config_to_json(self, config: dict[str, Any]) -> str:
175176
# SONAR_SCANNER_JAVA_OPTS are properties that shouldn't be passed to the engine, only to the JVM
176-
scanner_properties = [{"key": k, "value": v} for k, v in config.items() if k != SONAR_SCANNER_JAVA_OPTS]
177+
scanner_properties = [{"key": k, "value": v} for k, v in config.items() if k != SONAR_SCANNER_JAVA_OPTS and k!= SONAR_SCANNER_OPTS]
177178
return json.dumps({"scannerProperties": scanner_properties})
178179

179180
def __decompose_java_opts(self, java_opts: str) -> list[str]:

tests/test_environment_variables.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
SONAR_TOKEN,
3232
SONAR_USER_HOME,
3333
SONAR_PROJECT_KEY,
34+
SONAR_SCANNER_OPTS,
3435
)
3536

3637

@@ -134,3 +135,27 @@ def test_environment_variables_priority_over_json_params(self):
134135
}
135136
self.assertEqual(len(properties), 3)
136137
self.assertDictEqual(properties, expected_properties)
138+
139+
@patch("pysonar_scanner.configuration.environment_variables.logging")
140+
def test_SONAR_SCANNER_OPTS(self, mock_logging):
141+
env = {
142+
"SONAR_TOKEN": "my-token",
143+
"SONAR_HOST_URL": "https://sonarqube.example.com",
144+
"SONAR_USER_HOME": "/custom/sonar/home",
145+
"SONAR_SCANNER_OPTS": "-Xmx1024m -XX:MaxPermSize=256m",
146+
"SONAR_REGION": "us",
147+
}
148+
with patch.dict("os.environ", env, clear=True):
149+
properties = environment_variables.load()
150+
expected_properties = {
151+
SONAR_TOKEN: "my-token",
152+
SONAR_HOST_URL: "https://sonarqube.example.com",
153+
SONAR_USER_HOME: "/custom/sonar/home",
154+
SONAR_SCANNER_OPTS: "-Xmx1024m -XX:MaxPermSize=256m",
155+
SONAR_REGION: "us",
156+
}
157+
self.assertEqual(len(properties), 5)
158+
self.assertDictEqual(properties, expected_properties)
159+
mock_logging.warning.assert_called_once_with(
160+
"SONAR_SCANNER_OPTS is deprecated, please use SONAR_SCANNER_JAVA_OPTS instead.",
161+
)

tests/unit/test_scannerengine.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from pysonar_scanner import cache
3131
from pysonar_scanner import scannerengine
32-
from pysonar_scanner.configuration.properties import SONAR_SCANNER_JAVA_OPTS
32+
from pysonar_scanner.configuration.properties import SONAR_SCANNER_JAVA_OPTS, SONAR_SCANNER_OPTS
3333
from pysonar_scanner.exceptions import ChecksumException
3434
from pysonar_scanner.scannerengine import (
3535
LogLine,
@@ -280,6 +280,60 @@ def test_java_opts_edge_cases(self, execute_mock):
280280
self.assertEqual(actual_command[-2], "-jar")
281281
self.assertEqual(actual_command[-1], str(scanner_engine_mock))
282282

283+
@patch("pysonar_scanner.scannerengine.CmdExecutor")
284+
def test_command_building_with_scanner_opts(self, execute_mock):
285+
config = {
286+
"sonar.token": "myToken",
287+
"sonar.projectKey": "myProjectKey",
288+
SONAR_SCANNER_OPTS: "-Xmx1024m -XX:MaxPermSize=256m",
289+
}
290+
291+
java_path = pathlib.Path("jre/bin/java")
292+
jre_resolve_path_mock = Mock()
293+
jre_resolve_path_mock.path = java_path
294+
scanner_engine_mock = pathlib.Path("/test/scanner-engine.jar")
295+
296+
scannerengine.ScannerEngine(jre_resolve_path_mock, scanner_engine_mock).run(config)
297+
298+
called_args = execute_mock.call_args[0]
299+
actual_command = called_args[0]
300+
301+
expected_command = [
302+
str(java_path),
303+
"-Xmx1024m",
304+
"-XX:MaxPermSize=256m",
305+
"-jar",
306+
str(scanner_engine_mock),
307+
]
308+
self.assertEqual(actual_command, expected_command)
309+
310+
@patch("pysonar_scanner.scannerengine.CmdExecutor")
311+
def test_command_ignore_scanner_opts_for_java_opts(self, execute_mock):
312+
config = {
313+
"sonar.token": "myToken",
314+
"sonar.projectKey": "myProjectKey",
315+
SONAR_SCANNER_OPTS: "-Xmx512m -XX:MaxPermSize=128m",
316+
SONAR_SCANNER_JAVA_OPTS: "-Xmx1024m -XX:MaxPermSize=256m",
317+
}
318+
319+
java_path = pathlib.Path("jre/bin/java")
320+
jre_resolve_path_mock = Mock()
321+
jre_resolve_path_mock.path = java_path
322+
scanner_engine_mock = pathlib.Path("/test/scanner-engine.jar")
323+
324+
scannerengine.ScannerEngine(jre_resolve_path_mock, scanner_engine_mock).run(config)
325+
326+
called_args = execute_mock.call_args[0]
327+
actual_command = called_args[0]
328+
329+
expected_command = [
330+
str(java_path),
331+
"-Xmx1024m",
332+
"-XX:MaxPermSize=256m",
333+
"-jar",
334+
str(scanner_engine_mock),
335+
]
336+
self.assertEqual(actual_command, expected_command)
283337

284338
class TestScannerEngineProvisioner(pyfakefs.TestCase):
285339
def setUp(self):

0 commit comments

Comments
 (0)