Skip to content

Conversation

@FinlayRJW
Copy link
Contributor

@FinlayRJW FinlayRJW commented Aug 19, 2025

Before this PR

GradleExec.exec() returned a raw Provider<ExecResultWithOutput> that required manual exit code checking and error handling, leading to boilerplate code and inconsistent error messages.

After this PR

GradleExec.exec() returns a FailableProvider<ExecResultWithOutput> that:

  • Throws descriptive ExecFailedException on non-zero exit codes by default
  • Supports custom error handling via .mapFailure() and .handle()
  • Provides graceful fallbacks with .getOrElse() and .getOrNull()
  • Propagates failures through standard provider operations (map, flatMap, zip)

==COMMIT_MSG==
Introduce FailableProvider for better error handling in GradleExec

GradleExec.exec() now returns a FailableProvider that automatically throws
descriptive exceptions on non-zero exit codes while supporting custom error
handling through mapFailure() and fold() methods.
==COMMIT_MSG==

Possible downsides?

Breaking change: Return type changed from Provider<ExecResultWithOutput> to FailableProvider<ExecResultWithOutput>. While FailableProvider extends Provider, .get() now throws on non-zero exit codes instead of returning silently. Use .getRaw() for old behavior.

@changelog-app
Copy link

changelog-app bot commented Aug 19, 2025

Generate changelog in changelog/@unreleased

Type (Select exactly one)

  • Feature (Adding new functionality)
  • Improvement (Improving existing functionality)
  • Fix (Fixing an issue with existing functionality)
  • Break (Creating a new major version by breaking public APIs)
  • Deprecation (Removing functionality in a non-breaking way)
  • Migration (Automatically moving data/functionality to a new system)

Description

Introduce FailableProvider for better error handling in GradleExec

GradleExec.exec() now returns a FailableProvider that automatically throws
descriptive exceptions on non-zero exit codes while supporting custom error
handling through mapFailure() and handle() methods.

Check the box to generate changelog(s)

  • Generate changelog entry

@FinlayRJW FinlayRJW changed the title Finlayw/better error Introduce FailableProvider for better error handling in GradleExec Aug 20, 2025
@changelog-app
Copy link

changelog-app bot commented Aug 20, 2025

Successfully generated changelog entry!

What happened?

Your changelog entries have been stored in the database as part of our migration to ChangelogV3.

Need to regenerate?

Simply interact with the changelog bot comment again to regenerate these entries.

🔄 Changelog entries were re-generated at Fri, 29 Aug 2025 09:03:09 UTC!

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces FailableProvider<T> to improve error handling in GradleExec by automatically throwing descriptive exceptions on non-zero exit codes while providing flexible error handling options.

Key changes:

  • Introduces FailableProvider interface and DefaultFailableProvider implementation for failure-aware providers
  • Changes GradleExec.exec() return type from Provider<ExecResultWithOutput> to FailableProvider<ExecResultWithOutput>
  • Updates existing usage in GradleOperatingSystem to use the new error handling capabilities

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
providers/src/main/java/com/palantir/gradle/utils/providers/FailableProvider.java Defines the interface for failure-aware providers with methods like mapFailure() and fold()
providers/src/main/java/com/palantir/gradle/utils/providers/DefaultFailableProvider.java Implements the FailableProvider interface with failure detection and custom error handling
exec/src/main/java/com/palantir/gradle/utils/exec/GradleExec.java Updates exec() method to return FailableProvider with automatic failure detection
exec/src/main/java/com/palantir/gradle/utils/exec/ExecResultWithOutput.java Extracts ExecResultWithOutput interface into separate file
exec/src/main/java/com/palantir/gradle/utils/exec/ExecFailedException.java Adds specialized exception for process execution failures
platform/src/main/java/com/palantir/platform/GradleOperatingSystem.java Updates to use fold() method for handling both success and failure cases

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@FinlayRJW FinlayRJW marked this pull request as ready for review August 29, 2025 09:06
Copy link
Contributor

@kelvinou01 kelvinou01 left a comment

Choose a reason for hiding this comment

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

Very cool idea and clean implementation! Here are some initial thoughts before lunchtime, but I'm still simmering on this.

/**
* Exception thrown when a process execution fails with a non-zero exit code.
*/
public class ExecFailedException extends RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make ExecFailedException a checked exception, so that we force people to wrap it in SafeRuntimeException or UnsafeRuntimeException with their own context?

Not sure if the context added is worth the extra code it requires. But since this is a library, it might be a good idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm torn on this, I think FallibleProvider must use RuntimeException because it extends Gradle's Provider<T> interface, which doesn't declare checked exceptions on get(), so we would need to wrap ourselves in GradleExec. but it would be nice to force / encourage users to provider there own context

Comment on lines 40 to 57
* Executes a process and returns a FailableProvider for the result.
* <p>
* This method always sets {@code ignoreExitValue} to {@code true} on the {@code ExecSpec},
* ensuring that the build does not fail regardless of the process exit code. Callers do need
* to handle exit codes manually.
* <p>
* The result includes the process's standard output, standard error, and the {@link ExecResult}.
* Usage:
* <pre>
* def result = gradleExec.exec {
* commandLine 'git', 'status'
* }
*
* // Handle success/failure
* result.handle(
* { output -> println "Success: ${output.stdOut}" },
* { output -> println "Failed: ${output.stdErr}" }
* )
*
* @param action an action to configure the {@link ExecSpec} for the process to be executed
* @return a Provider of {@link ExecResultWithOutput} containing the standard output, standard error,
* and execution result
* // Or throw on failure
* def output = result.get().stdOut
* </pre>
*/
Copy link
Contributor

@kelvinou01 kelvinou01 Aug 29, 2025

Choose a reason for hiding this comment

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

We should explicate the tribal knowledge that GradleExec is needed, and people should use GradleExec, because ProviderFactory::exec doesn't give good stack traces.

Copy link
Contributor

@kelvinou01 kelvinou01 Aug 29, 2025

Choose a reason for hiding this comment

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

Maybe even errorprone away usages of ProviderFactory::exec to GradleExec

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah think an errorprone is great idea - I've updated the javadoc to be more explicit as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added an errorprone see - palantir/gradle-guide#210

@FinlayRJW FinlayRJW changed the title Introduce FailableProvider for better error handling in GradleExec Introduce FailableProvider for better error handling in GradleExec Sep 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants