thunk_cli/
lib.rs

1use anyhow::anyhow;
2use clap::Parser;
3use std::{collections::HashMap, path::PathBuf, process::Command};
4
5mod sys;
6use sys::*;
7
8const ENV_VAR_VC_LTL5: &str = "VC_LTL";
9const ENV_VAR_YY_THUNKS: &str = "YY_THUNKS";
10
11/// Use Thunk to build your Rust program that runs on old Windows platforms.
12#[derive(Debug, Parser)]
13pub struct ThunkBuilder {
14    /// Operating system: xp, vista, win7, win10, 20h1 (dafault: win7)
15    #[arg(short, long, value_name = "OS")]
16    os: Option<OS>,
17    /// Operating system arch: x86, x64, arm64 (dafault: current os arch)
18    #[arg(short, long)]
19    arch: Option<Arch>,
20    /// To build a shared library
21    #[arg(long, value_name = "IS_LIB")]
22    lib: bool,
23    /// Link arg: console, windows (default: console)
24    #[arg(short, long)]
25    subsystem: Option<Subsystem>,
26    /// Args pass to cargo: cargo build <CARGO_ARGS>
27    #[arg(last = true, value_name = "CARGO_ARGS")]
28    cargo_args: Vec<String>,
29}
30
31impl ThunkBuilder {
32    pub fn build(mut self) -> anyhow::Result<Thunk> {
33        let env_vars: HashMap<String, String> = std::env::vars().collect();
34
35        let mut vc_ltl = {
36            let vc_ltl_env_path = env_vars.get(ENV_VAR_VC_LTL5).ok_or_else(|| {
37                anyhow!("You need to set {} environment variable.", ENV_VAR_VC_LTL5)
38            })?;
39            PathBuf::from(vc_ltl_env_path)
40        };
41
42        let os = self.os.unwrap_or(OS::Windows7);
43
44        let arch = if let Ok(arch_from_args) = get_arch_from_args(self.cargo_args.as_slice()) {
45            arch_from_args
46        } else {
47            let un_arch = self.arch.unwrap_or(get_default_arch()?);
48            let target = un_arch
49                .to_rust_target()
50                .ok_or_else(|| anyhow!("arch {} fail translate to target", un_arch.to_string()))?;
51            self.cargo_args.extend(["--target".to_owned(), target]);
52            un_arch
53        };
54
55        let os_lib =
56            get_vc_ltl_os_lib_path(os, arch).ok_or_else(|| anyhow!("os or arch is wrong"))?;
57
58        vc_ltl.push(os_lib);
59
60        let os_version =
61            get_os_version(os, arch).ok_or_else(|| anyhow!("failed to get os version"))?;
62
63        let is_lib = { get_is_lib_from_args(self.cargo_args.as_slice()) || self.lib };
64
65        let mut subsystem = Some(self.subsystem.unwrap_or(Subsystem::Console));
66
67        if is_lib {
68            subsystem = None;
69        }
70
71        let subsystem_args =
72            subsystem.map(|x| format!("-Clink-args=/SUBSYSTEM:{},{}", x.to_string(), os_version));
73
74        let mut rust_flags = vec!["-L".into(), format!("{}", vc_ltl.to_string_lossy())];
75
76        if let Some(args) = subsystem_args {
77            rust_flags.push(args.into());
78
79            if let Some(Subsystem::Windows) = subsystem {
80                rust_flags.push("-Clink-args=/ENTRY:mainCRTStartup".into())
81            }
82        }
83
84        let thunks_obj = {
85            let mut thunks = {
86                let yy_thunks_env_path = env_vars.get(ENV_VAR_YY_THUNKS).ok_or_else(|| {
87                    anyhow!(
88                        "You need to set {} environment variable.",
89                        ENV_VAR_YY_THUNKS
90                    )
91                })?;
92                PathBuf::from(yy_thunks_env_path)
93            };
94
95            let os_obj = get_yy_thunks_obj_path(os, arch).ok_or_else(|| anyhow!(""))?;
96            thunks.push(os_obj);
97            Some(thunks)
98        };
99
100        if let Some(obj) = thunks_obj {
101            rust_flags.push(format!("-Clink-args={}", obj.to_string_lossy()));
102        }
103
104        let target_dir = format!("./target/win{}_build", os.to_string().to_ascii_lowercase());
105
106        let mut cargo_args = vec![
107            "build".to_owned(),
108            "--target-dir".to_owned(),
109            target_dir.clone(),
110        ];
111
112        cargo_args.extend(self.cargo_args);
113
114        let thunk = Thunk {
115            rust_flags,
116            cargo_args,
117            os,
118            arch,
119            target_dir,
120        };
121
122        Ok(thunk)
123    }
124}
125
126#[derive(Debug)]
127pub struct Thunk {
128    rust_flags: Vec<String>,
129    cargo_args: Vec<String>,
130    os: OS,
131    arch: Arch,
132    target_dir: String,
133}
134
135impl Thunk {
136    pub fn run(self) {
137        let rust_flags = self.rust_flags.join(" ");
138        let cargo_args = self.cargo_args;
139
140        println!(
141            "Start to build for Windows {}({}) using VC-LTL and YY-Thunks: ",
142            self.os.to_string(),
143            self.arch.to_string(),
144        );
145        println!(" * RUSTFLAGS = {}", rust_flags);
146        println!(" * Command = cargo {}", cargo_args.join(" "));
147        println!("Cargo Output:");
148
149        let _status = Command::new("cargo")
150            .env("RUSTFLAGS", rust_flags)
151            .args(cargo_args)
152            .status()
153            .unwrap();
154
155        println!(
156            "You can find the builds in target directory: {}",
157            self.target_dir
158        );
159    }
160}