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
1 change: 1 addition & 0 deletions src/compile/ir/tasks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod publish_build_artifacts;
pub mod publish_code_coverage_results;
pub mod publish_pipeline_artifact;
pub mod publish_test_results;
pub mod sonar_qube_analyze;
pub mod sonar_qube_publish;
pub mod twine_authenticate;
pub mod universal_packages;
Expand Down
28 changes: 26 additions & 2 deletions src/compile/ir/tasks/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ use super::{
helm_installer, java_tool_installer, manual_validation, maven, maven_authenticate, node_tool,
npm, npm_authenticate, nuget_authenticate, nuget_command, pip_authenticate, powershell,
publish_build_artifacts, publish_code_coverage_results, publish_pipeline_artifact,
publish_test_results, python_script, sonar_qube_publish, twine_authenticate, universal_packages,
use_dotnet, use_node, use_python_version, use_ruby_version, vs_build, vstest,
publish_test_results, python_script, sonar_qube_analyze, sonar_qube_publish, twine_authenticate,
universal_packages, use_dotnet, use_node, use_python_version, use_ruby_version, vs_build, vstest,
};

/// Registry mapping an ADO task id (`"CopyFiles@2"`) to a validator that checks
Expand Down Expand Up @@ -93,6 +93,7 @@ const VALIDATORS: &[(&str, fn(Value) -> Result<(), String>)] = &[
validate_by_deserialize::<publish_pipeline_artifact::PublishPipelineArtifact>,
),
("PublishTestResults@2", validate_by_deserialize::<publish_test_results::PublishTestResults>),
("SonarQubeAnalyze@8", validate_by_deserialize::<sonar_qube_analyze::SonarQubeAnalyze>),
("SonarQubePublish@8", validate_by_deserialize::<sonar_qube_publish::SonarQubePublish>),
("TwineAuthenticate@1", validate_by_deserialize::<twine_authenticate::TwineAuthenticate>),
("UseDotNet@2", validate_by_deserialize::<use_dotnet::UseDotNet>),
Expand Down Expand Up @@ -714,6 +715,29 @@ mod tests {
assert!(err.contains("SonarQubePublish@8"), "got: {err}");
}

#[test]
fn roundtrip_sonar_qube_analyze() {
assert_roundtrips(sonar_qube_analyze::SonarQubeAnalyze::new().into_step());
assert_roundtrips(
sonar_qube_analyze::SonarQubeAnalyze::new()
.jdk_version(sonar_qube_analyze::JdkVersion::JavaHome21X64)
.into_step(),
);
}

#[test]
fn sonar_qube_analyze_unknown_input_warns() {
let step = yaml(
r#"
task: SonarQubeAnalyze@8
inputs:
bogusInput: nope
"#,
);
let err = validate_task_step(&step).expect("recognized").unwrap_err();
assert!(err.contains("SonarQubeAnalyze@8"), "got: {err}");
}

#[test]
fn registry_has_no_duplicate_task_ids() {
let mut ids: Vec<&str> = VALIDATORS.iter().map(|(id, _)| *id).collect();
Expand Down
174 changes: 174 additions & 0 deletions src/compile/ir/tasks/sonar_qube_analyze.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//! Typed builder for `SonarQubeAnalyze@8`.
//!
//! ADO task reference:
//! <https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/sonar-qube-analyze-v8>

use crate::compile::ir::step::TaskStep;
use serde::Deserialize;

/// JDK version source used by the SonarQube scanner during analysis.
///
/// Maps to the `jdkversion` input of `SonarQubeAnalyze@8`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
pub enum JdkVersion {
/// Use the value of the `JAVA_HOME` environment variable.
#[serde(rename = "JAVA_HOME")]
JavaHome,
/// Use the built-in `JAVA_HOME_17_X64` path available on hosted agents
/// (ADO server default).
#[serde(rename = "JAVA_HOME_17_X64")]
JavaHome17X64,
/// Use the built-in `JAVA_HOME_21_X64` path available on hosted agents.
#[serde(rename = "JAVA_HOME_21_X64")]
JavaHome21X64,
}

impl JdkVersion {
/// Returns the exact ADO token for this value.
pub fn as_ado_str(self) -> &'static str {
match self {
Self::JavaHome => "JAVA_HOME",
Self::JavaHome17X64 => "JAVA_HOME_17_X64",
Self::JavaHome21X64 => "JAVA_HOME_21_X64",
}
}
}

/// Builder for a [`TaskStep`] invoking `SonarQubeAnalyze@8`.
///
/// Runs the SonarQube scanner analysis and uploads results to the SonarQube
/// server. This is the second step in the three-task SonarQube workflow:
/// `SonarQubePrepare@8` → `SonarQubeAnalyze@8` → `SonarQubePublish@8`.
///
/// All inputs are optional; calling `into_step()` with no inputs set uses
/// the ADO server-side default JDK version (`JAVA_HOME_17_X64`).
///
/// ADO task reference:
/// <https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/sonar-qube-analyze-v8>
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SonarQubeAnalyze {
#[serde(rename = "jdkversion", default)]
jdk_version: Option<JdkVersion>,
#[serde(skip)]
display_name: Option<String>,
}

impl SonarQubeAnalyze {
/// Create a new builder; all inputs are optional.
pub fn new() -> Self {
Self {
jdk_version: None,
display_name: None,
}
}

/// `jdkversion` — JDK version source for the analysis.
///
/// Defaults to [`JdkVersion::JavaHome17X64`] on the ADO server side when
/// not set. Only emit this input to override the default.
pub fn jdk_version(mut self, version: JdkVersion) -> Self {
self.jdk_version = Some(version);
self
}

/// Override the default `displayName` (`"Run Code Analysis"`).
pub fn with_display_name(mut self, value: impl Into<String>) -> Self {
self.display_name = Some(value.into());
self
}

/// Lower into a [`TaskStep`].
pub fn into_step(self) -> TaskStep {
let mut t = TaskStep::new(
"SonarQubeAnalyze@8",
self.display_name
.unwrap_or_else(|| "Run Code Analysis".into()),
);
if let Some(v) = self.jdk_version {
t = t.with_input("jdkversion", v.as_ado_str());
}
t
}
}

impl Default for SonarQubeAnalyze {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn sets_task_identifier() {
let t = SonarQubeAnalyze::new().into_step();
assert_eq!(t.task, "SonarQubeAnalyze@8");
}

#[test]
fn default_display_name() {
let t = SonarQubeAnalyze::new().into_step();
assert_eq!(t.display_name, "Run Code Analysis");
}

#[test]
fn no_inputs_by_default() {
let t = SonarQubeAnalyze::new().into_step();
assert!(
t.inputs.is_empty(),
"expected no inputs when none are set; got {:?}",
t.inputs
);
}

#[test]
fn jdk_version_java_home() {
let t = SonarQubeAnalyze::new()
.jdk_version(JdkVersion::JavaHome)
.into_step();
assert_eq!(
t.inputs.get("jdkversion").map(String::as_str),
Some("JAVA_HOME")
);
}

#[test]
fn jdk_version_java_home_17() {
let t = SonarQubeAnalyze::new()
.jdk_version(JdkVersion::JavaHome17X64)
.into_step();
assert_eq!(
t.inputs.get("jdkversion").map(String::as_str),
Some("JAVA_HOME_17_X64")
);
}

#[test]
fn jdk_version_java_home_21() {
let t = SonarQubeAnalyze::new()
.jdk_version(JdkVersion::JavaHome21X64)
.into_step();
assert_eq!(
t.inputs.get("jdkversion").map(String::as_str),
Some("JAVA_HOME_21_X64")
);
}

#[test]
fn display_name_override() {
let t = SonarQubeAnalyze::new()
.with_display_name("SonarQube Analysis")
.into_step();
assert_eq!(t.display_name, "SonarQube Analysis");
}

#[test]
fn default_impl_matches_new() {
let t = SonarQubeAnalyze::default().into_step();
assert_eq!(t.task, "SonarQubeAnalyze@8");
assert!(t.inputs.is_empty());
}
}
Loading