Skip to content

Commit

Permalink
Add track_buildpack_timing for buildpack timing output
Browse files Browse the repository at this point in the history
  • Loading branch information
Malax committed Feb 5, 2025
1 parent f96fa37 commit 0668ca3
Show file tree
Hide file tree
Showing 6 changed files with 443 additions and 398 deletions.
158 changes: 80 additions & 78 deletions buildpacks/gradle/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use crate::layers::gradle_home::handle_gradle_home_layer;
use crate::GradleBuildpackError::{GradleBuildIoError, GradleBuildUnexpectedStatusError};
use buildpacks_jvm_shared as shared;
use buildpacks_jvm_shared::output::{
print_buildpack_name, print_section, print_subsection, track_timing, BuildpackOutputText,
BuildpackOutputTextSection,
print_buildpack_name, print_section, print_subsection, track_buildpack_timing,
track_subsection_timing, BuildpackOutputText, BuildpackOutputTextSection,
};
#[cfg(test)]
use buildpacks_jvm_shared_test as _;
Expand Down Expand Up @@ -78,83 +78,85 @@ impl Buildpack for GradleBuildpack {
}

fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
print_buildpack_name("Heroku Gradle Buildpack");

let buildpack_config = GradleBuildpackConfig::from(&context);

let gradle_wrapper_executable_path = Some(context.app_dir.join("gradlew"))
.filter(|path| path.exists())
.ok_or(GradleBuildpackError::GradleWrapperNotFound)?;

shared::fs::set_executable(&gradle_wrapper_executable_path)
.map_err(GradleBuildpackError::CannotSetGradleWrapperExecutableBit)?;

let mut gradle_env = Env::from_current();
handle_gradle_home_layer(&context, &mut gradle_env)?;

print_section("Running Gradle build");

track_timing(|| {
print_subsection("Starting Gradle daemon");
gradle_command::start_daemon(&gradle_wrapper_executable_path, &gradle_env)
.map_err(GradleBuildpackError::StartGradleDaemonError)
})?;

let project_tasks = track_timing(|| {
print_subsection("Querying tasks");
gradle_command::tasks(&context.app_dir, &gradle_env)
.map_err(|command_error| command_error.map_parse_error(|_| ()))
.map_err(GradleBuildpackError::GetTasksError)
})?;

let dependency_report = track_timing(|| {
print_subsection("Querying 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(|| {
detect_framework(&dependency_report).map(|framework| match framework {
Framework::SpringBoot | Framework::Quarkus => "build",
Framework::Ratpack => "installDist",
Framework::Micronaut => "shadowJar",
track_buildpack_timing(|| {
print_buildpack_name("Heroku Gradle Buildpack");

let buildpack_config = GradleBuildpackConfig::from(&context);

let gradle_wrapper_executable_path = Some(context.app_dir.join("gradlew"))
.filter(|path| path.exists())
.ok_or(GradleBuildpackError::GradleWrapperNotFound)?;

shared::fs::set_executable(&gradle_wrapper_executable_path)
.map_err(GradleBuildpackError::CannotSetGradleWrapperExecutableBit)?;

let mut gradle_env = Env::from_current();
handle_gradle_home_layer(&context, &mut gradle_env)?;

print_section("Running Gradle build");

track_subsection_timing(|| {
print_subsection("Starting Gradle daemon");
gradle_command::start_daemon(&gradle_wrapper_executable_path, &gradle_env)
.map_err(GradleBuildpackError::StartGradleDaemonError)
})?;

let project_tasks = track_subsection_timing(|| {
print_subsection("Querying tasks");
gradle_command::tasks(&context.app_dir, &gradle_env)
.map_err(|command_error| command_error.map_parse_error(|_| ()))
.map_err(GradleBuildpackError::GetTasksError)
})?;

let dependency_report = track_subsection_timing(|| {
print_subsection("Querying 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(|| {
detect_framework(&dependency_report).map(|framework| match framework {
Framework::SpringBoot | Framework::Quarkus => "build",
Framework::Ratpack => "installDist",
Framework::Micronaut => "shadowJar",
})
})
})
.ok_or(GradleBuildpackError::BuildTaskUnknown)?;

print_section("Running Gradle build");
print_subsection(BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Running "),
BuildpackOutputTextSection::command(format!("./gradlew {task_name} -x check")),
]));

let output = Command::new(&gradle_wrapper_executable_path)
.current_dir(&context.app_dir)
.envs(&gradle_env)
.args([task_name, "-x", "check"])
.output_and_write_streams(stdout(), stderr())
.map_err(GradleBuildIoError)?;

if !output.status.success() {
Err(GradleBuildUnexpectedStatusError(output.status))?;
}

// Explicitly ignoring the result. If the daemon cannot be stopped, that is not a build
// failure, nor can we recover from it in any way.
let _ = gradle_command::stop_daemon(&gradle_wrapper_executable_path, &gradle_env);

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()
.ok_or(GradleBuildpackError::BuildTaskUnknown)?;

print_section("Running Gradle build");
print_subsection(BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Running "),
BuildpackOutputTextSection::command(format!("./gradlew {task_name} -x check")),
]));

let output = Command::new(&gradle_wrapper_executable_path)
.current_dir(&context.app_dir)
.envs(&gradle_env)
.args([task_name, "-x", "check"])
.output_and_write_streams(stdout(), stderr())
.map_err(GradleBuildIoError)?;

if !output.status.success() {
Err(GradleBuildUnexpectedStatusError(output.status))?;
}

// Explicitly ignoring the result. If the daemon cannot be stopped, that is not a build
// failure, nor can we recover from it in any way.
let _ = gradle_command::stop_daemon(&gradle_wrapper_executable_path, &gradle_env);

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
4 changes: 2 additions & 2 deletions buildpacks/jvm/src/layers/openjdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub(crate) fn handle_openjdk_layer(
_ => {}
}

output::track_timing(|| {
output::track_subsection_timing(|| {
output::print_subsection("Downloading and unpacking OpenJDK distribution");

let temp_dir =
Expand Down Expand Up @@ -127,7 +127,7 @@ pub(crate) fn handle_openjdk_layer(

jdk_overlay_applied = true;

output::track_timing(|| {
output::track_subsection_timing(|| {
let jdk_overlay_contents =
util::list_directory_contents(&app_jdk_overlay_dir_path)
.map_err(OpenJdkBuildpackError::CannotListJdkOverlayContents)?;
Expand Down
166 changes: 87 additions & 79 deletions buildpacks/jvm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use crate::version_resolver::{
resolve_version, OpenJdkArtifactRequirementSource, VersionResolveError,
};
use buildpacks_jvm_shared::output;
use buildpacks_jvm_shared::output::{BuildpackOutputText, BuildpackOutputTextSection};
use buildpacks_jvm_shared::output::{
track_buildpack_timing, BuildpackOutputText, BuildpackOutputTextSection,
};
use buildpacks_jvm_shared::system_properties::{read_system_properties, ReadSystemPropertiesError};
#[cfg(test)]
use buildpacks_jvm_shared_test as _;
Expand Down Expand Up @@ -92,18 +94,19 @@ impl Buildpack for OpenJdkBuildpack {
}

fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
output::print_buildpack_name("Heroku OpenJDK Buildpack");

let resolved_version = resolve_version(&context.app_dir)
.map_err(OpenJdkBuildpackError::ResolveVersionError)?;

if matches!(
resolved_version.source,
OpenJdkArtifactRequirementSource::DefaultVersionLatestLts
) {
output::print_warning(
"No OpenJDK version specified",
formatdoc! {"
track_buildpack_timing(|| {
output::print_buildpack_name("Heroku OpenJDK Buildpack");

let resolved_version = resolve_version(&context.app_dir)
.map_err(OpenJdkBuildpackError::ResolveVersionError)?;

if matches!(
resolved_version.source,
OpenJdkArtifactRequirementSource::DefaultVersionLatestLts
) {
output::print_warning(
"No OpenJDK version specified",
formatdoc! {"
Your application does not explicitly specify an OpenJDK version. The latest
long-term support (LTS) version will be installed. This currently is OpenJDK {OPENJDK_LATEST_LTS_VERSION}.
Expand All @@ -115,74 +118,79 @@ impl Buildpack for OpenJdkBuildpack {
directory of your application to contain:
java.runtime.version = {OPENJDK_LATEST_LTS_VERSION}"},
);
}

output::print_section("OpenJDK version resolution");

match resolved_version.source {
OpenJdkArtifactRequirementSource::SystemProperties => {
output::print_subsection(BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Using version string provided in "),
BuildpackOutputTextSection::value("system.properties"),
]));
);
}
OpenJdkArtifactRequirementSource::DefaultVersionLatestLts => {
output::print_subsection("No explicit configuration found, using latest LTS");
}
OpenJdkArtifactRequirementSource::DefaultVersionFunctions => {
output::print_subsection(BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("No explicit configuration found, using "),
BuildpackOutputTextSection::value("8"),
]));
}
};

let openjdk_inventory = include_str!("../openjdk_inventory.toml")
.parse::<Inventory<OpenJdkVersion, Sha256, OpenJdkArtifactMetadata>>()
.map_err(OpenJdkBuildpackError::ParseInventoryError)?;

let openjdk_artifact = openjdk_inventory
.partial_resolve(
context
.target
.os
.parse::<Os>()
.expect("OS should be always parseable, buildpack will not run on unsupported operating systems."),
// On platform API <= `0.9` together with lifecycle <= `0.17`, the `CNB_TARGET_ARCH` environment variable will not be set.
// This will be the case for the `salesforce-functions` builder. To ensure this buildpack can run there, we will
// fall back to Rust's architecture constant when the architecture cannot be determined. This workaround can be removed when
// the `salesforce-functions` builder is EOL.
Some(context.target.arch.as_str())
.filter(|value| !value.is_empty())
.unwrap_or(consts::ARCH)
.parse::<Arch>()
.expect("arch should be always parseable, buildpack will not run on unsupported architectures."),
&resolved_version.requirement,
)
.ok_or(OpenJdkBuildpackError::UnsupportedOpenJdkVersion(
resolved_version.requirement.clone(),
))?;

output::print_subsection(match resolved_version.requirement.version {
HerokuOpenJdkVersionRequirement::Major(major_version) => {
BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Selected major version "),
BuildpackOutputTextSection::value(format!("{major_version}")),
BuildpackOutputTextSection::regular(" resolves to "),
BuildpackOutputTextSection::value(format!("{}", openjdk_artifact.version)),
])
}
HerokuOpenJdkVersionRequirement::Specific(version) => BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Selected version "),
BuildpackOutputTextSection::value(format!("{version}")),
]),
});

handle_openjdk_layer(&context, openjdk_artifact)?;
handle_runtime_layer(&context)?;

BuildResultBuilder::new().build()
output::print_section("OpenJDK version resolution");

match resolved_version.source {
OpenJdkArtifactRequirementSource::SystemProperties => {
output::print_subsection(BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Using version string provided in "),
BuildpackOutputTextSection::value("system.properties"),
]));
}
OpenJdkArtifactRequirementSource::DefaultVersionLatestLts => {
output::print_subsection("No explicit configuration found, using latest LTS");
}
OpenJdkArtifactRequirementSource::DefaultVersionFunctions => {
output::print_subsection(BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular(
"No explicit configuration found, using ",
),
BuildpackOutputTextSection::value("8"),
]));
}
};

let openjdk_inventory = include_str!("../openjdk_inventory.toml")
.parse::<Inventory<OpenJdkVersion, Sha256, OpenJdkArtifactMetadata>>()
.map_err(OpenJdkBuildpackError::ParseInventoryError)?;

let openjdk_artifact = openjdk_inventory
.partial_resolve(
context
.target
.os
.parse::<Os>()
.expect("OS should be always parseable, buildpack will not run on unsupported operating systems."),
// On platform API <= `0.9` together with lifecycle <= `0.17`, the `CNB_TARGET_ARCH` environment variable will not be set.
// This will be the case for the `salesforce-functions` builder. To ensure this buildpack can run there, we will
// fall back to Rust's architecture constant when the architecture cannot be determined. This workaround can be removed when
// the `salesforce-functions` builder is EOL.
Some(context.target.arch.as_str())
.filter(|value| !value.is_empty())
.unwrap_or(consts::ARCH)
.parse::<Arch>()
.expect("arch should be always parseable, buildpack will not run on unsupported architectures."),
&resolved_version.requirement,
)
.ok_or(OpenJdkBuildpackError::UnsupportedOpenJdkVersion(
resolved_version.requirement.clone(),
))?;

output::print_subsection(match resolved_version.requirement.version {
HerokuOpenJdkVersionRequirement::Major(major_version) => {
BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Selected major version "),
BuildpackOutputTextSection::value(format!("{major_version}")),
BuildpackOutputTextSection::regular(" resolves to "),
BuildpackOutputTextSection::value(format!("{}", openjdk_artifact.version)),
])
}
HerokuOpenJdkVersionRequirement::Specific(version) => {
BuildpackOutputText::new(vec![
BuildpackOutputTextSection::regular("Selected version "),
BuildpackOutputTextSection::value(format!("{version}")),
])
}
});

handle_openjdk_layer(&context, openjdk_artifact)?;
handle_runtime_layer(&context)?;

BuildResultBuilder::new().build()
})
}

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

0 comments on commit 0668ca3

Please sign in to comment.