build_vcxproj/
lib.rs

1pub mod vcpkg;
2pub mod sample_builder;
3use std::{fs, io, env};
4use std::path::Path;
5use std::process::{Command, ExitStatus};
6use std::time::SystemTime;
7use std::io::BufReader;
8use xml::reader::{EventReader, XmlEvent};
9use cc;
10
11pub fn system(command_line: &str) -> io::Result<ExitStatus> {
12    #[cfg(target_os = "windows")]
13        let (shell, flag) = ("cmd", "/C");
14
15    #[cfg(not(target_os = "windows"))]
16        let (shell, flag) = ("sh", "-c");
17
18    Command::new(shell)
19        .arg(flag)
20        .arg(command_line)
21        .status()
22}
23
24pub fn need_build<T: AsRef<Path>, I: IntoIterator<Item = T>>(target: T, deps: I) -> bool {
25    let target_path = target.as_ref();
26    let target_metadata = fs::metadata(target_path);
27
28    let target_mod_time = match target_metadata {
29        Ok(metadata) => metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH),
30        Err(_) => return true, // Target does not exist, need to build
31    };
32
33    for dep in deps {
34        let dep_path = dep.as_ref();
35        match fs::metadata(dep_path) {
36            Ok(metadata) => {
37                let dep_mod_time = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
38                if dep_mod_time > target_mod_time {
39                    return true; // Dependency is newer than target, need to build
40                }
41            },
42            Err(_) => {
43                eprintln!("Warning: Dependency file not found: {:?}", dep_path);
44                // You might want to handle missing dependencies differently,
45                // e.g., return `true` to indicate a build is needed due to missing input.
46            },
47        }
48    }
49
50    // All dependencies are older than target, no need to build
51    false
52}
53
54#[derive(Debug, Default)]
55pub struct Vcxproj {
56    // the path to the library .vcxproj file, will read source files from this file
57    pub lib_proj: String,
58    pub condition: String,
59    // on input, add some other include dirs. on output, the target include dirs
60    pub include_dirs: Vec<String>,
61    // on input, add some other lib dirs. on output, the target lib dirs
62    pub lib_dirs: Vec<String>,
63    pub sources: Vec<String>,
64    pub flags: Vec<String>,
65    pub target: String,  // target basename
66    pub target_fn: String, // target full path
67}
68
69impl Vcxproj {
70    pub fn new(proj_fn:&str, is_debug:bool) -> Self {
71        Self {
72            lib_proj: proj_fn.to_string(),
73            condition: if is_debug {"Debug|x64"} else {"Release|x64"}.to_string(),
74            ..Default::default()
75        }
76    }
77
78    fn is_link(&self, v: &Vec<&str>) -> bool {
79        let p = v.join("/");
80        let p = Path::new(p.as_str());
81        p.symlink_metadata().map(|x| x.file_type().is_symlink()).unwrap_or(false)
82    }
83
84    fn rela_path(&self, x: &str) -> Option<String> {
85        if x.is_empty() {
86            return None;
87        }
88        if ! Path::new(x).is_relative() {
89            return None;
90        }
91        let mut items: Vec<&str> = self.lib_proj.split(|c| c=='/' || c=='\\').collect();
92        if items.len() > 0 {
93            items.pop();
94        }
95        items.extend(x.split(|c| c=='/' || c=='\\'));
96        let mut vec2 = Vec::new();
97        for item in items {
98            match item {
99                ""|"." => continue,
100                ".." => {
101                    if vec2.is_empty() || vec2[vec2.len()-1] == ".." || self.is_link(&vec2) {
102                        vec2.push(item);
103                    } else {
104                        vec2.pop();
105                    }
106                }
107                _ => vec2.push(item),
108            };
109        }
110        if vec2.is_empty() {
111            None
112        } else {
113            Some(vec2.join("/"))
114        }
115    }
116
117    fn load_vcxproj(&mut self) -> Result<(), std::io::Error> {
118        let file = fs::File::open(self.lib_proj.as_str())?;
119        let file = BufReader::new(file);
120        let parser = EventReader::new(file);
121        let mut xml_paths = Vec::new();
122        let mut skip = Vec::new();
123        let mut cur_path = String::new();
124        fn sum(iter: std::slice::Iter<i32>) -> i32 {
125            let mut sum = 0;
126            for i in iter {
127                sum += i;
128            }
129            sum
130        }
131
132        for e in parser {
133            match e {
134                Ok(XmlEvent::StartElement { name, attributes, .. }) => {
135                    xml_paths.push(name.local_name.clone());
136                    skip.push(0);
137                    cur_path = xml_paths.join("/");
138                    match cur_path.as_str() {
139                        "Project/ItemGroup/ClCompile" => {
140                            attributes.iter().find(|&x| x.name.local_name == "Include").map(|x| {
141                                if let Some(p) = self.rela_path(&x.value) {
142                                    self.sources.push(p);
143                                }
144                            });
145                        }
146                        "Project/ItemDefinitionGroup" => {
147                            attributes.iter().find(|&x| x.name.local_name == "Condition").map(|x| {
148                                if ! x.value.contains(self.condition.as_str()) {
149                                    skip.pop();
150                                    skip.push(1);
151                                }
152                            });
153                        }
154                        _ => {}
155                    };
156                }
157                Ok(XmlEvent::EndElement{name:_}) => {
158                    xml_paths.pop();
159                    skip.pop();
160                }
161                Ok(XmlEvent::Characters(heh)) => {
162                    if sum(skip.iter()) == 0 && cur_path.as_str() == "Project/ItemDefinitionGroup/ClCompile/AdditionalIncludeDirectories" {
163                        heh.split(";").for_each(|x| {
164                            if let Some(p) = self.rela_path(x) {
165                                self.include_dirs.push(p);
166                            }
167                        });
168                    }
169                }
170                Err(e) => {
171                    return Err(io::Error::other(e));
172                }
173                _ => {}
174            }
175        }
176
177        Ok(())
178    }
179
180    pub fn basename(&self) -> String{
181        Path::new(self.lib_proj.as_str()).file_stem()
182            .map_or(None, |x| x.to_str())
183            .map_or("".to_string(), |x| x.to_string())
184    }
185
186    pub fn find_lib(&self, name: &str) -> bool {
187        let is_windows = cfg!(target_os = "windows");
188        for p in &self.lib_dirs {
189            let p1 = if is_windows {
190                format!("{}/{}.lib", p, name)
191            } else {
192                format!("{}/lib{}.a", p, name)
193            };
194            if Path::new(p1.as_str()).is_file() {
195                return true;
196            }
197        }
198        false
199    }
200
201    pub fn load_config(&mut self) -> bool {
202        self.include_dirs.retain(|x| Path::new(x).is_dir());
203        self.lib_dirs.retain(|x| Path::new(x).is_dir());
204        let q = self.load_vcxproj();
205        let is_debug = self.condition.contains("Debug");
206        vcpkg::add_lib_paths(is_debug, &mut self.lib_dirs);
207        vcpkg::add_inc_paths(&mut self.include_dirs);
208        self.target = self.basename();
209        let is_windows = cfg!(target_os = "windows");
210        let mut def_flags: Vec<_> = if is_windows {
211            vec!["/EHsc", "/utf-8", "/D_CRT_SECURE_NO_WARNINGS", "/D_CRT_NONSTDC_NO_WARNINGS",
212                 "/DUNICODE", "/D_UNICODE", "/Zi", "/FS", "/W3"]
213        } else {
214            vec!["-Wno-unused-parameter", "-Wno-unused-result", "-Wno-multichar",
215                "-Wno-missing-field-initializers", "-g"]
216        };
217        if is_windows {
218            let tgt_dir = format!("x64/{}", if is_debug {"Debug"} else {"Release"});
219            let tgt_dir = self.rela_path(tgt_dir.as_str()).unwrap_or("".to_string());
220            self.target_fn = format!("{}/{}.lib", tgt_dir, self.target);
221            self.lib_dirs.push(tgt_dir);
222            if is_debug {
223                def_flags.push("/Od");
224            } else {
225                def_flags.push("/O2");
226            }
227        } else {
228            if is_debug {
229                def_flags.push("-O0");
230            } else {
231                def_flags.push("-O3");
232            }
233        }
234        self.flags.extend(def_flags.iter().map(|x| x.to_string()));
235        return q.is_ok();
236    }
237}
238
239#[cfg(target_os = "windows")]
240fn find_rc() -> Option<Command> {
241    if let Some(tl) = cc::windows_registry::find_tool("x86_64", "cl.exe") {
242        for (name, val) in tl.env() {
243            let mut s1 = name.to_str()?.to_string();
244            s1.make_ascii_lowercase();
245            if s1 == "path" {
246                if let Some(path) = val.to_str() {
247                    for path1 in path.split(';') {
248                        let rc_path = Path::new(path1).join("rc.exe");
249                        if rc_path.exists() && rc_path.is_file() {
250                            let mut command = Command::new(rc_path);
251                            for (k,v) in tl.env() {
252                                command.env(k, v);
253                            }
254                            return Some(command);
255                        }
256                    }
257                }
258                break;
259            }
260        }
261    }
262    None
263}
264
265#[cfg(target_os = "windows")]
266pub fn compile_rc(src: &str) -> Option<()> {
267    let mut cmd = find_rc()?;
268    let outdir = env::var("OUT_DIR").ok()?;
269    let outname = format!("{}\\{}.res", outdir, Path::new(src).file_stem()?.to_str()?);
270    cmd.arg("/fo").arg(&outname).arg(src);
271    let output = match cmd.output() {
272        Ok(output) => output,
273        Err(e) => panic!("Failed to run rc.exe: {}", e),
274    };
275    if !output.status.success() {
276        let stderr = String::from_utf8_lossy(&output.stdout);
277        panic!("rc.exe failed with status: {} {} bbb", output.status, stderr);
278    }
279    println!("cargo:rerun-if-changed={}", src);
280    println!("cargo:rustc-link-arg-bins={}", outname);
281    Some(())
282}
283#[cfg(not(target_os = "windows"))]
284pub fn compile_rc(src: &str) -> Option<()> {
285	let _ = src;
286	let _ = cc::Build::new();
287	let _ = env::var("OUT_DIR");
288    Some(())
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn it_works() {
297        let mut vcx = Vcxproj::new("../cpp_py/ctp_server/vs.proj/ctp_server_cpp.vcxproj", true);
298        vcx.load_config();
299        println!("{:?}", vcx);
300    }
301}