feat: support supplementary environment variables for hooks in App (#5916)

* feat: Implement launcher supplementary environmental variables, and env var substitution in hooks

* lint fix, prettier reformat

* descriptions added for each environment variable for hook settings

* alphabetize language keys

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Slackow
2026-08-04 22:23:07 +00:00
committed by GitHub
co-authored by Prospector
parent 474c6560fc
commit cc53d9a6f8
7 changed files with 430 additions and 41 deletions
+99 -39
View File
@@ -79,15 +79,95 @@ async fn run_credentials(
.into());
}
let pre_launch_hooks = context
let pre_launch_hook = context
.launch_overrides
.hooks
.pre_launch
.as_ref()
.or(settings.hooks.pre_launch.as_ref())
.filter(|hook_command| !hook_command.is_empty());
if let Some(hook) = pre_launch_hooks {
let mut cmd = shlex::split(hook)
let java_args = context
.launch_overrides
.extra_launch_args
.clone()
.unwrap_or(settings.extra_launch_args);
let wrapper = context
.launch_overrides
.hooks
.wrapper
.clone()
.or(settings.hooks.wrapper)
.filter(|hook_command| !hook_command.is_empty());
let env_args = context
.launch_overrides
.custom_env_vars
.clone()
.unwrap_or(settings.custom_env_vars);
let post_exit_hook = context
.launch_overrides
.hooks
.post_exit
.clone()
.or(settings.hooks.post_exit)
.filter(|hook_command| !hook_command.is_empty());
let memory = context.launch_overrides.memory.unwrap_or(settings.memory);
let resolution = context
.launch_overrides
.game_resolution
.unwrap_or(settings.game_resolution);
let has_hook_commands = pre_launch_hook.is_some()
|| wrapper.is_some()
|| post_exit_hook.is_some();
let full_path = if has_hook_commands {
Some(crate::util::io::canonicalize(
state
.directories
.instances_dir()
.join(&context.instance.path),
)?)
} else {
None
};
let hook_environment = if has_hook_commands {
let full_path = full_path
.as_ref()
.expect("hooked launches always resolve their instance path");
let java_version =
crate::launcher::resolve_java_for_launch(&context).await?;
Some(crate::launcher::hooks::HookEnvironment::from_current_env(
&env_args,
crate::launcher::hooks::HookVariables {
instance_name: context.instance.name.clone(),
instance_id: context.instance.path.clone(),
instance_dir: full_path.to_string_lossy().to_string(),
java_path: java_version.path.clone(),
java_args: crate::launcher::hooks::build_hook_java_args(
&java_args,
memory,
&java_version,
),
},
))
} else {
None
};
let launch_env_args = hook_environment
.as_ref()
.map_or_else(|| env_args.clone(), |env| env.injected_envs());
if let (Some(hook), Some(hook_environment), Some(full_path)) = (
pre_launch_hook,
hook_environment.as_ref(),
full_path.as_ref(),
) {
let expanded_hook = hook_environment.expand(hook);
let mut cmd = shlex::split(&expanded_hook)
.ok_or_else(|| {
crate::ErrorKind::LauncherError(format!(
"Invalid pre-launch command: {hook}",
@@ -96,17 +176,12 @@ async fn run_credentials(
.into_iter();
if let Some(command) = cmd.next() {
let full_path = crate::util::io::canonicalize(
state
.directories
.instances_dir()
.join(&context.instance.path),
)?;
let result = Command::new(command)
.args(cmd)
.current_dir(&full_path)
.envs(launch_env_args.iter().cloned())
.current_dir(full_path)
.spawn()
.map_err(|e| IOError::with_path(e, &full_path))?
.map_err(|e| IOError::with_path(e, full_path))?
.wait()
.await
.map_err(IOError::from)?;
@@ -121,34 +196,19 @@ async fn run_credentials(
}
}
let java_args = context
.launch_overrides
.extra_launch_args
.clone()
.unwrap_or(settings.extra_launch_args);
let wrapper = context
.launch_overrides
.hooks
.wrapper
.clone()
.or(settings.hooks.wrapper)
let wrapper = wrapper
.map(|hook| {
hook_environment
.as_ref()
.map_or(hook.clone(), |env| env.expand(&hook))
})
.filter(|hook_command| !hook_command.is_empty());
let memory = context.launch_overrides.memory.unwrap_or(settings.memory);
let resolution = context
.launch_overrides
.game_resolution
.unwrap_or(settings.game_resolution);
let env_args = context
.launch_overrides
.custom_env_vars
.clone()
.unwrap_or(settings.custom_env_vars);
let post_exit_hook = context
.launch_overrides
.hooks
.post_exit
.clone()
.or(settings.hooks.post_exit)
let post_exit_hook = post_exit_hook
.map(|hook| {
hook_environment
.as_ref()
.map_or(hook.clone(), |env| env.expand(&hook))
})
.filter(|hook_command| !hook_command.is_empty());
let mut mc_set_options: Vec<(String, String)> = vec![];
@@ -210,7 +270,7 @@ async fn run_credentials(
crate::minecraft_skins::flush_pending_skin_change().await?;
crate::launcher::launch_minecraft(
&java_args,
&env_args,
&launch_env_args,
&mc_set_options,
&wrapper,
&memory,