PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/crates/core/main.rs

https://github.com/BurntSushi/ripgrep
Rust | 325 lines | 254 code | 27 blank | 44 comment | 42 complexity | c2297b5eac91cb97fea3b9d700fc1369 MD5 | raw file
Possible License(s): MIT, Unlicense
  1. use std::error;
  2. use std::io::{self, Write};
  3. use std::process;
  4. use std::sync::Mutex;
  5. use std::time::Instant;
  6. use ignore::WalkState;
  7. use args::Args;
  8. use subject::Subject;
  9. #[macro_use]
  10. mod messages;
  11. mod app;
  12. mod args;
  13. mod config;
  14. mod logger;
  15. mod path_printer;
  16. mod search;
  17. mod subject;
  18. // Since Rust no longer uses jemalloc by default, ripgrep will, by default,
  19. // use the system allocator. On Linux, this would normally be glibc's
  20. // allocator, which is pretty good. In particular, ripgrep does not have a
  21. // particularly allocation heavy workload, so there really isn't much
  22. // difference (for ripgrep's purposes) between glibc's allocator and jemalloc.
  23. //
  24. // However, when ripgrep is built with musl, this means ripgrep will use musl's
  25. // allocator, which appears to be substantially worse. (musl's goal is not to
  26. // have the fastest version of everything. Its goal is to be small and amenable
  27. // to static compilation.) Even though ripgrep isn't particularly allocation
  28. // heavy, musl's allocator appears to slow down ripgrep quite a bit. Therefore,
  29. // when building with musl, we use jemalloc.
  30. //
  31. // We don't unconditionally use jemalloc because it can be nice to use the
  32. // system's default allocator by default. Moreover, jemalloc seems to increase
  33. // compilation times by a bit.
  34. //
  35. // Moreover, we only do this on 64-bit systems since jemalloc doesn't support
  36. // i686.
  37. #[cfg(all(target_env = "musl", target_pointer_width = "64"))]
  38. #[global_allocator]
  39. static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
  40. type Result<T> = ::std::result::Result<T, Box<dyn error::Error>>;
  41. fn main() {
  42. if let Err(err) = Args::parse().and_then(try_main) {
  43. eprintln!("{}", err);
  44. process::exit(2);
  45. }
  46. }
  47. fn try_main(args: Args) -> Result<()> {
  48. use args::Command::*;
  49. let matched = match args.command()? {
  50. Search => search(&args),
  51. SearchParallel => search_parallel(&args),
  52. SearchNever => Ok(false),
  53. Files => files(&args),
  54. FilesParallel => files_parallel(&args),
  55. Types => types(&args),
  56. PCRE2Version => pcre2_version(&args),
  57. }?;
  58. if matched && (args.quiet() || !messages::errored()) {
  59. process::exit(0)
  60. } else if messages::errored() {
  61. process::exit(2)
  62. } else {
  63. process::exit(1)
  64. }
  65. }
  66. /// The top-level entry point for single-threaded search. This recursively
  67. /// steps through the file list (current directory by default) and searches
  68. /// each file sequentially.
  69. fn search(args: &Args) -> Result<bool> {
  70. let started_at = Instant::now();
  71. let quit_after_match = args.quit_after_match()?;
  72. let subject_builder = args.subject_builder();
  73. let mut stats = args.stats()?;
  74. let mut searcher = args.search_worker(args.stdout())?;
  75. let mut matched = false;
  76. for result in args.walker()? {
  77. let subject = match subject_builder.build_from_result(result) {
  78. Some(subject) => subject,
  79. None => continue,
  80. };
  81. let search_result = match searcher.search(&subject) {
  82. Ok(search_result) => search_result,
  83. Err(err) => {
  84. // A broken pipe means graceful termination.
  85. if err.kind() == io::ErrorKind::BrokenPipe {
  86. break;
  87. }
  88. err_message!("{}: {}", subject.path().display(), err);
  89. continue;
  90. }
  91. };
  92. matched = matched || search_result.has_match();
  93. if let Some(ref mut stats) = stats {
  94. *stats += search_result.stats().unwrap();
  95. }
  96. if matched && quit_after_match {
  97. break;
  98. }
  99. }
  100. if let Some(ref stats) = stats {
  101. let elapsed = Instant::now().duration_since(started_at);
  102. // We don't care if we couldn't print this successfully.
  103. let _ = searcher.print_stats(elapsed, stats);
  104. }
  105. Ok(matched)
  106. }
  107. /// The top-level entry point for multi-threaded search. The parallelism is
  108. /// itself achieved by the recursive directory traversal. All we need to do is
  109. /// feed it a worker for performing a search on each file.
  110. fn search_parallel(args: &Args) -> Result<bool> {
  111. use std::sync::atomic::AtomicBool;
  112. use std::sync::atomic::Ordering::SeqCst;
  113. let quit_after_match = args.quit_after_match()?;
  114. let started_at = Instant::now();
  115. let subject_builder = args.subject_builder();
  116. let bufwtr = args.buffer_writer()?;
  117. let stats = args.stats()?.map(Mutex::new);
  118. let matched = AtomicBool::new(false);
  119. let mut searcher_err = None;
  120. args.walker_parallel()?.run(|| {
  121. let bufwtr = &bufwtr;
  122. let stats = &stats;
  123. let matched = &matched;
  124. let subject_builder = &subject_builder;
  125. let mut searcher = match args.search_worker(bufwtr.buffer()) {
  126. Ok(searcher) => searcher,
  127. Err(err) => {
  128. searcher_err = Some(err);
  129. return Box::new(move |_| WalkState::Quit);
  130. }
  131. };
  132. Box::new(move |result| {
  133. let subject = match subject_builder.build_from_result(result) {
  134. Some(subject) => subject,
  135. None => return WalkState::Continue,
  136. };
  137. searcher.printer().get_mut().clear();
  138. let search_result = match searcher.search(&subject) {
  139. Ok(search_result) => search_result,
  140. Err(err) => {
  141. err_message!("{}: {}", subject.path().display(), err);
  142. return WalkState::Continue;
  143. }
  144. };
  145. if search_result.has_match() {
  146. matched.store(true, SeqCst);
  147. }
  148. if let Some(ref locked_stats) = *stats {
  149. let mut stats = locked_stats.lock().unwrap();
  150. *stats += search_result.stats().unwrap();
  151. }
  152. if let Err(err) = bufwtr.print(searcher.printer().get_mut()) {
  153. // A broken pipe means graceful termination.
  154. if err.kind() == io::ErrorKind::BrokenPipe {
  155. return WalkState::Quit;
  156. }
  157. // Otherwise, we continue on our merry way.
  158. err_message!("{}: {}", subject.path().display(), err);
  159. }
  160. if matched.load(SeqCst) && quit_after_match {
  161. WalkState::Quit
  162. } else {
  163. WalkState::Continue
  164. }
  165. })
  166. });
  167. if let Some(err) = searcher_err.take() {
  168. return Err(err);
  169. }
  170. if let Some(ref locked_stats) = stats {
  171. let elapsed = Instant::now().duration_since(started_at);
  172. let stats = locked_stats.lock().unwrap();
  173. let mut searcher = args.search_worker(args.stdout())?;
  174. // We don't care if we couldn't print this successfully.
  175. let _ = searcher.print_stats(elapsed, &stats);
  176. }
  177. Ok(matched.load(SeqCst))
  178. }
  179. /// The top-level entry point for listing files without searching them. This
  180. /// recursively steps through the file list (current directory by default) and
  181. /// prints each path sequentially using a single thread.
  182. fn files(args: &Args) -> Result<bool> {
  183. let quit_after_match = args.quit_after_match()?;
  184. let subject_builder = args.subject_builder();
  185. let mut matched = false;
  186. let mut path_printer = args.path_printer(args.stdout())?;
  187. for result in args.walker()? {
  188. let subject = match subject_builder.build_from_result(result) {
  189. Some(subject) => subject,
  190. None => continue,
  191. };
  192. matched = true;
  193. if quit_after_match {
  194. break;
  195. }
  196. if let Err(err) = path_printer.write_path(subject.path()) {
  197. // A broken pipe means graceful termination.
  198. if err.kind() == io::ErrorKind::BrokenPipe {
  199. break;
  200. }
  201. // Otherwise, we have some other error that's preventing us from
  202. // writing to stdout, so we should bubble it up.
  203. return Err(err.into());
  204. }
  205. }
  206. Ok(matched)
  207. }
  208. /// The top-level entry point for listing files without searching them. This
  209. /// recursively steps through the file list (current directory by default) and
  210. /// prints each path sequentially using multiple threads.
  211. fn files_parallel(args: &Args) -> Result<bool> {
  212. use std::sync::atomic::AtomicBool;
  213. use std::sync::atomic::Ordering::SeqCst;
  214. use std::sync::mpsc;
  215. use std::thread;
  216. let quit_after_match = args.quit_after_match()?;
  217. let subject_builder = args.subject_builder();
  218. let mut path_printer = args.path_printer(args.stdout())?;
  219. let matched = AtomicBool::new(false);
  220. let (tx, rx) = mpsc::channel::<Subject>();
  221. let print_thread = thread::spawn(move || -> io::Result<()> {
  222. for subject in rx.iter() {
  223. path_printer.write_path(subject.path())?;
  224. }
  225. Ok(())
  226. });
  227. args.walker_parallel()?.run(|| {
  228. let subject_builder = &subject_builder;
  229. let matched = &matched;
  230. let tx = tx.clone();
  231. Box::new(move |result| {
  232. let subject = match subject_builder.build_from_result(result) {
  233. Some(subject) => subject,
  234. None => return WalkState::Continue,
  235. };
  236. matched.store(true, SeqCst);
  237. if quit_after_match {
  238. WalkState::Quit
  239. } else {
  240. match tx.send(subject) {
  241. Ok(_) => WalkState::Continue,
  242. Err(_) => WalkState::Quit,
  243. }
  244. }
  245. })
  246. });
  247. drop(tx);
  248. if let Err(err) = print_thread.join().unwrap() {
  249. // A broken pipe means graceful termination, so fall through.
  250. // Otherwise, something bad happened while writing to stdout, so bubble
  251. // it up.
  252. if err.kind() != io::ErrorKind::BrokenPipe {
  253. return Err(err.into());
  254. }
  255. }
  256. Ok(matched.load(SeqCst))
  257. }
  258. /// The top-level entry point for --type-list.
  259. fn types(args: &Args) -> Result<bool> {
  260. let mut count = 0;
  261. let mut stdout = args.stdout();
  262. for def in args.type_defs()? {
  263. count += 1;
  264. stdout.write_all(def.name().as_bytes())?;
  265. stdout.write_all(b": ")?;
  266. let mut first = true;
  267. for glob in def.globs() {
  268. if !first {
  269. stdout.write_all(b", ")?;
  270. }
  271. stdout.write_all(glob.as_bytes())?;
  272. first = false;
  273. }
  274. stdout.write_all(b"\n")?;
  275. }
  276. Ok(count > 0)
  277. }
  278. /// The top-level entry point for --pcre2-version.
  279. fn pcre2_version(args: &Args) -> Result<bool> {
  280. #[cfg(feature = "pcre2")]
  281. fn imp(args: &Args) -> Result<bool> {
  282. use grep::pcre2;
  283. let mut stdout = args.stdout();
  284. let (major, minor) = pcre2::version();
  285. writeln!(stdout, "PCRE2 {}.{} is available", major, minor)?;
  286. if cfg!(target_pointer_width = "64") && pcre2::is_jit_available() {
  287. writeln!(stdout, "JIT is available")?;
  288. }
  289. Ok(true)
  290. }
  291. #[cfg(not(feature = "pcre2"))]
  292. fn imp(args: &Args) -> Result<bool> {
  293. let mut stdout = args.stdout();
  294. writeln!(stdout, "PCRE2 is not available in this build of ripgrep.")?;
  295. Ok(false)
  296. }
  297. imp(args)
  298. }