| use crate::utils::{ErrAction, cargo_cmd, expect_action}; | |
| use core::fmt::Display; | |
| use core::mem; | |
| use std::path::Path; | |
| use std::process::Command; | |
| use std::time::{Duration, SystemTime}; | |
| use std::{fs, thread}; | |
| use walkdir::WalkDir; | |
| #[cfg(windows)] | |
| const PYTHON: &str = "python"; | |
| #[cfg(not(windows))] | |
| const PYTHON: &str = "python3"; | |
| /// # Panics | |
| /// | |
| /// Panics if the python commands could not be spawned | |
| pub fn run(port: u16, lint: Option<String>) -> ! { | |
| let mut url = Some(match lint { | |
| None => format!("http://localhost:{port}"), | |
| Some(lint) => format!("http://localhost:{port}/#{lint}"), | |
| }); | |
| let mut last_update = mtime("util/gh-pages/index.html"); | |
| loop { | |
| if is_metadata_outdated(mem::replace(&mut last_update, SystemTime::now())) { | |
| // Ignore the command result; we'll fall back to displaying the old metadata. | |
| let _ = expect_action( | |
| cargo_cmd().arg("collect-metadata").status(), | |
| ErrAction::Run, | |
| "cargo collect-metadata", | |
| ); | |
| last_update = SystemTime::now(); | |
| } | |
| // Only start the web server the first time around. | |
| if let Some(url) = url.take() { | |
| thread::spawn(move || { | |
| let mut child = expect_action( | |
| Command::new(PYTHON) | |
| .args(["-m", "http.server", port.to_string().as_str()]) | |
| .current_dir("util/gh-pages") | |
| .spawn(), | |
| ErrAction::Run, | |
| "python -m http.server", | |
| ); | |
| // Give some time for python to start | |
| thread::sleep(Duration::from_millis(500)); | |
| // Launch browser after first export.py has completed and http.server is up | |
| let _result = opener::open(url); | |
| expect_action(child.wait(), ErrAction::Run, "python -m http.server"); | |
| }); | |
| } | |
| // Delay to avoid updating the metadata too aggressively. | |
| thread::sleep(Duration::from_secs(1)); | |
| } | |
| } | |
| fn log_err_and_continue<T>(res: Result<T, impl Display>, path: &Path) -> Option<T> { | |
| match res { | |
| Ok(x) => Some(x), | |
| Err(ref e) => { | |
| eprintln!("error reading `{}`: {e}", path.display()); | |
| None | |
| }, | |
| } | |
| } | |
| fn mtime(path: &str) -> SystemTime { | |
| log_err_and_continue(fs::metadata(path), path.as_ref()) | |
| .and_then(|metadata| log_err_and_continue(metadata.modified(), path.as_ref())) | |
| .unwrap_or(SystemTime::UNIX_EPOCH) | |
| } | |
| fn is_metadata_outdated(time: SystemTime) -> bool { | |
| // Ignore all IO errors here. We don't want to stop them from hosting the server. | |
| if time < mtime("util/gh-pages/index_template.html") || time < mtime("tests/compile-test.rs") { | |
| return true; | |
| } | |
| let Some(dir) = log_err_and_continue(fs::read_dir("."), ".".as_ref()) else { | |
| return false; | |
| }; | |
| dir.map_while(|e| log_err_and_continue(e, ".".as_ref())).any(|e| { | |
| let name = e.file_name(); | |
| let name_bytes = name.as_encoded_bytes(); | |
| if (name_bytes.starts_with(b"clippy_lints") && name_bytes != b"clippy_lints_internal") | |
| || name_bytes == b"clippy_config" | |
| { | |
| WalkDir::new(&name) | |
| .into_iter() | |
| .map_while(|e| log_err_and_continue(e, name.as_ref())) | |
| .filter(|e| e.file_type().is_file()) | |
| .filter_map(|e| { | |
| log_err_and_continue(e.metadata(), e.path()) | |
| .and_then(|m| log_err_and_continue(m.modified(), e.path())) | |
| }) | |
| .any(|ftime| time < ftime) | |
| } else { | |
| false | |
| } | |
| }) | |
| } |