Skip to content

Commit 4050dd7

Browse files
committed
feat: warn if init seems out of date
This is a fairly large feature, as it involves persisting data about runs to disk. This scheme is fairly general, and can be used to store a lot of things (for example, debug output for a future rage command).
1 parent 8b95e7b commit 4050dd7

14 files changed

+194
-13
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ env_logger = "0.9.0"
2222
indicatif = "0.16.2"
2323
regex = "1.5"
2424
itertools = "0.10.3"
25+
directories = "4.0"
26+
blake3 = "1.3.1"
2527

2628
[dev-dependencies]
2729
assert_cmd = "2.0"

src/init.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use anyhow::Result;
2+
use console::Term;
3+
use log::debug;
4+
use std::path::{Path, PathBuf};
5+
6+
use crate::{lint_config::LintRunnerConfig, path::AbsPath};
7+
8+
fn get_data_file(config_path: &AbsPath, data_path: &Path) -> Result<PathBuf> {
9+
// We store the old copy of the config in `data_dir/<hash of path>`
10+
let config_path_hash = blake3::hash(config_path.to_string_lossy().as_bytes()).to_string();
11+
let data_file = data_path.join(config_path_hash);
12+
Ok(data_file)
13+
}
14+
15+
// Check whether or not the currently configured init commands are different
16+
// from the last time we ran `init`, and warn the user if so.
17+
pub fn check_init_changed(
18+
config_path: &AbsPath,
19+
data_path: &Path,
20+
current_config: &LintRunnerConfig,
21+
) -> Result<()> {
22+
let stderr = Term::stderr();
23+
24+
let data_file = get_data_file(config_path, data_path)?;
25+
debug!(
26+
"Checking data file {} to see if config has changed",
27+
data_file.display()
28+
);
29+
30+
if !data_file.exists() {
31+
stderr.write_line(
32+
"No previous init data found. If this is the first time you're \
33+
running lintrunner, you should run `lintrunner init`",
34+
)?;
35+
return Ok(());
36+
}
37+
let data_file = AbsPath::try_from(data_file)?;
38+
39+
let old_config = LintRunnerConfig::new(&data_file)?;
40+
41+
let old_init_commands: Vec<_> = old_config.linters.iter().map(|l| &l.init_command).collect();
42+
let current_init_commands: Vec<_> = current_config
43+
.linters
44+
.iter()
45+
.map(|l| &l.init_command)
46+
.collect();
47+
48+
if old_init_commands != current_init_commands {
49+
stderr.write_line(
50+
"The init commands have changed since you last ran lintrunner. \
51+
You may need to run `lintrunner init`",
52+
)?;
53+
}
54+
55+
Ok(())
56+
}
57+
58+
pub fn write_config(config_path: &AbsPath, data_path: &Path) -> Result<()> {
59+
let data_file = get_data_file(config_path, data_path)?;
60+
debug!("Writing used config to {}", data_file.display());
61+
62+
let config_contents = std::fs::read_to_string(config_path)?;
63+
std::fs::write(data_file, config_contents)?;
64+
65+
Ok(())
66+
}

src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ use render::{render_lint_messages, render_lint_messages_json};
99
use std::collections::HashMap;
1010
use std::collections::HashSet;
1111
use std::convert::TryFrom;
12+
use std::path::Path;
1213
use std::sync::{Arc, Mutex};
1314
use std::thread;
1415

1516
mod git;
17+
pub mod init;
1618
pub mod lint_config;
1719
pub mod lint_message;
1820
pub mod linter;
@@ -27,6 +29,7 @@ use lint_message::LintMessage;
2729
use render::PrintedLintErrors;
2830

2931
use crate::git::get_merge_base_with;
32+
use crate::init::write_config;
3033
use crate::render::render_lint_messages_oneline;
3134

3235
fn group_lints_by_file(
@@ -64,7 +67,12 @@ fn apply_patches(lint_messages: &[LintMessage]) -> Result<()> {
6467
Ok(())
6568
}
6669

67-
pub fn do_init(linters: Vec<Linter>, dry_run: bool) -> Result<i32> {
70+
pub fn do_init(
71+
config_path: &AbsPath,
72+
data_path: &Path,
73+
linters: Vec<Linter>,
74+
dry_run: bool,
75+
) -> Result<i32> {
6876
debug!(
6977
"Initializing linters: {:?}",
7078
linters.iter().map(|l| &l.code).collect::<Vec<_>>()
@@ -74,6 +82,8 @@ pub fn do_init(linters: Vec<Linter>, dry_run: bool) -> Result<i32> {
7482
linter.init(dry_run)?;
7583
}
7684

85+
write_config(config_path, data_path)?;
86+
7787
Ok(0)
7888
}
7989

src/lint_config.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use log::debug;
77
use serde::{Deserialize, Serialize};
88

99
#[derive(Serialize, Deserialize)]
10-
struct LintRunnerConfig {
10+
pub struct LintRunnerConfig {
1111
#[serde(rename = "linter")]
12-
linters: Vec<LintConfig>,
12+
pub linters: Vec<LintConfig>,
1313
}
1414

1515
/// Represents a single linter, along with all the information necessary to invoke it.
@@ -107,13 +107,13 @@ pub struct LintConfig {
107107

108108
/// Given options specified by the user, return a list of linters to run.
109109
pub fn get_linters_from_config(
110-
config_path: &AbsPath,
110+
linter_configs: &[LintConfig],
111111
skipped_linters: Option<HashSet<String>>,
112112
taken_linters: Option<HashSet<String>>,
113+
config_path: &AbsPath,
113114
) -> Result<Vec<Linter>> {
114-
let lint_runner_config = LintRunnerConfig::new(config_path)?;
115115
let mut linters = Vec::new();
116-
for lint_config in lint_runner_config.linters {
116+
for lint_config in linter_configs {
117117
let include_patterns = patterns_from_strs(&lint_config.include_patterns)?;
118118
let exclude_patterns = if let Some(exclude_patterns) = &lint_config.exclude_patterns {
119119
patterns_from_strs(exclude_patterns)?
@@ -127,11 +127,11 @@ pub fn get_linters_from_config(
127127
lint_config.code
128128
);
129129
linters.push(Linter {
130-
code: lint_config.code,
130+
code: lint_config.code.clone(),
131131
include_patterns,
132132
exclude_patterns,
133-
commands: lint_config.command,
134-
init_commands: lint_config.init_command,
133+
commands: lint_config.command.clone(),
134+
init_commands: lint_config.init_command.clone(),
135135
config_path: config_path.clone(),
136136
});
137137
}

src/main.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
use std::{collections::HashSet, convert::TryFrom, io::Write};
22

3-
use anyhow::{Context, Result};
3+
use anyhow::{anyhow, Context, Result};
44
use clap::Parser;
55

6+
use directories::ProjectDirs;
67
use lintrunner::{
7-
do_init, do_lint, lint_config::get_linters_from_config, path::AbsPath, render::print_error,
8+
do_init, do_lint,
9+
init::check_init_changed,
10+
lint_config::{get_linters_from_config, LintRunnerConfig},
11+
path::AbsPath,
12+
render::print_error,
813
PathsToLint, RenderOpt, RevisionOpt,
914
};
1015

@@ -72,6 +77,12 @@ struct Args {
7277
/// not a user-attended terminal.
7378
#[clap(long)]
7479
force_color: bool,
80+
81+
/// If set, use ths provided path to store any metadata generated by
82+
/// lintrunner. By default, this is a platform-specific location for
83+
/// application data (e.g. $XDG_DATA_HOME for UNIX systems.)
84+
#[clap(long)]
85+
data_path: Option<String>,
7586
}
7687

7788
#[derive(Debug, Parser)]
@@ -120,7 +131,14 @@ fn do_main() -> Result<i32> {
120131
.collect::<HashSet<_>>()
121132
});
122133

123-
let linters = get_linters_from_config(&config_path, skipped_linters, taken_linters)?;
134+
let lint_runner_config = LintRunnerConfig::new(&config_path)?;
135+
136+
let linters = get_linters_from_config(
137+
&lint_runner_config.linters,
138+
skipped_linters,
139+
taken_linters,
140+
&config_path,
141+
)?;
124142

125143
let enable_spinners = args.verbose == 0 && args.output == RenderOpt::Default;
126144

@@ -144,13 +162,18 @@ fn do_main() -> Result<i32> {
144162
RevisionOpt::Head
145163
};
146164

165+
let project_dirs = ProjectDirs::from("", "", "lintrunner");
166+
let project_dirs = project_dirs.ok_or(anyhow!("Could not find project directories"))?;
167+
let data_dir = project_dirs.data_dir();
168+
147169
match args.cmd {
148170
Some(SubCommand::Init { dry_run }) => {
149171
// Just run initialization commands, don't actually lint.
150-
do_init(linters, dry_run)
172+
do_init(&config_path, data_dir, linters, dry_run)
151173
}
152174
None => {
153175
// Default command is to just lint.
176+
check_init_changed(&config_path, data_dir, &lint_runner_config)?;
154177
do_lint(
155178
linters,
156179
paths_to_lint,

0 commit comments

Comments
 (0)