Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Add build command #18

Merged
merged 2 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions cli/src/commands/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use colored::*;
use std::env;
use std::io::{self, BufRead, Error};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command as TokioCommand;

use crate::util::ShellCommand;
use crate::Environment;

pub async fn execute(working_directory: &str, environment: &Environment) -> Result<(), Error> {
// TODO: Extract these out
let cwd_path = Path::new(working_directory);
let build_native_path = Path::new(&"build-native");
let build_native_wd = cwd_path.join(build_native_path);

let build_wasm_path = Path::new(&"build-wasm");
let build_wasm_wd = cwd_path.join(build_wasm_path);

let native_build_command = ShellCommand {
prefix: "[2: Build Native]",
cmd: "cargo",
cwd: build_native_wd.to_str().unwrap(),
env: vec![],
args: vec!["build"],
};

let wasm_build_command = ShellCommand {
prefix: "[2: Build WASM]",
cmd: "wasm-pack",
cwd: build_wasm_wd.to_str().unwrap(),
env: vec![],
args: vec!["build", "--target=web"],
};

let build_command = match environment {
Environment::Native => native_build_command,
Environment::Wasm => wasm_build_command,
};

let commands = vec![
ShellCommand {
prefix: "[1: Build F#]",
cmd: "npm",
cwd: working_directory,
env: vec![],
args: vec!["run", "build:examples:pong:rust"],
},
build_command,
];

ShellCommand::run_sequential(commands).await?;

Ok(())
}
62 changes: 5 additions & 57 deletions cli/src/commands/develop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@ use std::process::{Command, Stdio};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command as TokioCommand;

struct CommandSpec<'a> {
prefix: &'a str,
cmd: &'a str,
cwd: &'a str,
args: Vec<&'a str>,
env: Vec<(&'a str, &'a str)>,
}
use crate::util::{self, ShellCommand};

pub async fn execute(working_directory: &str) -> io::Result<()> {
let cwd_path = Path::new(working_directory);
Expand All @@ -27,7 +21,7 @@ pub async fn execute(working_directory: &str) -> io::Result<()> {
let game_lib = target_dir.join(Path::new(&library_name));

let commands = vec![
CommandSpec {
ShellCommand {
prefix: "[1: Build F#]",
cmd: "watchexec",
cwd: working_directory,
Expand All @@ -40,14 +34,14 @@ pub async fn execute(working_directory: &str) -> io::Result<()> {
"npm run build:examples:pong:rust",
],
},
CommandSpec {
ShellCommand {
prefix: "[2: Build Rust]",
cmd: "watchexec",
cwd: build_native_wd.to_str().unwrap(),
env: vec![],
args: vec!["-w", "..", "-e", "rs", "--", "cargo build"],
},
CommandSpec {
ShellCommand {
prefix: "[3: Functor Runner]",
cmd: functor_runner_exe.to_str().unwrap(),
cwd: build_native_wd.to_str().unwrap(),
Expand All @@ -56,56 +50,10 @@ pub async fn execute(working_directory: &str) -> io::Result<()> {
},
];

// Spawn the processes
let mut handles = vec![];
for command_spec in commands {
println!("Using working dir: {}", &command_spec.cwd);
let mut command = TokioCommand::new(&command_spec.cmd);
println!("-- Running command: {}", &command_spec.cmd);

// Set environment variables if any
for (key, value) in command_spec.env {
command.env(key, value);
}

command
.current_dir(Path::new(&command_spec.cwd))
.args(&command_spec.args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = command.spawn()?;
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();

// Handle stdout
let stdout_handle = handle_output(command_spec.prefix.to_string(), stdout, true);
handles.push(tokio::spawn(stdout_handle));

// Handle stderr
let stderr_handle = handle_output(command_spec.prefix.to_string(), stderr, false);
handles.push(tokio::spawn(stderr_handle));
}

// Wait for all processes to finish
for handle in handles {
handle.await?;
}
util::ShellCommand::run_parallel(commands).await?;

Ok(())
}
async fn handle_output(prefix: String, stream: impl tokio::io::AsyncRead + Unpin, is_stdout: bool) {
let mut reader = BufReader::new(stream).lines();

while let Some(line) = reader.next_line().await.unwrap_or(None) {
let colored_prefix = if is_stdout {
prefix.blue()
} else {
prefix.red()
};

println!("{}: {}", colored_prefix, line);
}
}

fn get_nearby_bin(file: &str) -> io::Result<PathBuf> {
let curent_exe = env::current_exe()?;
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod build;
pub mod develop;
45 changes: 29 additions & 16 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
use std::{env, io, process};

use tokio::macros::*;

mod commands;

pub mod util;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
Expand All @@ -17,16 +19,28 @@ struct Args {
command: Command,
}

#[derive(ValueEnum, Clone, Debug)]
enum Environment {
Wasm,
Native,
}

impl Environment {
fn default(maybe_env: &Option<Environment>) -> Environment {
maybe_env.clone().unwrap_or(Environment::Native)
}
}

#[derive(Subcommand, Debug)]
enum Command {
#[command(aliases = ["i"])]
Init {
#[arg()]
template: String,
},
#[command(aliases = ["b"])]
Build,
#[command(aliases = ["d", "dev"])]
Build {
#[arg(value_enum)]
environment: Option<Environment>,
},
Develop,
}

Expand All @@ -40,25 +54,24 @@ async fn main() -> tokio::io::Result<()> {
let working_directory_os_str = working_directory.into_os_string();
let working_directory_str = working_directory_os_str.into_string().unwrap();

match &args.command {
println!("Running command: {:?}", args.command);
let res = match &args.command {
Command::Init { template } => {
// TODO: Handle init
println!(
"TODO: Initialize with template '{}' in directory: {}",
template, &working_directory_str,
)
}
Command::Build => {
println!("TODO: Build in directory: {}", &working_directory_str);
);
Ok(())
}
Command::Develop => {
println!("Running develop... {}", &working_directory_str);
let res = commands::develop::execute(&working_directory_str).await;
println!("Done!");
Command::Build { environment } => {
commands::build::execute(&working_directory_str, &Environment::default(environment))
.await
}
}
Command::Develop => commands::develop::execute(&working_directory_str).await,
};
println!("Done");

println!("Running command: {:?}", args.command);
Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions cli/src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod shell_command;
pub use shell_command::*;
102 changes: 102 additions & 0 deletions cli/src/util/shell_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use colored::*;
use std::env;
use std::io::{self, BufRead, Error};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command as TokioCommand;

pub struct ShellCommand<'a> {
pub prefix: &'a str,
pub cmd: &'a str,
pub cwd: &'a str,
pub args: Vec<&'a str>,
pub env: Vec<(&'a str, &'a str)>,
}

impl<'A> ShellCommand<'A> {
pub async fn run_sequential(commands: Vec<ShellCommand<'A>>) -> Result<(), Error> {
for command_spec in commands {
println!("Using working dir: {}", &command_spec.cwd);
let mut command = TokioCommand::new(&command_spec.cmd);
println!("-- Running command: {}", &command_spec.cmd);

// Set environment variables if any
for (key, value) in command_spec.env {
command.env(key, value);
}

command
.current_dir(Path::new(&command_spec.cwd))
.args(&command_spec.args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = command.spawn()?;
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();

// Handle stdout
let stdout_handle = handle_output(command_spec.prefix.to_string(), stdout, true);

// Handle stderr
let stderr_handle = handle_output(command_spec.prefix.to_string(), stderr, false);

// Wait for process to finish
tokio::spawn(stdout_handle).await?;
tokio::spawn(stderr_handle).await?;
}

Ok(())
}
pub async fn run_parallel(commands: Vec<ShellCommand<'A>>) -> Result<(), Error> {
let mut handles = vec![];
for command_spec in commands {
println!("Using working dir: {}", &command_spec.cwd);
let mut command = TokioCommand::new(&command_spec.cmd);
println!("-- Running command: {}", &command_spec.cmd);

// Set environment variables if any
for (key, value) in command_spec.env {
command.env(key, value);
}

command
.current_dir(Path::new(&command_spec.cwd))
.args(&command_spec.args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = command.spawn()?;
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();

// Handle stdout
let stdout_handle = handle_output(command_spec.prefix.to_string(), stdout, true);
handles.push(tokio::spawn(stdout_handle));

// Handle stderr
let stderr_handle = handle_output(command_spec.prefix.to_string(), stderr, false);
handles.push(tokio::spawn(stderr_handle));
}

// Wait for all processes to finish
for handle in handles {
handle.await?;
}

Ok(())
}
}

async fn handle_output(prefix: String, stream: impl tokio::io::AsyncRead + Unpin, is_stdout: bool) {
let mut reader = BufReader::new(stream).lines();

while let Some(line) = reader.next_line().await.unwrap_or(None) {
let colored_prefix = if is_stdout {
prefix.blue()
} else {
prefix.red()
};

println!("{}: {}", colored_prefix, line);
}
}
Loading