Skip to content
Draft
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 src/gradle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod detekt;
pub mod global;
pub mod health;
pub mod paths;
pub mod proto;
pub mod test_filter;

use crate::tracking;
Expand Down Expand Up @@ -261,6 +262,7 @@ pub fn filter_gradle_output(raw: &str, task_type: &TaskType) -> String {
TaskType::Test => test_filter::filter_test(&filtered),
TaskType::Detekt => detekt::filter_detekt(&filtered),
TaskType::Health => health::filter_health(&filtered),
TaskType::Proto => proto::filter_proto(&filtered),
TaskType::Generic => filtered,
// Per-task filters added in subsequent PRs
_ => filtered,
Expand Down
103 changes: 103 additions & 0 deletions src/gradle/proto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
/// Proto-specific noise pattern
static ref PROTO_NOISE: Regex = Regex::new(r"^> Task.*extract.*Proto").unwrap();
/// Proto error lines (keep these)
static ref PROTO_ERROR: Regex = Regex::new(r"^e: ").unwrap();
}

/// Returns true if the task name is a proto generation task.
/// Case-insensitive via internal lowercasing.
pub fn matches_task(task_name: &str) -> bool {
let t = task_name.to_ascii_lowercase();
t == "buildprotos" || t == "generateprotos" || t.contains("proto")
}

/// Apply PROTO-specific filtering.
///
/// Keeps error lines, BUILD result, What went wrong.
/// Drops proto extraction noise.
pub fn filter_proto(input: &str) -> String {
let mut result = Vec::new();

for line in input.lines() {
let trimmed = line.trim();

// Drop proto-specific noise
if PROTO_NOISE.is_match(trimmed) {
continue;
}

result.push(line.to_string());
}

// Trim leading/trailing blank lines
let start = result
.iter()
.position(|l| !l.trim().is_empty())
.unwrap_or(0);
let end = result
.iter()
.rposition(|l| !l.trim().is_empty())
.map(|i| i + 1)
.unwrap_or(result.len());
result[start..end].join("\n")
}

#[cfg(test)]
mod tests {
use super::*;
use crate::gradle::global::apply_global_filters;
use insta::assert_snapshot;

#[test]
fn test_matches_build_protos() {
assert!(matches_task("buildProtos"));
}

#[test]
fn test_matches_generate_protos() {
assert!(matches_task("generateProtos"));
}

#[test]
fn test_matches_contains_proto() {
assert!(matches_task("extractProto"));
assert!(matches_task("generateTestProto"));
}

#[test]
fn test_matches_proto_case_insensitive() {
assert!(matches_task("BuildProtos"));
assert!(matches_task("GenerateProtos"));
}

#[test]
fn test_no_match_test() {
assert!(!matches_task("test"));
}

#[test]
fn test_no_match_compile() {
assert!(!matches_task("compileKotlin"));
}

#[test]
fn test_proto_failure_snapshot() {
let input = include_str!("../../tests/fixtures/gradle/proto_failure_raw.txt");
let globally_filtered = apply_global_filters(input);
let output = filter_proto(&globally_filtered);
assert_snapshot!(output);
}

#[test]
fn test_proto_preserves_errors() {
let input = include_str!("../../tests/fixtures/gradle/proto_failure_raw.txt");
let globally_filtered = apply_global_filters(input);
let output = filter_proto(&globally_filtered);
assert!(output.contains("Field number 5 has already been used"));
assert!(output.contains("is already defined"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: src/gradle/proto.rs
expression: output
---
> Task :app-payments-api:generateProtos FAILED
e: proto/payments/v1/payment.proto:42: Field number 5 has already been used in "Payment".
e: proto/payments/v1/payment.proto:55: "PaymentStatus" is already defined in "payments.v1".

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app-payments-api:generateProtos'.
> protoc returned exit code 1

BUILD FAILED in 15s
3 actionable tasks: 1 executed, 2 up-to-date
27 changes: 27 additions & 0 deletions tests/fixtures/gradle/proto_failure_raw.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Starting Gradle Daemon...
Gradle Daemon started in 1854 ms
> Configure project :
> Configure project :app-payments-api
Calculating task graph as no cached configuration is available for tasks: :app-payments-api:generateProtos
> Task :app-payments-api:extractIncludeProto UP-TO-DATE
> Task :app-payments-api:extractProto UP-TO-DATE
> Task :app-payments-api:generateProtos FAILED
e: proto/payments/v1/payment.proto:42: Field number 5 has already been used in "Payment".
e: proto/payments/v1/payment.proto:55: "PaymentStatus" is already defined in "payments.v1".
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
For more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app-payments-api:generateProtos'.
> protoc returned exit code 1

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 15s
3 actionable tasks: 1 executed, 2 up-to-date