diff --git a/.cirrus.yml b/.cirrus.yml index c63f6cf4..616c89d4 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -78,7 +78,7 @@ poetry_win_install: &POETRY_WIN_INSTALL poetry_cache_template: &POETRY_CACHE poetry_cache: folder: ~/.cache/poetry/ - fingerprint_script: cat poetry.lock + fingerprint_script: cat poetry.lock .poetry_template: &POETRY_TEMPLATE <<: *POETRY_CACHE @@ -152,6 +152,14 @@ formatting_task: - poetry run licenseheaders -t license_header.tmpl -o "SonarSource SA" -y 2011-2024 -n "Sonar Scanner Python" -E .py -d tests/ - git diff --name-only --exit-code ./src ./tests +documentation_task: + <<: *POETRY_LINUX_TEMPLATE + alias: documentation + name: "CLI Documentation" + cli_docs_script: + - poetry run python tools/generate_cli_documentation.py + - git diff --exit-code CLI_ARGS.md + analysis_linux_task: <<: *POETRY_LINUX_TEMPLATE alias: analysis diff --git a/CLI_ARGS.md b/CLI_ARGS.md new file mode 100644 index 00000000..20285aff --- /dev/null +++ b/CLI_ARGS.md @@ -0,0 +1,100 @@ +# Sonar Scanner Python CLI Arguments + +## Authentication + +| Option | Description | +| ------ | ----------- | +| `--sonar-host-url`, `-Dsonar.host.url` | SonarQube Server base URL. For example, http://localhost:9000 for a local instance of SonarQube Server | +| `--sonar-organization`, `-Dsonar.organization` | The key of the organization to which the project belongs | +| `--sonar-region`, `-Dsonar.region` | The region to contact, only for SonarQube Cloud | +| `-t`, `--token`, `--sonar-token`, `-Dsonar.token` | Token used to authenticate against the SonarQube Server or SonarQube Cloud | + +## Project Configuration + +| Option | Description | +| ------ | ----------- | +| `--sonar-project-base-dir`, `-Dsonar.projectBaseDir` | Directory containing the project to be analyzed. Default is the current directory | +| `--sonar-project-description`, `-Dsonar.projectDescription` | Description of the project | +| `--sonar-project-key`, `-Dsonar.projectKey` | Key of the project that usually corresponds to the project name in SonarQube | +| `--sonar-project-name`, `-Dsonar.projectName` | Name of the project in SonarQube | +| `--sonar-project-version`, `-Dsonar.projectVersion` | Version of the project | +| `--sonar-sources`, `-Dsonar.sources` | The analysis scope for main source code (non-test code) in the project | +| `--sonar-tests`, `-Dsonar.tests` | The analysis scope for test source code in the project | + +## Analysis Configuration + +| Option | Description | +| ------ | ----------- | +| `--sonar-filesize-limit`, `-Dsonar.filesize.limit` | Sets the limit in MB for files to be discarded from the analysis scope if the size is greater than specified | +| `--sonar-python-version`, `-Dsonar.python.version` | Python version used for the project | +| `-v`, `--verbose`, `--no-verbose`, `--sonar-verbose`, `--no-sonar-verbose`, `-Dsonar.verbose` | Increase output verbosity | + +## Report Integration + +| Option | Description | +| ------ | ----------- | +| `--sonar-external-issues-report-paths`, `-Dsonar.externalIssuesReportPaths` | Comma-delimited list of paths to generic issue reports | +| `--sonar-python-bandit-report-paths`, `--bandit-report-paths`, `-Dsonar.python.bandit.reportPaths` | Comma-separated bandit report paths, relative to project's root | +| `--sonar-python-coverage-report-paths`, `--coverage-report-paths`, `-Dsonar.python.coverage.reportPaths` | Comma-delimited list of paths to coverage reports in the Cobertura XML format. | +| `--sonar-python-flake8-report-paths`, `--flake8-report-paths`, `-Dsonar.python.flake8.reportPaths` | Comma-separated flake8 report paths, relative to project's root | +| `--sonar-python-mypy-report-paths`, `--mypy-report-paths`, `-Dsonar.python.mypy.reportPaths` | Comma-separated mypy report paths, relative to project's root | +| `--sonar-python-pylint-report-path`, `--pylint-report-path`, `-Dsonar.python.pylint.reportPath` | Path to third-parties issues report file for pylint | +| `--sonar-python-ruff-report-paths`, `--ruff-report-paths`, `-Dsonar.python.ruff.reportPaths` | Comma-separated ruff report paths, relative to project's root | +| `--sonar-python-xunit-report-path`, `--xunit-report-path`, `-Dsonar.python.xunit.reportPath` | Path to the report of test execution, relative to project's root | +| `--sonar-python-xunit-skip-details`, `--no-sonar-python-xunit-skip-details`, `--xunit-skip-details`, `--no-xunit-skip-details` | When enabled, the test execution statistics is provided only on project level | +| `--sonar-sarif-report-paths`, `-Dsonar.sarifReportPaths` | Comma-delimited list of paths to SARIF issue reports | + +## Other + +| Option | Description | +| ------ | ----------- | +| `--skip-jre-provisioning`, `-Dsonar.scanner.skipJreProvisioning` | If provided, the provisioning of the JRE will be skipped | +| `--sonar-branch-name`, `-Dsonar.branch.name` | Name of the branch being analyzed | +| `--sonar-build-string`, `-Dsonar.buildString` | The string passed with this property will be stored with the analysis and available in the results of api/project_analyses/search, thus allowing you to later identify a specific analysis and obtain its key for use with api/new_code_periods/set on the SPECIFIC_ANALYSIS type | +| `--sonar-cpd-python-minimum-lines`, `-Dsonar.cpd.python.minimumLines` | Minimum number of tokens to be considered as a duplicated block of code | +| `--sonar-cpd-python-minimum-tokens`, `-Dsonar.cpd.python.minimumTokens` | Minimum number of tokens to be considered as a duplicated block of code | +| `--sonar-links-ci`, `-Dsonar.links.ci` | The URL of the continuous integration system used | +| `--sonar-links-homepage`, `-Dsonar.links.homepage` | The URL of the build project home page | +| `--sonar-links-issue`, `-Dsonar.links.issue` | The URL to the issue tracker being used | +| `--sonar-links-scm`, `-Dsonar.links.scm` | The URL of the build project source code repository | +| `--sonar-log-level`, `-Dsonar.log.level` | Log level during the analysis | +| `--sonar-modules`, `-Dsonar.modules` | Comma-delimited list of modules to analyze | +| `--sonar-newcode-reference-branch`, `-Dsonar.newCode.referenceBranch` | Reference branch for new code definition | +| `--sonar-pullrequest-base`, `-Dsonar.pullrequest.base` | Base branch of the pull request being analyzed | +| `--sonar-pullrequest-branch`, `-Dsonar.pullrequest.branch` | Branch of the pull request being analyzed | +| `--sonar-pullrequest-key`, `-Dsonar.pullrequest.key` | Key of the pull request being analyzed | +| `--sonar-python-skip-unchanged`, `--no-sonar-python-skip-unchanged` | Override the SonarQube configuration of skipping or not the analysis of unchanged Python files | +| `--sonar-qualitygate-timeout`, `-Dsonar.qualitygate.timeout` | The number of seconds that the scanner should wait for a report to be processed | +| `--sonar-qualitygate-wait`, `--no-sonar-qualitygate-wait` | Forces the analysis step to poll the server instance and wait for the Quality Gate status | +| `--sonar-scanner-api-url`, `-Dsonar.scanner.apiUrl` | Base URL for all REST-compliant API calls, https://api.sonarcloud.io for example | +| `--sonar-scanner-arch`, `-Dsonar.scanner.arch` | Architecture on which the scanner will be running | +| `--sonar-scanner-cloud-url`, `-Dsonar.scanner.cloudUrl` | SonarQube Cloud base URL, https://sonarcloud.io for example | +| `--sonar-scanner-connect-timeout`, `-Dsonar.scanner.connectTimeout` | Time period to establish connections with the server (in seconds) | +| `--sonar-scanner-internal-dump-to-file`, `-Dsonar.scanner.internal.dumpToFile` | Filename where the input to the scanner engine will be dumped. Useful for debugging | +| `--sonar-scanner-internal-sq-version`, `-Dsonar.scanner.internal.sqVersion` | Emulate the result of the call to get SQ server version. Useful for debugging with --sonar-scanner-internal-dump-to-file | +| `--sonar-scanner-java-exe-path`, `-Dsonar.scanner.javaExePath` | If defined, the scanner engine will be run with this JRE | +| `--sonar-scanner-java-opts`, `-Dsonar.scanner.javaOpts` | Arguments provided to the JVM when running the scanner | +| `--sonar-scanner-keystore-password`, `-Dsonar.scanner.keystorePassword` | Password to access the keystore | +| `--sonar-scanner-keystore-path`, `-Dsonar.scanner.keystorePath` | Path to the keystore containing the client certificates used by the scanner. By default, /ssl/keystore.p12 | +| `--sonar-scanner-metadata-filepath`, `-Dsonar.scanner.metadataFilepath` | Sets the location where the scanner writes the report-task.txt file containing among other things the ceTaskId | +| `--sonar-scanner-os`, `-Dsonar.scanner.os` | OS running the scanner | +| `--sonar-scanner-proxy-host`, `-Dsonar.scanner.proxyHost` | Proxy host | +| `--sonar-scanner-proxy-password`, `-Dsonar.scanner.proxyPassword` | Proxy password | +| `--sonar-scanner-proxy-port`, `-Dsonar.scanner.proxyPort` | Proxy port | +| `--sonar-scanner-proxy-user`, `-Dsonar.scanner.proxyUser` | Proxy user | +| `--sonar-scanner-response-timeout`, `-Dsonar.scanner.responseTimeout` | Time period required to process an HTTP call: from sending a request to receiving a response (in seconds) | +| `--sonar-scanner-socket-timeout`, `-Dsonar.scanner.socketTimeout` | Maximum time of inactivity between two data packets when exchanging data with the server (in seconds) | +| `--sonar-scanner-truststore-password`, `-Dsonar.scanner.truststorePassword` | Password to access the truststore | +| `--sonar-scanner-truststore-path`, `-Dsonar.scanner.truststorePath` | Path to the keystore containing trusted server certificates, used by the Scanner in addition to OS and the built-in certificates | +| `--sonar-scm-exclusions-disabled`, `--no-sonar-scm-exclusions-disabled` | Defines whether files ignored by the SCM, e.g., files listed in .gitignore, will be excluded from the analysis or not | +| `--sonar-scm-force-reload-all`, `--no-sonar-scm-force-reload-all` | Set this property to true to load blame information for all files, which may significantly increase analysis duration | +| `--sonar-scm-revision`, `-Dsonar.scm.revision` | Overrides the revision, for instance, the Git sha1, displayed in analysis results | +| `--sonar-source-encoding`, `-Dsonar.sourceEncoding` | Encoding of the source files. For example, UTF-8, MacRoman, Shift_JIS | +| `--sonar-user-home`, `-Dsonar.userHome` | Base sonar directory, ~/.sonar by default | +| `--sonar-working-directory`, `-Dsonar.working.directory` | Path to the working directory used by the Sonar scanner during a project analysis to store temporary data | +| `--toml-path` | Path to the pyproject.toml file. If not provided, it will look in the SONAR_PROJECT_BASE_DIR | +| `-Dsonar.python.skipUnchanged` | Equivalent to --sonar-python-skip-unchanged | +| `-Dsonar.python.xunit.skipDetails` | Equivalent to -Dsonar.python.xunit.skipDetails | +| `-Dsonar.qualitygate.wait` | Equivalent to --sonar-qualitygate-wait | +| `-Dsonar.scm.exclusions.disabled` | Equivalent to --sonar-scm-exclusions-disabled | +| `-Dsonar.scm.forceReloadAll` | Equivalent to --sonar-scm-force-reload-all | diff --git a/README.md b/README.md index 588264e9..3ba0effc 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ # pysonar -A wrapper around SonarScanner CLI, available on PyPI. - -# Disclaimer - -This project is currently in beta and APIs are subject to change. -These changes include configuration parameter names. +A Python scanner for SonarQube, available on PyPI. # Requirements - - SonarQube v9.9 or higher - - Python 3.8 or above + - SonarQube v10.6 or above + - Python 3.9 or above # Installation @@ -28,25 +23,33 @@ It assumes a running SonarQube server or a project configured on SonarCloud. In order for the analysis to run, analysis properties need to be defined. There are multiple ways of providing these properties, described below in descending order of priority: -* Through CLI arguments to the `pysonar` command -* Under the `[tool.sonar]` key of the `pyproject.toml` file -* Through common properties extracted from the `pyproject.toml` -* In a dedicated `sonar-project.properties` file -* Through environment variables +1. Through CLI arguments to the `pysonar` command +2. Environment variables for individual properties (e.g. `SONAR_TOKEN`, `SONAR_VERBOSE`, `SONAR_HOST_URL`, ...) +3. Generic environment variable `SONAR_SCANNER_JSON_PARAMS` +4. Under the `[tool.sonar]` key of the `pyproject.toml` file +5. In a dedicated `sonar-project.properties` file +6. Through common properties extracted from the `pyproject.toml` ### Through CLI arguments Analysis properties can be provided as CLI arguments to the `pysonar` command. -They follow the same convention as when running the SonarScanner CLI directly -(see [documentation](https://docs.sonarsource.com/sonarqube/9.9/analyzing-source-code/scanners/sonarscanner/#running-from-zip-file)). +They can be provided in a similar way as when running the SonarScanner CLI directly +(see [documentation](https://docs.sonarsource.com/sonarqube-server/2025.1/analyzing-source-code/scanners/sonarscanner/#running-from-zip-file)). This means that analysis properties provided that way should be prepended with `-D`, for instance: ``` -$ pysonar -Dsonar.login=myAuthenticationToken +$ pysonar -Dsonar.token=myAuthenticationToken ``` -You can use all the argument allowed by __SonarScanner__. -For more information on __SonarScanner__ please refer to the [SonarScanner documentation](https://docs.sonarsource.com/sonarqube/9.9/analyzing-source-code/scanners/sonarscanner/) +You can use all the arguments allowed by __SonarScanner__. +For more information on __SonarScanner__ please refer to the [SonarScanner documentation](https://docs.sonarsource.com/sonarqube-server/2025.1/analyzing-source-code/analysis-parameters/). + +Additionally, some common properties can be provided using a shorter alias, such as: +``` +pysonar --token "MyToken" +``` + +See [CLI_ARGS](https://github.com/SonarSource/sonar-scanner-python/blob/master/CLI_ARGS.md) for more details. ### With a pyproject.toml file @@ -70,37 +73,43 @@ projectKey=my:project #sourceEncoding=UTF-8 ``` -The configuration parameters can be found in the [SonarQube documentation](https://docs.sonarsource.com/sonarqube/9.9/analyzing-source-code/analysis-parameters/). +The configuration parameters can be found in the [SonarQube documentation](https://docs.sonarsource.com/sonarqube-server/2025.1/analyzing-source-code/analysis-parameters/). In the `pyproject.toml` file the prefix `sonar.` for parameter keys should be omitted. For example, `sonar.scm.provider` in the documentation will become `scm.provider` in the `pyproject.toml` file. -By default, the scanner will expect the `pyproject.toml` file to be present in the current directory. -However, its path can be provided manually through the `toml.path` ([SCANPY-40](https://sonarsource.atlassian.net/jira/software/c/projects/PYSCAN/issues/PYSCAN-40)) CLI argument as well as through the `sonar.projectHome` argument. For instance: +Properties in `pyproject.toml` files are expected to be provided in camel case. However, kebab case is also accepted: ``` -pysonar --toml.path "path/to/pyproject.toml" +[tool.sonar] +project-key=My Project key # valid alias for projectKey ``` -Or: +By default, the scanner will expect the `pyproject.toml` file to be present in the current directory. However, its path can be provided manually through the `toml-path` CLI argument as well as through the `sonar.projectBaseDir` argument. For instance: ``` -pysonar --sonar-project-home "path/to/projectHome" +pysonar --toml-path "path/to/pyproject.toml" ``` +Or: -### Through project properties extracted from the `pyproject.toml` +``` +pysonar --sonar-project-base-dir "path/to/projectBaseDir" +``` + +Or: -When a `pyproject.toml` file is available, it is possible to set the `-read-project-config` flag -to allow the scanner to deduce analysis properties from the project configuration. +``` +pysonar -Dsonar.projectBaseDir="path/to/projectBaseDir" +``` -This is currently supported only for projects using `poetry`. +### Through project properties extracted from the `pyproject.toml` -The Sonar scanner will then use the project name and version defined through Poetry, they won't have to be duplicated under a dedicated `tool.sonar` section. +When a `pyproject.toml` file is available, the scanner can deduce analysis properties from the project configuration. This is currently supported only for projects using `poetry`. ### With a sonar-project.properties file -Exactly like [__SonarScanner__](https://docs.sonarsource.com/sonarqube/9.9/analyzing-source-code/scanners/sonarscanner/), +Exactly like [__SonarScanner__](https://docs.sonarsource.com/sonarqube-server/2025.1/analyzing-source-code/scanners/sonarscanner/), the analysis can also be configured with a `sonar-project.properties` file: ``` @@ -130,7 +139,7 @@ $ export SONAR_HOST_URL="http://localhost:9000" $ pysonar ``` -See the __SonarScanner__ [documentation](https://docs.sonarsource.com/sonarqube/9.9/analyzing-source-code/scanners/sonarscanner/) for more information. +See the __SonarScanner__ [documentation](https://docs.sonarsource.com/sonarqube-server/2025.1/setup-and-upgrade/environment-variables/) for more information. # Installation from testPyPI diff --git a/src/pysonar_scanner/configuration/cli.py b/src/pysonar_scanner/configuration/cli.py index c80549ca..cd509d0e 100644 --- a/src/pysonar_scanner/configuration/cli.py +++ b/src/pysonar_scanner/configuration/cli.py @@ -50,6 +50,11 @@ def load(cls) -> dict[str, any]: @classmethod def __parse_cli_args(cls) -> tuple[argparse.Namespace, list[str]]: + parser = cls.__create_parser() + return parser.parse_known_args() + + @classmethod + def __create_parser(cls): parser = argparse.ArgumentParser( description="Sonar scanner CLI for Python", epilog="Analysis properties not listed here will also be accepted, as long as they start with the -D prefix.", @@ -493,4 +498,4 @@ def __parse_cli_args(cls) -> tuple[argparse.Namespace, list[str]]: "--sonar-modules", "-Dsonar.modules", type=str, help="Comma-delimited list of modules to analyze" ) - return parser.parse_known_args() + return parser diff --git a/tools/generate_cli_documentation.py b/tools/generate_cli_documentation.py new file mode 100644 index 00000000..caab2b6c --- /dev/null +++ b/tools/generate_cli_documentation.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +""" +Script to generate CLI documentation from the argument parser. +Usage: + python generate_cli_documentation.py +""" + +import sys +from pathlib import Path + + +def generate_cli_docs(): + # Add the src directory to the path so we can import the modules + sys.path.insert(0, str(find_project_root() / "src")) + from pysonar_scanner.configuration.cli import CliConfigurationLoader + + """Generate markdown documentation for CLI arguments.""" + parser = CliConfigurationLoader._CliConfigurationLoader__create_parser() + + # Group arguments by category + categories = { + "Authentication": ["token", "sonar_host_url", "sonar_region", "sonar_organization"], + "Project Configuration": [ + "sonar_project_key", + "sonar_project_name", + "sonar_project_version", + "sonar_project_description", + "sonar_project_base_dir", + "sonar_sources", + "sonar_tests", + ], + "Analysis Configuration": ["verbose", "sonar_python_version", "sonar_filesize_limit"], + "Report Integration": [ + "sonar_python_coverage_report_paths", + "sonar_python_pylint_report_path", + "sonar_sarif_report_paths", + "sonar_python_xunit_report_path", + "sonar_python_xunit_skip_details", + "sonar_python_mypy_report_paths", + "sonar_python_bandit_report_paths", + "sonar_python_flake8_report_paths", + "sonar_python_ruff_report_paths", + "sonar_external_issues_report_paths", + "coverage_report_paths", + "pylint_report_path", + "xunit_report_path", + "xunit_skip_details", + "mypy_report_paths", + "bandit_report_paths", + "flake8_report_paths", + "ruff_report_paths", + ], + "Other": [], # Will contain everything else + } + + grouped_actions = {category: [] for category in categories} + + # Group actions by category + for action in parser._actions: + if action.dest == "help": + continue + + categorized = False + for category, dest_prefixes in categories.items(): + for prefix in dest_prefixes: + if action.dest.startswith(prefix): + grouped_actions[category].append(action) + categorized = True + break + if categorized: + break + + if not categorized: + grouped_actions["Other"].append(action) + + # Generate markdown + lines = ["# Sonar Scanner Python CLI Arguments", ""] + + for category, actions in grouped_actions.items(): + if not actions: + continue + + lines.append(f"## {category}") + lines.append("") + lines.append("| Option | Description |") + lines.append("| ------ | ----------- |") + + for action in sorted(actions, key=lambda a: a.option_strings[0] if a.option_strings else ""): + options = ", ".join(f"`{opt}`" for opt in action.option_strings) + help_text = action.help or "" + lines.append(f"| {options} | {help_text} |") + + lines.append("") + + return "\n".join(lines) + + +def find_project_root(): + """Find the project root by looking for pyproject.toml""" + current_dir = Path(__file__).resolve().parent + while current_dir != current_dir.parent: + if (current_dir / "pyproject.toml").exists(): + return current_dir + current_dir = current_dir.parent + raise FileNotFoundError("Could not find project root (no pyproject.toml found)") + + +if __name__ == "__main__": + docs = generate_cli_docs() + + project_root = find_project_root() + output_path = project_root / "CLI_ARGS.md" + + with open(output_path, "w") as f: + f.write(docs)