things
This commit is contained in:
+172
-29
@@ -26,6 +26,36 @@ use crate::{
|
||||
recipe::{GitSource, OutputPackage, Recipe, RecipeKind, RecipeSet, Source},
|
||||
};
|
||||
|
||||
const SPLIT_SUBPACKAGE_SCRIPT: &str = r#"
|
||||
base=$1
|
||||
dest=$2
|
||||
package=$3
|
||||
shift 3
|
||||
|
||||
mkdir -p "$dest"
|
||||
for pattern do
|
||||
matches=$(mktemp)
|
||||
find "$base" -depth -path "$base/$pattern" -print > "$matches"
|
||||
if ! [ -s "$matches" ]; then
|
||||
echo "subpackage $package: glob '$pattern' matched no files under $base" >&2
|
||||
rm -f "$matches"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r path; do
|
||||
[ "$path" != "$base" ] || continue
|
||||
rel=${path#"$base"/}
|
||||
target=$dest/$rel
|
||||
mkdir -p "$(dirname "$target")"
|
||||
echo "$rel"
|
||||
mv "$path" "$target"
|
||||
done < "$matches"
|
||||
rm -f "$matches"
|
||||
done
|
||||
|
||||
find "$base" -depth -type d -empty -delete
|
||||
"#;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Builder {
|
||||
root: PathBuf,
|
||||
@@ -52,7 +82,7 @@ impl Builder {
|
||||
let requested = filter_skipped(recipes, requested);
|
||||
let plan = TaskPlanner::new(&self.root, &self.config.arch, recipes)
|
||||
.build_plan(&requested, rebuild)?;
|
||||
self.print_plan(&plan);
|
||||
self.print_plan(recipes, &plan)?;
|
||||
if dry_run {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -68,7 +98,7 @@ impl Builder {
|
||||
let requested = filter_skipped(recipes, requested);
|
||||
let plan =
|
||||
TaskPlanner::new(&self.root, &self.config.arch, recipes).fetch_plan(&requested)?;
|
||||
self.print_plan(&plan);
|
||||
self.print_plan(recipes, &plan)?;
|
||||
if dry_run {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -128,6 +158,11 @@ impl Builder {
|
||||
let recipe = recipes.recipe(key)?;
|
||||
self.task_prepare_sources(layout, recipe)
|
||||
}
|
||||
TaskId::PrepareRecipe(key) => {
|
||||
let recipe = recipes.recipe(key)?;
|
||||
let active = active.expect("recipe prepare task requires an active container");
|
||||
self.task_prepare_recipe(layout, recipe, active)
|
||||
}
|
||||
TaskId::ConfigureRecipe(key) => {
|
||||
let recipe = recipes.recipe(key)?;
|
||||
let active = active.expect("configure task requires an active container");
|
||||
@@ -206,7 +241,7 @@ impl Builder {
|
||||
let mut tmp = tempfile::NamedTempFile::new_in(&cache_dir)
|
||||
.with_context(|| format!("creating temp file in {}", cache_dir.display()))?;
|
||||
let mut hasher = Sha256::new();
|
||||
let mut buf = [0u8; 64 * 1024];
|
||||
let mut buf: [u8; 65536] = [0u8; 64 * 1024];
|
||||
loop {
|
||||
let n = std::io::Read::read(&mut response, &mut buf)
|
||||
.with_context(|| format!("reading response body for {url}"))?;
|
||||
@@ -235,12 +270,6 @@ impl Builder {
|
||||
})?;
|
||||
}
|
||||
|
||||
if unknown {
|
||||
bail!(
|
||||
"{}: source sha256 is unknown; update the recipe with sha256 = \"{hash}\"",
|
||||
recipe.key()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -418,6 +447,22 @@ impl Builder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn task_prepare_recipe(
|
||||
&self,
|
||||
layout: &Layout<'_>,
|
||||
recipe: &Recipe,
|
||||
active: &ActiveContainer,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(func) = recipe.phases().prepare() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
log::step("prepare", &format!("{} (recipe phase)", recipe.key()));
|
||||
let ctx = phase_context_for(recipe);
|
||||
self.invoke_with_runtime(active, &[PhaseArg::Ctx(ctx)], func)?;
|
||||
self.write_recipe_stamp(layout, recipe, "prepare")
|
||||
}
|
||||
|
||||
fn task_configure(
|
||||
&self,
|
||||
layout: &Layout<'_>,
|
||||
@@ -452,6 +497,10 @@ impl Builder {
|
||||
active: &ActiveContainer,
|
||||
) -> anyhow::Result<()> {
|
||||
log::step("install", &output.key());
|
||||
if !output.is_base() {
|
||||
return self.task_split_subpackage(layout, recipe, output, active);
|
||||
}
|
||||
|
||||
let dest = format!("/dest/{}", output.name());
|
||||
active.container.borrow().exec(
|
||||
&["mkdir".to_owned(), "-p".to_owned(), dest.clone()],
|
||||
@@ -468,6 +517,36 @@ impl Builder {
|
||||
self.write_output_stamp(layout, recipe, output, "install")
|
||||
}
|
||||
|
||||
fn task_split_subpackage(
|
||||
&self,
|
||||
layout: &Layout<'_>,
|
||||
recipe: &Recipe,
|
||||
output: &OutputPackage,
|
||||
active: &ActiveContainer,
|
||||
) -> anyhow::Result<()> {
|
||||
let base = recipe
|
||||
.base_output()
|
||||
.ok_or_else(|| anyhow::anyhow!("recipe `{}` has no base output", recipe.key()))?;
|
||||
let base_dest = format!("/dest/{}", base.name());
|
||||
let dest = format!("/dest/{}", output.name());
|
||||
let mut argv: Vec<String> = vec![
|
||||
"sh".to_owned(),
|
||||
"-eu".to_owned(),
|
||||
"-c".to_owned(),
|
||||
SPLIT_SUBPACKAGE_SCRIPT.to_owned(),
|
||||
"split-subpackage".to_owned(),
|
||||
base_dest,
|
||||
dest,
|
||||
output.name().to_owned(),
|
||||
];
|
||||
argv.extend(output.file_globs().iter().cloned());
|
||||
active
|
||||
.container
|
||||
.borrow()
|
||||
.exec(&argv, &base_env(&active.base_path), "/")?;
|
||||
self.write_output_stamp(layout, recipe, output, "install")
|
||||
}
|
||||
|
||||
fn task_install_host(
|
||||
&self,
|
||||
layout: &Layout<'_>,
|
||||
@@ -594,6 +673,10 @@ impl Builder {
|
||||
}
|
||||
|
||||
let env = base_env(&active.base_path);
|
||||
log::step(
|
||||
"sysroot",
|
||||
&format!("{} from {} target apk(s)", recipe.key(), deps.len()),
|
||||
);
|
||||
for dep in deps {
|
||||
let output = recipes.output(dep)?;
|
||||
let owning = recipes.recipe(output.recipe())?;
|
||||
@@ -616,6 +699,7 @@ impl Builder {
|
||||
"apk".to_owned(),
|
||||
"extract".to_owned(),
|
||||
"--allow-untrusted".to_owned(),
|
||||
"--force-overwrite".to_owned(),
|
||||
"--destination".to_owned(),
|
||||
"/sysroot".to_owned(),
|
||||
format!("/pkgs/{file_name}"),
|
||||
@@ -693,10 +777,10 @@ impl Builder {
|
||||
let bare = dep_key.strip_prefix("host:").unwrap_or(dep_key);
|
||||
mounts.push(Mount {
|
||||
host: install,
|
||||
container: format!("/tools/{bare}/usr/local"),
|
||||
container: format!("/tools/{bare}"),
|
||||
read_only: true,
|
||||
});
|
||||
tools_bins.push(format!("/tools/{bare}/usr/local/bin"));
|
||||
tools_bins.push(format!("/tools/{bare}/bin"));
|
||||
}
|
||||
|
||||
let name = format!(
|
||||
@@ -837,23 +921,54 @@ impl Builder {
|
||||
Ok(hex::encode(hasher.finalize()))
|
||||
}
|
||||
|
||||
fn print_plan(&self, plan: &TaskPlan) {
|
||||
fn print_plan(&self, recipes: &RecipeSet, plan: &TaskPlan) -> anyhow::Result<()> {
|
||||
if plan.is_empty() {
|
||||
log::skip("plan", "nothing to do");
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let task_count = plan.order().len();
|
||||
let edge_count = plan.dependency_count();
|
||||
log::step(
|
||||
"plan",
|
||||
&format!(
|
||||
"{} active task(s), {} edge(s)",
|
||||
plan.order().len(),
|
||||
plan.dependency_count()
|
||||
"{} {}, {} {}",
|
||||
task_count,
|
||||
plural(task_count, "task", "tasks"),
|
||||
edge_count,
|
||||
plural(edge_count, "dependency edge", "dependency edges")
|
||||
),
|
||||
);
|
||||
for task in plan.order() {
|
||||
println!("{task}");
|
||||
|
||||
let step_width = task_count.to_string().len() * 2 + 1;
|
||||
let action_width = plan
|
||||
.order()
|
||||
.iter()
|
||||
.map(|task| plan_task_parts(task).0.len())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let mut current_recipe: Option<String> = None;
|
||||
|
||||
for (index, task) in plan.order().iter().enumerate() {
|
||||
let recipe_slug = task_recipe_slug(task, recipes)?;
|
||||
if current_recipe.as_deref() != Some(recipe_slug.as_str()) {
|
||||
if current_recipe.is_some() {
|
||||
println!();
|
||||
}
|
||||
println!(" {recipe_slug}");
|
||||
current_recipe = Some(recipe_slug.clone());
|
||||
}
|
||||
|
||||
let step = format!("{}/{}", index + 1, task_count);
|
||||
let (action, target) = plan_task_parts(task);
|
||||
if target == recipe_slug {
|
||||
println!(" {step:>step_width$} {action}");
|
||||
} else {
|
||||
println!(" {step:>step_width$} {action:<action_width$} {target}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn abs_config_path(&self, path: &Path) -> PathBuf {
|
||||
@@ -874,14 +989,7 @@ struct ActiveContainer {
|
||||
fn filter_skipped(recipes: &RecipeSet, requested: &[String]) -> Vec<String> {
|
||||
requested
|
||||
.iter()
|
||||
.filter(|key| {
|
||||
if recipes.is_skipped(key) {
|
||||
log::skip("skip", &format!("{key} (build_if returned false)"));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.filter(|key| !recipes.is_skipped(key))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
@@ -894,9 +1002,27 @@ fn task_needs_container(task: &TaskId) -> bool {
|
||||
| TaskId::InstallPackageFiles(_)
|
||||
| TaskId::ProduceApk(_)
|
||||
| TaskId::InstallHostRecipe(_)
|
||||
| TaskId::PrepareRecipe(_)
|
||||
)
|
||||
}
|
||||
|
||||
fn plan_task_parts(task: &TaskId) -> (&'static str, &str) {
|
||||
match task {
|
||||
TaskId::FetchSources(recipe) => ("fetch sources", recipe),
|
||||
TaskId::PrepareSources(recipe) => ("prepare sources", recipe),
|
||||
TaskId::PrepareRecipe(recipe) => ("recipe prepare", recipe),
|
||||
TaskId::ConfigureRecipe(recipe) => ("configure", recipe),
|
||||
TaskId::BuildRecipe(recipe) => ("build", recipe),
|
||||
TaskId::InstallPackageFiles(output) => ("install files", output),
|
||||
TaskId::ProduceApk(output) => ("produce apk", output),
|
||||
TaskId::InstallHostRecipe(recipe) => ("install host", recipe),
|
||||
}
|
||||
}
|
||||
|
||||
fn plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
|
||||
if count == 1 { singular } else { plural }
|
||||
}
|
||||
|
||||
fn prefix_for(kind: RecipeKind) -> &'static str {
|
||||
match kind {
|
||||
RecipeKind::Package => "/usr",
|
||||
@@ -937,13 +1063,30 @@ fn default_jobs() -> i32 {
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
const TERMINAL_ENV_VARS: &[&str] = &[
|
||||
"TERM",
|
||||
"COLORTERM",
|
||||
"NO_COLOR",
|
||||
"CLICOLOR",
|
||||
"CLICOLOR_FORCE",
|
||||
"FORCE_COLOR",
|
||||
"TERM_PROGRAM",
|
||||
"TERM_PROGRAM_VERSION",
|
||||
];
|
||||
|
||||
fn bare_env() -> Vec<(String, String)> {
|
||||
vec![
|
||||
let mut env = vec![
|
||||
("PATH".to_owned(), String::new()),
|
||||
("HOME".to_owned(), "/tmp".to_owned()),
|
||||
("TERM".to_owned(), "dumb".to_owned()),
|
||||
("LC_ALL".to_owned(), "C".to_owned()),
|
||||
]
|
||||
];
|
||||
env.extend(TERMINAL_ENV_VARS.iter().filter_map(|key| {
|
||||
std::env::var(key)
|
||||
.ok()
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| ((*key).to_owned(), value))
|
||||
}));
|
||||
env
|
||||
}
|
||||
|
||||
fn base_env(path: &str) -> Vec<(String, String)> {
|
||||
|
||||
Reference in New Issue
Block a user