Last active
May 12, 2026 06:47
-
-
Save foriequal0/af3d4c1542c4efc444f8a91449eaf304 to your computer and use it in GitHub Desktop.
Resonable Rust port of `msys2_shell.cmd`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// | |
| /// Reasonable Rust port of | |
| /// https://github.com/msys2/MSYS2-packages/blob/master/filesystem/msys2_shell.cmd | |
| /// | |
| use std::ffi::{OsStr, OsString}; | |
| use std::os::windows::process::CommandExt; | |
| use std::path::{Path, PathBuf}; | |
| use std::process::{Command, ExitCode, Stdio}; | |
| fn main() -> Result<(), ExitCode> { | |
| let mut wd = std::env::current_dir().expect("current directory"); | |
| if !wd.join("msys-2.0.dll").exists() { | |
| wd = std::env::current_exe() | |
| .expect("current executable") | |
| .parent() | |
| .expect("parent directory") | |
| .join("usr/bin/"); | |
| } | |
| let params = checkparams(&wd)?; | |
| let msystem = params.msystem.as_deref(); | |
| let msyscon = params.msyscon.as_deref(); | |
| let msys2_nostart = params.msys2_nostart.clone(); | |
| let con = if msystem == Some(OsStr::new("MINGW32")) { | |
| TitleAndIcon::new("MinGW x32", "mingw32.ico") | |
| } else if msystem == Some(OsStr::new("MINGW64")) { | |
| TitleAndIcon::new("MinGW x64", "mingw64.ico") | |
| } else if msystem == Some(OsStr::new("UCRT64")) { | |
| TitleAndIcon::new("MinGW UCRT x64", "ucrt64.ico") | |
| } else if msystem == Some(OsStr::new("CLANG64")) { | |
| TitleAndIcon::new("MinGW Clang x64", "clang64.ico") | |
| } else if msystem == Some(OsStr::new("CLANGARM64")) { | |
| TitleAndIcon::new("MinGW Clang ARM64", "clangarm64.ico") | |
| } else { | |
| TitleAndIcon::new("MSYS2 MSYS", "msys2.ico") | |
| }; | |
| let command = if msyscon == Some(OsStr::new("mintty.exe")) { | |
| startmintty(&wd, ¶ms, &con) | |
| } else if msyscon == Some(OsStr::new("conemu")) { | |
| startconemu(&wd, ¶ms, &con)? | |
| } else if msyscon == Some(OsStr::new("defterm")) { | |
| startsh(&wd, ¶ms) | |
| } else { | |
| let mintty = wd.join("mintty.exe"); | |
| if !mintty.exists() { | |
| startsh(&wd, ¶ms) | |
| } else { | |
| startmintty( | |
| &wd, | |
| &Params { | |
| msyscon: Some("mintty.exe".into()), | |
| ..params | |
| }, | |
| &con, | |
| ) | |
| } | |
| }; | |
| spawn_or_start(command, msys2_nostart.as_deref()) | |
| } | |
| fn spawn_or_start(mut command: Command, msys2_nostart: Option<&OsStr>) -> Result<(), ExitCode> { | |
| let nostart = msys2_nostart.is_some_and(|x| !x.is_empty()); | |
| if !nostart { | |
| // try to match the behavior of the ` start ` command in `CMD`. | |
| // TODO: set title | |
| // https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags#flags:~:text=behavior%2E-,CREATE%5FNEW%5FCONSOLE | |
| const CREATE_NEW_CONSOLE: u32 = 0x0000_0010; | |
| command.creation_flags(CREATE_NEW_CONSOLE); | |
| if let Err(err) = command.spawn() { | |
| eprintln!("{err}"); | |
| return Err(ExitCode::FAILURE); | |
| } | |
| } | |
| match command.status() { | |
| Ok(status) => { | |
| if status.success() { | |
| return Ok(()); | |
| } | |
| if let Some(status_code) = status.code() | |
| && let Ok(exit_code) = u8::try_from(status_code) | |
| { | |
| return Err(ExitCode::from(exit_code)); | |
| } | |
| Err(ExitCode::FAILURE) | |
| } | |
| Err(err) => { | |
| eprintln!("{err}"); | |
| Err(ExitCode::FAILURE) | |
| } | |
| } | |
| } | |
| struct Params { | |
| msystem: Option<OsString>, | |
| msyscon: Option<OsString>, | |
| msys2_path_type: Option<OsString>, | |
| msys2_nostart: Option<OsString>, | |
| chere_invoking: Option<OsString>, | |
| where_dir: Option<PathBuf>, | |
| loginshell: OsString, | |
| shell_args: Vec<OsString>, | |
| } | |
| /// `:checkparams` | |
| fn checkparams(wd: &Path) -> Result<Params, ExitCode> { | |
| let mut msystem = std::env::var_os("MSYSTEM"); | |
| let mut msyscon = std::env::var_os("MSYSCON"); | |
| let mut msys2_path_type = std::env::var_os("MSYS2_PATH_TYPE"); | |
| let mut msys2_nostart = std::env::var_os("MSYS2_NOSTART"); | |
| let mut chere_invoking = std::env::var_os("CHERE_INVOKING"); | |
| // TODO: inherit from environment variable? | |
| let mut shell_args = Vec::new(); | |
| let mut where_dir = None; | |
| let mut loginshell = "bash".into(); | |
| let mut args = std::env::args_os(); | |
| args.next(); // skip args[0]; | |
| while let Some(arg) = args.next() { | |
| // Help option | |
| if arg == OsStr::new("-help") | |
| || arg == OsStr::new("--help") | |
| || arg == OsStr::new("-?") | |
| || arg == OsStr::new("/?") | |
| { | |
| printhelp(); | |
| return Err(ExitCode::SUCCESS); | |
| } else if arg == OsStr::new("-msys") || arg == OsStr::new("-msys2") { | |
| msystem = Some("MSYS".into()); | |
| } else if arg == OsStr::new("-mingw32") { | |
| msystem = Some("MINGW32".into()); | |
| } else if arg == OsStr::new("-mingw64") { | |
| msystem = Some("MINGW64".into()); | |
| } else if arg == OsStr::new("-ucrt64") { | |
| msystem = Some("UCRT64".into()); | |
| } else if arg == OsStr::new("-clang64") { | |
| msystem = Some("CLANG64".into()); | |
| } else if arg == OsStr::new("-clangarm64") { | |
| msystem = Some("CLANGARM64".into()); | |
| } else if arg == OsStr::new("-mingw") { | |
| if wd.join("../../mingw64").exists() { | |
| msystem = Some("MINGW64".into()); | |
| } else { | |
| msystem = Some("MINGW32".into()); | |
| } | |
| } | |
| // Console types | |
| else if arg == OsStr::new("-mintty") { | |
| msyscon = Some("mintty.exe".into()); | |
| } else if arg == OsStr::new("-conemu") { | |
| msyscon = Some("conemu".into()); | |
| } else if arg == OsStr::new("-defterm") { | |
| msyscon = Some("defterm".into()); | |
| } | |
| // Other parameters | |
| else if arg == OsStr::new("-full-path") || arg == OsStr::new("-use-full-path") { | |
| msys2_path_type = Some("inherit".into()); | |
| } else if arg == OsStr::new("-here") { | |
| chere_invoking = Some("enabled_from_arguments".into()); | |
| } else if arg == OsStr::new("-where") { | |
| let Some(dir) = args.next() else { | |
| eprintln!("Working directory is not specified for -where parameter."); | |
| return Err(ExitCode::from(2)); | |
| }; | |
| let dir = PathBuf::from(dir); | |
| if !dir.exists() { | |
| eprintln!("Cannot set specified working directory \"{dir:?}\""); | |
| return Err(ExitCode::from(2)); | |
| } | |
| chere_invoking = Some("enabled_from_arguments".into()); | |
| where_dir = Some(dir); | |
| } else if arg == OsStr::new("-no-start") { | |
| msys2_nostart = Some("yes".into()); | |
| } else if arg == OsStr::new("-shell") { | |
| let Some(shell) = args.next() else { | |
| eprintln!("Shell not specified for -shell parameter."); | |
| return Err(ExitCode::from(2)); | |
| }; | |
| loginshell = shell; | |
| } else { | |
| // Collect remaining command line arguments to be passed to shell | |
| shell_args.push(arg); | |
| shell_args.extend(args); | |
| break; | |
| } | |
| } | |
| Ok(Params { | |
| msystem, | |
| msyscon, | |
| msys2_path_type, | |
| msys2_nostart, | |
| chere_invoking, | |
| where_dir, | |
| loginshell, | |
| shell_args, | |
| }) | |
| } | |
| struct TitleAndIcon { | |
| contitle: &'static str, | |
| conicon: &'static str, | |
| } | |
| impl TitleAndIcon { | |
| fn new(title: &'static str, icon: &'static str) -> Self { | |
| TitleAndIcon { | |
| contitle: title, | |
| conicon: icon, | |
| } | |
| } | |
| } | |
| /// `:startmintty` | |
| fn startmintty(wd: &Path, params: &Params, con: &TitleAndIcon) -> Command { | |
| let mut command = Command::new(wd.join("mintty")); | |
| command.arg("-i").arg(concat_os_str(["/", con.conicon])); | |
| command.arg("-t").arg(con.contitle); | |
| command.arg(concat_os_str([OsStr::new("/usr/bin/"), ¶ms.loginshell])); | |
| command.arg("-l"); | |
| command.args(params.shell_args.as_slice()); | |
| set_envs(&mut command, params, params.msyscon.as_deref()); | |
| return command; | |
| fn concat_os_str<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(iter: I) -> OsString { | |
| let mut result = OsString::new(); | |
| for s in iter.into_iter() { | |
| result.push(s.as_ref()); | |
| } | |
| result | |
| } | |
| } | |
| /// `:startconemu` | |
| fn startconemu(wd: &Path, params: &Params, con: &TitleAndIcon) -> Result<Command, ExitCode> { | |
| let Some(conemu) = conemudetect() else { | |
| eprintln!("ConEmu not found. Exiting."); | |
| return Err(ExitCode::from(1)); | |
| }; | |
| let mut command = Command::new(conemu.command); | |
| command.arg("/Here"); | |
| command | |
| .arg("/Icon") | |
| .arg(wd.join("..\\..\\").join(con.conicon)); | |
| command.arg("/cmd").arg(wd.join(¶ms.loginshell)); | |
| command.arg("-l"); | |
| command.args(params.shell_args.as_slice()); | |
| set_envs(&mut command, params, Some(&conemu.msyscon)); | |
| Ok(command) | |
| } | |
| /// `:startsh` | |
| fn startsh(wd: &Path, params: &Params) -> Command { | |
| let mut command = Command::new(wd.join(¶ms.loginshell)); | |
| command.arg("-l"); | |
| command.args(params.shell_args.as_slice()); | |
| set_envs(&mut command, params, None); | |
| command | |
| } | |
| fn set_envs(command: &mut Command, params: &Params, msyscon: Option<&OsStr>) { | |
| if let Some(where_dir) = params.where_dir.as_deref() { | |
| command.current_dir(where_dir); | |
| } | |
| set_env(command, "MSYSTEM", params.msystem.as_ref()); | |
| set_env(command, "MSYSCON", msyscon); | |
| set_env(command, "MSYS2_PATH_TYPE", params.msys2_path_type.as_ref()); | |
| set_env(command, "CHERE_INVOKING", params.chere_invoking.as_ref()); | |
| } | |
| fn set_env(command: &mut Command, key: impl AsRef<OsStr>, value: Option<impl AsRef<OsStr>>) { | |
| if let Some(value) = value { | |
| let str = value.as_ref(); | |
| if !str.is_empty() { | |
| command.env(key, value); | |
| } else { | |
| command.env_remove(key); | |
| } | |
| } | |
| } | |
| struct ConEmu { | |
| command: PathBuf, | |
| msyscon: OsString, | |
| } | |
| /// `:conemudetect` | |
| fn conemudetect() -> Option<ConEmu> { | |
| if let Some(con_emu_dir) = std::env::var_os("ConEmuDir") { | |
| let con_emu64 = PathBuf::from(&con_emu_dir).join("ConEmu64.exe"); | |
| if con_emu64.exists() { | |
| return Some(ConEmu { | |
| command: con_emu64, | |
| msyscon: "conemu64.exe".into(), | |
| }); | |
| } | |
| let con_emu = PathBuf::from(&con_emu_dir).join("ConEmu64.exe"); | |
| if con_emu.exists() { | |
| return Some(ConEmu { | |
| command: con_emu, | |
| msyscon: "conemu.exe".into(), | |
| }); | |
| } | |
| } | |
| if check("ConEmu64.exe") { | |
| return Some(ConEmu { | |
| command: PathBuf::from("ConEmu64.exe"), | |
| msyscon: "conemu64.exe".into(), | |
| }); | |
| } else if check("ConEmu.exe") { | |
| return Some(ConEmu { | |
| command: PathBuf::from("ConEmu.exe"), | |
| msyscon: "conemu.exe".into(), | |
| }); | |
| } | |
| fn check(program: &str) -> bool { | |
| Command::new(program) | |
| .args(["/Exit"]) | |
| .stderr(Stdio::null()) | |
| .spawn() | |
| .and_then(|mut child| child.wait()) | |
| .is_ok_and(|exit_status| exit_status.success()) | |
| } | |
| // TODO: query registry | |
| None | |
| } | |
| /// `:printhelp` | |
| fn printhelp() { | |
| let current_exe = std::env::current_exe().expect("current executable"); | |
| let name = current_exe.file_name().unwrap().to_string_lossy(); | |
| println!( | |
| r#" | |
| echo Usage: | |
| echo {name} [options] [login shell parameters] | |
| echo. | |
| echo Options: | |
| echo -mingw32 | -mingw64 | -ucrt64 | -clang64 | | |
| echo -msys[2] | -clangarm64 Set shell type | |
| echo -defterm | -mintty | -conemu Set terminal type | |
| echo -here Use current directory as working | |
| echo directory | |
| echo -where DIRECTORY Use specified DIRECTORY as working | |
| echo directory | |
| echo -[use-]full-path Use full current PATH variable | |
| echo instead of trimming to minimal | |
| echo -no-start Do not use "start" command and | |
| echo return login shell resulting | |
| echo errorcode as this batch file | |
| echo resulting errorcode | |
| echo -shell SHELL Set login shell | |
| echo -help | --help | -? | /? Display this help and exit | |
| echo. | |
| echo Any parameter that cannot be treated as valid option and all | |
| echo following parameters are passed as login shell command parameters. | |
| echo."# | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm annoyed by the following prompt when I terminate the terminal with
Ctrl+Din Windows Terminal.So I ported
msys2_shell.cmdinto a native binary with Rust.