WIP adding FPGA support -- build module should be complete
This commit is contained in:
parent
64ec6c0dcc
commit
cec0eb410e
4 changed files with 2252 additions and 0 deletions
1622
crates/fayalite/src/build.rs
Normal file
1622
crates/fayalite/src/build.rs
Normal file
File diff suppressed because it is too large
Load diff
426
crates/fayalite/src/build/external.rs
Normal file
426
crates/fayalite/src/build/external.rs
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{EscapeForUnixShell, JobItem, JobItemName, JobKind},
|
||||||
|
intern::{Intern, Interned},
|
||||||
|
util::job_server::AcquiredJob,
|
||||||
|
};
|
||||||
|
use clap::builder::StyledStr;
|
||||||
|
use eyre::{Context, eyre};
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
fmt::{self, Write},
|
||||||
|
mem,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
enum TemplateArg {
|
||||||
|
Literal(String),
|
||||||
|
InputPath { before: String, after: String },
|
||||||
|
OutputPath { before: String, after: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemplateArg {
|
||||||
|
fn after_mut(&mut self) -> &mut String {
|
||||||
|
match self {
|
||||||
|
TemplateArg::Literal(after)
|
||||||
|
| TemplateArg::InputPath { after, .. }
|
||||||
|
| TemplateArg::OutputPath { after, .. } => after,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TemplatedExternalJobKind {
|
||||||
|
template: Interned<[TemplateArg]>,
|
||||||
|
command_line_prefix: Interned<[Interned<str>]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
enum Token {
|
||||||
|
Char(char),
|
||||||
|
ArgSeparator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
fn as_ident_start(self) -> Option<char> {
|
||||||
|
match self {
|
||||||
|
Self::Char(ch @ '_') => Some(ch),
|
||||||
|
Self::Char(ch) if ch.is_alphabetic() => Some(ch),
|
||||||
|
Self::Char(_) | Self::ArgSeparator => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn as_ident_continue(self) -> Option<char> {
|
||||||
|
match self {
|
||||||
|
Self::Char(ch @ '_') => Some(ch),
|
||||||
|
Self::Char(ch) if ch.is_alphanumeric() => Some(ch),
|
||||||
|
Self::Char(_) | Self::ArgSeparator => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Tokens<'a> {
|
||||||
|
current: std::str::Chars<'a>,
|
||||||
|
rest: std::slice::Iter<'a, &'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Tokens<'a> {
|
||||||
|
fn new(args: &'a [&'a str]) -> Self {
|
||||||
|
Self {
|
||||||
|
current: "".chars(),
|
||||||
|
rest: args.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Tokens<'_> {
|
||||||
|
type Item = Token;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.current.next() {
|
||||||
|
Some(c) => Some(Token::Char(c)),
|
||||||
|
None => {
|
||||||
|
self.current = self.rest.next()?.chars();
|
||||||
|
Some(Token::ArgSeparator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser<'a> {
|
||||||
|
tokens: std::iter::Peekable<Tokens<'a>>,
|
||||||
|
template: Vec<TemplateArg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
fn new(args_template: &'a [&'a str]) -> Self {
|
||||||
|
Self {
|
||||||
|
tokens: Tokens::new(args_template).peekable(),
|
||||||
|
template: vec![TemplateArg::Literal(String::new())], // placeholder for program path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn parse_var(&mut self) -> Result<(), ParseErrorKind> {
|
||||||
|
let last_arg = self.template.last_mut().expect("known to be non-empty");
|
||||||
|
let TemplateArg::Literal(before) = last_arg else {
|
||||||
|
return Err(ParseErrorKind::EachArgMustHaveAtMostOneVar);
|
||||||
|
};
|
||||||
|
let before = mem::take(before);
|
||||||
|
self.tokens
|
||||||
|
.next_if_eq(&Token::Char('$'))
|
||||||
|
.ok_or(ParseErrorKind::ExpectedVar)?;
|
||||||
|
self.tokens
|
||||||
|
.next_if_eq(&Token::Char('{'))
|
||||||
|
.ok_or(ParseErrorKind::ExpectedVar)?;
|
||||||
|
let mut var_name = String::new();
|
||||||
|
self.tokens
|
||||||
|
.next_if(|token| {
|
||||||
|
token.as_ident_start().is_some_and(|ch| {
|
||||||
|
var_name.push(ch);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok_or(ParseErrorKind::ExpectedVar)?;
|
||||||
|
while let Some(_) = self.tokens.next_if(|token| {
|
||||||
|
token.as_ident_continue().is_some_and(|ch| {
|
||||||
|
var_name.push(ch);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
}) {}
|
||||||
|
self.tokens
|
||||||
|
.next_if_eq(&Token::Char('}'))
|
||||||
|
.ok_or(ParseErrorKind::ExpectedVar)?;
|
||||||
|
let after = String::new();
|
||||||
|
*last_arg = match &*var_name {
|
||||||
|
"input" => TemplateArg::InputPath { before, after },
|
||||||
|
"output" => TemplateArg::OutputPath { before, after },
|
||||||
|
"" => return Err(ParseErrorKind::ExpectedVar),
|
||||||
|
_ => {
|
||||||
|
return Err(ParseErrorKind::UnknownIdentifierExpectedInputOrOutput(
|
||||||
|
var_name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn parse(&mut self) -> Result<(), ParseErrorKind> {
|
||||||
|
while let Some(&peek) = self.tokens.peek() {
|
||||||
|
match peek {
|
||||||
|
Token::ArgSeparator => {
|
||||||
|
self.template.push(TemplateArg::Literal(String::new()));
|
||||||
|
let _ = self.tokens.next();
|
||||||
|
}
|
||||||
|
Token::Char('$') => self.parse_var()?,
|
||||||
|
Token::Char(ch) => {
|
||||||
|
self.template
|
||||||
|
.last_mut()
|
||||||
|
.expect("known to be non-empty")
|
||||||
|
.after_mut()
|
||||||
|
.push(ch);
|
||||||
|
let _ = self.tokens.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn finish(self, program_path: String) -> TemplatedExternalJobKind {
|
||||||
|
let Self {
|
||||||
|
mut tokens,
|
||||||
|
mut template,
|
||||||
|
} = self;
|
||||||
|
assert!(
|
||||||
|
tokens.next().is_none(),
|
||||||
|
"parse() must be called before finish()"
|
||||||
|
);
|
||||||
|
assert_eq!(template[0], TemplateArg::Literal(String::new()));
|
||||||
|
*template[0].after_mut() = program_path;
|
||||||
|
let template: Interned<[_]> = Intern::intern_owned(template);
|
||||||
|
let mut command_line_prefix = Vec::new();
|
||||||
|
for arg in &template {
|
||||||
|
match arg {
|
||||||
|
TemplateArg::Literal(arg) => command_line_prefix.push(str::intern(arg)),
|
||||||
|
TemplateArg::InputPath { before, after: _ }
|
||||||
|
| TemplateArg::OutputPath { before, after: _ } => {
|
||||||
|
command_line_prefix.push(str::intern(before));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TemplatedExternalJobKind {
|
||||||
|
template,
|
||||||
|
command_line_prefix: Intern::intern_owned(command_line_prefix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_program<'a>(
|
||||||
|
default_program_name: &'a str,
|
||||||
|
program_path_env_var: Option<&str>,
|
||||||
|
) -> eyre::Result<String> {
|
||||||
|
let var = program_path_env_var
|
||||||
|
.and_then(env::var_os)
|
||||||
|
.filter(|v| !v.is_empty());
|
||||||
|
let program_path = var.as_deref().unwrap_or(default_program_name.as_ref());
|
||||||
|
let program_path = which::which(program_path)
|
||||||
|
.wrap_err_with(|| format!("can't find program {program_path:?}"))?;
|
||||||
|
program_path
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.map_err(|program_path| eyre!("path to program is not valid UTF-8: {program_path:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ParseErrorKind {
|
||||||
|
ExpectedVar,
|
||||||
|
UnknownIdentifierExpectedInputOrOutput(String),
|
||||||
|
EachArgMustHaveAtMostOneVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TemplateParseError(ParseErrorKind);
|
||||||
|
|
||||||
|
impl From<ParseErrorKind> for TemplateParseError {
|
||||||
|
fn from(value: ParseErrorKind) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TemplateParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match &self.0 {
|
||||||
|
ParseErrorKind::ExpectedVar => {
|
||||||
|
f.write_str("expected `${{ident}}` for some identifier `ident`")
|
||||||
|
}
|
||||||
|
ParseErrorKind::UnknownIdentifierExpectedInputOrOutput(ident) => write!(
|
||||||
|
f,
|
||||||
|
"unknown identifier: expected `input` or `output`: {ident:?}",
|
||||||
|
),
|
||||||
|
ParseErrorKind::EachArgMustHaveAtMostOneVar => {
|
||||||
|
f.write_str("each argument must have at most one variable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for TemplateParseError {}
|
||||||
|
|
||||||
|
impl TemplatedExternalJobKind {
|
||||||
|
pub fn try_new(
|
||||||
|
default_program_name: &str,
|
||||||
|
program_path_env_var: Option<&str>,
|
||||||
|
args_template: &[&str],
|
||||||
|
) -> Result<eyre::Result<Self>, TemplateParseError> {
|
||||||
|
let mut parser = Parser::new(args_template);
|
||||||
|
parser.parse()?;
|
||||||
|
Ok(find_program(default_program_name, program_path_env_var)
|
||||||
|
.map(|program_path| parser.finish(program_path)))
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(
|
||||||
|
default_program_name: &str,
|
||||||
|
program_path_env_var: Option<&str>,
|
||||||
|
args_template: &[&str],
|
||||||
|
) -> eyre::Result<Self> {
|
||||||
|
match Self::try_new(default_program_name, program_path_env_var, args_template) {
|
||||||
|
Ok(retval) => retval,
|
||||||
|
Err(e) => panic!("{e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn usage(&self) -> StyledStr {
|
||||||
|
let mut retval = String::from("Usage:");
|
||||||
|
let mut last_input_index = 0usize;
|
||||||
|
let mut last_output_index = 0usize;
|
||||||
|
for arg in &self.template {
|
||||||
|
let mut write_arg = |before: &str, middle: fmt::Arguments<'_>, after: &str| {
|
||||||
|
retval.push_str(" ");
|
||||||
|
let start_len = retval.len();
|
||||||
|
if before != "" {
|
||||||
|
write!(retval, "{}", EscapeForUnixShell::new(before)).expect("won't error");
|
||||||
|
}
|
||||||
|
retval.write_fmt(middle).expect("won't error");
|
||||||
|
if after != "" {
|
||||||
|
write!(retval, "{}", EscapeForUnixShell::new(after)).expect("won't error");
|
||||||
|
}
|
||||||
|
if retval.len() == start_len {
|
||||||
|
write!(retval, "{}", EscapeForUnixShell::new("")).expect("won't error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match arg {
|
||||||
|
TemplateArg::Literal(s) => write_arg(s, format_args!(""), ""),
|
||||||
|
TemplateArg::InputPath { before, after } => {
|
||||||
|
last_input_index += 1;
|
||||||
|
write_arg(before, format_args!("<INPUT{last_input_index}>"), after);
|
||||||
|
}
|
||||||
|
TemplateArg::OutputPath { before, after } => {
|
||||||
|
last_output_index += 1;
|
||||||
|
write_arg(before, format_args!("<OUTPUT{last_output_index}>"), after);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval.into()
|
||||||
|
}
|
||||||
|
fn with_usage(&self, mut e: clap::Error) -> clap::Error {
|
||||||
|
e.insert(
|
||||||
|
clap::error::ContextKind::Usage,
|
||||||
|
clap::error::ContextValue::StyledStr(self.usage()),
|
||||||
|
);
|
||||||
|
e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKind for TemplatedExternalJobKind {
|
||||||
|
type Job = TemplatedExternalJob;
|
||||||
|
|
||||||
|
fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
job.inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
job.outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_line_prefix(&self) -> Interned<[Interned<str>]> {
|
||||||
|
self.command_line_prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned<str>]> {
|
||||||
|
job.command_line
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand(&self) -> Option<clap::Command> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_arg_matches(&self, _matches: &mut clap::ArgMatches) -> Result<Self::Job, clap::Error> {
|
||||||
|
panic!(
|
||||||
|
"a TemplatedExternalJob is not a subcommand of this program -- TemplatedExternalJobKind::subcommand() always returns None"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_command_line(
|
||||||
|
&self,
|
||||||
|
command_line: Interned<[Interned<str>]>,
|
||||||
|
) -> clap::error::Result<Self::Job> {
|
||||||
|
let mut inputs = Vec::new();
|
||||||
|
let mut outputs = Vec::new();
|
||||||
|
let mut command_line_iter = command_line.iter();
|
||||||
|
for template_arg in &self.template {
|
||||||
|
let Some(command_line_arg) = command_line_iter.next() else {
|
||||||
|
return Err(self.with_usage(clap::Error::new(
|
||||||
|
clap::error::ErrorKind::MissingRequiredArgument,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let match_io = |before: &str, after: &str| -> clap::error::Result<_> {
|
||||||
|
Ok(JobItemName::File {
|
||||||
|
path: command_line_arg
|
||||||
|
.strip_prefix(before)
|
||||||
|
.and_then(|s| s.strip_suffix(after))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
self.with_usage(clap::Error::new(
|
||||||
|
clap::error::ErrorKind::MissingRequiredArgument,
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.intern(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
match template_arg {
|
||||||
|
TemplateArg::Literal(template_arg) => {
|
||||||
|
if **command_line_arg != **template_arg {
|
||||||
|
return Err(self.with_usage(clap::Error::new(
|
||||||
|
clap::error::ErrorKind::MissingRequiredArgument,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TemplateArg::InputPath { before, after } => inputs.push(match_io(before, after)?),
|
||||||
|
TemplateArg::OutputPath { before, after } => outputs.push(match_io(before, after)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(_) = command_line_iter.next() {
|
||||||
|
Err(self.with_usage(clap::Error::new(clap::error::ErrorKind::UnknownArgument)))
|
||||||
|
} else {
|
||||||
|
Ok(TemplatedExternalJob {
|
||||||
|
command_line,
|
||||||
|
inputs: Intern::intern_owned(inputs),
|
||||||
|
outputs: Intern::intern_owned(outputs),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
job: &Self::Job,
|
||||||
|
inputs: &[JobItem],
|
||||||
|
acquired_job: &mut AcquiredJob,
|
||||||
|
) -> eyre::Result<Vec<JobItem>> {
|
||||||
|
assert!(inputs.iter().map(JobItem::name).eq(job.inputs));
|
||||||
|
let mut cmd: std::process::Command = std::process::Command::new(&*job.command_line[0]);
|
||||||
|
cmd.args(job.command_line[1..].iter().map(|arg| &**arg));
|
||||||
|
acquired_job
|
||||||
|
.run_command(cmd, |cmd: &mut std::process::Command| {
|
||||||
|
let status = cmd.status()?;
|
||||||
|
if status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!("process exited with status: {status}"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrap_err_with(|| format!("when trying to run: {:?}", job.command_line))?;
|
||||||
|
Ok(Vec::from_iter(job.outputs.iter().map(
|
||||||
|
|&output| match output {
|
||||||
|
JobItemName::Module { .. } => unreachable!(),
|
||||||
|
JobItemName::File { path } => JobItem::File { path },
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TemplatedExternalJob {
|
||||||
|
command_line: Interned<[Interned<str>]>,
|
||||||
|
inputs: Interned<[JobItemName]>,
|
||||||
|
outputs: Interned<[JobItemName]>,
|
||||||
|
}
|
|
@ -87,6 +87,7 @@ pub mod _docs;
|
||||||
|
|
||||||
pub mod annotations;
|
pub mod annotations;
|
||||||
pub mod array;
|
pub mod array;
|
||||||
|
pub mod build;
|
||||||
pub mod bundle;
|
pub mod bundle;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
|
@ -104,6 +105,7 @@ pub mod reg;
|
||||||
pub mod reset;
|
pub mod reset;
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
pub mod source_location;
|
pub mod source_location;
|
||||||
|
pub mod target;
|
||||||
pub mod testing;
|
pub mod testing;
|
||||||
pub mod ty;
|
pub mod ty;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
202
crates/fayalite/src/target.rs
Normal file
202
crates/fayalite/src/target.rs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{intern::Interned, util::job_server::AcquiredJob};
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
fmt,
|
||||||
|
iter::FusedIterator,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Peripheral: Any + Send + Sync + fmt::Debug {}
|
||||||
|
|
||||||
|
pub trait Tool: Any + Send + Sync + fmt::Debug {
|
||||||
|
fn name(&self) -> Interned<str>;
|
||||||
|
fn run(&self, acquired_job: &mut AcquiredJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Target: Any + Send + Sync + fmt::Debug {
|
||||||
|
fn name(&self) -> Interned<str>;
|
||||||
|
fn peripherals(&self) -> Interned<[Interned<dyn Peripheral>]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TargetsMap(Vec<(Interned<str>, Interned<dyn Target>)>);
|
||||||
|
|
||||||
|
impl TargetsMap {
|
||||||
|
fn sort(&mut self) {
|
||||||
|
self.0.sort_by(|(k1, _), (k2, _)| str::cmp(k1, k2));
|
||||||
|
self.0.dedup_by_key(|(k, _)| *k);
|
||||||
|
}
|
||||||
|
fn from_unsorted_vec(unsorted_vec: Vec<(Interned<str>, Interned<dyn Target>)>) -> Self {
|
||||||
|
let mut retval = Self(unsorted_vec);
|
||||||
|
retval.sort();
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
fn extend_from_unsorted_slice(&mut self, additional: &[(Interned<str>, Interned<dyn Target>)]) {
|
||||||
|
self.0.extend_from_slice(additional);
|
||||||
|
self.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TargetsMap {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::from_unsorted_vec(vec![
|
||||||
|
// TODO: add default targets here
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn access_targets<F: FnOnce(&mut Option<Arc<TargetsMap>>) -> R, R>(f: F) -> R {
|
||||||
|
static TARGETS: Mutex<Option<Arc<TargetsMap>>> = Mutex::new(None);
|
||||||
|
let mut targets_lock = TARGETS.lock().expect("shouldn't be poisoned");
|
||||||
|
f(&mut targets_lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_targets<I: IntoIterator<Item = Interned<dyn Target>>>(additional: I) {
|
||||||
|
// run iterator and target methods outside of lock
|
||||||
|
let additional = Vec::from_iter(additional.into_iter().map(|v| (v.name(), v)));
|
||||||
|
access_targets(|targets| {
|
||||||
|
Arc::make_mut(targets.get_or_insert_default()).extend_from_unsorted_slice(&additional);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn targets() -> TargetsSnapshot {
|
||||||
|
access_targets(|targets| match targets {
|
||||||
|
Some(targets) => TargetsSnapshot {
|
||||||
|
targets: targets.clone(),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let new_targets = Arc::<TargetsMap>::default();
|
||||||
|
*targets = Some(new_targets.clone());
|
||||||
|
TargetsSnapshot {
|
||||||
|
targets: new_targets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TargetsSnapshot {
|
||||||
|
targets: Arc<TargetsMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TargetsSnapshot {
|
||||||
|
pub fn get(&self, key: &str) -> Option<Interned<dyn Target>> {
|
||||||
|
let index = self
|
||||||
|
.targets
|
||||||
|
.0
|
||||||
|
.binary_search_by_key(&key, |(k, _v)| k)
|
||||||
|
.ok()?;
|
||||||
|
Some(self.targets.0[index].1)
|
||||||
|
}
|
||||||
|
pub fn iter(&self) -> TargetsIter {
|
||||||
|
self.into_iter()
|
||||||
|
}
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.targets.0.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TargetsSnapshot {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("TargetsSnapshot ")?;
|
||||||
|
f.debug_map().entries(self).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for &'_ mut TargetsSnapshot {
|
||||||
|
type Item = (Interned<str>, Interned<dyn Target>);
|
||||||
|
type IntoIter = TargetsIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for &'_ TargetsSnapshot {
|
||||||
|
type Item = (Interned<str>, Interned<dyn Target>);
|
||||||
|
type IntoIter = TargetsIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for TargetsSnapshot {
|
||||||
|
type Item = (Interned<str>, Interned<dyn Target>);
|
||||||
|
type IntoIter = TargetsIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
TargetsIter {
|
||||||
|
indexes: 0..self.targets.0.len(),
|
||||||
|
targets: self.targets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TargetsIter {
|
||||||
|
targets: Arc<TargetsMap>,
|
||||||
|
indexes: std::ops::Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TargetsIter {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("TargetsIter ")?;
|
||||||
|
f.debug_map().entries(self.clone()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for TargetsIter {
|
||||||
|
type Item = (Interned<str>, Interned<dyn Target>);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
Some(self.targets.0[self.indexes.next()?])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.indexes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(self) -> usize {
|
||||||
|
self.indexes.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(mut self) -> Option<Self::Item> {
|
||||||
|
self.next_back()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
Some(self.targets.0[self.indexes.nth(n)?])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold<B, F: FnMut(B, Self::Item) -> B>(self, init: B, mut f: F) -> B {
|
||||||
|
self.indexes
|
||||||
|
.fold(init, move |retval, index| f(retval, self.targets.0[index]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FusedIterator for TargetsIter {}
|
||||||
|
|
||||||
|
impl DoubleEndedIterator for TargetsIter {
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
Some(self.targets.0[self.indexes.next_back()?])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
Some(self.targets.0[self.indexes.nth_back(n)?])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfold<B, F: FnMut(B, Self::Item) -> B>(self, init: B, mut f: F) -> B {
|
||||||
|
self.indexes
|
||||||
|
.rfold(init, move |retval, index| f(retval, self.targets.0[index]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExactSizeIterator for TargetsIter {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.indexes.len()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue