source.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. use log::info;
  2. use regex::Regex;
  3. use reqwest::Url;
  4. use serde::{Deserialize, Serialize};
  5. use std::os::unix::fs::PermissionsExt;
  6. use std::{
  7. fs::File,
  8. path::PathBuf,
  9. process::{Command, Stdio},
  10. };
  11. use zip::ZipArchive;
  12. use crate::utils::{file::FileUtils, stdio::StdioUtils};
  13. use super::cache::CacheDir;
  14. /// # Git源
  15. ///
  16. /// 从Git仓库获取源码
  17. #[derive(Debug, Clone, Serialize, Deserialize)]
  18. pub struct GitSource {
  19. /// Git仓库地址
  20. url: String,
  21. /// 分支(可选,如果为空,则拉取master)branch和revision只能同时指定一个
  22. branch: Option<String>,
  23. /// 特定的提交的hash值(可选,如果为空,则拉取branch的最新提交)
  24. revision: Option<String>,
  25. }
  26. impl GitSource {
  27. pub fn new(url: String, branch: Option<String>, revision: Option<String>) -> Self {
  28. Self {
  29. url,
  30. branch,
  31. revision,
  32. }
  33. }
  34. /// # 验证参数合法性
  35. ///
  36. /// 仅进行形式校验,不会检查Git仓库是否存在,以及分支是否存在、是否有权限访问等
  37. pub fn validate(&mut self) -> Result<(), String> {
  38. if self.url.is_empty() {
  39. return Err("url is empty".to_string());
  40. }
  41. // branch和revision不能同时为空
  42. if self.branch.is_none() && self.revision.is_none() {
  43. self.branch = Some("master".to_string());
  44. }
  45. // branch和revision只能同时指定一个
  46. if self.branch.is_some() && self.revision.is_some() {
  47. return Err("branch and revision are both specified".to_string());
  48. }
  49. if self.branch.is_some() {
  50. if self.branch.as_ref().unwrap().is_empty() {
  51. return Err("branch is empty".to_string());
  52. }
  53. }
  54. if self.revision.is_some() {
  55. if self.revision.as_ref().unwrap().is_empty() {
  56. return Err("revision is empty".to_string());
  57. }
  58. }
  59. return Ok(());
  60. }
  61. pub fn trim(&mut self) {
  62. self.url = self.url.trim().to_string();
  63. if let Some(branch) = &mut self.branch {
  64. *branch = branch.trim().to_string();
  65. }
  66. if let Some(revision) = &mut self.revision {
  67. *revision = revision.trim().to_string();
  68. }
  69. }
  70. /// # 确保Git仓库已经克隆到指定目录,并且切换到指定分支/Revision
  71. ///
  72. /// 如果目录不存在,则会自动创建
  73. ///
  74. /// ## 参数
  75. ///
  76. /// * `target_dir` - 目标目录
  77. ///
  78. /// ## 返回
  79. ///
  80. /// * `Ok(())` - 成功
  81. /// * `Err(String)` - 失败,错误信息
  82. pub fn prepare(&self, target_dir: &CacheDir) -> Result<(), String> {
  83. info!(
  84. "Preparing git repo: {}, branch: {:?}, revision: {:?}",
  85. self.url, self.branch, self.revision
  86. );
  87. target_dir.create().map_err(|e| {
  88. format!(
  89. "Failed to create target dir: {}, message: {e:?}",
  90. target_dir.path.display()
  91. )
  92. })?;
  93. if target_dir.is_empty().map_err(|e| {
  94. format!(
  95. "Failed to check if target dir is empty: {}, message: {e:?}",
  96. target_dir.path.display()
  97. )
  98. })? {
  99. info!("Target dir is empty, cloning repo");
  100. self.clone_repo(target_dir)?;
  101. }
  102. self.checkout(target_dir)?;
  103. self.pull(target_dir)?;
  104. return Ok(());
  105. }
  106. fn checkout(&self, target_dir: &CacheDir) -> Result<(), String> {
  107. let do_checkout = || -> Result<(), String> {
  108. let mut cmd = Command::new("git");
  109. cmd.current_dir(&target_dir.path);
  110. cmd.arg("checkout");
  111. if let Some(branch) = &self.branch {
  112. cmd.arg(branch);
  113. }
  114. if let Some(revision) = &self.revision {
  115. cmd.arg(revision);
  116. }
  117. // 强制切换分支,且安静模式
  118. cmd.arg("-f").arg("-q");
  119. // 创建子进程,执行命令
  120. let proc: std::process::Child = cmd
  121. .stderr(Stdio::piped())
  122. .spawn()
  123. .map_err(|e| e.to_string())?;
  124. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  125. if !output.status.success() {
  126. return Err(format!(
  127. "Failed to checkout {}, message: {}",
  128. target_dir.path.display(),
  129. String::from_utf8_lossy(&output.stdout)
  130. ));
  131. }
  132. return Ok(());
  133. };
  134. if let Err(_) = do_checkout() {
  135. // 如果切换分支失败,则尝试重新fetch
  136. if self.revision.is_some() {
  137. self.set_fetch_config(target_dir)?;
  138. self.unshallow(target_dir)?
  139. };
  140. self.fetch_all(target_dir).ok();
  141. do_checkout()?;
  142. }
  143. return Ok(());
  144. }
  145. pub fn clone_repo(&self, cache_dir: &CacheDir) -> Result<(), String> {
  146. let path: &PathBuf = &cache_dir.path;
  147. let mut cmd = Command::new("git");
  148. cmd.arg("clone").arg(&self.url).arg(".").arg("--recursive");
  149. if let Some(branch) = &self.branch {
  150. cmd.arg("--branch").arg(branch).arg("--depth").arg("1");
  151. }
  152. // 对于克隆,如果指定了revision,则直接克隆整个仓库,稍后再切换到指定的revision
  153. // 设置工作目录
  154. cmd.current_dir(path);
  155. // 创建子进程,执行命令
  156. let proc: std::process::Child = cmd
  157. .stderr(Stdio::piped())
  158. .stdout(Stdio::inherit())
  159. .spawn()
  160. .map_err(|e| e.to_string())?;
  161. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  162. if !output.status.success() {
  163. return Err(format!(
  164. "clone git repo failed, status: {:?}, stderr: {:?}",
  165. output.status,
  166. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  167. ));
  168. }
  169. return Ok(());
  170. }
  171. /// 设置fetch所有分支
  172. fn set_fetch_config(&self, target_dir: &CacheDir) -> Result<(), String> {
  173. let mut cmd = Command::new("git");
  174. cmd.current_dir(&target_dir.path);
  175. cmd.arg("config")
  176. .arg("remote.origin.fetch")
  177. .arg("+refs/heads/*:refs/remotes/origin/*");
  178. // 创建子进程,执行命令
  179. let proc: std::process::Child = cmd
  180. .stderr(Stdio::piped())
  181. .spawn()
  182. .map_err(|e| e.to_string())?;
  183. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  184. if !output.status.success() {
  185. return Err(format!(
  186. "Failed to set fetch config {}, message: {}",
  187. target_dir.path.display(),
  188. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  189. ));
  190. }
  191. return Ok(());
  192. }
  193. /// # 把浅克隆的仓库变成深克隆
  194. fn unshallow(&self, target_dir: &CacheDir) -> Result<(), String> {
  195. if self.is_shallow(target_dir)? == false {
  196. return Ok(());
  197. }
  198. let mut cmd = Command::new("git");
  199. cmd.current_dir(&target_dir.path);
  200. cmd.arg("fetch").arg("--unshallow");
  201. cmd.arg("-f");
  202. // 创建子进程,执行命令
  203. let proc: std::process::Child = cmd
  204. .stderr(Stdio::piped())
  205. .spawn()
  206. .map_err(|e| e.to_string())?;
  207. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  208. if !output.status.success() {
  209. return Err(format!(
  210. "Failed to unshallow {}, message: {}",
  211. target_dir.path.display(),
  212. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  213. ));
  214. }
  215. return Ok(());
  216. }
  217. /// 判断当前仓库是否是浅克隆
  218. fn is_shallow(&self, target_dir: &CacheDir) -> Result<bool, String> {
  219. let mut cmd = Command::new("git");
  220. cmd.current_dir(&target_dir.path);
  221. cmd.arg("rev-parse").arg("--is-shallow-repository");
  222. let proc: std::process::Child = cmd
  223. .stderr(Stdio::piped())
  224. .spawn()
  225. .map_err(|e| e.to_string())?;
  226. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  227. if !output.status.success() {
  228. return Err(format!(
  229. "Failed to check if shallow {}, message: {}",
  230. target_dir.path.display(),
  231. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  232. ));
  233. }
  234. let is_shallow = String::from_utf8_lossy(&output.stdout).trim() == "true";
  235. return Ok(is_shallow);
  236. }
  237. fn fetch_all(&self, target_dir: &CacheDir) -> Result<(), String> {
  238. self.set_fetch_config(target_dir)?;
  239. let mut cmd = Command::new("git");
  240. cmd.current_dir(&target_dir.path);
  241. cmd.arg("fetch").arg("--all");
  242. // 安静模式
  243. cmd.arg("-f").arg("-q");
  244. // 创建子进程,执行命令
  245. let proc: std::process::Child = cmd
  246. .stderr(Stdio::piped())
  247. .spawn()
  248. .map_err(|e| e.to_string())?;
  249. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  250. if !output.status.success() {
  251. return Err(format!(
  252. "Failed to fetch all {}, message: {}",
  253. target_dir.path.display(),
  254. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  255. ));
  256. }
  257. return Ok(());
  258. }
  259. fn pull(&self, target_dir: &CacheDir) -> Result<(), String> {
  260. // 如果没有指定branch,则不执行pull
  261. if !self.branch.is_some() {
  262. return Ok(());
  263. }
  264. info!("git pulling: {}", target_dir.path.display());
  265. let mut cmd = Command::new("git");
  266. cmd.current_dir(&target_dir.path);
  267. cmd.arg("pull");
  268. // 安静模式
  269. cmd.arg("-f").arg("-q");
  270. // 创建子进程,执行命令
  271. let proc: std::process::Child = cmd
  272. .stderr(Stdio::piped())
  273. .spawn()
  274. .map_err(|e| e.to_string())?;
  275. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  276. // 如果pull失败,且指定了branch,则报错
  277. if !output.status.success() {
  278. return Err(format!(
  279. "Failed to pull {}, message: {}",
  280. target_dir.path.display(),
  281. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  282. ));
  283. }
  284. return Ok(());
  285. }
  286. }
  287. /// # 本地源
  288. #[derive(Debug, Clone, Serialize, Deserialize)]
  289. pub struct LocalSource {
  290. /// 本地目录/文件的路径
  291. path: PathBuf,
  292. }
  293. impl LocalSource {
  294. #[allow(dead_code)]
  295. pub fn new(path: PathBuf) -> Self {
  296. Self { path }
  297. }
  298. pub fn validate(&self, expect_file: Option<bool>) -> Result<(), String> {
  299. if !self.path.exists() {
  300. return Err(format!("path {:?} not exists", self.path));
  301. }
  302. if let Some(expect_file) = expect_file {
  303. if expect_file && !self.path.is_file() {
  304. return Err(format!("path {:?} is not a file", self.path));
  305. }
  306. if !expect_file && !self.path.is_dir() {
  307. return Err(format!("path {:?} is not a directory", self.path));
  308. }
  309. }
  310. return Ok(());
  311. }
  312. pub fn trim(&mut self) {}
  313. pub fn path(&self) -> &PathBuf {
  314. &self.path
  315. }
  316. }
  317. /// # 在线压缩包源
  318. #[derive(Debug, Clone, Serialize, Deserialize)]
  319. pub struct ArchiveSource {
  320. /// 压缩包的URL
  321. url: String,
  322. }
  323. impl ArchiveSource {
  324. #[allow(dead_code)]
  325. pub fn new(url: String) -> Self {
  326. Self { url }
  327. }
  328. pub fn validate(&self) -> Result<(), String> {
  329. if self.url.is_empty() {
  330. return Err("url is empty".to_string());
  331. }
  332. // 判断是一个网址
  333. if let Ok(url) = Url::parse(&self.url) {
  334. if url.scheme() != "http" && url.scheme() != "https" {
  335. return Err(format!("url {:?} is not a http/https url", self.url));
  336. }
  337. } else {
  338. return Err(format!("url {:?} is not a valid url", self.url));
  339. }
  340. return Ok(());
  341. }
  342. pub fn trim(&mut self) {
  343. self.url = self.url.trim().to_string();
  344. }
  345. /// @brief 下载压缩包并把其中的文件提取至target_dir目录下
  346. ///
  347. ///从URL中下载压缩包到临时文件夹 target_dir/DRAGONOS_ARCHIVE_TEMP 后
  348. ///原地解压,提取文件后删除下载的压缩包。如果 target_dir 非空,就直接使用
  349. ///其中内容,不进行重复下载和覆盖
  350. ///
  351. /// @param target_dir 文件缓存目录
  352. ///
  353. /// @return 根据结果返回OK或Err
  354. pub fn download_unzip(&self, target_dir: &CacheDir) -> Result<(), String> {
  355. let url = Url::parse(&self.url).unwrap();
  356. let archive_name = url.path_segments().unwrap().last().unwrap();
  357. let path = &(target_dir.path.join("DRAGONOS_ARCHIVE_TEMP"));
  358. //如果source目录没有临时文件夹,且不为空,说明之前成功执行过一次,那么就直接使用之前的缓存
  359. if !path.exists()
  360. && !target_dir.is_empty().map_err(|e| {
  361. format!(
  362. "Failed to check if target dir is empty: {}, message: {e:?}",
  363. target_dir.path.display()
  364. )
  365. })?
  366. {
  367. //如果source文件夹非空,就直接使用,不再重复下载压缩文件,这里可以考虑加入交互
  368. info!("Source files already exist. Using previous source file cache. You should clean {:?} before re-download the archive ", target_dir.path);
  369. return Ok(());
  370. }
  371. if path.exists() {
  372. std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
  373. }
  374. //创建临时目录
  375. std::fs::create_dir(path).map_err(|e| e.to_string())?;
  376. info!("downloading {:?}", archive_name);
  377. FileUtils::download_file(&self.url, path).map_err(|e| e.to_string())?;
  378. //下载成功,开始尝试解压
  379. info!("download {:?} finished, start unzip", archive_name);
  380. let archive_file = ArchiveFile::new(&path.join(archive_name));
  381. archive_file.unzip()?;
  382. //删除创建的临时文件夹
  383. std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
  384. return Ok(());
  385. }
  386. }
  387. pub struct ArchiveFile {
  388. archive_path: PathBuf,
  389. archive_name: String,
  390. archive_type: ArchiveType,
  391. }
  392. impl ArchiveFile {
  393. pub fn new(archive_path: &PathBuf) -> Self {
  394. info!("archive_path: {:?}", archive_path);
  395. //匹配压缩文件类型
  396. let archive_name = archive_path.file_name().unwrap().to_str().unwrap();
  397. for (regex, archivetype) in [
  398. (Regex::new(r"^(.+)\.tar\.gz$").unwrap(), ArchiveType::TarGz),
  399. (Regex::new(r"^(.+)\.tar\.xz$").unwrap(), ArchiveType::TarXz),
  400. (Regex::new(r"^(.+)\.zip$").unwrap(), ArchiveType::Zip),
  401. ] {
  402. if regex.is_match(archive_name) {
  403. return Self {
  404. archive_path: archive_path.parent().unwrap().to_path_buf(),
  405. archive_name: archive_name.to_string(),
  406. archive_type: archivetype,
  407. };
  408. }
  409. }
  410. Self {
  411. archive_path: archive_path.parent().unwrap().to_path_buf(),
  412. archive_name: archive_name.to_string(),
  413. archive_type: ArchiveType::Undefined,
  414. }
  415. }
  416. /// @brief 对self.archive_path路径下名为self.archive_name的压缩文件(tar.gz或zip)进行解压缩
  417. ///
  418. /// 在此函数中进行路径和文件名有效性的判断,如果有效的话就开始解压缩,根据ArchiveType枚举类型来
  419. /// 生成不同的命令来对压缩文件进行解压缩,暂时只支持tar.gz和zip格式,并且都是通过调用bash来解压缩
  420. /// 没有引入第三方rust库
  421. ///
  422. ///
  423. /// @return 根据结果返回OK或Err
  424. pub fn unzip(&self) -> Result<(), String> {
  425. let path = &self.archive_path;
  426. if !path.is_dir() {
  427. return Err(format!("Archive directory {:?} is wrong", path));
  428. }
  429. if !path.join(&self.archive_name).is_file() {
  430. return Err(format!(
  431. " {:?} is not a file",
  432. path.join(&self.archive_name)
  433. ));
  434. }
  435. //根据压缩文件的类型生成cmd指令
  436. match &self.archive_type {
  437. ArchiveType::TarGz | ArchiveType::TarXz => {
  438. let mut cmd = Command::new("tar");
  439. cmd.arg("-xf").arg(&self.archive_name);
  440. let proc: std::process::Child = cmd
  441. .current_dir(path)
  442. .stderr(Stdio::piped())
  443. .stdout(Stdio::inherit())
  444. .spawn()
  445. .map_err(|e| e.to_string())?;
  446. let output = proc.wait_with_output().map_err(|e| e.to_string())?;
  447. if !output.status.success() {
  448. return Err(format!(
  449. "unzip failed, status: {:?}, stderr: {:?}",
  450. output.status,
  451. StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
  452. ));
  453. }
  454. }
  455. ArchiveType::Zip => {
  456. let file = File::open(&self.archive_path.join(&self.archive_name))
  457. .map_err(|e| e.to_string())?;
  458. let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
  459. for i in 0..archive.len() {
  460. let mut file = archive.by_index(i).map_err(|e| e.to_string())?;
  461. let outpath = match file.enclosed_name() {
  462. Some(path) => self.archive_path.join(path),
  463. None => continue,
  464. };
  465. if (*file.name()).ends_with('/') {
  466. std::fs::create_dir_all(&outpath).map_err(|e| e.to_string())?;
  467. } else {
  468. if let Some(p) = outpath.parent() {
  469. if !p.exists() {
  470. std::fs::create_dir_all(&p).map_err(|e| e.to_string())?;
  471. }
  472. }
  473. let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
  474. std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?;
  475. }
  476. //设置解压后权限,在Linux中Unzip会丢失权限
  477. #[cfg(unix)]
  478. {
  479. if let Some(mode) = file.unix_mode() {
  480. std::fs::set_permissions(
  481. &outpath,
  482. std::fs::Permissions::from_mode(mode),
  483. )
  484. .map_err(|e| e.to_string())?;
  485. }
  486. }
  487. }
  488. }
  489. _ => {
  490. return Err("unsupported archive type".to_string());
  491. }
  492. }
  493. //删除下载的压缩包
  494. info!("unzip successfully, removing archive ");
  495. std::fs::remove_file(path.join(&self.archive_name)).map_err(|e| e.to_string())?;
  496. //从解压的文件夹中提取出文件并删除下载的压缩包等价于指令"cd *;mv ./* ../../"
  497. for entry in path.read_dir().map_err(|e| e.to_string())? {
  498. let entry = entry.map_err(|e| e.to_string())?;
  499. let path = entry.path();
  500. FileUtils::move_files(&path, &self.archive_path.parent().unwrap())
  501. .map_err(|e| e.to_string())?;
  502. //删除空的单独文件夹
  503. std::fs::remove_dir_all(&path).map_err(|e| e.to_string())?;
  504. }
  505. return Ok(());
  506. }
  507. }
  508. pub enum ArchiveType {
  509. TarGz,
  510. TarXz,
  511. Zip,
  512. Undefined,
  513. }