Skip to content

Commit

Permalink
feat(cli): Add build command (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
tommy-xr authored Jun 13, 2024
1 parent 5e89110 commit 4c49715
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 73 deletions.
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);
}
}

0 comments on commit 4c49715

Please sign in to comment.