PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/build.rs

https://github.com/BurntSushi/ripgrep
Rust | 239 lines | 192 code | 25 blank | 22 comment | 21 complexity | 38d7fc499fcc95f432b9bde3cadbc8ff MD5 | raw file
Possible License(s): MIT, Unlicense
  1. use std::env;
  2. use std::fs::{self, File};
  3. use std::io::{self, Read, Write};
  4. use std::path::Path;
  5. use std::process;
  6. use clap::Shell;
  7. use app::{RGArg, RGArgKind};
  8. #[allow(dead_code)]
  9. #[path = "crates/core/app.rs"]
  10. mod app;
  11. fn main() {
  12. // OUT_DIR is set by Cargo and it's where any additional build artifacts
  13. // are written.
  14. let outdir = match env::var_os("OUT_DIR") {
  15. Some(outdir) => outdir,
  16. None => {
  17. eprintln!(
  18. "OUT_DIR environment variable not defined. \
  19. Please file a bug: \
  20. https://github.com/BurntSushi/ripgrep/issues/new"
  21. );
  22. process::exit(1);
  23. }
  24. };
  25. fs::create_dir_all(&outdir).unwrap();
  26. let stamp_path = Path::new(&outdir).join("ripgrep-stamp");
  27. if let Err(err) = File::create(&stamp_path) {
  28. panic!("failed to write {}: {}", stamp_path.display(), err);
  29. }
  30. if let Err(err) = generate_man_page(&outdir) {
  31. eprintln!("failed to generate man page: {}", err);
  32. }
  33. // Use clap to build completion files.
  34. let mut app = app::app();
  35. app.gen_completions("rg", Shell::Bash, &outdir);
  36. app.gen_completions("rg", Shell::Fish, &outdir);
  37. app.gen_completions("rg", Shell::PowerShell, &outdir);
  38. // Note that we do not use clap's support for zsh. Instead, zsh completions
  39. // are manually maintained in `complete/_rg`.
  40. // Make the current git hash available to the build.
  41. if let Some(rev) = git_revision_hash() {
  42. println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", rev);
  43. }
  44. }
  45. fn git_revision_hash() -> Option<String> {
  46. let result = process::Command::new("git")
  47. .args(&["rev-parse", "--short=10", "HEAD"])
  48. .output();
  49. result.ok().and_then(|output| {
  50. let v = String::from_utf8_lossy(&output.stdout).trim().to_string();
  51. if v.is_empty() {
  52. None
  53. } else {
  54. Some(v)
  55. }
  56. })
  57. }
  58. fn generate_man_page<P: AsRef<Path>>(outdir: P) -> io::Result<()> {
  59. // If asciidoctor isn't installed, fallback to asciidoc.
  60. if let Err(err) = process::Command::new("asciidoctor").output() {
  61. eprintln!(
  62. "Could not run 'asciidoctor' binary, falling back to 'a2x'."
  63. );
  64. eprintln!("Error from running 'asciidoctor': {}", err);
  65. return legacy_generate_man_page::<P>(outdir);
  66. }
  67. // 1. Read asciidoctor template.
  68. // 2. Interpolate template with auto-generated docs.
  69. // 3. Save interpolation to disk.
  70. // 4. Use asciidoctor to convert to man page.
  71. let outdir = outdir.as_ref();
  72. let cwd = env::current_dir()?;
  73. let tpl_path = cwd.join("doc").join("rg.1.txt.tpl");
  74. let txt_path = outdir.join("rg.1.txt");
  75. let mut tpl = String::new();
  76. File::open(&tpl_path)?.read_to_string(&mut tpl)?;
  77. let options =
  78. formatted_options()?.replace("&#123;", "{").replace("&#125;", "}");
  79. tpl = tpl.replace("{OPTIONS}", &options);
  80. let githash = git_revision_hash();
  81. let githash = githash.as_ref().map(|x| &**x);
  82. tpl = tpl.replace("{VERSION}", &app::long_version(githash, false));
  83. File::create(&txt_path)?.write_all(tpl.as_bytes())?;
  84. let result = process::Command::new("asciidoctor")
  85. .arg("--doctype")
  86. .arg("manpage")
  87. .arg("--backend")
  88. .arg("manpage")
  89. .arg(&txt_path)
  90. .spawn()?
  91. .wait()?;
  92. if !result.success() {
  93. let msg =
  94. format!("'asciidoctor' failed with exit code {:?}", result.code());
  95. return Err(ioerr(msg));
  96. }
  97. Ok(())
  98. }
  99. fn legacy_generate_man_page<P: AsRef<Path>>(outdir: P) -> io::Result<()> {
  100. // If asciidoc isn't installed, then don't do anything.
  101. if let Err(err) = process::Command::new("a2x").output() {
  102. eprintln!("Could not run 'a2x' binary, skipping man page generation.");
  103. eprintln!("Error from running 'a2x': {}", err);
  104. return Ok(());
  105. }
  106. // 1. Read asciidoc template.
  107. // 2. Interpolate template with auto-generated docs.
  108. // 3. Save interpolation to disk.
  109. // 4. Use a2x (part of asciidoc) to convert to man page.
  110. let outdir = outdir.as_ref();
  111. let cwd = env::current_dir()?;
  112. let tpl_path = cwd.join("doc").join("rg.1.txt.tpl");
  113. let txt_path = outdir.join("rg.1.txt");
  114. let mut tpl = String::new();
  115. File::open(&tpl_path)?.read_to_string(&mut tpl)?;
  116. tpl = tpl.replace("{OPTIONS}", &formatted_options()?);
  117. let githash = git_revision_hash();
  118. let githash = githash.as_ref().map(|x| &**x);
  119. tpl = tpl.replace("{VERSION}", &app::long_version(githash, false));
  120. File::create(&txt_path)?.write_all(tpl.as_bytes())?;
  121. let result = process::Command::new("a2x")
  122. .arg("--no-xmllint")
  123. .arg("--doctype")
  124. .arg("manpage")
  125. .arg("--format")
  126. .arg("manpage")
  127. .arg(&txt_path)
  128. .spawn()?
  129. .wait()?;
  130. if !result.success() {
  131. let msg = format!("'a2x' failed with exit code {:?}", result.code());
  132. return Err(ioerr(msg));
  133. }
  134. Ok(())
  135. }
  136. fn formatted_options() -> io::Result<String> {
  137. let mut args = app::all_args_and_flags();
  138. args.sort_by(|x1, x2| x1.name.cmp(&x2.name));
  139. let mut formatted = vec![];
  140. for arg in args {
  141. if arg.hidden {
  142. continue;
  143. }
  144. // ripgrep only has two positional arguments, and probably will only
  145. // ever have two positional arguments, so we just hardcode them into
  146. // the template.
  147. if let app::RGArgKind::Positional { .. } = arg.kind {
  148. continue;
  149. }
  150. formatted.push(formatted_arg(&arg)?);
  151. }
  152. Ok(formatted.join("\n\n"))
  153. }
  154. fn formatted_arg(arg: &RGArg) -> io::Result<String> {
  155. match arg.kind {
  156. RGArgKind::Positional { .. } => {
  157. panic!("unexpected positional argument")
  158. }
  159. RGArgKind::Switch { long, short, multiple } => {
  160. let mut out = vec![];
  161. let mut header = format!("--{}", long);
  162. if let Some(short) = short {
  163. header = format!("-{}, {}", short, header);
  164. }
  165. if multiple {
  166. header = format!("*{}* ...::", header);
  167. } else {
  168. header = format!("*{}*::", header);
  169. }
  170. writeln!(out, "{}", header)?;
  171. writeln!(out, "{}", formatted_doc_txt(arg)?)?;
  172. Ok(String::from_utf8(out).unwrap())
  173. }
  174. RGArgKind::Flag { long, short, value_name, multiple, .. } => {
  175. let mut out = vec![];
  176. let mut header = format!("--{}", long);
  177. if let Some(short) = short {
  178. header = format!("-{}, {}", short, header);
  179. }
  180. if multiple {
  181. header = format!("*{}* _{}_ ...::", header, value_name);
  182. } else {
  183. header = format!("*{}* _{}_::", header, value_name);
  184. }
  185. writeln!(out, "{}", header)?;
  186. writeln!(out, "{}", formatted_doc_txt(arg)?)?;
  187. Ok(String::from_utf8(out).unwrap())
  188. }
  189. }
  190. }
  191. fn formatted_doc_txt(arg: &RGArg) -> io::Result<String> {
  192. let paragraphs: Vec<String> = arg
  193. .doc_long
  194. .replace("{", "&#123;")
  195. .replace("}", r"&#125;")
  196. // Hack to render ** literally in man page correctly. We can't put
  197. // these crazy +++ in the help text directly, since that shows
  198. // literally in --help output.
  199. .replace("*-g 'foo/**'*", "*-g +++'foo/**'+++*")
  200. .split("\n\n")
  201. .map(|s| s.to_string())
  202. .collect();
  203. if paragraphs.is_empty() {
  204. return Err(ioerr(format!("missing docs for --{}", arg.name)));
  205. }
  206. let first = format!(" {}", paragraphs[0].replace("\n", "\n "));
  207. if paragraphs.len() == 1 {
  208. return Ok(first);
  209. }
  210. Ok(format!("{}\n+\n{}", first, paragraphs[1..].join("\n+\n")))
  211. }
  212. fn ioerr(msg: String) -> io::Error {
  213. io::Error::new(io::ErrorKind::Other, msg)
  214. }