Skip to content

Commit 0a449ed

Browse files
bors[bot]xFrednet
andauthored
Merge #66
66: `cargo-marker` refactoring and review r=xFrednet a=xFrednet I've refactored parts of cargo-marker and reviewed the implementation. I found several limitations which are now documented in #60. This PR already fixes a few of these. CC: #60 --- Same as always: If I don't get a review, I'll merge this, when a depending PR is ready. :) Co-authored-by: xFrednet <[email protected]>
2 parents 20bc739 + c26b868 commit 0a449ed

File tree

13 files changed

+433
-193
lines changed

13 files changed

+433
-193
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@ jobs:
6666
- run: rustc -vV
6767

6868
# Test
69-
- run: cargo build --package cargo-marker
69+
- run: cargo build --package cargo_marker
7070
- run: cargo build --package marker_api
7171
- run: cargo build --package marker_lints

.github/workflows/rust_bors.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ jobs:
5555
- run: rustc -vV
5656

5757
# Test
58-
- run: cargo build --package cargo-marker
58+
- run: cargo build --package cargo_marker
5959
- run: cargo build --package marker_api
6060
- run: cargo build --package marker_lints

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LICENSE-MIT

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022-2022 Rust-linting
3+
Copyright (c) 2022-2022 Rust-marker
44

55
Permission is hereby granted, free of charge, to any
66
person obtaining a copy of this software and associated

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ The project is currently only available in this GitHub repo. For a quick test, c
1212

1313
## License
1414

15-
Copyright (c) 2022-2022 Rust-linting
15+
Copyright (c) 2022-2022 Rust-marker
1616

17-
Rust-linting is distributed under the terms of both the MIT license
17+
Rust-marker is distributed under the terms of both the MIT license
1818
and the Apache License (Version 2.0).
1919

2020
See [LICENSE-APACHE](./LICENSE-APACHE), [LICENSE-MIT](./LICENSE-MIT).

cargo-marker/Cargo.toml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
[package]
2-
name = "cargo-marker"
2+
name = "cargo_marker"
33
version = "0.1.0"
44
edition = "2021"
55
license = "MIT OR Apache-2.0"
66

7+
# Crate names in Rust are fun. I reserved `cargo_marker` as a crate name. However,
8+
# Cargo requires it's subcommands to use a dash like `cargo-marker`. Unable to rename
9+
# rename the create on crates.io we now have this hack... At least it works
10+
[[bin]]
11+
name = "cargo-marker"
12+
path = "src/main.rs"
13+
714
[dependencies]
8-
clap = "4.0.26"
15+
clap = {version = "4.0.26", features = ["string"]}
916
once_cell = "1.16.0"
1017

1118
[features]
1219
default = []
13-
# Indicates that development features like auto driver building etc should be enabled.
14-
# This option assumes that it's being executed at the project root.
20+
# This enables developer features used to automatically build the local version
21+
# assuming, that it's being executed at the root of the repo.
1522
dev-build = []

cargo-marker/src/cli.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use clap::{builder::ValueParser, Arg, ArgAction, Command};
2+
3+
use crate::VERSION;
4+
5+
const AFTER_HELP_MSG: &str = r#"CARGO ARGS
6+
All arguments after double dashes(`--`) will be passed to cargo.
7+
These options are the same as for `cargo check`.
8+
9+
EXAMPLES:
10+
* `cargo marker -l ./marker_lints`
11+
"#;
12+
13+
pub fn get_clap_config() -> Command {
14+
Command::new(VERSION)
15+
.arg(
16+
Arg::new("version")
17+
.short('V')
18+
.long("version")
19+
.action(ArgAction::SetTrue)
20+
.help("Print version info and exit"),
21+
)
22+
.arg(
23+
Arg::new("verbose")
24+
.short('v')
25+
.long("verbose")
26+
.action(ArgAction::SetTrue)
27+
.help("Print additional debug information to the console"),
28+
)
29+
.arg(
30+
Arg::new("test-setup")
31+
.long("test-setup")
32+
.action(ArgAction::SetTrue)
33+
.help("This flag will compile the lint crate and print all relevant environment values"),
34+
)
35+
.subcommand(setup_command())
36+
.subcommand(check_command())
37+
.args(check_command_args())
38+
.after_help(AFTER_HELP_MSG)
39+
.override_usage("cargo-marker [OPTIONS] -- <CARGO ARGS>")
40+
}
41+
42+
fn setup_command() -> Command {
43+
Command::new("setup")
44+
.about("A collection of commands to setup marker")
45+
.after_help("By default this will install the driver for rustc.")
46+
}
47+
48+
fn check_command() -> Command {
49+
Command::new("check")
50+
.about("Run marker on a local package")
51+
.args(check_command_args())
52+
}
53+
54+
fn check_command_args() -> impl IntoIterator<Item = impl Into<Arg>> {
55+
vec![
56+
Arg::new("lints")
57+
.short('l')
58+
.long("lints")
59+
.num_args(1..)
60+
.value_parser(ValueParser::os_string())
61+
.help("Defines a set of lints crates that should be used"),
62+
]
63+
}

cargo-marker/src/driver.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//! This module hosts functions required to run rustc as a driver.
2+
//!
3+
//! The rustc driver depends on rustc, which interfaces is unstable. This means
4+
//! that each driver version is bound to a specific version of rustc. The same
5+
//! goes for Clippy. However, Clippy has the advantage, that it's distributes via
6+
//! rustup, which handles the version matching for it. We're not so lucky, at
7+
//! least not yet. Therefore, we're responsible that the driver is compiled and
8+
//! run with the correct toolchain.
9+
//!
10+
//! If no driver is installed, the user will be requested to run the setup command.
11+
//! That command will first ensure that the required toolchain is installed and then
12+
//! run `cargo install` for the driver with a specific toolchain. The version and
13+
//! toolchain are hardcoded in this crate.
14+
15+
use std::{ffi::OsString, path::PathBuf, process::Command};
16+
17+
use once_cell::sync::Lazy;
18+
19+
use crate::ExitStatus;
20+
21+
/// This is the driver version and toolchain, that is used by the setup command
22+
/// to install the driver.
23+
static DEFAULT_DRIVER_INFO: Lazy<RustcDriverInfo> = Lazy::new(|| RustcDriverInfo {
24+
toolchain: "nightly-2022-11-03".to_string(),
25+
version: "0.1.0".to_string(),
26+
api_version: "0.1.0".to_string(),
27+
});
28+
29+
struct RustcDriverInfo {
30+
toolchain: String,
31+
version: String,
32+
#[allow(unused)]
33+
api_version: String,
34+
}
35+
36+
pub fn print_driver_version() {
37+
println!(
38+
"rustc driver version: {} (toolchain: {}, api: {})",
39+
DEFAULT_DRIVER_INFO.version, DEFAULT_DRIVER_INFO.toolchain, DEFAULT_DRIVER_INFO.api_version
40+
);
41+
}
42+
43+
/// This tries to install the rustc driver specified in [`DEFAULT_DRIVER_INFO`].
44+
pub fn install_driver(verbose: bool) -> Result<(), ExitStatus> {
45+
// The toolchain, driver version and api version should ideally be configurable.
46+
// However, that will require more prototyping and has a low priority rn.
47+
// See #60
48+
49+
// Prerequisites
50+
let toolchain = &DEFAULT_DRIVER_INFO.toolchain;
51+
check_toolchain(toolchain)?;
52+
53+
build_driver(
54+
toolchain,
55+
&DEFAULT_DRIVER_INFO.version,
56+
verbose,
57+
cfg!(feature = "dev-build"),
58+
)?;
59+
60+
// We don't want to advice the user, to install the driver again.
61+
check_driver(verbose, false)
62+
}
63+
64+
/// This function checks if the specified toolchain is installed. This requires
65+
/// rustup. A dependency we have to live with for now.
66+
fn check_toolchain(toolchain: &str) -> Result<(), ExitStatus> {
67+
let mut cmd = Command::new("cargo");
68+
cmd.args([&format!("+{toolchain}"), "-V"]);
69+
if cmd.output().is_err() {
70+
eprintln!("Error: The required toolchain `{toolchain}` can't be found");
71+
eprintln!();
72+
eprintln!("You can install the toolchain by running: rustup toolchain install {toolchain}");
73+
Err(ExitStatus::InvalidToolchain)
74+
} else {
75+
Ok(())
76+
}
77+
}
78+
79+
/// This tries to compile the driver. If successful the driver binary will
80+
/// be places next to the executable of `cargo-linter`.
81+
fn build_driver(toolchain: &str, version: &str, verbose: bool, dev_build: bool) -> Result<(), ExitStatus> {
82+
if dev_build {
83+
println!("Compiling rustc driver");
84+
} else {
85+
println!("Compiling rustc driver v{version} with {toolchain}");
86+
}
87+
88+
// Build driver
89+
let mut cmd = Command::new("cargo");
90+
91+
if !dev_build {
92+
cmd.arg(&format!("+{toolchain}"));
93+
}
94+
95+
if verbose {
96+
cmd.arg("--verbose");
97+
}
98+
99+
if dev_build {
100+
cmd.args(["build", "--bin", "marker_driver_rustc"]);
101+
} else {
102+
// FIXME: This currently installs the binary in Cargo's default location.
103+
// Ideally this should install the driver in the toolchain folder for the
104+
// installed nightly version. This would allow the user to have multiple
105+
// drivers depending on the project toolchain.
106+
//
107+
// We can just reuse rustup to select the correct driver for a defined
108+
// toolchain. This would also simulate how the driver would be delivered
109+
// in a perfect world.
110+
//
111+
// See #60
112+
cmd.args(["install", "marker_rustc_driver", "--version", version]);
113+
}
114+
115+
let status = cmd
116+
.spawn()
117+
.expect("unable to start cargo install for the driver")
118+
.wait()
119+
.expect("unable to wait on cargo install for the driver");
120+
if status.success() {
121+
Ok(())
122+
} else {
123+
// The user can see cargo's output, as the command output was passed on
124+
// to the user via the `.spawn()` call.
125+
Err(ExitStatus::DriverInstallationFailed)
126+
}
127+
}
128+
129+
fn check_driver(verbose: bool, print_advice: bool) -> Result<(), ExitStatus> {
130+
let path = get_driver_path();
131+
if verbose {
132+
println!("Searching for driver at: {}", path.display());
133+
}
134+
135+
if !path.exists() || !path.is_file() {
136+
if print_advice {
137+
eprintln!("Error: The driver binary could not be found.");
138+
eprintln!();
139+
eprintln!("Try installing it via `cargo marker setup`");
140+
}
141+
142+
Err(ExitStatus::MissingDriver)
143+
} else {
144+
Ok(())
145+
}
146+
}
147+
148+
pub fn run_driver(
149+
env: Vec<(OsString, OsString)>,
150+
cargo_args: impl Iterator<Item = String>,
151+
verbose: bool,
152+
) -> Result<(), ExitStatus> {
153+
check_driver(verbose, true)?;
154+
println!();
155+
println!("Start linting:");
156+
157+
let mut cmd = Command::new("cargo");
158+
cmd.envs(env).arg("check").args(cargo_args);
159+
if verbose {
160+
cmd.arg("--verbose");
161+
}
162+
163+
let exit_status = cmd
164+
.spawn()
165+
.expect("could not run cargo")
166+
.wait()
167+
.expect("failed to wait for cargo?");
168+
169+
if exit_status.success() {
170+
Ok(())
171+
} else {
172+
Err(ExitStatus::MarkerCheckFailed)
173+
}
174+
}
175+
176+
pub fn get_driver_path() -> PathBuf {
177+
#[allow(unused_mut)]
178+
let mut path = std::env::current_exe()
179+
.expect("unable to retrieve the path of the current executable")
180+
.with_file_name("marker_driver_rustc");
181+
182+
#[cfg(target_os = "windows")]
183+
path.set_extension("exe");
184+
185+
path
186+
}

cargo-marker/src/lints.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use std::{
2+
ffi::OsStr,
3+
path::{Path, PathBuf},
4+
process::Command,
5+
};
6+
7+
use crate::ExitStatus;
8+
9+
/// This creates a debug build for a local crate. The path of the build library
10+
/// will be returned, if the operation was successful.
11+
pub fn build_local_lint_crate(crate_dir: &Path, target_dir: &Path, verbose: bool) -> Result<PathBuf, ExitStatus> {
12+
if !crate_dir.exists() {
13+
eprintln!("The given lint can't be found, searched at: `{}`", crate_dir.display());
14+
return Err(ExitStatus::LintCrateNotFound);
15+
}
16+
17+
// Compile the lint crate
18+
let mut cmd = Command::new("cargo");
19+
if verbose {
20+
cmd.arg("--verbose");
21+
}
22+
let exit_status = cmd
23+
.current_dir(std::fs::canonicalize(crate_dir).unwrap())
24+
.args(["build", "--lib", "--target-dir"])
25+
.arg(target_dir.as_os_str())
26+
.env("RUSTFLAGS", "--cap-lints=allow")
27+
.spawn()
28+
.expect("could not run cargo")
29+
.wait()
30+
.expect("failed to wait for cargo?");
31+
32+
if !exit_status.success() {
33+
return Err(ExitStatus::LintCrateBuildFail);
34+
}
35+
36+
// Find the final binary and return the string
37+
#[cfg(any(target_os = "linux", target_os = "macos"))]
38+
let lib_file_prefix = "lib";
39+
#[cfg(target_os = "windows")]
40+
let lib_file_prefix = "";
41+
42+
// FIXME: currently this expect, that the lib name is the same as the crate dir.
43+
// See marker#60
44+
let file_name = format!(
45+
"{lib_file_prefix}{}",
46+
crate_dir.file_name().and_then(OsStr::to_str).unwrap_or_default()
47+
);
48+
// Here `debug` is attached as the crate is build with the `cargo build` command
49+
let mut krate_path = target_dir.join("debug").join(file_name);
50+
51+
#[cfg(target_os = "linux")]
52+
krate_path.set_extension("so");
53+
#[cfg(target_os = "macos")]
54+
krate_path.set_extension("dylib");
55+
#[cfg(target_os = "windows")]
56+
krate_path.set_extension("dll");
57+
58+
if !krate_path.exists() && !krate_path.is_file() {
59+
Err(ExitStatus::LintCrateLibNotFound)
60+
} else {
61+
Ok(krate_path)
62+
}
63+
}

0 commit comments

Comments
 (0)