Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Gradle Python Plugin

![Conformance](https://img.shields.io/badge/Conformance-Check--All%20Passing-brightgreen)

[![Test](https://github.com/jurgenei/gradle-python-plugin/actions/workflows/test.yml/badge.svg)](https://github.com/jurgenei/gradle-python-plugin/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Expand Down
100 changes: 100 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
plugins {
id 'java-gradle-plugin'
id 'maven-publish'
id 'signing'
id 'org.owasp.dependencycheck' version '10.0.3'
id 'com.github.spotbugs' version '6.1.0'
id 'org.sonarqube' version '6.0.1.5171'
}

group = 'name.jurgenei.gradle'
Expand All @@ -26,6 +31,101 @@ gradlePlugin {
}
}

publishing {
publications {
mavenJava(MavenPublication) {
from components.java

pom {
name = 'Python Plugin'
description = 'Gradle plugin for Python project integration'
url = 'https://github.com/jurgenei/gradle-python-plugin'

licenses {
license {
name = 'MIT License'
url = 'https://opensource.org/licenses/MIT'
}
}

developers {
developer {
id = 'jurgenei'
name = 'Jurgen Hildebrand'
}
}

scm {
connection = 'scm:git:https://github.com/jurgenei/gradle-python-plugin.git'
developerConnection = 'scm:git:ssh://git@github.com/jurgenei/gradle-python-plugin.git'
url = 'https://github.com/jurgenei/gradle-python-plugin'
}
}
}
}

repositories {
mavenLocal()
}
}

signing {
required = { gradle.taskGraph.hasTask("publish") }
def signingKey = findProperty("signingKey")
def signingPassword = findProperty("signingPassword")
if (signingKey && signingPassword) {
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.mavenJava
}
}

// OWASP Dependency-Check configuration
dependencyCheck {
format = 'HTML,JSON,XML'
failBuildOnCVSS = 7.0
suppressionFile = 'dependency-check-suppressions.xml'

// NVD API key configuration (improves scan speed by 30-50%)
// Get key from: https://nvd.nist.gov/developers/request-an-api-key
nvd {
apiKey = findProperty('org.owasp.dependencycheck.nvd.api.key') ?: System.getenv('NVD_API_KEY')
}
}

// SpotBugs configuration
spotbugs {
ignoreFailures = false
effort = com.github.spotbugs.snom.Effort.valueOf('DEFAULT')
reportLevel = com.github.spotbugs.snom.Confidence.valueOf('MEDIUM')
}

tasks.named('spotbugsMain') {
reports {
html.required = true
xml.required = false
}
}

// SonarQube configuration
sonar {
properties {
property "sonar.projectKey", "gradle-python-plugin"
property "sonar.projectName", "Gradle Python Plugin"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.java.source", "21"
}
}

// Aggregate security scanning task
tasks.register('allSecurityChecks') {
group = 'verification'
description = 'Run all security and quality checks (Dependency-Check, SpotBugs, SonarQube)'
dependsOn tasks.named('check')
dependsOn tasks.named('dependencyCheck')
dependsOn tasks.named('spotbugsMain')
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
}

25 changes: 20 additions & 5 deletions src/main/java/name/jurgenei/gradle/python/PythonRunnerTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.gradle.api.GradleException;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;

Expand Down Expand Up @@ -77,12 +78,16 @@ public void runPython() throws Exception {

private File resolveWorkDir() {
if (workDir != null) {
workDir.mkdirs();
if (!workDir.exists() && !workDir.mkdirs()) {
throw new GradleException("Failed to create workDir: " + workDir.getAbsolutePath());
}
return workDir;
}
File parent = script.getParentFile();
if (parent != null) {
parent.mkdirs();
if (!parent.exists() && !parent.mkdirs()) {
throw new GradleException("Failed to create script parent directory: " + parent.getAbsolutePath());
}
return parent;
}
return getProject().getProjectDir();
Expand Down Expand Up @@ -231,12 +236,22 @@ public void setRequirements(File requirements) {
*
* @return working directory or {@code null} when default resolution is used
*/
@Optional
@Input
@Internal
public File getWorkDir() {
return workDir;
}

/**
* Returns the configured working directory path for incremental input tracking.
*
* @return absolute path of configured workDir, or {@code null} when unset
*/
@Optional
@Input
public String getWorkDirPath() {
return workDir == null ? null : workDir.getAbsolutePath();
}

/**
* Sets the working directory used for command execution and venv storage.
*
Expand All @@ -254,7 +269,7 @@ public void setWorkDir(File workDir) {
@Optional
@Input
public List<String> getArgs() {
return args;
return new ArrayList<>(args);
}
Comment on lines 269 to 273
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Args mutation ignored 🐞 Bug ≡ Correctness

getArgs() now returns a defensive copy, so build scripts that do task.args.add(...) mutate only
the copy and the task still executes with the original internal args field, dropping expected CLI
arguments without any error. This is hard to diagnose because runScript() reads the internal field
directly, not the getter result.
Agent Prompt
### Issue description
`getArgs()` returns a new `ArrayList` every call. In Gradle/Groovy DSL, users commonly configure list properties via mutation (e.g., `PythonRunnerTask { args.add('foo') }`). With the current implementation, those mutations do not affect the task’s internal `args` field that `runScript()` uses, so the script runs without the intended arguments.

### Issue Context
The task executes with `cmd.addAll(args)` (field), while the getter returns a copy. This creates a silent configuration no-op.

### Fix Focus Areas
- src/main/java/name/jurgenei/gradle/python/PythonRunnerTask.java[41-45]
- src/main/java/name/jurgenei/gradle/python/PythonRunnerTask.java[122-127]
- src/main/java/name/jurgenei/gradle/python/PythonRunnerTask.java[264-282]

### Suggested fix approaches
Pick one:
1) **Gradle-native properties (best):** migrate `args` to a `ListProperty<String>` and use it consistently (execution reads from the property). This supports safe configuration (`args.add(...)`, `args.set(...)`) and proper input tracking.
2) **Minimal behavioral fix:** return the live list from `getArgs()` (so DSL mutations work) and, if SpotBugs complains, suppress that warning for this getter. Also consider making the field `final` and having `setArgs()` clear/add rather than reassigning the list.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class PythonRunnerTaskTest {

private final String pythonExecutable = Files.exists(new File("/usr/bin/python3").toPath()) ? "/usr/bin/python3" : "python3";
private final String pythonExecutable = System.getenv().getOrDefault("PYTHON_EXECUTABLE", "python3");

@Test
void should_register_python_runner_task_from_plugin() {
Expand Down
Loading