Skip to content

Commit a0cafd6

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

File tree

5 files changed

+221
-44
lines changed

5 files changed

+221
-44
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: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
# along with this program; if not, write to the Free Software Foundation,
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
20-
import time
2120
import argparse
21+
import time
2222
from dataclasses import dataclass
2323
from typing import Any, Callable, Optional
2424

@@ -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:
@@ -113,6 +116,10 @@ class Property:
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"""
115118

119+
deprecated: bool = False
120+
121+
deprecation_message: Optional[str] = None
122+
116123
def python_name(self) -> str:
117124
"""Convert Java-style camel case name to Python-style kebab-case name."""
118125
result = []
@@ -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: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@
2020
import json
2121
import logging
2222
import pathlib
23-
from dataclasses import dataclass
2423
import shlex
25-
from subprocess import Popen, PIPE
24+
from dataclasses import dataclass
25+
from subprocess import PIPE, Popen
2626
from threading import Thread
2727
from typing import IO, Any, Callable, Optional
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 (
32+
SONAR_SCANNER_JAVA_OPTS,
33+
SONAR_SCANNER_OPTS,
34+
)
3235
from pysonar_scanner.exceptions import ChecksumException
3336
from pysonar_scanner.jre import JREResolvedPath
3437

@@ -97,7 +100,9 @@ def __log_output(self, stream: IO[bytes]):
97100
log_line = parse_log_line(decoded_line)
98101
self.log_line_listener(log_line)
99102

100-
def __process_output(self, output_thread: Thread, error_thread: Thread, process: Popen) -> int:
103+
def __process_output(
104+
self, output_thread: Thread, error_thread: Thread, process: Popen
105+
) -> int:
101106
output_thread.start()
102107
error_thread.start()
103108
process.wait()
@@ -117,7 +122,9 @@ def provision(self) -> pathlib.Path:
117122
if scanner_file is not None:
118123
return scanner_file.filepath
119124
# Retry once in case the checksum failed due to the scanner engine being updated between getting the checksum and downloading the jar
120-
logging.warning("Something went wrong while downloading the scanner engine. Retrying...")
125+
logging.warning(
126+
"Something went wrong while downloading the scanner engine. Retrying..."
127+
)
121128
scanner_file = self.__download_and_verify()
122129
if scanner_file is not None:
123130
return scanner_file.filepath
@@ -132,7 +139,9 @@ def __download_and_verify(self) -> Optional[CacheFile]:
132139
self.__download_scanner_engine(cache_file, engine_info)
133140
return cache_file if cache_file.is_valid() else None
134141

135-
def __download_scanner_engine(self, cache_file: CacheFile, engine_info: EngineInfo) -> None:
142+
def __download_scanner_engine(
143+
self, cache_file: CacheFile, engine_info: EngineInfo
144+
) -> None:
136145
with cache_file.open(mode="wb") as f:
137146
if engine_info.download_url is not None:
138147
self.api.download_file_from_url(engine_info.download_url, f)
@@ -148,6 +157,7 @@ def __init__(self, jre_path: JREResolvedPath, scanner_engine_path: pathlib.Path)
148157
def run(self, config: dict[str, Any]):
149158
# Extract Java options if present; they must influence the JVM invocation, not the scanner engine itself
150159
java_opts = config.get(SONAR_SCANNER_JAVA_OPTS)
160+
java_opts = config.get(SONAR_SCANNER_OPTS) if not java_opts else java_opts
151161

152162
cmd = self.__build_command(self.jre_path, self.scanner_engine_path, java_opts)
153163
logging.debug(f"Command: {cmd}")
@@ -173,7 +183,11 @@ def __build_command(
173183

174184
def __config_to_json(self, config: dict[str, Any]) -> str:
175185
# 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]
186+
scanner_properties = [
187+
{"key": k, "value": v}
188+
for k, v in config.items()
189+
if k != SONAR_SCANNER_JAVA_OPTS and k != SONAR_SCANNER_OPTS
190+
]
177191
return json.dumps({"scannerProperties": scanner_properties})
178192

179193
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+
)

0 commit comments

Comments
 (0)