Add command stdout pipe

This commit is contained in:
Wildan M 2025-10-24 18:41:35 +07:00
parent e03e843abd
commit 2af2a5bd26
6 changed files with 197 additions and 67 deletions

View File

@ -21,8 +21,8 @@ fn cook(
is_offline: bool,
) -> Result<(), String> {
let source_dir = match is_offline {
true => fetch_offline(recipe_dir, recipe),
false => fetch(recipe_dir, recipe),
true => fetch_offline(recipe_dir, recipe, &None),
false => fetch(recipe_dir, recipe, &None),
}
.map_err(|err| format!("failed to fetch: {}", err))?;
@ -40,6 +40,7 @@ fn cook(
recipe,
is_offline,
!is_deps,
&None,
)
.map_err(|err| format!("failed to build: {}", err))?;

View File

@ -1,4 +1,5 @@
use std::io::stdout;
use std::collections::HashMap;
use std::io::{BufRead, BufReader, PipeReader, stdout};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::mpsc;
@ -11,7 +12,7 @@ use cookbook::WALK_DEPTH;
use cookbook::config::{CookConfig, get_config, init_config};
use cookbook::cook::cook_build::build;
use cookbook::cook::fetch::{fetch, fetch_offline};
use cookbook::cook::fs::create_target_dir;
use cookbook::cook::fs::{Stdout, create_target_dir};
use cookbook::cook::package::package;
use cookbook::recipe::CookRecipe;
use pkg::PackageName;
@ -20,7 +21,7 @@ use ratatui::Terminal;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::prelude::TermionBackend;
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph, Wrap};
use termion::screen::{ToAlternateScreen, ToMainScreen};
// A repo manager, to replace repo.sh
@ -141,11 +142,11 @@ fn main_inner() -> anyhow::Result<()> {
for recipe in &recipe_names {
match command {
CliCommand::Fetch => {
handle_fetch(recipe, &config)?;
handle_fetch(recipe, &config, &None)?;
}
CliCommand::Cook => {
let source_dir = handle_fetch(recipe, &config)?;
handle_cook(recipe, &config, source_dir, recipe.is_deps)?
let source_dir = handle_fetch(recipe, &config, &None)?;
handle_cook(recipe, &config, source_dir, recipe.is_deps, &None)?
}
CliCommand::Unfetch => handle_clean(recipe, &config, true, true)?,
CliCommand::Clean => handle_clean(recipe, &config, false, true)?,
@ -241,11 +242,15 @@ fn parse_args(args: Vec<String>) -> anyhow::Result<(CliConfig, CliCommand, Vec<C
Ok((config, command, recipes))
}
fn handle_fetch(recipe: &CookRecipe, config: &CliConfig) -> anyhow::Result<PathBuf> {
fn handle_fetch(
recipe: &CookRecipe,
config: &CliConfig,
logger: &Stdout,
) -> anyhow::Result<PathBuf> {
let recipe_dir = &recipe.dir;
let source_dir = match config.cook.offline {
true => fetch_offline(recipe_dir, &recipe.recipe),
false => fetch(recipe_dir, &recipe.recipe),
true => fetch_offline(recipe_dir, &recipe.recipe, logger),
false => fetch(recipe_dir, &recipe.recipe, logger),
}
.map_err(|e| anyhow!(e))?;
@ -257,10 +262,10 @@ fn handle_cook(
config: &CliConfig,
source_dir: PathBuf,
is_deps: bool,
logger: &Stdout,
) -> anyhow::Result<()> {
let recipe_dir = &recipe.dir;
let target_dir = create_target_dir(recipe_dir).map_err(|e| anyhow!(e))?;
let (stage_dir, auto_deps) = build(
recipe_dir,
&source_dir,
@ -269,6 +274,7 @@ fn handle_cook(
&recipe.recipe,
config.cook.offline,
!is_deps,
logger,
)
.map_err(|err| anyhow!("failed to build: {}", err))?;
@ -332,6 +338,7 @@ enum StatusUpdate {
Fetched(PackageName),
FailFetch(PackageName, String),
StartCook(PackageName),
CookLog(PackageName, String),
Cooked(PackageName),
FailCook(PackageName, String),
}
@ -342,6 +349,9 @@ struct TuiApp {
cook_queue: Vec<PackageName>,
done: Vec<PackageName>,
failed: Vec<PackageName>,
active_fetch: Option<PackageName>,
active_cook: Option<PackageName>,
logs: HashMap<PackageName, Vec<String>>,
}
impl TuiApp {
@ -356,16 +366,36 @@ impl TuiApp {
cook_queue: Vec::new(),
done: Vec::new(),
failed: Vec::new(),
active_fetch: None,
active_cook: None,
logs: HashMap::new(),
}
}
// Update the state based on a message from a worker thread
fn update_status(&mut self, update: StatusUpdate) {
let (name, new_status) = match update {
StatusUpdate::StartFetch(name) => (name, RecipeStatus::Fetching),
StatusUpdate::StartFetch(name) => {
self.active_fetch = Some(name.clone());
self.logs.insert(name.clone(), Vec::new()); // Clear old logs
(name.clone(), RecipeStatus::Fetching)
}
StatusUpdate::Fetched(name) => (name, RecipeStatus::Fetched),
StatusUpdate::FailFetch(name, err) => (name, RecipeStatus::Failed(err)),
StatusUpdate::StartCook(name) => (name, RecipeStatus::Cooking),
StatusUpdate::StartCook(name) => {
self.active_cook = Some(name.clone()); // Set active cook
self.logs.insert(name.clone(), Vec::new()); // Clear old logs
(name.clone(), RecipeStatus::Cooking)
}
StatusUpdate::CookLog(name, line) => {
self.logs.entry(name.clone()).or_default().push(line);
// No status change, just return the current state
if let Some((_, status)) = self.recipes.iter().find(|(r, _)| r.name == name) {
(name, status.clone())
} else {
return; // Should not happen
}
}
StatusUpdate::Cooked(name) => (name, RecipeStatus::Done),
StatusUpdate::FailCook(name, err) => (name, RecipeStatus::Failed(err)),
};
@ -402,6 +432,26 @@ impl TuiApp {
}
}
fn spawn_log_reader(
mut pipe_reader: PipeReader,
package_name: PackageName,
status_tx: mpsc::Sender<StatusUpdate>,
) {
thread::spawn(move || {
let reader = BufReader::new(&mut pipe_reader);
for line in reader.lines() {
let line_str = line.unwrap_or_else(|e| format!("[IO Error] {}", e));
if status_tx
.send(StatusUpdate::CookLog(package_name.clone(), line_str))
.is_err()
{
// TUI thread hung up
break;
}
}
});
}
fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<()> {
let (work_tx, work_rx) = mpsc::channel::<(CookRecipe, PathBuf)>();
let (status_tx, status_rx) = mpsc::channel::<StatusUpdate>();
@ -416,8 +466,9 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
cooker_status_tx
.send(StatusUpdate::StartCook(name.clone()))
.unwrap();
match handle_cook(&recipe, &cooker_config, source_dir, is_deps) {
let (mut stdout_writer, mut stderr_writer) = setup_logger(&cooker_status_tx, &name);
let logger = Some((&mut stdout_writer, &mut stderr_writer));
match handle_cook(&recipe, &cooker_config, source_dir, is_deps, &logger) {
Ok(_) => cooker_status_tx.send(StatusUpdate::Cooked(name)).unwrap(),
Err(e) => cooker_status_tx
.send(StatusUpdate::FailCook(name, e.to_string()))
@ -427,18 +478,22 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
});
// ---- Fetcher Thread ----
let fetcher_recipes = recipes.clone();
let fetcher_config = config.clone();
let fetcher_handle = thread::spawn(move || {
for recipe in recipes {
for recipe in fetcher_recipes {
let name = recipe.name.clone();
status_tx
.send(StatusUpdate::StartFetch(name.clone()))
.unwrap();
match handle_fetch(&recipe, &fetcher_config) {
let (mut stdout_writer, mut stderr_writer) = setup_logger(&status_tx, &name);
let logger = Some((&mut stdout_writer, &mut stderr_writer));
match handle_fetch(&recipe, &fetcher_config, &logger) {
Ok(source_dir) => {
status_tx.send(StatusUpdate::Fetched(name)).unwrap();
if work_tx.send((recipe, source_dir)).is_err() {
if work_tx.send((recipe.clone(), source_dir)).is_err() {
// Cooker thread died
break;
}
@ -455,15 +510,22 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
let mut terminal = Terminal::new(TermionBackend::new(stdout()))?;
terminal.clear()?;
let mut app = TuiApp::new(Vec::new());
let total_recipes = app.recipes.len();
let mut app = TuiApp::new(recipes);
// let total_recipes = app.recipes.len();
let mut running = true;
while running {
terminal.draw(|f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.constraints(
[
Constraint::Percentage(20),
Constraint::Percentage(20),
Constraint::Percentage(60),
]
.as_ref(),
)
.split(f.area());
// Left Pane
@ -509,13 +571,34 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
.block(Block::default().title("Cook Queue").borders(Borders::ALL));
f.render_widget(cook_list, chunks[1]);
let footer = Paragraph::new(format!(
"Done: {}/{} | Failed: {}",
app.done.len(),
total_recipes,
app.failed.len()
));
f.render_widget(footer, f.area());
let log_title = if let Some(active_name) = &app.active_cook {
format!("Build Log: {}", active_name.as_str())
} else {
"Build Log".to_string()
};
let log_text: Vec<String> = if let Some(active_name) = &app.active_cook {
app.logs
.get(active_name)
.cloned()
.unwrap_or_else(|| vec!["Waiting for logs...".to_string()])
} else {
vec!["No active cook job.".to_string()]
};
let log_paragraph = Paragraph::new(log_text.join("\n"))
.block(Block::default().title(log_title).borders(Borders::ALL))
.wrap(Wrap { trim: false });
f.render_widget(log_paragraph, chunks[2]);
// let footer = Paragraph::new(format!(
// "Done: {}/{} | Failed: {}",
// app.done.len(),
// total_recipes,
// app.failed.len()
// ));
// f.render_widget(footer, f.area());
})?;
while let Ok(update) = status_rx.try_recv() {
@ -536,3 +619,14 @@ fn run_tui_cook(config: CliConfig, recipes: Vec<CookRecipe>) -> anyhow::Result<(
Ok(())
}
fn setup_logger(
cooker_status_tx: &mpsc::Sender<StatusUpdate>,
name: &PackageName,
) -> (std::io::PipeWriter, std::io::PipeWriter) {
let (stdout_reader, stdout_writer) = std::io::pipe().expect("Failed to create stdout pipe");
let (stderr_reader, stderr_writer) = std::io::pipe().expect("Failed to create stderr pipe");
spawn_log_reader(stdout_reader, name.clone(), cooker_status_tx.clone());
spawn_log_reader(stderr_reader, name.clone(), cooker_status_tx.clone());
(stdout_writer, stderr_writer)
}

View File

@ -148,6 +148,7 @@ pub fn build(
recipe: &Recipe,
offline_mode: bool,
check_source: bool,
logger: &Stdout,
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
let sysroot_dir = target_dir.join("sysroot");
let stage_dir = target_dir.join("stage");
@ -296,7 +297,7 @@ pub fn build(
flags_fn("COOKBOOK_MESON_FLAGS", mesonflags),
),
BuildKind::Custom { script } => script.clone(),
BuildKind::Remote => return build_remote(target_dir, name, offline_mode),
BuildKind::Remote => return build_remote(target_dir, name, offline_mode, logger),
BuildKind::None => "".to_owned(),
};
@ -341,7 +342,7 @@ pub fn build(
"{}\n{}\n{}\n{}",
BUILD_PRESCRIPT, SHARED_PRESCRIPT, script, BUILD_POSTSCRIPT
);
run_command_stdin(command, full_script.as_bytes())?;
run_command_stdin(command, full_script.as_bytes(), logger)?;
// Move stage.tmp to stage atomically
rename(&stage_dir_tmp, &stage_dir)?;
@ -389,6 +390,7 @@ pub fn build_remote(
target_dir: &Path,
name: &PackageName,
offline_mode: bool,
logger: &Stdout,
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
// download straight from remote source then declare pkg dependencies as autodeps dependency
let stage_dir = target_dir.join("stage");
@ -398,9 +400,9 @@ pub fn build_remote(
let source_pubkey = target_dir.join("id_ed25519.pub.toml");
if !offline_mode {
download_wget(&get_remote_url(name, "pkgar"), &source_pkgar)?;
download_wget(&get_remote_url(name, "toml"), &source_toml)?;
download_wget(&get_pubkey_url(), &source_pubkey)?;
download_wget(&get_remote_url(name, "pkgar"), &source_pkgar, logger)?;
download_wget(&get_remote_url(name, "toml"), &source_toml, logger)?;
download_wget(&get_pubkey_url(), &source_pubkey, logger)?;
} else {
offline_check_exists(&source_pkgar)?;
offline_check_exists(&source_toml)?;

View File

@ -25,7 +25,11 @@ pub(crate) fn get_blake3(path: &PathBuf, show_progress: bool) -> Result<String,
})
}
pub fn fetch_offline(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
pub fn fetch_offline(
recipe_dir: &Path,
recipe: &Recipe,
logger: &Stdout,
) -> Result<PathBuf, String> {
let source_dir = recipe_dir.join("source");
if recipe.build.kind == BuildKind::None || recipe.build.kind == BuildKind::Remote {
// the build function doesn't need source dir exists
@ -33,10 +37,10 @@ pub fn fetch_offline(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, Stri
}
match &recipe.source {
Some(SourceRecipe::Path { path: _ }) | None => {
return fetch(recipe_dir, recipe);
return fetch(recipe_dir, recipe, logger);
}
Some(SourceRecipe::SameAs { same_as: _ }) => {
return fetch(recipe_dir, recipe);
return fetch(recipe_dir, recipe, logger);
}
Some(SourceRecipe::Git {
git: _,
@ -65,8 +69,8 @@ pub fn fetch_offline(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, Stri
"The downloaded tar blake3 '{source_tar_blake3}' is not equal to blake3 in recipe.toml."
));
}
fetch_extract_tar(source_tar, &source_dir)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir)?;
fetch_extract_tar(source_tar, &source_dir, logger)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir, logger)?;
} else {
// need to trust this tar file
return Err(format!(
@ -84,7 +88,7 @@ pub fn fetch_offline(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, Stri
Ok(source_dir)
}
pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &Stdout) -> Result<PathBuf, String> {
let source_dir = recipe_dir.join("source");
if recipe.build.kind == BuildKind::None || recipe.build.kind == BuildKind::Remote {
// the build function doesn't need source dir exists
@ -94,7 +98,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
Some(SourceRecipe::SameAs { same_as }) => {
let (canon_dir, recipe) = fetch_resolve_canon(recipe_dir, &same_as)?;
// recursively fetch
fetch(&canon_dir, &recipe)?;
fetch(&canon_dir, &recipe, logger)?;
fetch_make_symlink(&source_dir, &same_as)?;
}
Some(SourceRecipe::Path { path }) => {
@ -140,7 +144,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
command.arg("--depth").arg("1").arg("--shallow-submodules");
}
command.arg(&source_dir_tmp);
run_command(command)?;
run_command(command, logger)?;
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
@ -158,13 +162,13 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("remote").arg("set-url").arg("origin").arg(git);
run_command(command)?;
run_command(command, logger)?;
// Fetch origin
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("fetch").arg("origin");
run_command(command)?;
run_command(command, logger)?;
}
if let Some(_upstream) = upstream {
@ -179,7 +183,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("checkout").arg(rev);
run_command(command)?;
run_command(command, logger)?;
} else if !shallow_clone && !is_redox() {
//TODO: complicated stuff to check and reset branch to origin
//TODO: redox can't undestand this (got exit status 1)
@ -189,7 +193,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
command.env("BRANCH", branch);
}
command.current_dir(&source_dir);
run_command(command)?;
run_command(command, logger)?;
}
if !patches.is_empty() || script.is_some() {
@ -197,7 +201,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
run_command(command)?;
run_command(command, logger)?;
}
if !shallow_clone {
@ -205,7 +209,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("submodule").arg("sync").arg("--recursive");
run_command(command)?;
run_command(command, logger)?;
// Update submodules
let mut command = Command::new("git");
@ -215,10 +219,10 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
.arg("update")
.arg("--init")
.arg("--recursive");
run_command(command)?;
run_command(command, logger)?;
}
fetch_apply_patches(recipe_dir, patches, script, &source_dir)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir, logger)?;
}
Some(SourceRecipe::Tar {
tar,
@ -231,7 +235,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
while {
if !source_tar.is_file() {
tar_updated = true;
download_wget(&tar, &source_tar)?;
download_wget(&tar, &source_tar, logger)?;
}
let source_tar_blake3 = get_blake3(&source_tar, tar_updated)?;
if let Some(blake3) = blake3 {
@ -268,8 +272,8 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe) -> Result<PathBuf, String> {
// Create source.tmp
let source_dir_tmp = recipe_dir.join("source.tmp");
create_dir_clean(&source_dir_tmp)?;
fetch_extract_tar(source_tar, &source_dir_tmp)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir_tmp)?;
fetch_extract_tar(source_tar, &source_dir_tmp, logger)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir_tmp, logger)?;
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
@ -342,6 +346,7 @@ pub(crate) fn fetch_resolve_canon(
pub(crate) fn fetch_extract_tar(
source_tar: PathBuf,
source_dir_tmp: &PathBuf,
logger: &Stdout,
) -> Result<(), String> {
let mut command = Command::new("tar");
if is_redox() {
@ -354,7 +359,7 @@ pub(crate) fn fetch_extract_tar(
command.arg(&source_tar);
command.arg("--directory").arg(source_dir_tmp);
command.arg("--strip-components").arg("1");
run_command(command)?;
run_command(command, logger)?;
Ok(())
}
@ -387,6 +392,7 @@ pub(crate) fn fetch_apply_patches(
patches: &Vec<String>,
script: &Option<String>,
source_dir_tmp: &PathBuf,
logger: &Stdout,
) -> Result<(), String> {
for patch_name in patches {
let patch_file = recipe_dir.join(patch_name);
@ -409,12 +415,16 @@ pub(crate) fn fetch_apply_patches(
let mut command = Command::new("patch");
command.arg("--directory").arg(source_dir_tmp);
command.arg("--strip=1");
run_command_stdin(command, patch.as_bytes())?;
run_command_stdin(command, patch.as_bytes(), logger)?;
}
Ok(if let Some(script) = script {
let mut command = Command::new("bash");
command.arg("-ex");
command.current_dir(source_dir_tmp);
run_command_stdin(command, format!("{SHARED_PRESCRIPT}\n{script}").as_bytes())?;
run_command_stdin(
command,
format!("{SHARED_PRESCRIPT}\n{script}").as_bytes(),
logger,
)?;
})
}

View File

@ -1,7 +1,7 @@
use serde::Serialize;
use std::{
fs,
io::{self, Write},
io::{self, PipeWriter, Write},
path::{Path, PathBuf},
process::{self, Command, Stdio},
time::SystemTime,
@ -146,7 +146,25 @@ pub fn rename(src: &Path, dst: &Path) -> Result<(), String> {
})
}
pub fn run_command(mut command: process::Command) -> Result<(), String> {
pub type Stdout<'a> = Option<(&'a mut PipeWriter, &'a mut PipeWriter)>;
fn pipe_to_cmd(command: &mut Command, stdout_pipe: &Stdout) -> Result<(), String> {
Ok(if let Some((stdout, stderr)) = stdout_pipe {
command.stdout::<PipeWriter>(
stdout
.try_clone()
.map_err(|e| format!("unable to clone stdout fd: {:?}", e))?,
);
command.stderr(
stderr
.try_clone()
.map_err(|e| format!("unable to clone stderr fd: {:?}", e))?,
);
})
}
pub fn run_command(mut command: process::Command, stdout_pipe: &Stdout) -> Result<(), String> {
pipe_to_cmd(&mut command, stdout_pipe)?;
let status = command
.status()
.map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?;
@ -161,8 +179,13 @@ pub fn run_command(mut command: process::Command) -> Result<(), String> {
Ok(())
}
pub fn run_command_stdin(mut command: process::Command, stdin_data: &[u8]) -> Result<(), String> {
pub fn run_command_stdin(
mut command: process::Command,
stdin_data: &[u8],
stdout_pipe: &Stdout,
) -> Result<(), String> {
command.stdin(Stdio::piped());
pipe_to_cmd(&mut command, stdout_pipe)?;
let mut child = command
.spawn()
@ -217,13 +240,13 @@ pub fn offline_check_exists(path: &PathBuf) -> Result<(), String> {
Ok(())
}
pub fn download_wget(url: &str, dest: &PathBuf) -> Result<(), String> {
pub fn download_wget(url: &str, dest: &PathBuf, logger: &Stdout) -> Result<(), String> {
if !dest.is_file() {
let dest_tmp = PathBuf::from(format!("{}.tmp", dest.display()));
let mut command = Command::new("wget");
command.arg(translate_mirror(url));
command.arg("--continue").arg("-O").arg(&dest_tmp);
run_command(command)?;
run_command(command, logger)?;
rename(&dest_tmp, &dest)?;
}
Ok(())

View File

@ -15,7 +15,7 @@ use serde::{
use crate::WALK_DEPTH;
/// Specifies how to download the source for a recipe
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum SourceRecipe {
/// Reuse the source directory of another package
@ -88,7 +88,7 @@ impl SourceRecipe {
}
/// Specifies how to build a recipe
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(tag = "template")]
pub enum BuildKind {
/// Will not build (for meta packages)
@ -134,7 +134,7 @@ impl Default for BuildKind {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct BuildRecipe {
#[serde(flatten, default)]
pub kind: BuildKind,
@ -142,7 +142,7 @@ pub struct BuildRecipe {
pub dependencies: Vec<PackageName>,
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct PackageRecipe {
#[serde(default)]
pub dependencies: Vec<PackageName>,
@ -151,7 +151,7 @@ pub struct PackageRecipe {
}
/// Everything required to build a Redox package
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct Recipe {
/// Specifies how to download the source for this recipe
pub source: Option<SourceRecipe>,
@ -175,7 +175,7 @@ impl Recipe {
Ok(recipe)
}
}
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct CookRecipe {
pub name: PackageName,
pub dir: PathBuf,