Skip to content

Commit

Permalink
Continue replacing Layer trait with Struct API and using bullet stream (
Browse files Browse the repository at this point in the history
#334)

* Remove section logger from bundle list

* Change top level bullet to a noun

* Refactor command to functional style

* Move logic to function

* Replace section logger with bullet stream

* Replace logging with bullet stream

* Remove SectionLogger guard

* Remove SectionLogger guard

* Replace section logger with bullet stream

* Wire bullet stream input and output

* Replace section log with bullet stream

* Replace section log with bullet stream

* Replace section log with bullet stream

* Pluralize word

* Replace section log with bullet stream

* Move associated function to function

* Wire up bullet stream

* Use bullet stream for `rake -p`

* Wire bullet stream

* Replace section logger with bullet stream

* Replace section logger with bullet stream

* Replace section logger with bullet stream

* Inline AppCacheCollection logic

This is a pretty useless struct that hides the underlying logic I want to deprecate it.

* Remove output fmt module import

* Inline command call

* Replace section log with bullet stream

* Use value directly instead of making a variable

* Inline command creation

* Use value directly

* Remove log_* calls in favor of bullet stream

* Revert changes to AppCacheCollection

* Deprecate AppCacheCollection

* Remove commons output imports

* Remove commons output imports

* Remove unused

* Remove clippy allow

* Update buildpacks/ruby/src/steps/rake_assets_install.rs

Co-authored-by: Rune Soerensen <rsoerensen@salesforce.com>

* Changelog entry

---------

Co-authored-by: Rune Soerensen <rsoerensen@salesforce.com>
  • Loading branch information
schneems and runesoerensen authored Oct 15, 2024
1 parent c8cdfd0 commit cc50d62
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 240 deletions.
55 changes: 27 additions & 28 deletions buildpacks/ruby/src/gem_list.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use bullet_stream::{state::SubBullet, style, Print};
use commons::gem_version::GemVersion;
use commons::output::{
fmt,
section_log::{log_step_timed, SectionLogger},
};
use core::str::FromStr;
use fun_run::{CmdError, CommandWithName};
use regex::Regex;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::io::Stdout;
use std::process::Command;

/// ## Gets list of an application's dependencies
Expand All @@ -18,6 +16,31 @@ pub(crate) struct GemList {
pub(crate) gems: HashMap<String, GemVersion>,
}

/// Calls `bundle list` and returns a `GemList` struct
///
/// # Errors
///
/// Errors if the command `bundle list` is unsuccessful.
pub(crate) fn bundle_list<T, K, V>(
bullet: Print<SubBullet<Stdout>>,
envs: T,
) -> Result<(Print<SubBullet<Stdout>>, GemList), CmdError>
where
T: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut cmd = Command::new("bundle");
cmd.arg("list").env_clear().envs(envs);

let timer = bullet.start_timer(format!("Running {}", style::command(cmd.name())));
let gem_list = cmd
.named_output()
.map(|output| output.stdout_lossy())
.and_then(|output| GemList::from_str(&output))?;
Ok((timer.done(), gem_list))
}

/// Converts the output of `$ gem list` into a data structure that can be inspected and compared
///
/// ```
Expand Down Expand Up @@ -54,30 +77,6 @@ pub(crate) struct GemList {
/// );
/// ```
impl GemList {
/// Calls `bundle list` and returns a `GemList` struct
///
/// # Errors
///
/// Errors if the command `bundle list` is unsuccessful.
pub(crate) fn from_bundle_list<T, K, V>(
envs: T,
_logger: &dyn SectionLogger,
) -> Result<Self, CmdError>
where
T: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut cmd = Command::new("bundle");
cmd.arg("list").env_clear().envs(envs);

let output = log_step_timed(format!("Running {}", fmt::command(cmd.name())), || {
cmd.named_output()
})?;

GemList::from_str(&output.stdout_lossy())
}

#[must_use]
pub(crate) fn has(&self, str: &str) -> bool {
self.gems.contains_key(&str.trim().to_lowercase())
Expand Down
41 changes: 19 additions & 22 deletions buildpacks/ruby/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ use bullet_stream::{style, Print};
use commons::cache::CacheError;
use commons::gemfile_lock::GemfileLock;
use commons::metadata_digest::MetadataDigest;
#[allow(clippy::wildcard_imports)]
use commons::output::build_log::*;
use commons::output::warn_later::WarnGuard;
use core::str::FromStr;
use fs_err::PathExt;
use fun_run::CmdError;
Expand Down Expand Up @@ -111,11 +108,8 @@ impl Buildpack for RubyBuildpack {
}

#[allow(clippy::too_many_lines)]
#[allow(deprecated)]
fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
let mut build_output = Print::new(stdout()).h2("Heroku Ruby Buildpack");
let logger = BuildLog::new(stdout()).without_buildpack_name();
let warn_later = WarnGuard::new(stdout());

// ## Set default environment
let (mut env, store) =
Expand Down Expand Up @@ -185,7 +179,7 @@ impl Buildpack for RubyBuildpack {
};

// ## Bundle install
(_, env) = {
(build_output, env) = {
let bullet = build_output.bullet("Bundle install gems");
let (bullet, layer_env) = layers::bundle_install_layer::handle(
&context,
Expand Down Expand Up @@ -219,30 +213,33 @@ impl Buildpack for RubyBuildpack {
};

// ## Detect gems
let (mut logger, gem_list, default_process) = {
let section = logger.section("Setting default processes");
let (mut build_output, gem_list, default_process) = {
let bullet = build_output.bullet("Default process detection");

let gem_list = gem_list::GemList::from_bundle_list(&env, section.as_ref())
.map_err(RubyBuildpackError::GemListGetError)?;
let default_process = steps::get_default_process(section.as_ref(), &context, &gem_list);
let (bullet, gem_list) =
gem_list::bundle_list(bullet, &env).map_err(RubyBuildpackError::GemListGetError)?;
let (bullet, default_process) = steps::get_default_process(bullet, &context, &gem_list);

(section.end_section(), gem_list, default_process)
(bullet.done(), gem_list, default_process)
};

// ## Assets install
logger = {
let section = logger.section("Rake assets install");
let rake_detect =
crate::steps::detect_rake_tasks(section.as_ref(), &gem_list, &context, &env)?;
build_output = {
let (bullet, rake_detect) = crate::steps::detect_rake_tasks(
build_output.bullet("Rake assets install"),
&gem_list,
&context,
&env,
)?;

if let Some(rake_detect) = rake_detect {
crate::steps::rake_assets_install(section.as_ref(), &context, &env, &rake_detect)?;
crate::steps::rake_assets_install(bullet, &context, &env, &rake_detect)?
} else {
bullet
}

section.end_section()
.done()
};
logger.finish_logging();
warn_later.warn_now();
build_output.done();

if let Some(default_process) = default_process {
BuildResultBuilder::new()
Expand Down
67 changes: 31 additions & 36 deletions buildpacks/ruby/src/rake_task_detect.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use commons::output::{
fmt,
section_log::{log_step_timed, SectionLogger},
use bullet_stream::{
state::SubBullet,
{style, Print},
};

use core::str::FromStr;
use fun_run::{CmdError, CommandWithName};
use std::io::Stdout;
use std::{ffi::OsStr, process::Command};

/// Run `rake -P` and parse output to show what rake tasks an application has
Expand All @@ -21,41 +21,36 @@ pub(crate) struct RakeDetect {
output: String,
}

impl RakeDetect {
/// # Errors
///
/// Will return `Err` if `bundle exec rake -p` command cannot be invoked by the operating system.
pub(crate) fn from_rake_command<
T: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
>(
_logger: &dyn SectionLogger,
envs: T,
error_on_failure: bool,
) -> Result<Self, CmdError> {
let mut cmd = Command::new("bundle");
cmd.args(["exec", "rake", "-P", "--trace"])
.env_clear()
.envs(envs);
/// # Errors
///
/// Will return `Err` if `bundle exec rake -p` command cannot be invoked by the operating system.
pub(crate) fn call<T: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>>(
bullet: Print<SubBullet<Stdout>>,
envs: T,
error_on_failure: bool,
) -> Result<(Print<SubBullet<Stdout>>, RakeDetect), CmdError> {
let mut cmd = Command::new("bundle");
cmd.args(["exec", "rake", "-P", "--trace"])
.env_clear()
.envs(envs);

log_step_timed(format!("Running {}", fmt::command(cmd.name())), || {
cmd.named_output()
})
.or_else(|error| {
if error_on_failure {
Err(error)
} else {
match error {
CmdError::SystemError(_, _) => Err(error),
CmdError::NonZeroExitNotStreamed(output)
| CmdError::NonZeroExitAlreadyStreamed(output) => Ok(output),
}
let timer = bullet.start_timer(format!("Running {}", style::command(cmd.name())));
let output = cmd.named_output().or_else(|error| {
if error_on_failure {
Err(error)
} else {
match error {
CmdError::SystemError(_, _) => Err(error),
CmdError::NonZeroExitNotStreamed(output)
| CmdError::NonZeroExitAlreadyStreamed(output) => Ok(output),
}
})
.and_then(|output| RakeDetect::from_str(&output.stdout_lossy()))
}
}
})?;

RakeDetect::from_str(&output.stdout_lossy()).map(|rake_detect| (timer.done(), rake_detect))
}

impl RakeDetect {
#[must_use]
pub(crate) fn has_task(&self, string: &str) -> bool {
let task_re = regex::Regex::new(&format!("\\s{string}")).expect("clippy");
Expand Down
102 changes: 52 additions & 50 deletions buildpacks/ruby/src/steps/detect_rake_tasks.rs
Original file line number Diff line number Diff line change
@@ -1,78 +1,80 @@
use commons::output::{
fmt::{self, HELP},
section_log::{log_step, SectionLogger},
};

use crate::gem_list::GemList;
use crate::rake_status::{check_rake_ready, RakeStatus};
use crate::rake_task_detect::RakeDetect;
use crate::rake_task_detect::{self, RakeDetect};
use crate::RubyBuildpack;
use crate::RubyBuildpackError;
use bullet_stream::state::SubBullet;
use bullet_stream::{style, Print};
use libcnb::build::BuildContext;
use libcnb::Env;
use std::io::Stdout;

pub(crate) fn detect_rake_tasks(
logger: &dyn SectionLogger,
bullet: Print<SubBullet<Stdout>>,
gem_list: &GemList,
context: &BuildContext<RubyBuildpack>,
env: &Env,
) -> Result<Option<RakeDetect>, RubyBuildpackError> {
let rake = fmt::value("rake");
let gemfile = fmt::value("Gemfile");
let rakefile = fmt::value("Rakefile");
) -> Result<(Print<SubBullet<Stdout>>, Option<RakeDetect>), RubyBuildpackError> {
let help = style::important("HELP");
let rake = style::value("rake");
let gemfile = style::value("Gemfile");
let rakefile = style::value("Rakefile");

match check_rake_ready(
&context.app_dir,
gem_list,
[".sprockets-manifest-*.json", "manifest-*.json"],
) {
RakeStatus::MissingRakeGem => {
log_step(format!(
"Skipping rake tasks {}",
fmt::details(format!("no {rake} gem in {gemfile}"))
));

log_step(format!(
"{HELP} Add {gem} to your {gemfile} to enable",
gem = fmt::value("gem 'rake'")
));

Ok(None)
}
RakeStatus::MissingRakefile => {
log_step(format!(
"Skipping rake tasks {}",
fmt::details(format!("no {rakefile}"))
));
log_step(format!("{HELP} Add {rakefile} to your project to enable",));

Ok(None)
}
RakeStatus::MissingRakeGem => Ok((
bullet
.sub_bullet(format!(
"Skipping rake tasks ({rake} gem not found in {gemfile})"
))
.sub_bullet(format!(
"{help} Add {gem} to your {gemfile} to enable",
gem = style::value("gem 'rake'")
)),
None,
)),
RakeStatus::MissingRakefile => Ok((
bullet
.sub_bullet(format!("Skipping rake tasks ({rakefile} not found)",))
.sub_bullet(format!("{help} Add {rakefile} to your project to enable",)),
None,
)),
RakeStatus::SkipManifestFound(paths) => {
let files = paths
let manifest_files = paths
.iter()
.map(|path| fmt::value(path.to_string_lossy()))
.map(|path| style::value(path.to_string_lossy()))
.collect::<Vec<_>>()
.join(", ");

log_step(format!(
"Skipping rake tasks {}",
fmt::details(format!("Manifest files found {files}"))
));
log_step(format!("{HELP} Delete files to enable running rake tasks"));

Ok(None)
Ok((
bullet
.sub_bullet(format!(
"Skipping rake tasks (Manifest {files} found {manifest_files})",
files = if manifest_files.len() > 1 {
"files"
} else {
"file"
}
))
.sub_bullet(format!("{help} Delete files to enable running rake tasks")),
None,
))
}
RakeStatus::Ready(path) => {
log_step(format!(
"Detected rake ({rake} gem found, {rakefile} found at {path})",
path = fmt::value(path.to_string_lossy())
));

let rake_detect = RakeDetect::from_rake_command(logger, env, true)
.map_err(RubyBuildpackError::RakeDetectError)?;
let (bullet, rake_detect) = rake_task_detect::call(
bullet.sub_bullet(format!(
"Detected rake ({rake} gem found, {rakefile} found at {path})",
path = style::value(path.to_string_lossy())
)),
env,
true,
)
.map_err(RubyBuildpackError::RakeDetectError)?;

Ok(Some(rake_detect))
Ok((bullet, Some(rake_detect)))
}
}
}
Loading

0 comments on commit cc50d62

Please sign in to comment.