Skip to content

Commit

Permalink
support for extending fud cli + setting up pyenv
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel Thomas authored and Samuel Thomas committed Aug 15, 2024
1 parent c41fdae commit 1493874
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 20 deletions.
18 changes: 10 additions & 8 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions fud2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ fud-core = { path = "fud-core", version = "0.0.2", features = ["egg_planner"] }
anyhow.workspace = true
manifest-dir-macros = "0.1"
include_dir = "0.7"
argh.workspace = true
toml_edit = "0.22.20"

[lib]
name = "fud2"
Expand Down
77 changes: 67 additions & 10 deletions fud2/fud-core/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::config;
use crate::exec::{plan, Driver, Request, StateRef};
use crate::run::Run;
use anyhow::{anyhow, bail};
use argh::FromArgs;
use argh::{DynamicSubCommand, FromArgs};
use camino::Utf8PathBuf;
use std::fmt::Display;
use std::str::FromStr;
Expand Down Expand Up @@ -104,10 +104,45 @@ pub struct GetResource {
#[argh(subcommand, name = "list")]
pub struct ListCommand {}

pub struct DefaultDynamic {}

pub trait FakeDynamic: DynamicSubCommand {
fn run(&self, driver: &Driver) -> anyhow::Result<()>;
}

impl DynamicSubCommand for DefaultDynamic {
fn commands() -> &'static [&'static argh::CommandInfo] {
&[]
}

fn try_redact_arg_values(
_command_name: &[&str],
_args: &[&str],
) -> Option<Result<Vec<String>, argh::EarlyExit>> {
None
}

fn try_from_args(
_command_name: &[&str],
_args: &[&str],
) -> Option<Result<Self, argh::EarlyExit>> {
None
}
}

impl FakeDynamic for DefaultDynamic {
fn run(&self, _driver: &Driver) -> anyhow::Result<()> {
Ok(())
}
}

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

Expand All @@ -116,13 +151,19 @@ pub enum Subcommand {

/// list the available states and ops
List(ListCommand),

#[argh(dynamic)]
Dynamic(T),
}

#[derive(FromArgs)]
/// A generic compiler driver.
struct FakeArgs {
pub struct FakeArgs<T>
where
T: FakeDynamic,
{
#[argh(subcommand)]
pub sub: Option<Subcommand>,
pub sub: Option<Subcommand<T>>,

/// the input file
#[argh(positional)]
Expand Down Expand Up @@ -205,9 +246,9 @@ fn get_states_with_errors(
Ok(states)
}

fn from_states(
fn from_states<T: FakeDynamic>(
driver: &Driver,
args: &FakeArgs,
args: &FakeArgs<T>,
) -> anyhow::Result<Vec<StateRef>> {
get_states_with_errors(
driver,
Expand All @@ -219,7 +260,10 @@ fn from_states(
)
}

fn to_state(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Vec<StateRef>> {
fn to_state<T: FakeDynamic>(
driver: &Driver,
args: &FakeArgs<T>,
) -> anyhow::Result<Vec<StateRef>> {
get_states_with_errors(
driver,
&args.to,
Expand All @@ -230,7 +274,10 @@ fn to_state(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Vec<StateRef>> {
)
}

fn get_request(driver: &Driver, args: &FakeArgs) -> anyhow::Result<Request> {
fn get_request<T: FakeDynamic>(
driver: &Driver,
args: &FakeArgs<T>,
) -> anyhow::Result<Request> {
// The default working directory (if not specified) depends on the mode.
let workdir = args.dir.clone().unwrap_or_else(|| match args.mode {
Mode::Generate | Mode::Run => {
Expand Down Expand Up @@ -316,7 +363,7 @@ fn get_resource(driver: &Driver, cmd: GetResource) -> anyhow::Result<()> {

/// Given the name of a Driver, returns a config based on that name and CLI arguments.
pub fn config_from_cli(name: &str) -> anyhow::Result<figment::Figment> {
let args: FakeArgs = argh::from_env();
let args: FakeArgs<DefaultDynamic> = argh::from_env();
let mut config = config::load_config(name);

// Use `--set` arguments to override configuration values.
Expand All @@ -334,8 +381,15 @@ pub fn config_from_cli(name: &str) -> anyhow::Result<figment::Figment> {
}

pub fn cli(driver: &Driver, config: &figment::Figment) -> anyhow::Result<()> {
let args: FakeArgs = argh::from_env();
let args: FakeArgs<DefaultDynamic> = argh::from_env();
cli_dynamic(args, driver, config)
}

pub fn cli_dynamic<T: FakeDynamic>(
args: FakeArgs<T>,
driver: &Driver,
config: &figment::Figment,
) -> anyhow::Result<()> {
// Configure logging.
env_logger::Builder::new()
.format_timestamp(None)
Expand All @@ -355,6 +409,9 @@ pub fn cli(driver: &Driver, config: &figment::Figment) -> anyhow::Result<()> {
driver.print_info();
return Ok(());
}
Some(Subcommand::Dynamic(cmd)) => {
return cmd.run(driver);
}
None => {}
}

Expand Down
10 changes: 10 additions & 0 deletions fud2/fud-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ pub fn config_path(name: &str) -> std::path::PathBuf {
config_path
}

/// Location of the data directory
pub fn data_dir(name: &str) -> std::path::PathBuf {
// The configuration is usually at `~/.config/driver_name.toml`.
let config_base = env::var("XDG_DATA_HOME").unwrap_or_else(|_| {
let home = env::var("HOME").expect("$HOME not set");
home + "/.local/share"
});
Path::new(&config_base).join(name)
}

/// Get raw configuration data with some default options.
pub fn default_config() -> Figment {
Figment::from(Serialized::defaults(GlobalConfig::default()))
Expand Down
123 changes: 123 additions & 0 deletions fud2/src/cli_pyenv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::{fs, path::Path, process::Command, sync::OnceLock};

use argh::{CommandInfo, DynamicSubCommand, EarlyExit};
use fud_core::{cli::FakeDynamic, config};

#[derive(PartialEq, Debug)]
pub struct PyenvCommand {}

impl DynamicSubCommand for PyenvCommand {
fn commands() -> &'static [&'static CommandInfo] {
static RET: OnceLock<Vec<&'static CommandInfo>> = OnceLock::new();
RET.get_or_init(|| {
let mut commands = Vec::new();

let env_cmdinfo = CommandInfo {
name: "env",
description: "initialize fud2 python environment",
};

commands.push(&*Box::leak(Box::new(env_cmdinfo)));

commands
})
}

fn try_redact_arg_values(
command_name: &[&str],
args: &[&str],
) -> Option<Result<Vec<String>, EarlyExit>> {
for command in Self::commands() {
if command_name.last() == Some(&command.name) {
// Process arguments and redact values here.
if !args.is_empty() {
return Some(Err(
"Our example dynamic command never takes arguments!"
.to_string()
.into(),
));
}
return Some(Ok(Vec::new()));
}
}
None
}

fn try_from_args(
command_name: &[&str],
args: &[&str],
) -> Option<Result<Self, EarlyExit>> {
for command in Self::commands() {
if command_name.last() == Some(&command.name) {
if !args.is_empty() {
return Some(Err(
"Our example dynamic command never takes arguments!"
.to_string()
.into(),
));
}
return Some(Ok(PyenvCommand {}));
}
}
None
}
}

impl FakeDynamic for PyenvCommand {
fn run(&self, driver: &fud_core::Driver) -> anyhow::Result<()> {
let data_dir = config::data_dir(&driver.name);

fs::create_dir_all(&data_dir)?;

let pyenv = data_dir.join("venv");

// create new venv
Command::new("python3")
.args(["-m", "venv"])
.arg(&pyenv)
.stdout(std::io::stdout())
.output()?;

// install flit
Command::new(pyenv.join("bin").join("pip"))
.arg("install")
.arg("flit")
.stdout(std::io::stdout())
.output()?;

// grab the location of the calyx base install
let config = config::load_config(&driver.name);
let calyx_base: String = config.extract_inner("calyx.base")?;

// install fud python library
Command::new(pyenv.join("bin").join("python"))
.args(["-m", "flit", "install"])
.current_dir(Path::new(&calyx_base).join("fud"))
.stdout(std::io::stdout())
.output()?;

// install calyx-py library
Command::new(pyenv.join("bin").join("python"))
.args(["-m", "flit", "install"])
.current_dir(Path::new(&calyx_base).join("calyx-py"))
.stdout(std::io::stdout())
.output()?;

// add python location to fud2.toml
let config_path = config::config_path(&driver.name);
let contents = fs::read_to_string(&config_path)?;
let mut toml_doc: toml_edit::DocumentMut = contents.parse()?;

toml_doc["python"] = toml_edit::value(
pyenv
.join("bin")
.join("python")
.to_string_lossy()
.to_string(),
);

fs::write(&config_path, toml_doc.to_string())?;

Ok(())
}
}
3 changes: 3 additions & 0 deletions fud2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod cli_pyenv;
pub use cli_pyenv::PyenvCommand;

use std::str::FromStr;

use fud_core::{
Expand Down
9 changes: 7 additions & 2 deletions fud2/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use fud_core::{cli, DriverBuilder};
use fud2::PyenvCommand;
use fud_core::{
cli::{self, FakeArgs},
DriverBuilder,
};

fn main() -> anyhow::Result<()> {
let mut bld = DriverBuilder::new("fud2");
Expand Down Expand Up @@ -45,5 +49,6 @@ fn main() -> anyhow::Result<()> {
}

let driver = bld.build();
cli::cli(&driver, &config)
let args: FakeArgs<PyenvCommand> = argh::from_env();
cli::cli_dynamic(args, &driver, &config)
}

0 comments on commit 1493874

Please sign in to comment.