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, };
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; }
41 },
42 Err(_) => {
43 eprintln!("Warning: Dependency file not found: {:?}", dep_path);
44 },
47 }
48 }
49
50 false
52}
53
54#[derive(Debug, Default)]
55pub struct Vcxproj {
56 pub lib_proj: String,
58 pub condition: String,
59 pub include_dirs: Vec<String>,
61 pub lib_dirs: Vec<String>,
63 pub sources: Vec<String>,
64 pub flags: Vec<String>,
65 pub target: String, pub target_fn: String, }
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}