use std::path::Path;
use std::process::Stdio;
use std::sync::Arc;
use anyhow::{Context, Result};
use tracing::debug;
use crate::cli::reporter::HookInstallReporter;
use crate::cli::run::HookRunReporter;
use crate::git::GitCommandExt;
use crate::hook::{Hook, InstallInfo, InstalledHook};
use crate::languages::LanguageBackend;
use crate::process::Cmd;
use crate::run::run_by_batch;
use crate::store::Store;
#[derive(Debug, Copy, Clone)]
pub(crate) struct Julia;
#[async_trait::async_trait(?Send)]
impl LanguageBackend for Julia {
async fn install(
&self,
store: &Store,
hook: Arc<Hook>,
install_cwd: &Path,
reporter: &HookInstallReporter,
) -> Result<InstalledHook> {
let progress = reporter.on_install_start(&hook);
let mut info = InstallInfo::new(&hook, &store.hooks_dir())?;
debug!(%hook, target = %info.env_path.display(), "Installing Julia environment");
fs_err::tokio::create_dir_all(&info.env_path).await?;
let search_path = hook.repo_path().unwrap_or_else(|| hook.work_dir());
let find_src = |names: &[&str]| {
names
.iter()
.map(|n| search_path.join(n))
.find(|p| p.exists())
};
let project_dest = info.env_path.join("Project.toml");
if let Some(src) = find_src(&["JuliaProject.toml", "Project.toml"]) {
fs_err::tokio::copy(src, project_dest).await?;
} else {
fs_err::tokio::File::create(project_dest).await?;
}
if let Some(src) = find_src(&["JuliaManifest.toml", "Manifest.toml"]) {
fs_err::tokio::copy(src, info.env_path.join("Manifest.toml")).await?;
}
let julia_code = indoc::indoc! {r#"
using Pkg
Pkg.instantiate()
if !isempty(ARGS)
Pkg.REPLMode.pkgstr("add " * join(ARGS, " "))
end
"#};
Cmd::new("julia")
.current_dir(install_cwd)
.arg("--startup-file=no")
.arg(format!("--project={}", info.env_path.display()))
.arg("-e")
.arg(julia_code)
.arg("--")
.args(&hook.additional_dependencies)
.sanitize_git_repo_env()
.check(true)
.output()
.await
.context("Failed to instantiate Julia environment")?;
info.persist_env_path();
reporter.on_install_complete(progress);
Ok(InstalledHook::Installed {
hook,
info: Arc::new(info),
})
}
async fn check_health(&self, _info: &InstallInfo) -> Result<()> {
Cmd::new("julia")
.arg("--version")
.check(true)
.output()
.await
.context("Julia is not available")?;
Ok(())
}
async fn run(
&self,
_store: &Store,
hook: &InstalledHook,
filenames: &[&Path],
reporter: &HookRunReporter,
) -> Result<(i32, Vec<u8>)> {
let progress = reporter.on_run_start(hook, filenames.len());
let env_dir = hook.env_path().expect("Julia must have env path");
let mut entry = hook.entry.expect_argv_entry().split()?;
if let Some(repo_path) = hook.repo_path() {
let jl_path = repo_path.join(&entry[0]);
if jl_path.exists() {
entry[0] = jl_path.into_os_string();
}
}
let run = async |batch: &[&Path]| {
let output = Cmd::new("julia")
.current_dir(hook.work_dir())
.preserve_current_worktree(hook.work_dir())
.arg("--startup-file=no")
.arg(format!("--project={}", env_dir.display()))
.args(&entry)
.envs(&hook.env)
.args(&hook.args)
.file_args(batch)
.check(false)
.stdin(Stdio::null())
.pty_output_with_sink(reporter.output_sink(progress))
.await?;
reporter.on_run_progress(progress, batch.len() as u64);
anyhow::Ok(output)
};
let output = run_by_batch(hook, filenames, &entry, run).await?;
reporter.on_run_complete(progress);
Ok(output)
}
}