Skip to content

Commit

Permalink
introduce Command struct and split run to make it easier to test
Browse files Browse the repository at this point in the history
  • Loading branch information
giacomocavalieri committed Feb 18, 2025
1 parent f7af356 commit de06739
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 129 deletions.
39 changes: 23 additions & 16 deletions compiler-cli/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use gleam_core::{
build::{NullTelemetry, Target},
error::{parse_os, Error, FileIoAction, FileKind, ShellCommandFailureReason, OS},
io::{
BeamCompiler, CommandExecutor, Content, DirEntry, FileSystemReader, FileSystemWriter,
OutputFile, ReadDir, Stdio, WrappedReader,
BeamCompiler, Command, CommandExecutor, Content, DirEntry, FileSystemReader,
FileSystemWriter, OutputFile, ReadDir, Stdio, WrappedReader,
},
language_server::{DownloadDependencies, Locker, MakeLocker},
manifest::Manifest,
Expand Down Expand Up @@ -186,34 +186,34 @@ impl FileSystemWriter for ProjectIO {
}

impl CommandExecutor for ProjectIO {
fn exec(
&self,
program: &str,
args: &[String],
env: &[(&str, String)],
cwd: Option<&Utf8Path>,
stdio: Stdio,
) -> Result<i32, Error> {
fn exec(&self, command: Command) -> Result<i32, Error> {
let Command {
program,
args,
env,
cwd,
stdio,
} = command;
tracing::trace!(program=program, args=?args.join(" "), env=?env, cwd=?cwd, "command_exec");
let result = std::process::Command::new(program)
let result = std::process::Command::new(&program)
.args(args)
.stdin(stdio.get_process_stdio())
.stdout(stdio.get_process_stdio())
.envs(env.iter().map(|pair| (pair.0, &pair.1)))
.current_dir(cwd.unwrap_or_else(|| Utf8Path::new("./")))
.envs(env.iter().map(|pair| (&pair.0, &pair.1)))
.current_dir(cwd.unwrap_or_else(|| Utf8Path::new("./").to_path_buf()))
.status();

match result {
Ok(status) => Ok(status.code().unwrap_or_default()),

Err(error) => Err(match error.kind() {
io::ErrorKind::NotFound => Error::ShellProgramNotFound {
program: program.to_string(),
program,
os: get_os(),
},

other => Error::ShellCommand {
program: program.to_string(),
program,
reason: ShellCommandFailureReason::IoError(other),
},
}),
Expand Down Expand Up @@ -679,7 +679,14 @@ pub fn git_init(path: &Utf8Path) -> Result<(), Error> {

let args = vec!["init".into(), "--quiet".into(), path.to_string()];

match ProjectIO::new().exec("git", &args, &[], None, Stdio::Inherit) {
let command = Command {
program: "git".to_string(),
args,
env: vec![],
cwd: None,
stdio: Stdio::Inherit,
};
match ProjectIO::new().exec(command) {
Ok(_) => Ok(()),
Err(err) => match err {
Error::ShellProgramNotFound { .. } => Ok(()),
Expand Down
90 changes: 68 additions & 22 deletions compiler-cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use gleam_core::{
build::{Built, Codegen, Compile, Mode, NullTelemetry, Options, Runtime, Target, Telemetry},
config::{DenoFlag, PackageConfig},
error::Error,
io::{CommandExecutor, Stdio},
io::{Command, CommandExecutor, Stdio},
paths::ProjectPaths,
type_::ModuleFunction,
};
Expand All @@ -30,6 +30,28 @@ pub fn command(
which: Which,
no_print_progress: bool,
) -> Result<(), Error> {
let command = setup(
paths,
arguments,
target,
runtime,
module,
which,
no_print_progress,
)?;
let status = ProjectIO::new().exec(command)?;
std::process::exit(status);
}

fn setup(
paths: &ProjectPaths,
arguments: Vec<String>,
target: Option<Target>,
runtime: Option<Runtime>,
module: Option<String>,
which: Which,
no_print_progress: bool,
) -> Result<Command, Error> {
// Validate the module path
if let Some(mod_path) = &module {
if !is_gleam_module(mod_path) {
Expand Down Expand Up @@ -106,39 +128,39 @@ pub fn command(

telemetry.running(&format!("{module}.main"));

// Run the command
let status = match target {
// Get the command to run the project.
match target {
Target::Erlang => match runtime {
Some(r) => Err(Error::InvalidRuntime {
target: Target::Erlang,
invalid_runtime: r,
}),
_ => run_erlang(paths, &root_config.name, &module, arguments),
_ => run_erlang_command(paths, &root_config.name, &module, arguments),
},
Target::JavaScript => match runtime.unwrap_or(mod_config.javascript.runtime) {
Runtime::Deno => run_javascript_deno(
Runtime::Deno => run_javascript_deno_command(
paths,
&root_config,
&main_function.package,
&module,
arguments,
),
Runtime::NodeJs => {
run_javascript_node(paths, &main_function.package, &module, arguments)
run_javascript_node_command(paths, &main_function.package, &module, arguments)
}
Runtime::Bun => {
run_javascript_bun_command(paths, &main_function.package, &module, arguments)
}
Runtime::Bun => run_javascript_bun(paths, &main_function.package, &module, arguments),
},
}?;

std::process::exit(status);
}
}

fn run_erlang(
fn run_erlang_command(
paths: &ProjectPaths,
package: &str,
module: &str,
arguments: Vec<String>,
) -> Result<i32, Error> {
) -> Result<Command, Error> {
let mut args = vec![];

// Specify locations of Erlang applications
Expand All @@ -164,15 +186,21 @@ fn run_erlang(
args.push(argument);
}

ProjectIO::new().exec("erl", &args, &[], None, Stdio::Inherit)
Ok(Command {
program: "erl".to_string(),
args,
env: vec![],
cwd: None,
stdio: Stdio::Inherit,
})
}

fn run_javascript_bun(
fn run_javascript_bun_command(
paths: &ProjectPaths,
package: &str,
module: &str,
arguments: Vec<String>,
) -> Result<i32, Error> {
) -> Result<Command, Error> {
let mut args = vec!["run".to_string()];
let entry = write_javascript_entrypoint(paths, package, module)?;

Expand All @@ -182,15 +210,21 @@ fn run_javascript_bun(
args.push(arg);
}

ProjectIO::new().exec("bun", &args, &[], None, Stdio::Inherit)
Ok(Command {
program: "bun".to_string(),
args,
env: vec![],
cwd: None,
stdio: Stdio::Inherit,
})
}

fn run_javascript_node(
fn run_javascript_node_command(
paths: &ProjectPaths,
package: &str,
module: &str,
arguments: Vec<String>,
) -> Result<i32, Error> {
) -> Result<Command, Error> {
let mut args = vec![];
let entry = write_javascript_entrypoint(paths, package, module)?;

Expand All @@ -200,7 +234,13 @@ fn run_javascript_node(
args.push(argument);
}

ProjectIO::new().exec("node", &args, &[], None, Stdio::Inherit)
Ok(Command {
program: "node".to_string(),
args,
env: vec![],
cwd: None,
stdio: Stdio::Inherit,
})
}

fn write_javascript_entrypoint(
Expand All @@ -221,13 +261,13 @@ main();
Ok(path)
}

fn run_javascript_deno(
fn run_javascript_deno_command(
paths: &ProjectPaths,
config: &PackageConfig,
package: &str,
module: &str,
arguments: Vec<String>,
) -> Result<i32, Error> {
) -> Result<Command, Error> {
let mut args = vec![];

// Run the main function.
Expand Down Expand Up @@ -294,7 +334,13 @@ fn run_javascript_deno(
args.push(argument);
}

ProjectIO::new().exec("deno", &args, &[], None, Stdio::Inherit)
Ok(Command {
program: "deno".to_string(),
args,
env: vec![],
cwd: None,
stdio: Stdio::Inherit,
})
}

fn add_deno_flag(args: &mut Vec<String>, flag: &str, flags: &DenoFlag) {
Expand Down
25 changes: 12 additions & 13 deletions compiler-core/src/build/elixir_libraries.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
error::ShellCommandFailureReason,
io::{CommandExecutor, FileSystemReader, FileSystemWriter, Stdio},
io::{Command, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio},
Error,
};
use camino::Utf8PathBuf;
Expand Down Expand Up @@ -73,28 +73,27 @@ where
// Any existing core lib links will get updated
update_links = true;
// TODO: test
let env = [("TERM", "dumb".into())];
let env = vec![("TERM".to_string(), "dumb".to_string())];
// Prepare the libs for Erlang's code:lib_dir function
let elixir_atoms: Vec<String> =
ELIXIR_LIBS.iter().map(|lib| format!(":{}", lib)).collect();
// Use Elixir to find its core lib paths and write the pathfinder file
let args = [
"--eval".into(),
let args = vec![
"--eval".to_string(),
format!(
":ok = File.write(~s({}), [{}] |> Stream.map(fn(lib) -> lib |> :code.lib_dir |> Path.expand end) |> Enum.join(~s(\\n)))",
self.paths_cache_filename(),
elixir_atoms.join(", "),
)
.into(),
),
];
tracing::debug!("writing_elixir_paths_to_build");
let status = self.io.exec(
ELIXIR_EXECUTABLE,
&args,
&env,
Some(&self.build_dir),
self.subprocess_stdio,
)?;
let status = self.io.exec(Command {
program: ELIXIR_EXECUTABLE.into(),
args,
env,
cwd: Some(self.build_dir.clone()),
stdio: self.subprocess_stdio,
})?;
if status != 0 {
return Err(Error::ShellCommand {
program: "elixir".into(),
Expand Down
Loading

0 comments on commit de06739

Please sign in to comment.