Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continue replacing Layer trait with Struct API and using bullet stream #334

Merged
merged 39 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5d45679
Remove section logger from bundle list
schneems Oct 8, 2024
7a10615
Change top level bullet to a noun
schneems Oct 8, 2024
6ebbf05
Refactor command to functional style
schneems Oct 8, 2024
7b3b12c
Move logic to function
schneems Oct 8, 2024
95c5e0f
Replace section logger with bullet stream
schneems Oct 8, 2024
ee7a3e0
Replace logging with bullet stream
schneems Oct 8, 2024
caced42
Remove SectionLogger guard
schneems Oct 8, 2024
04c887a
Remove SectionLogger guard
schneems Oct 8, 2024
76af8fc
Replace section logger with bullet stream
schneems Oct 8, 2024
960fa98
Wire bullet stream input and output
schneems Oct 8, 2024
0c03dc5
Replace section log with bullet stream
schneems Oct 8, 2024
5ffe2d5
Replace section log with bullet stream
schneems Oct 8, 2024
49981b4
Replace section log with bullet stream
schneems Oct 8, 2024
c40b5d1
Pluralize word
schneems Oct 8, 2024
f6127e7
Replace section log with bullet stream
schneems Oct 8, 2024
8fa7ee1
Move associated function to function
schneems Oct 8, 2024
9859169
Wire up bullet stream
schneems Oct 8, 2024
876b8f4
Use bullet stream for `rake -p`
schneems Oct 8, 2024
89c7cc7
Wire bullet stream
schneems Oct 8, 2024
e429456
Replace section logger with bullet stream
schneems Oct 8, 2024
27fc631
Replace section logger with bullet stream
schneems Oct 8, 2024
8ca4359
Replace section logger with bullet stream
schneems Oct 8, 2024
4fd2888
Inline AppCacheCollection logic
schneems Oct 8, 2024
0f983c6
Remove output fmt module import
schneems Oct 8, 2024
5aad895
Inline command call
schneems Oct 8, 2024
8b1d3fa
Replace section log with bullet stream
schneems Oct 8, 2024
3f1c7e0
Use value directly instead of making a variable
schneems Oct 8, 2024
f3613b1
Inline command creation
schneems Oct 8, 2024
9e11414
Use value directly
schneems Oct 8, 2024
fd0b52d
Remove log_* calls in favor of bullet stream
schneems Oct 8, 2024
4cee2f2
Revert changes to AppCacheCollection
schneems Oct 8, 2024
d74c55d
Deprecate AppCacheCollection
schneems Oct 8, 2024
ecf7706
Remove commons output imports
schneems Oct 8, 2024
f0a84db
Remove commons output imports
schneems Oct 8, 2024
a7acbe5
Remove unused
schneems Oct 8, 2024
2b8a261
Remove clippy allow
schneems Oct 9, 2024
dcd03fd
Update buildpacks/ruby/src/steps/rake_assets_install.rs
schneems Oct 15, 2024
c047175
Changelog entry
schneems Oct 15, 2024
4f2c9ea
Update commons/CHANGELOG.md
schneems Oct 15, 2024
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
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
Loading