140 lines
4.7 KiB
Rust
140 lines
4.7 KiB
Rust
//! The `sausage` binary: subcommands only, each delegates to its module.
|
|
use clap::{Parser, Subcommand};
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Parser)]
|
|
#[command(version, about)]
|
|
struct Cli {
|
|
/// Shared host-user compile cache (otherwise SAUSAGE_CACHE or the XDG cache folder).
|
|
#[arg(long, global = true)]
|
|
cache: Option<PathBuf>,
|
|
#[command(subcommand)]
|
|
command: Command,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Command {
|
|
/// Create a world with default tools and /etc/sausage/config.yaml.
|
|
Init {
|
|
world: PathBuf,
|
|
#[arg(long)]
|
|
userland: Option<PathBuf>,
|
|
},
|
|
/// Start a world: the harness, its services and a console.
|
|
Run {
|
|
world: PathBuf,
|
|
/// Run the native Rust builder without namespace isolation.
|
|
#[arg(long)]
|
|
no_builder_sandbox: bool,
|
|
},
|
|
/// Run a shell in a world. With `-c`, run one command and exit with its status.
|
|
Sh {
|
|
world: PathBuf,
|
|
#[arg(short = 'c')]
|
|
command: Option<String>,
|
|
#[arg(long)]
|
|
userland: Option<PathBuf>,
|
|
},
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let cli = Cli::parse();
|
|
if let Some(cache) = cli.cache {
|
|
sausage::process::configure_cache(cache)?;
|
|
}
|
|
match cli.command {
|
|
Command::Init { world, userland } => {
|
|
let bundle = userland
|
|
.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from))
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"Copy tools failed: default tools unavailable. Set SAUSAGE_USERLAND or pass --userland."
|
|
)
|
|
})?;
|
|
use std::io::{IsTerminal, Write};
|
|
let terminal = std::io::stderr().is_terminal();
|
|
let mut progress_line = false;
|
|
let initialized = sausage::world::World::init_with_progress(&world, &bundle, |line| {
|
|
if terminal && line.starts_with("Precompile tools ") {
|
|
eprint!("\r\x1b[2K{line}");
|
|
let _ = std::io::stderr().flush();
|
|
progress_line = true;
|
|
} else {
|
|
if progress_line {
|
|
eprintln!();
|
|
progress_line = false;
|
|
}
|
|
eprintln!("{line}");
|
|
}
|
|
})
|
|
.await;
|
|
if progress_line {
|
|
eprintln!();
|
|
}
|
|
initialized?;
|
|
eprintln!("Next steps:");
|
|
eprintln!(
|
|
" Edit {} to configure providers and API keys.",
|
|
world.join("world/etc/sausage/config.yaml").display()
|
|
);
|
|
eprintln!(
|
|
" sausage run '{}'",
|
|
world.to_string_lossy().replace('\'', "'\\''")
|
|
);
|
|
Ok(())
|
|
}
|
|
Command::Run {
|
|
world,
|
|
no_builder_sandbox,
|
|
} => {
|
|
anyhow::ensure!(
|
|
world.join("world").is_dir(),
|
|
"No world at {}. Run sausage init {} first.",
|
|
world.display(),
|
|
world.display()
|
|
);
|
|
sausage::api::run_with_userland(
|
|
&world,
|
|
None,
|
|
sausage::process::RuntimeOptions { no_builder_sandbox },
|
|
)
|
|
.await
|
|
}
|
|
Command::Sh {
|
|
world,
|
|
command,
|
|
userland,
|
|
} => {
|
|
use sausage::exec::{Command, Exec, Options, Stream};
|
|
let bundle =
|
|
userland.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from));
|
|
let world = sausage::world::World::open_with_userland(&world, bundle.as_deref())?;
|
|
let runtime = sausage::process::Runtime::new(world)?;
|
|
let script =
|
|
command.ok_or_else(|| anyhow::anyhow!("interactive shell is not implemented"))?;
|
|
let status = runtime
|
|
.exec(
|
|
Command::Shell(script),
|
|
Options {
|
|
cwd: "/".into(),
|
|
..Default::default()
|
|
},
|
|
&mut |stream, bytes| {
|
|
use std::io::Write;
|
|
match stream {
|
|
Stream::Stdout => {
|
|
let _ = std::io::stdout().write_all(bytes);
|
|
}
|
|
Stream::Stderr => {
|
|
let _ = std::io::stderr().write_all(bytes);
|
|
}
|
|
}
|
|
},
|
|
)
|
|
.await?;
|
|
std::process::exit(status);
|
|
}
|
|
}
|
|
}
|