Skip to content

Commit

Permalink
fud2: Embed resources in executable, in release mode (#1910)
Browse files Browse the repository at this point in the history
* Skeleton for `fud2 get-rsrc` command

* Plumb rsrc_dir through

* Actually extract resource files

* Extract a file for a build!

* Add resource extraction utility

* Replace all remaining uses of `rsrc` config option

* Remove `rsrc` config option from fud2 docs

* Embed resources in release mode

* Better error for missing resource files

* Simplify macro stuff in main
  • Loading branch information
sampsyo authored Feb 27, 2024
1 parent f52e552 commit 221173a
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 67 deletions.
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions docs/running-calyx/fud2.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ Run the following command to edit `fud2`'s configuration file (usually `~/.confi
fud2 edit-config
```

Add the following fields:
Add these lines:
```toml
rsrc = "<path to calyx checkout>/fud2/rsrc"

[calyx]
base = "<path to calyx checkout>"
```
Expand Down
2 changes: 2 additions & 0 deletions fud2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ description = "Compiler driver for the Calyx infrastructure"
[dependencies]
fud-core = { path = "fud-core", version = "0.0.2" }
anyhow.workspace = true
manifest-dir-macros = "0.1"
include_dir = "0.7"

[[bin]]
name = "fud2"
Expand Down
88 changes: 70 additions & 18 deletions fud2/fud-core/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,28 @@ pub struct EditConfig {
pub editor: Option<String>,
}

/// extract a resource file
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "get-rsrc")]
pub struct GetResource {
/// the filename to extract
#[argh(positional)]
filename: Utf8PathBuf,

/// destination for the resource file
#[argh(option, short = 'o')]
output: Option<Utf8PathBuf>,
}

/// supported subcommands
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
pub enum Subcommand {
/// edit the configuration file
EditConfig(EditConfig),

/// extract a resource file
GetResource(GetResource),
}

#[derive(FromArgs)]
Expand Down Expand Up @@ -167,34 +183,70 @@ fn get_request(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Request> {
})
}

fn edit_config(driver: &Driver, cmd: EditConfig) -> anyhow::Result<()> {
let editor =
if let Some(e) = cmd.editor.or_else(|| std::env::var("EDITOR").ok()) {
e
} else {
bail!("$EDITOR not specified. Use -e")
};
let config_path = config::config_path(&driver.name);
log::info!("Editing config at {}", config_path.display());
let status = std::process::Command::new(editor)
.arg(config_path)
.status()
.expect("failed to execute editor");
if !status.success() {
bail!("editor exited with status {}", status);
}
Ok(())
}

fn get_resource(driver: &Driver, cmd: GetResource) -> anyhow::Result<()> {
let to_path = cmd.output.as_deref().unwrap_or(&cmd.filename);

// Try extracting embedded resource data.
if let Some(rsrc_files) = &driver.rsrc_files {
if let Some(data) = rsrc_files.get(cmd.filename.as_str()) {
log::info!("extracting {} to {}", cmd.filename, to_path);
std::fs::write(to_path, data)?;
return Ok(());
}
}

// Try copying a resource file from the resource directory.
if let Some(rsrc_dir) = &driver.rsrc_dir {
let from_path = rsrc_dir.join(&cmd.filename);
if !from_path.exists() {
bail!("resource file not found: {}", cmd.filename);
}
log::info!("copying {} to {}", cmd.filename, to_path);
std::fs::copy(from_path, to_path)?;
return Ok(());
}

bail!("unknown resource file {}", cmd.filename);
}

pub fn cli(driver: &Driver) -> anyhow::Result<()> {
let args: FakeArgs = argh::from_env();

// enable tracing
// Configure logging.
env_logger::Builder::new()
.format_timestamp(None)
.filter_level(args.log_level)
.target(env_logger::Target::Stderr)
.init();

// edit the configuration file
if let Some(Subcommand::EditConfig(EditConfig { editor })) = args.sub {
let editor =
if let Some(e) = editor.or_else(|| std::env::var("EDITOR").ok()) {
e
} else {
bail!("$EDITOR not specified. Use -e")
};
let config_path = config::config_path(&driver.name);
log::info!("Editing config at {}", config_path.display());
let status = std::process::Command::new(editor)
.arg(config_path)
.status()
.expect("failed to execute editor");
if !status.success() {
bail!("editor exited with status {}", status);
// Special commands that bypass the normal behavior.
match args.sub {
Some(Subcommand::EditConfig(cmd)) => {
return edit_config(driver, cmd);
}
return Ok(());
Some(Subcommand::GetResource(cmd)) => {
return get_resource(driver, cmd);
}
None => {}
}

// Make a plan.
Expand Down
19 changes: 19 additions & 0 deletions fud2/fud-core/src/exec/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ use super::{OpRef, Operation, Request, Setup, SetupRef, State, StateRef};
use crate::{run, utils};
use camino::{Utf8Path, Utf8PathBuf};
use cranelift_entity::{PrimaryMap, SecondaryMap};
use std::collections::HashMap;

#[derive(PartialEq)]
enum Destination {
State(StateRef),
Op(OpRef),
}

type FileData = HashMap<&'static str, &'static [u8]>;

/// A Driver encapsulates a set of States and the Operations that can transform between them. It
/// contains all the machinery to perform builds in a given ecosystem.
pub struct Driver {
pub name: String,
pub setups: PrimaryMap<SetupRef, Setup>,
pub states: PrimaryMap<StateRef, State>,
pub ops: PrimaryMap<OpRef, Operation>,
pub rsrc_dir: Option<Utf8PathBuf>,
pub rsrc_files: Option<FileData>,
}

impl Driver {
Expand Down Expand Up @@ -192,6 +197,8 @@ pub struct DriverBuilder {
setups: PrimaryMap<SetupRef, Setup>,
states: PrimaryMap<StateRef, State>,
ops: PrimaryMap<OpRef, Operation>,
rsrc_dir: Option<Utf8PathBuf>,
rsrc_files: Option<FileData>,
}

impl DriverBuilder {
Expand All @@ -201,6 +208,8 @@ impl DriverBuilder {
setups: Default::default(),
states: Default::default(),
ops: Default::default(),
rsrc_dir: None,
rsrc_files: None,
}
}

Expand Down Expand Up @@ -272,12 +281,22 @@ impl DriverBuilder {
)
}

pub fn rsrc_dir(&mut self, path: &str) {
self.rsrc_dir = Some(path.into());
}

pub fn rsrc_files(&mut self, files: FileData) {
self.rsrc_files = Some(files);
}

pub fn build(self) -> Driver {
Driver {
name: self.name,
setups: self.setups,
states: self.states,
ops: self.ops,
rsrc_dir: self.rsrc_dir,
rsrc_files: self.rsrc_files,
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions fud2/fud-core/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,17 @@ impl<'a> Run<'a> {
self.plan.workdir.clone(),
);

// Emit preamble.
emitter.var(
"build-tool",
std::env::current_exe()
.expect("executable path unknown")
.to_str()
.expect("invalid executable name"),
)?;
emitter.rule("get-rsrc", "$build-tool get-rsrc $out")?;
writeln!(emitter.out)?;

// Emit the setup for each operation used in the plan, only once.
let mut done_setups = HashSet::<SetupRef>::new();
for (op, _) in &self.plan.steps {
Expand Down Expand Up @@ -389,4 +400,9 @@ impl Emitter {
writeln!(self.out, " {} = {}", name, value)?;
Ok(())
}

/// Add a build command to extract a resource file into the build directory.
pub fn rsrc(&mut self, filename: &str) -> std::io::Result<()> {
self.build_cmd(&[filename], "get-rsrc", &[], &[])
}
}
Loading

0 comments on commit 221173a

Please sign in to comment.