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: refactor script and add perl as an interpreter #1229

Merged
merged 2 commits into from
Nov 30, 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
3 changes: 1 addition & 2 deletions src/render/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ fn print_as_table(packages: &[RepoDataRecord]) {
package
.channel
.as_ref()
.map(|s| s.rsplit('/').find(|s| !s.is_empty()))
.flatten()
.and_then(|s| s.rsplit('/').find(|s| !s.is_empty()))
.expect("expected channel to be defined and contain '/'")
.to_string()
} else {
Expand Down
66 changes: 66 additions & 0 deletions src/script/interpreter/bash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::path::PathBuf;

use rattler_conda_types::Platform;
use rattler_shell::shell;

use crate::script::{interpreter::DEBUG_HELP, run_process_with_replacements, ExecutionArgs};

use super::{find_interpreter, Interpreter};

const BASH_PREAMBLE: &str = r#"#!/bin/bash
## Start of bash preamble
if [ -z ${CONDA_BUILD+x} ]; then
source ((script_path))
fi
# enable debug mode for the rest of the script
set -x
## End of preamble
"#;

pub(crate) struct BashInterpreter;

impl Interpreter for BashInterpreter {
async fn run(&self, args: ExecutionArgs) -> Result<(), std::io::Error> {
let script = self.get_script(&args, shell::Bash).unwrap();

let build_env_path = args.work_dir.join("build_env.sh");
let build_script_path = args.work_dir.join("conda_build.sh");

tokio::fs::write(&build_env_path, script).await?;

let preamble = BASH_PREAMBLE.replace("((script_path))", &build_env_path.to_string_lossy());
let script = format!("{}\n{}", preamble, args.script.script());
tokio::fs::write(&build_script_path, script).await?;

let build_script_path_str = build_script_path.to_string_lossy().to_string();
let cmd_args = ["bash", "-e", &build_script_path_str];

let output = run_process_with_replacements(
&cmd_args,
&args.work_dir,
&args.replacements("$((var))"),
)
.await?;

if !output.status.success() {
let status_code = output.status.code().unwrap_or(1);
tracing::error!("Script failed with status {}", status_code);
tracing::error!("Work directory: '{}'", args.work_dir.display());
tracing::error!("{}", DEBUG_HELP);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Script failed".to_string(),
));
}

Ok(())
}

async fn find_interpreter(
&self,
build_prefix: Option<&PathBuf>,
platform: &Platform,
) -> Result<Option<PathBuf>, which::Error> {
find_interpreter("bash", build_prefix, platform)
}
}
82 changes: 82 additions & 0 deletions src/script/interpreter/cmd_exe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::path::PathBuf;

use rattler_conda_types::Platform;
use rattler_shell::shell;

use crate::script::{interpreter::DEBUG_HELP, run_process_with_replacements, ExecutionArgs};

use super::{find_interpreter, Interpreter};

const CMDEXE_PREAMBLE: &str = r#"
@chcp 65001 > nul
@echo on
IF "%CONDA_BUILD%" == "" (
@rem special behavior from conda-build for Windows
call ((script_path))
)
@rem re-enable echo because the activation scripts might have messed with it
@echo on
"#;

pub(crate) struct CmdExeInterpreter;

impl Interpreter for CmdExeInterpreter {
async fn run(&self, args: ExecutionArgs) -> Result<(), std::io::Error> {
let script = self.get_script(&args, shell::CmdExe).unwrap();

let build_env_path = args.work_dir.join("build_env.bat");
let build_script_path = args.work_dir.join("conda_build.bat");

tokio::fs::write(&build_env_path, script).await?;

let build_script = format!(
"{}\n{}",
CMDEXE_PREAMBLE.replace("((script_path))", &build_env_path.to_string_lossy()),
args.script.script()
);
tokio::fs::write(
&build_script_path,
&build_script.replace('\n', "\r\n").as_bytes(),
)
.await?;

let build_script_path_str = build_script_path.to_string_lossy().to_string();
let cmd_args = ["cmd.exe", "/d", "/c", &build_script_path_str];

let output = run_process_with_replacements(
&cmd_args,
&args.work_dir,
&args.replacements("%((var))%"),
)
.await?;

if !output.status.success() {
let status_code = output.status.code().unwrap_or(1);
tracing::error!("Script failed with status {}", status_code);
tracing::error!("Work directory: '{}'", args.work_dir.display());
tracing::error!("{}", DEBUG_HELP);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Script failed".to_string(),
));
}

Ok(())
}

async fn find_interpreter(
&self,
build_prefix: Option<&PathBuf>,
platform: &Platform,
) -> Result<Option<PathBuf>, which::Error> {
// check if COMSPEC is set to cmd.exe
if let Ok(comspec) = std::env::var("COMSPEC") {
if comspec.to_lowercase().contains("cmd.exe") {
return Ok(Some(PathBuf::from(comspec)));
}
}

// check if cmd.exe is in PATH
find_interpreter("cmd", build_prefix, platform)
}
}
100 changes: 100 additions & 0 deletions src/script/interpreter/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
mod bash;
mod cmd_exe;
mod nushell;
mod perl;
mod python;

use std::path::PathBuf;

pub(crate) use bash::BashInterpreter;
pub(crate) use cmd_exe::CmdExeInterpreter;
pub(crate) use nushell::NuShellInterpreter;
pub(crate) use perl::PerlInterpreter;
pub(crate) use python::PythonInterpreter;

use rattler_conda_types::Platform;
use rattler_shell::{
activation::{prefix_path_entries, ActivationError, ActivationVariables, Activator},
shell::{self, Shell},
};

use super::ExecutionArgs;

const DEBUG_HELP : &str = "To debug the build, run it manually in the work directory (execute the `./conda_build.sh` or `conda_build.bat` script)";

fn find_interpreter(
name: &str,
build_prefix: Option<&PathBuf>,
platform: &Platform,
) -> Result<Option<PathBuf>, which::Error> {
let exe_name = format!("{}{}", name, std::env::consts::EXE_SUFFIX);

let path = std::env::var("PATH").unwrap_or_default();
if let Some(build_prefix) = build_prefix {
let mut prepend_path = prefix_path_entries(build_prefix, platform)
.into_iter()
.collect::<Vec<_>>();
prepend_path.extend(std::env::split_paths(&path));
return Ok(
which::which_in_global(exe_name, std::env::join_paths(prepend_path).ok())?.next(),
);
}

Ok(which::which_in_global(exe_name, Some(path))?.next())
}

pub trait Interpreter {
fn get_script<T: Shell + Copy + 'static>(
&self,
args: &ExecutionArgs,
shell_type: T,
) -> Result<String, ActivationError> {
let mut shell_script = shell::ShellScript::new(shell_type, Platform::current());
for (k, v) in args.env_vars.iter() {
shell_script.set_env_var(k, v)?;
}
let host_prefix_activator =
Activator::from_path(&args.run_prefix, shell_type, args.execution_platform)?;

let current_path = std::env::var(shell_type.path_var(&args.execution_platform))
.ok()
.map(|p| std::env::split_paths(&p).collect::<Vec<_>>());
let conda_prefix = std::env::var("CONDA_PREFIX").ok().map(|p| p.into());

let activation_vars = ActivationVariables {
conda_prefix,
path: current_path,
path_modification_behavior: Default::default(),
};

let host_activation = host_prefix_activator.activation(activation_vars)?;

if let Some(build_prefix) = &args.build_prefix {
let build_prefix_activator =
Activator::from_path(build_prefix, shell_type, args.execution_platform)?;

let activation_vars = ActivationVariables {
conda_prefix: None,
path: Some(host_activation.path.clone()),
path_modification_behavior: Default::default(),
};

let build_activation = build_prefix_activator.activation(activation_vars)?;
shell_script.append_script(&host_activation.script);
shell_script.append_script(&build_activation.script);
} else {
shell_script.append_script(&host_activation.script);
}

Ok(shell_script.contents()?)
}

async fn run(&self, args: ExecutionArgs) -> Result<(), std::io::Error>;

#[allow(dead_code)]
async fn find_interpreter(
&self,
build_prefix: Option<&PathBuf>,
platform: &Platform,
) -> Result<Option<PathBuf>, which::Error>;
}
Loading