Skip to content

Commit bc5848e

Browse files
bors[bot]xFrednet
andauthored
Merge #168
168: Cargo: Remove `cargo_fetch` dependency and rework the architecture r=xFrednet a=xFrednet This PR exploded a bit 😅. Removing the dependency required a few more changes than expected. The good news are, that marker now uses a stable way to fetch lint crates which also supports registries directly. No more `cargo` dependency, just command calls. I'm happy :D It also addresses or at least touches several other issues. Generally speaking, a nice update IMO. (The only big negative thing is the error handling. I've never learned how to do that properly, and cargo is getting more messy with less information after each refactoring 😅. I'll create an issue for that :)) --- Closes: #167 Closes: #155 Closes: #87 CC: #60 Co-authored-by: xFrednet <[email protected]>
2 parents c5e85f4 + af6d518 commit bc5848e

File tree

22 files changed

+1223
-1960
lines changed

22 files changed

+1223
-1960
lines changed

Cargo.lock

Lines changed: 41 additions & 1080 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ members = [
1111
resolver = "2"
1212

1313
[workspace.metadata.marker.lints]
14-
marker_uitest = { path = "marker_uitest" }
14+
marker_lints = { path = "./marker_lints" }
15+
marker_uitest = { path = "./marker_uitest" }

cargo-marker/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ name = "cargo-marker"
1616
path = "src/main.rs"
1717

1818
[dependencies]
19-
clap = { version = "4.0.26", features = ["string"] }
19+
clap = { version = "4.0", features = ["string"] }
2020
serde = { version = "1.0", features = ["derive"] }
21-
toml = { version = "0.7.3" }
21+
toml = { version = "0.7" }
2222
once_cell = "1.17.0"
23-
cargo_fetch = "0.1.2"
23+
cargo_metadata = "0.15.4"
2424

2525
[features]
2626
default = []

cargo-marker/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ cargo marker setup --auto-install-toolchain
4646

4747
Marker requires lint crates to be specified. The best way, is to add them to the `Cargo.toml` file, like this:
4848

49-
```sh
49+
```toml
5050
[workspace.metadata.marker.lints]
51-
# Add a local crate as a path
52-
local_lint_crate = { path = "path/to/lint_crate" }
53-
# Add an external crate via git
54-
git_lint_crate = { git = "https://github.com/rust-marker/marker" }
51+
# A local crate as a path
52+
marker_lints = { path = "./marker_lints" }
53+
# An external crate via git
54+
marker_lints = { git = "https://github.com/rust-marker/marker" }
55+
# An external crate from a registry
56+
marker_lints = "0.1.0"
5557
```
5658

57-
Lints from registries, like crates.io, are sadly not yet supported. See [rust-marker/marker#87](https://github.com/rust-marker/marker/issues/87).
58-
5959
### Running Marker
6060

6161
Running Marker is as simple as running its sibling *[Clippy]*. Navigate to your Rust project directory and run the following command:

cargo-marker/src/backend.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! The backend is the brains of rust-marker, it's responsible for installing or
2+
//! finding the correct driver, building lints and start linting. The backend should
3+
//! be decoupled from the frontend. Most of the time the frontend will be the
4+
//! `cargo-marker` CLI. However, `cargo-marker` might also be used as a library for UI
5+
//! tests later down the line.
6+
7+
use std::{
8+
collections::HashMap,
9+
ffi::{OsStr, OsString},
10+
path::PathBuf,
11+
};
12+
13+
use crate::{config::LintDependencyEntry, ExitStatus};
14+
15+
use self::{lints::LintCrate, toolchain::Toolchain};
16+
17+
pub mod driver;
18+
pub mod lints;
19+
pub mod toolchain;
20+
21+
/// Markers configuration for any action that requires lint crates to be available.
22+
///
23+
/// It's assumed that all paths in this struct are absolute paths.
24+
#[derive(Debug)]
25+
pub struct Config {
26+
/// The base directory used by Marker to fetch and compile lints.
27+
/// This will default to something like `./target/marker`.
28+
///
29+
/// This should generally be used as a base path for everything. Notable
30+
/// exceptions can be the installation of a driver or the compilation of
31+
/// a lint for uitests.
32+
pub marker_dir: PathBuf,
33+
/// The list of lints.
34+
pub lints: HashMap<String, LintDependencyEntry>,
35+
/// Additional flags, which should be passed to rustc during the compilation
36+
/// of crates.
37+
pub build_rustc_flags: String,
38+
/// Indicates if this is a release or debug build.
39+
pub debug_build: bool,
40+
/// Indicates if this is a development build.
41+
pub dev_build: bool,
42+
pub toolchain: Toolchain,
43+
}
44+
45+
impl Config {
46+
pub fn try_base_from(toolchain: Toolchain) -> Result<Self, ExitStatus> {
47+
Ok(Self {
48+
marker_dir: toolchain.find_target_dir()?.join("marker"),
49+
lints: HashMap::default(),
50+
build_rustc_flags: String::new(),
51+
debug_build: false,
52+
dev_build: cfg!(feature = "dev-build"),
53+
toolchain,
54+
})
55+
}
56+
57+
fn markers_target_dir(&self) -> PathBuf {
58+
self.marker_dir.join("target")
59+
}
60+
61+
fn lint_crate_dir(&self) -> PathBuf {
62+
self.marker_dir.join("lints")
63+
}
64+
}
65+
66+
/// This struct contains all information to use rustc as a driver.
67+
pub struct CheckInfo {
68+
pub env: Vec<(&'static str, OsString)>,
69+
}
70+
71+
pub fn prepare_check(config: &Config) -> Result<CheckInfo, ExitStatus> {
72+
println!();
73+
println!("Compiling Lints:");
74+
let lints = lints::build_lints(config)?;
75+
76+
#[rustfmt::skip]
77+
let mut env = vec![
78+
("RUSTC_WORKSPACE_WRAPPER", config.toolchain.driver_path.as_os_str().to_os_string()),
79+
("MARKER_LINT_CRATES", to_marker_lint_crates_env(&lints)),
80+
];
81+
if let Some(toolchain) = &config.toolchain.toolchain {
82+
env.push(("RUSTUP_TOOLCHAIN", toolchain.into()));
83+
}
84+
85+
Ok(CheckInfo { env })
86+
}
87+
88+
pub fn run_check(config: &Config, info: CheckInfo, additional_cargo_args: &[String]) -> Result<(), ExitStatus> {
89+
println!();
90+
println!("Start linting:");
91+
92+
let mut cmd = config.toolchain.cargo_with_driver();
93+
cmd.arg("check");
94+
cmd.args(additional_cargo_args);
95+
96+
cmd.envs(info.env);
97+
98+
let exit_status = cmd
99+
.spawn()
100+
.expect("could not run cargo")
101+
.wait()
102+
.expect("failed to wait for cargo?");
103+
104+
if exit_status.success() {
105+
Ok(())
106+
} else {
107+
Err(ExitStatus::MarkerCheckFailed)
108+
}
109+
}
110+
111+
pub fn to_marker_lint_crates_env(lints: &[LintCrate]) -> OsString {
112+
let lint_paths: Vec<_> = lints
113+
.iter()
114+
.map(|krate| OsString::from(krate.file.as_os_str()))
115+
.collect();
116+
lint_paths.join(OsStr::new(";"))
117+
}

cargo-marker/src/backend/driver.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
use std::{path::Path, process::Command, str::from_utf8};
2+
3+
use once_cell::sync::Lazy;
4+
5+
use crate::ExitStatus;
6+
7+
use super::toolchain::{get_toolchain_folder, rustup_which, Toolchain};
8+
9+
#[cfg(unix)]
10+
pub const MARKER_DRIVER_BIN_NAME: &str = "marker_rustc_driver";
11+
#[cfg(windows)]
12+
pub const MARKER_DRIVER_BIN_NAME: &str = "marker_rustc_driver.exe";
13+
14+
/// This is the driver version and toolchain, that is used by the setup command
15+
/// to install the driver.
16+
pub static DEFAULT_DRIVER_INFO: Lazy<DriverVersionInfo> = Lazy::new(|| DriverVersionInfo {
17+
toolchain: "nightly-2023-06-01".to_string(),
18+
version: "0.1.0".to_string(),
19+
api_version: "0.1.0".to_string(),
20+
});
21+
22+
/// The version info of one specific driver
23+
pub struct DriverVersionInfo {
24+
pub toolchain: String,
25+
pub version: String,
26+
pub api_version: String,
27+
}
28+
29+
impl DriverVersionInfo {
30+
pub fn try_from_toolchain(toolchain: &Toolchain, manifest: &Path) -> Result<DriverVersionInfo, ExitStatus> {
31+
// The driver has to be invoked via cargo, to ensure that the libraries
32+
// are correctly linked. Toolchains are truly fun...
33+
if let Ok(output) = toolchain
34+
.cargo_with_driver()
35+
.arg("rustc")
36+
.arg("--quiet")
37+
.arg("--manifest-path")
38+
.arg(manifest.as_os_str())
39+
.arg("--")
40+
.arg("--toolchain")
41+
.output()
42+
{
43+
if !output.status.success() {
44+
return Err(ExitStatus::DriverFailed);
45+
}
46+
47+
if let Ok(info) = from_utf8(&output.stdout) {
48+
let mut toolchain = Err(ExitStatus::InvalidValue);
49+
let mut driver_version = Err(ExitStatus::InvalidValue);
50+
let mut api_version = Err(ExitStatus::InvalidValue);
51+
for line in info.lines() {
52+
if let Some(value) = line.strip_prefix("toolchain: ") {
53+
toolchain = Ok(value.trim().to_string());
54+
} else if let Some(value) = line.strip_prefix("driver: ") {
55+
driver_version = Ok(value.trim().to_string());
56+
} else if let Some(value) = line.strip_prefix("marker-api: ") {
57+
api_version = Ok(value.trim().to_string());
58+
}
59+
}
60+
61+
return Ok(DriverVersionInfo {
62+
toolchain: toolchain?,
63+
version: driver_version?,
64+
api_version: api_version?,
65+
});
66+
}
67+
}
68+
69+
Err(ExitStatus::DriverFailed)
70+
}
71+
}
72+
73+
/// This tries to install the rustc driver specified in [`DEFAULT_DRIVER_INFO`].
74+
pub fn install_driver(
75+
auto_install_toolchain: bool,
76+
dev_build: bool,
77+
additional_rustc_flags: &str,
78+
) -> Result<(), ExitStatus> {
79+
// The toolchain, driver version and api version should ideally be configurable.
80+
// However, that will require more prototyping and has a low priority rn.
81+
// See #60
82+
83+
// Prerequisites
84+
let toolchain = &DEFAULT_DRIVER_INFO.toolchain;
85+
if rustup_which(toolchain, "cargo", false).is_err() {
86+
if auto_install_toolchain {
87+
install_toolchain(toolchain)?;
88+
} else {
89+
eprintln!("Error: The required toolchain `{toolchain}` can't be found");
90+
eprintln!();
91+
eprintln!("You can install the toolchain by running: `rustup toolchain install {toolchain}`");
92+
eprintln!("Or by adding the `--auto-install-toolchain` flag");
93+
return Err(ExitStatus::InvalidToolchain);
94+
}
95+
}
96+
97+
build_driver(
98+
toolchain,
99+
&DEFAULT_DRIVER_INFO.version,
100+
dev_build,
101+
additional_rustc_flags,
102+
)
103+
}
104+
105+
fn install_toolchain(toolchain: &str) -> Result<(), ExitStatus> {
106+
let mut cmd = Command::new("rustup");
107+
108+
cmd.args(["toolchain", "install", toolchain]);
109+
110+
let status = cmd
111+
.spawn()
112+
.expect("unable to start rustup to install the toolchain")
113+
.wait()
114+
.expect("unable to wait on rustup to install the toolchain");
115+
if status.success() {
116+
Ok(())
117+
} else {
118+
// The user can see rustup's output, as the command output was passed on
119+
// to the user via the `.spawn()` call.
120+
Err(ExitStatus::InvalidToolchain)
121+
}
122+
}
123+
124+
/// This tries to compile the driver.
125+
fn build_driver(
126+
toolchain: &str,
127+
version: &str,
128+
dev_build: bool,
129+
additional_rustc_flags: &str,
130+
) -> Result<(), ExitStatus> {
131+
if dev_build {
132+
println!("Compiling rustc driver");
133+
} else {
134+
println!("Compiling rustc driver v{version} with {toolchain}");
135+
}
136+
137+
let mut rustc_flags = additional_rustc_flags.to_string();
138+
139+
// Build driver
140+
let mut cmd = Command::new("cargo");
141+
if dev_build {
142+
cmd.args(["build", "--bin", "marker_rustc_driver"]);
143+
} else {
144+
cmd.env("RUSTUP_TOOLCHAIN", toolchain);
145+
cmd.args(["install", "marker_rustc_driver", "--version", version]);
146+
rustc_flags += " --cap-lints=allow";
147+
148+
let install_root = get_toolchain_folder(toolchain)?;
149+
cmd.arg("--root");
150+
cmd.arg(install_root.as_os_str());
151+
cmd.arg("--no-track");
152+
}
153+
cmd.env("RUSTFLAGS", rustc_flags);
154+
155+
let status = cmd
156+
.spawn()
157+
.expect("unable to start cargo install for the driver")
158+
.wait()
159+
.expect("unable to wait on cargo install for the driver");
160+
if status.success() {
161+
Ok(())
162+
} else {
163+
// The user can see cargo's output, as the command output was passed on
164+
// to the user via the `.spawn()` call.
165+
Err(ExitStatus::DriverInstallationFailed)
166+
}
167+
}

cargo-marker/src/backend/lints.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::path::PathBuf;
2+
3+
use crate::ExitStatus;
4+
5+
use super::Config;
6+
7+
mod build;
8+
mod fetch;
9+
10+
/// This struct contains all information of a lint crate required to compile
11+
/// the crate. See the [fetch] module for how external crates are fetched and
12+
/// this info is retrieved.
13+
#[derive(Debug)]
14+
#[allow(unused)]
15+
pub struct LintCrateSource {
16+
/// The name of the package, for now we can assume that this is the name
17+
/// that will be used to construct the dynamic library.
18+
name: String,
19+
/// The absolute path to the manifest of this lint crate
20+
manifest: PathBuf,
21+
}
22+
23+
/// The information of a compiled lint crate.
24+
#[derive(Debug)]
25+
pub struct LintCrate {
26+
/// The absolute path of the compiled crate, as a dynamic library.
27+
pub file: PathBuf,
28+
}
29+
30+
/// This function fetches and builds all lints specified in the given [`Config`]
31+
pub fn build_lints(config: &Config) -> Result<Vec<LintCrate>, ExitStatus> {
32+
// FIXME(xFrednet): Potentially handle local crates compiled for UI tests
33+
// differently. Like running the build command in the project root. This
34+
// would allow cargo to cache the compilation better. Right now normal
35+
// Cargo and cargo-marker might invalidate each others caches.
36+
let sources = fetch::fetch_crates(config)?;
37+
build::build_lints(&sources, config)
38+
}

0 commit comments

Comments
 (0)