Skip to content

Commit

Permalink
Improve framework support (#726)
Browse files Browse the repository at this point in the history
  • Loading branch information
Malax authored Sep 25, 2024
1 parent 57fd1ea commit 85776cc
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 14 deletions.
5 changes: 5 additions & 0 deletions buildpacks/gradle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- The buildpack will add a default process type if a supported framework is detected and the expected build output is found. This mirrors the same feature from the Maven buildpack. ([#726](https://github.com/heroku/buildpacks-jvm/pull/726))
- Support for the Micronaut and Quarkus frameworks. Both previously worked with the buildpack but required some configuration. Unless heavily customized, no build task needs to be specified anymore. ([#726](https://github.com/heroku/buildpacks-jvm/pull/726))

## [6.0.1] - 2024-07-19

- No changes.
Expand Down
7 changes: 7 additions & 0 deletions buildpacks/gradle/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,12 @@ pub(crate) fn on_error_gradle_buildpack(error: GradleBuildpackError) {
error,
);
}
GradleBuildpackError::CannotDetermineDefaultAppProcess(error) => {
log_please_try_again_error(
"Failed to determine default app process",
"Failed to determine default app process",
error,
);
}
}
}
75 changes: 68 additions & 7 deletions buildpacks/gradle/src/framework.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,87 @@
use crate::gradle_command::GradleDependencyReport;
use buildpacks_jvm_shared::fs::list_directory_contents;
use libcnb::data::launch::{Process, ProcessBuilder};
use libcnb::data::process_type;
use std::path::Path;

pub(crate) fn detect_framework(dependency_report: &GradleDependencyReport) -> Option<Framework> {
DEPENDENCY_TO_FRAMEWORK_MAPPINGS
.into_iter()
.find_map(|(group_id, artifact_id, framework)| {
DEPENDENCY_TO_FRAMEWORK_MAPPINGS.into_iter().find_map(
|(configuration, group_id, artifact_id, framework)| {
dependency_report
.contains_dependency("runtimeClasspath", group_id, artifact_id)
.contains_dependency(configuration, group_id, artifact_id)
.then_some(framework)
})
},
)
}

#[allow(clippy::case_sensitive_file_extension_comparisons)]
pub(crate) fn default_app_process<P: AsRef<Path>>(
dependency_report: &GradleDependencyReport,
app_dir: P,
) -> Result<Option<Process>, std::io::Error> {
let jar_path = match detect_framework(dependency_report) {
Some(Framework::SpringBoot | Framework::Micronaut) => {
list_directory_contents(app_dir.as_ref().join("build/libs"))?.find(|path| {
path.file_name()
.map(|file_name| file_name.to_string_lossy().to_string())
.is_some_and(|file_name| {
file_name.ends_with(".jar")
&& !file_name.ends_with("-plain.jar") // Spring Boot JAR without dependencies
&& !file_name.ends_with("-sources.jar")
&& !file_name.ends_with("-javadoc.jar")
})
})
}
Some(Framework::Quarkus) => {
let quarkus_run_jar = app_dir.as_ref().join("build/quarkus-app/quarkus-run.jar");
quarkus_run_jar.is_file().then_some(quarkus_run_jar)
}
_ => None,
};

let process = jar_path.map(|jar_path| {
ProcessBuilder::new(
process_type!("web"),
["java", "-jar", &jar_path.to_string_lossy()],
)
.default(true)
.build()
});

Ok(process)
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum Framework {
Ratpack,
SpringBoot,
Micronaut,
Quarkus,
}

const DEPENDENCY_TO_FRAMEWORK_MAPPINGS: [(&str, &str, Framework); 2] = [
("io.ratpack", "ratpack-core", Framework::Ratpack),
const DEPENDENCY_TO_FRAMEWORK_MAPPINGS: [(&str, &str, &str, Framework); 4] = [
(
"runtimeClasspath",
"org.springframework.boot",
"spring-boot",
Framework::SpringBoot,
),
(
"runtimeClasspath",
"io.ratpack",
"ratpack-core",
Framework::Ratpack,
),
(
"runtimeClasspath",
"io.micronaut",
"micronaut-core",
Framework::Micronaut,
),
(
"quarkusProdRuntimeClasspathConfigurationDeployment",
"io.quarkus",
"quarkus-core",
Framework::Quarkus,
),
];
23 changes: 16 additions & 7 deletions buildpacks/gradle/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::config::GradleBuildpackConfig;
use crate::detect::is_gradle_project_directory;
use crate::errors::on_error_gradle_buildpack;
use crate::framework::{detect_framework, Framework};
use crate::framework::{default_app_process, detect_framework, Framework};
use crate::gradle_command::GradleCommandError;
use crate::layers::gradle_home::handle_gradle_home_layer;
use crate::GradleBuildpackError::{GradleBuildIoError, GradleBuildUnexpectedStatusError};
Expand All @@ -10,6 +10,7 @@ use buildpacks_jvm_shared as shared;
use buildpacks_jvm_shared_test as _;
use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
use libcnb::data::build_plan::BuildPlanBuilder;
use libcnb::data::launch::LaunchBuilder;
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
use libcnb::generic::GenericPlatform;
use libcnb::{buildpack_main, Buildpack, Env};
Expand Down Expand Up @@ -41,6 +42,7 @@ enum GradleBuildpackError {
WriteGradlePropertiesError(std::io::Error),
WriteGradleInitScriptError(std::io::Error),
CannotSetGradleWrapperExecutableBit(std::io::Error),
CannotDetermineDefaultAppProcess(std::io::Error),
StartGradleDaemonError(GradleCommandError<()>),
BuildTaskUnknown,
}
Expand Down Expand Up @@ -94,18 +96,18 @@ impl Buildpack for GradleBuildpack {
.map_err(|command_error| command_error.map_parse_error(|_| ()))
.map_err(GradleBuildpackError::GetTasksError)?;

let detected_framework = gradle_command::dependency_report(&context.app_dir, &gradle_env)
.map_err(GradleBuildpackError::GetDependencyReportError)
.map(|dependency_report| detect_framework(&dependency_report))?;
let dependency_report = gradle_command::dependency_report(&context.app_dir, &gradle_env)
.map_err(GradleBuildpackError::GetDependencyReportError)?;

let task_name = buildpack_config
.gradle_task
.as_deref()
.or_else(|| project_tasks.has_task("stage").then_some("stage"))
.or_else(|| {
detected_framework.map(|framework| match framework {
Framework::SpringBoot => "build",
detect_framework(&dependency_report).map(|framework| match framework {
Framework::SpringBoot | Framework::Quarkus => "build",
Framework::Ratpack => "installDist",
Framework::Micronaut => "shadowJar",
})
})
.ok_or(GradleBuildpackError::BuildTaskUnknown)?;
Expand All @@ -127,7 +129,14 @@ impl Buildpack for GradleBuildpack {
// failure, nor can we recover from it in any way.
let _ = gradle_command::stop_daemon(&gradle_wrapper_executable_path, &gradle_env);

BuildResultBuilder::new().build()
let process = default_app_process(&dependency_report, &context.app_dir)
.map_err(GradleBuildpackError::CannotDetermineDefaultAppProcess)?;

process
.map_or(BuildResultBuilder::new(), |process| {
BuildResultBuilder::new().launch(LaunchBuilder::new().process(process).build())
})
.build()
}

fn on_error(&self, error: libcnb::Error<Self::Error>) {
Expand Down

0 comments on commit 85776cc

Please sign in to comment.