Skip to content

Commit

Permalink
[fud2] update path finding to support multi state input and output (#…
Browse files Browse the repository at this point in the history
…2134)

This PR begins progress towards addressing #1958. It updates the
algorithm used by `fud2` for finding paths from input files to output
files to take in operations which have many inputs and outputs.

The behavior concurs with the current `fud2` in some but not all cases
and does not maintain `fud2`'s always finding the shortest path of
operations.

Specifying operations themselves and emitting the Ninja file still
assume single state inputs and outputs. This will be changed in further
PRs.
  • Loading branch information
jku20 committed Jul 8, 2024
1 parent ed4ce6e commit 259c2c8
Show file tree
Hide file tree
Showing 24 changed files with 673 additions and 368 deletions.
107 changes: 73 additions & 34 deletions fud2/fud-core/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::config;
use crate::exec::{Driver, Request, StateRef};
use crate::exec::{
Driver, EnumeratePlanner, Request, SingleOpOutputPlanner, StateRef,
};
use crate::run::Run;
use anyhow::{anyhow, bail};
use argh::FromArgs;
Expand Down Expand Up @@ -91,19 +93,23 @@ struct FakeArgs {

/// the input file
#[argh(positional)]
input: Option<Utf8PathBuf>,
input: Vec<Utf8PathBuf>,

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

/// the state to start from
/// the states to start from.
/// The ith state is applied to the ith input file.
/// If more states are specified than files, files for these states are read from stdin.
#[argh(option)]
from: Option<String>,
from: Vec<String>,

/// the state to produce
/// The ith state is applied to the ith output file.
/// If more states are specified than files, files for these states are written to stdout
#[argh(option)]
to: Option<String>,
to: Vec<String>,

/// execution mode (run, plan, emit, gen, dot)
#[argh(option, short = 'm', default = "Mode::Run")]
Expand Down Expand Up @@ -132,34 +138,63 @@ struct FakeArgs {
/// log level for debugging fud internal
#[argh(option, long = "log", default = "log::LevelFilter::Warn")]
pub log_level: log::LevelFilter,

/// use new enumeration algorithm for finding operation sequences
#[argh(switch)]
planner: bool,
}

fn from_state(driver: &Driver, args: &FakeArgs) -> anyhow::Result<StateRef> {
match &args.from {
Some(name) => driver
.get_state(name)
.ok_or(anyhow!("unknown --from state")),
None => match args.input {
Some(ref input) => driver
.guess_state(input)
.ok_or(anyhow!("could not infer input state")),
None => bail!("specify an input file or use --from"),
},
fn get_states_with_errors(
driver: &Driver,
explicit_states: &[String],
files: &[Utf8PathBuf],
unknown_state: &str,
uninferable_file: &str,
no_states: &str,
) -> anyhow::Result<Vec<StateRef>> {
let explicit_states = explicit_states.iter().map(|state_str| {
driver
.get_state(state_str)
.ok_or(anyhow!("{unknown_state}"))
});
let inferred_states =
files.iter().skip(explicit_states.len()).map(|input_str| {
driver
.guess_state(input_str)
.ok_or(anyhow!("{uninferable_file}"))
});
let states = explicit_states
.chain(inferred_states)
.collect::<Result<Vec<_>, _>>()?;
if states.is_empty() {
bail!("{no_states}");
}
Ok(states)
}

fn to_state(driver: &Driver, args: &FakeArgs) -> anyhow::Result<StateRef> {
match &args.to {
Some(name) => {
driver.get_state(name).ok_or(anyhow!("unknown --to state"))
}
None => match &args.output {
Some(out) => driver
.guess_state(out)
.ok_or(anyhow!("could not infer output state")),
None => Err(anyhow!("specify an output file or use --to")),
},
}
fn from_states(
driver: &Driver,
args: &FakeArgs,
) -> anyhow::Result<Vec<StateRef>> {
get_states_with_errors(
driver,
&args.from,
&args.input,
"unknown --from state",
"could not infer input state",
"specify and input file or use --from",
)
}

fn to_state(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Vec<StateRef>> {
get_states_with_errors(
driver,
&args.to,
&args.output,
"unknown --to state",
"could no infer output state",
"specify an output file or use --to",
)
}

fn get_request(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Request> {
Expand All @@ -185,14 +220,18 @@ fn get_request(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Request> {
.ok_or(anyhow!("unknown --through op {}", s))
})
.collect();

Ok(Request {
start_file: args.input.clone(),
start_state: from_state(driver, args)?,
end_file: args.output.clone(),
end_state: to_state(driver, args)?,
start_files: args.input.clone(),
start_states: from_states(driver, args)?,
end_files: args.output.clone(),
end_states: to_state(driver, args)?,
through: through?,
workdir,
planner: if args.planner {
Box::new(EnumeratePlanner {})
} else {
Box::new(SingleOpOutputPlanner {})
},
})
}

Expand Down
6 changes: 3 additions & 3 deletions fud2/fud-core/src/exec/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl State {
self.extensions.iter().any(|e| e == ext)
}

/// Is this a "pseudo-state": doesn't correspond to an actual file, and must be an output state?
/// Is this a "pseudo-state": doesn't correspond to an actual file
pub fn is_pseudo(&self) -> bool {
self.extensions.is_empty()
}
Expand All @@ -38,8 +38,8 @@ entity_impl!(StateRef, "state");
/// An Operation transforms files from one State to another.
pub struct Operation {
pub name: String,
pub input: StateRef,
pub output: StateRef,
pub input: Vec<StateRef>,
pub output: Vec<StateRef>,
pub setups: Vec<SetupRef>,
pub emit: Box<dyn run::EmitBuild>,
/// Describes where this operation was defined.
Expand Down
Loading

0 comments on commit 259c2c8

Please sign in to comment.