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

Feature/additional activation scripts #217

Merged
4 changes: 3 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ pub async fn execute() -> miette::Result<()> {
.from_env()
.into_diagnostic()?
// filter logs from apple codesign because they are very noisy
.add_directive("apple_codesign=off".parse().into_diagnostic()?);
.add_directive("apple_codesign=off".parse().into_diagnostic()?)
// set pixi's tracing level to warn
.add_directive("pixi=warn".parse().into_diagnostic()?);

// Setup the tracing subscriber
tracing_subscriber::fmt()
Expand Down
20 changes: 15 additions & 5 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,13 @@ pub async fn get_task_env(project: &Project) -> miette::Result<HashMap<String, S
let prefix = get_up_to_date_prefix(project).await?;

// Get environment variables from the activation
let activation_env = await_in_progress("activating environment", run_activation(prefix))
.await
.wrap_err("failed to activate environment")?;
let additional_activation_scripts = project.activation_scripts(Platform::current())?;
let activation_env = await_in_progress(
"activating environment",
run_activation(prefix, additional_activation_scripts.into_iter().collect()),
)
.await
.wrap_err("failed to activate environment")?;

// Get environment variables from the manifest
let manifest_env = get_metadata_env(project);
Expand All @@ -236,13 +240,19 @@ pub async fn get_task_env(project: &Project) -> miette::Result<HashMap<String, S
}

/// Runs and caches the activation script.
async fn run_activation(prefix: Prefix) -> miette::Result<HashMap<String, String>> {
async fn run_activation(
prefix: Prefix,
additional_activation_scripts: Vec<PathBuf>,
) -> miette::Result<HashMap<String, String>> {
let activator_result = tokio::task::spawn_blocking(move || {
// Run and cache the activation script
let shell: ShellEnum = ShellEnum::default();

// Construct an activator for the script
let activator = Activator::from_path(prefix.root(), shell, Platform::current())?;
let mut activator = Activator::from_path(prefix.root(), shell, Platform::current())?;
activator
.activation_scripts
.extend(additional_activation_scripts);

// Run the activation
activator.run_activation(ActivationVariables {
Expand Down
8 changes: 7 additions & 1 deletion src/cli/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ pub async fn execute(args: Args) -> miette::Result<()> {

// Construct an activator so we can run commands from the environment
let prefix = get_up_to_date_prefix(&project).await?;
let activator = Activator::from_path(prefix.root(), shell.clone(), Platform::current())
let activation_scripts: Vec<_> = project
.activation_scripts(Platform::current())?
.into_iter()
.collect();
let mut activator = Activator::from_path(prefix.root(), shell.clone(), Platform::current())
.into_diagnostic()?;

activator.activation_scripts.extend(activation_scripts);

let activator_result = activator
.activation(ActivationVariables {
// Get the current PATH variable
Expand Down
36 changes: 36 additions & 0 deletions src/project/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ pub struct ProjectManifest {
/// manifest.
#[serde(default)]
pub target: IndexMap<Spanned<TargetSelector>, TargetMetadata>,

/// Environment activation information.
///
/// We use an [`IndexMap`] to preserve the order in which the items where defined in the
/// manifest.
pub activation: Option<Activation>,
}

impl ProjectManifest {
Expand Down Expand Up @@ -131,6 +137,10 @@ pub struct TargetMetadata {
#[serde(default, rename = "build-dependencies")]
#[serde_as(as = "Option<IndexMap<_, DisplayFromStr>>")]
pub build_dependencies: Option<IndexMap<String, NamelessMatchSpec>>,

/// Additional information to activate an environment.
#[serde(default)]
pub activation: Option<Activation>,
}

/// Describes the contents of the `[package]` section of the project manifest.
Expand Down Expand Up @@ -255,6 +265,11 @@ impl From<LibCFamilyAndVersion> for LibC {
}
}
}
#[derive(Default, Clone, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Activation {
pub scripts: Option<Vec<String>>,
}

// Create an error report for usign a platform that is not supported by the project.
fn create_unsupported_platform_report(
Expand Down Expand Up @@ -345,4 +360,25 @@ mod test {
.collect::<Vec<_>>()
.join("\n"))
}

#[test]
fn test_activation_scripts() {
let contents = format!(
r#"
{PROJECT_BOILERPLATE}
[activation]
scripts = [".pixi/install/setup.sh"]

[target.win-64.activation]
scripts = [".pixi/install/setup.ps1"]

[target.linux-64.activation]
scripts = [".pixi/install/setup.sh", "test"]
"#
);

assert_debug_snapshot!(
toml_edit::de::from_str::<ProjectManifest>(&contents).expect("parsing should succeed!")
);
}
}
71 changes: 71 additions & 0 deletions src/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,48 @@ impl Project {
self.manifest.project.platforms.as_ref().as_slice()
}

/// Returns the all specified activation scripts that are used in the current platform.
pub fn activation_scripts(&self, platform: Platform) -> miette::Result<Vec<PathBuf>> {
let mut full_paths = Vec::new();
let mut all_scripts = Vec::new();

// Gather platform-specific activation scripts
for target_metadata in self.target_specific_metadata(platform) {
if let Some(activation) = &target_metadata.activation {
if let Some(scripts) = &activation.scripts {
all_scripts.extend(scripts.clone());
}
}
}

// Gather the main activation scripts if there are no target scripts defined.
if all_scripts.is_empty() {
if let Some(activation) = &self.manifest.activation {
if let Some(scripts) = &activation.scripts {
all_scripts.extend(scripts.clone());
}
}
}

// Check if scripts exist
let mut missing_scripts = Vec::new();
for script_name in &all_scripts {
let script_path = self.root().join(script_name);
if script_path.exists() {
full_paths.push(script_path);
tracing::debug!("Found activation script: {:?}", script_name);
} else {
missing_scripts.push(script_name);
}
}

if !missing_scripts.is_empty() {
tracing::warn!("can't find activation scripts: {:?}", missing_scripts);
}

Ok(full_paths)
}

/// Get the task with the specified name or `None` if no such task exists.
pub fn task_opt(&self, name: &str) -> Option<&Task> {
self.manifest.tasks.get(name)
Expand Down Expand Up @@ -742,4 +784,33 @@ mod tests {

assert_debug_snapshot!(project.all_dependencies(Platform::Linux64).unwrap());
}
#[test]
fn test_activation_scripts() {
// Using known files in the project so the test succeed including the file check.
let file_contents = r#"
[target.linux-64.activation]
scripts = ["Cargo.toml"]

[target.win-64.activation]
scripts = ["Cargo.lock"]

[activation]
scripts = ["pixi.toml", "pixi.lock"]
"#;

let manifest = toml_edit::de::from_str::<ProjectManifest>(&format!(
"{PROJECT_BOILERPLATE}\n{file_contents}"
))
.unwrap();
let project = Project {
root: Default::default(),
source: "".to_string(),
doc: Default::default(),
manifest,
};

assert_debug_snapshot!(project.activation_scripts(Platform::Linux64).unwrap());
assert_debug_snapshot!(project.activation_scripts(Platform::Win64).unwrap());
assert_debug_snapshot!(project.activation_scripts(Platform::OsxArm64).unwrap());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
source: src/project/manifest.rs
expression: "toml_edit::de::from_str::<ProjectManifest>(&contents).expect(\"parsing should succeed!\")"
---
ProjectManifest {
project: ProjectMetadata {
name: "foo",
version: Version {
version: [[0], [0], [1], [0]],
local: [],
},
description: None,
authors: [],
channels: [],
platforms: Spanned {
span: 121..123,
value: [],
},
},
tasks: {},
system_requirements: SystemRequirements {
windows: None,
unix: None,
macos: None,
linux: None,
cuda: None,
libc: None,
archspec: None,
},
dependencies: {},
host_dependencies: None,
build_dependencies: None,
target: {
Spanned {
span: 228..234,
value: Platform(
Win64,
),
}: TargetMetadata {
dependencies: {},
host_dependencies: None,
build_dependencies: None,
activation: Some(
Activation {
scripts: Some(
[
".pixi/install/setup.ps1",
],
),
},
),
},
Spanned {
span: 318..326,
value: Platform(
Linux64,
),
}: TargetMetadata {
dependencies: {},
host_dependencies: None,
build_dependencies: None,
activation: Some(
Activation {
scripts: Some(
[
".pixi/install/setup.sh",
"test",
],
),
},
),
},
},
activation: Some(
Activation {
scripts: Some(
[
".pixi/install/setup.sh",
],
),
},
),
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,5 @@ ProjectManifest {
},
),
target: {},
activation: None,
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ ProjectManifest {
},
host_dependencies: None,
build_dependencies: None,
activation: None,
},
Spanned {
span: 206..212,
Expand Down Expand Up @@ -90,6 +91,8 @@ ProjectManifest {
},
host_dependencies: None,
build_dependencies: None,
activation: None,
},
},
activation: None,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: src/project/mod.rs
expression: "project.activation_scripts(Platform::Win64).unwrap()"
---
[
"Cargo.lock",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: src/project/mod.rs
expression: "project.activation_scripts(Platform::OsxArm64).unwrap()"
---
[
"pixi.toml",
"pixi.lock",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: src/project/mod.rs
expression: "project.activation_scripts(Platform::Linux64).unwrap()"
---
[
"Cargo.toml",
]