mirror of
https://github.com/Noratrieb/cargo-bisect-rustc-service.git
synced 2026-01-16 00:55:01 +01:00
Compare commits
No commits in common. "main" and "v0.1-alpha.1" have entirely different histories.
main
...
v0.1-alpha
11 changed files with 342 additions and 969 deletions
1
.envrc
1
.envrc
|
|
@ -1 +0,0 @@
|
||||||
use nix
|
|
||||||
955
Cargo.lock
generated
955
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,14 +9,10 @@ edition = "2021"
|
||||||
axum = "0.5.16"
|
axum = "0.5.16"
|
||||||
chrono = { version = "0.4.22", features = ["serde", "clock"] }
|
chrono = { version = "0.4.22", features = ["serde", "clock"] }
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
metrics = "0.22.3"
|
|
||||||
metrics-exporter-prometheus = { version = "0.14.0", default-features = false, features = ["http-listener"] }
|
|
||||||
once_cell = "1.19.0"
|
|
||||||
rusqlite = { version = "0.28.0", features = ["bundled", "uuid", "chrono"] }
|
rusqlite = { version = "0.28.0", features = ["bundled", "uuid", "chrono"] }
|
||||||
serde = { version = "1.0.145", features = ["derive"] }
|
serde = { version = "1.0.145", features = ["derive"] }
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
tokio = { version = "1.21.2", features = ["full"] }
|
tokio = { version = "1.21.2", features = ["full"] }
|
||||||
tower-http = { version = "0.3.4", features = ["trace"] }
|
|
||||||
tracing = "0.1.36"
|
tracing = "0.1.36"
|
||||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||||
|
|
|
||||||
16
Dockerfile
16
Dockerfile
|
|
@ -1,4 +1,4 @@
|
||||||
FROM rust:1.77 as build
|
FROM rust as build
|
||||||
|
|
||||||
RUN rustup toolchain install nightly
|
RUN rustup toolchain install nightly
|
||||||
RUN rustup default nightly
|
RUN rustup default nightly
|
||||||
|
|
@ -12,30 +12,22 @@ COPY Cargo.toml Cargo.lock ./
|
||||||
RUN mkdir src
|
RUN mkdir src
|
||||||
RUN echo "fn main() {}" > src/main.rs
|
RUN echo "fn main() {}" > src/main.rs
|
||||||
|
|
||||||
RUN cargo build --release
|
RUN cargo build --release -Zsparse-registry
|
||||||
|
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY index.html index.html
|
COPY index.html index.html
|
||||||
|
|
||||||
# now rebuild with the proper main
|
# now rebuild with the proper main
|
||||||
RUN touch src/main.rs
|
RUN touch src/main.rs
|
||||||
RUN cargo build --release
|
RUN cargo build --release -Zsparse-registry
|
||||||
|
|
||||||
### RUN
|
### RUN
|
||||||
FROM rust:1.77
|
FROM rust
|
||||||
|
|
||||||
RUN cargo install cargo-bisect-rustc
|
RUN cargo install cargo-bisect-rustc
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# random user
|
|
||||||
RUN useradd --create-home bisector
|
|
||||||
USER bisector
|
|
||||||
|
|
||||||
# this server listens on port 4000 and 4001 (metrics)
|
|
||||||
EXPOSE 4000
|
|
||||||
EXPOSE 4001
|
|
||||||
|
|
||||||
COPY --from=build /app/target/release/cargo-bisect-rustc-service cargo-bisect-rustc-service
|
COPY --from=build /app/target/release/cargo-bisect-rustc-service cargo-bisect-rustc-service
|
||||||
|
|
||||||
CMD ["/app/cargo-bisect-rustc-service"]
|
CMD ["/app/cargo-bisect-rustc-service"]
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
# cargo-bisect-rustc-service
|
# cargo-bisect-rustc-service
|
||||||
|
|
||||||
**⚠️ this service has been turned off due to lack of usage ⚠️**
|
|
||||||
|
|
||||||
if you think it's useful you should tell me so i can turn it on again
|
|
||||||
|
|
||||||
a small web service that bisects rustc for you
|
a small web service that bisects rustc for you
|
||||||
|
|
||||||
https://bisect-rustc.noratrieb.dev
|
https://nilstrieb.dev/bisect-rustc
|
||||||
|
|
|
||||||
131
index.html
131
index.html
|
|
@ -32,15 +32,11 @@
|
||||||
<body>
|
<body>
|
||||||
<h1>Bisect rustc</h1>
|
<h1>Bisect rustc</h1>
|
||||||
|
|
||||||
<form name="bisect" id="bisect-form">
|
|
||||||
<label for="code">Code</label>
|
|
||||||
<br />
|
|
||||||
<textarea
|
<textarea
|
||||||
id="code"
|
id="code"
|
||||||
rows="30"
|
rows="30"
|
||||||
cols="80"
|
cols="80"
|
||||||
placeholder="// Rust code goes here..."
|
placeholder="// Rust code goes here..."
|
||||||
name="code"
|
|
||||||
>
|
>
|
||||||
struct Struct<T>(T);
|
struct Struct<T>(T);
|
||||||
|
|
||||||
|
|
@ -53,13 +49,11 @@ impl<T> Struct<T> {
|
||||||
>
|
>
|
||||||
<br />
|
<br />
|
||||||
<label for="start">Start</label>
|
<label for="start">Start</label>
|
||||||
<input id="start" value="2022-01-01" name="start" />
|
<input id="start" value="2022-01-01" />
|
||||||
|
|
||||||
<label for="end">End (optional)</label>
|
<label for="end">End (optional)</label>
|
||||||
<input id="end" value="2022-06-01" name="end" />
|
<input id="end" value="2022-06-01" />
|
||||||
|
|
||||||
<label for="kind">Regression Kind</label>
|
<label for="kind">Regression Kind</label>
|
||||||
<select id="kind" name="select">
|
<select id="kind">
|
||||||
<option>error</option>
|
<option>error</option>
|
||||||
<option>success</option>
|
<option>success</option>
|
||||||
<option selected="selected">ice</option>
|
<option selected="selected">ice</option>
|
||||||
|
|
@ -69,8 +63,8 @@ impl<T> Struct<T> {
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<input type="submit" class="bisect-btn" value="Bisect!" />
|
|
||||||
</form>
|
<button class="bisect-btn" onclick="bisect()">Bisect!</button>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
@ -84,53 +78,17 @@ impl<T> Struct<T> {
|
||||||
readonly
|
readonly
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
<div>
|
|
||||||
Made by Nilstrieb
|
|
||||||
<a href="https://github.com/Nilstrieb/cargo-bisect-rustc-service"
|
|
||||||
>Github</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let BASE_URL = `${document.location}`.split("?")[0];
|
const BASE_URL = `${document.location}`;
|
||||||
if (!BASE_URL.endsWith("/")) {
|
const htmlCode = document.getElementById("code");
|
||||||
// ugh, i hate url paths and their subtle interactions with things
|
|
||||||
BASE_URL = `${BASE_URL}/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The important HTML elements.
|
|
||||||
const htmlStatus = document.getElementById("status");
|
const htmlStatus = document.getElementById("status");
|
||||||
const htmlResult = document.getElementById("result");
|
const htmlResult = document.getElementById("result");
|
||||||
const htmlCode = document.getElementById("code");
|
const htmlStart = document.getElementById("start");
|
||||||
|
const htmlEnd = document.getElementById("end");
|
||||||
// A list of all ongoing fetches in fetchAndUpdate.
|
const htmlKind = document.getElementById("kind");
|
||||||
const fetches = [];
|
|
||||||
|
|
||||||
let bisecting = false;
|
let bisecting = false;
|
||||||
|
|
||||||
initialLoad();
|
|
||||||
|
|
||||||
document.getElementById("bisect-form").addEventListener("submit", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
submitForm();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function initialLoad() {
|
|
||||||
const params = document.location.search;
|
|
||||||
const urlParams = new URLSearchParams(params);
|
|
||||||
const bisection = urlParams.get("bisection");
|
|
||||||
|
|
||||||
if (bisection) {
|
|
||||||
const response = await fetchAndUpdate(bisection);
|
|
||||||
if (!response) {
|
|
||||||
fatal(`Bisection with ID \`${bisection}\` not found.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
code.value = response.code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report a fatal error to the console and screen.
|
|
||||||
function fatal(err, ...extra) {
|
function fatal(err, ...extra) {
|
||||||
console.error(err, ...(extra || []));
|
console.error(err, ...(extra || []));
|
||||||
htmlStatus.innerHTML = `ERROR: ${err}`;
|
htmlStatus.innerHTML = `ERROR: ${err}`;
|
||||||
|
|
@ -139,30 +97,15 @@ impl<T> Struct<T> {
|
||||||
bisecting = false;
|
bisecting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitForm() {
|
async function bisect() {
|
||||||
const form = document.forms.bisect;
|
|
||||||
const options = {
|
|
||||||
code: form.code.value,
|
|
||||||
start: form.start.value?.trim(),
|
|
||||||
end: form.end.value?.trim(),
|
|
||||||
kind: form.kind.value,
|
|
||||||
};
|
|
||||||
console.log("Options", options);
|
|
||||||
|
|
||||||
if (!options.start) {
|
|
||||||
fatal("Must provide a start");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bisect(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bisect(options) {
|
|
||||||
if (bisecting) {
|
if (bisecting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetches.forEach((fetchId) => clearTimeout(fetchId));
|
if (!htmlStart.value.trim()) {
|
||||||
|
fatal("Must provide a start");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bisecting = true;
|
bisecting = true;
|
||||||
|
|
||||||
|
|
@ -173,14 +116,16 @@ impl<T> Struct<T> {
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
params.append("start", options.start);
|
params.append("start", htmlStart.value.trim());
|
||||||
params.append("kind", options.kind);
|
params.append("kind", htmlKind.value);
|
||||||
|
|
||||||
if (options.end) {
|
if (htmlEnd.value.trim()) {
|
||||||
params.append("end", options.end);
|
params.append("end", htmlEnd.value.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = options.code;
|
const body = htmlCode.value;
|
||||||
|
|
||||||
|
console.log("Bisecting", body);
|
||||||
|
|
||||||
let jobId;
|
let jobId;
|
||||||
try {
|
try {
|
||||||
|
|
@ -198,11 +143,6 @@ impl<T> Struct<T> {
|
||||||
}
|
}
|
||||||
const data = await fetched.json();
|
const data = await fetched.json();
|
||||||
jobId = data?.job_id;
|
jobId = data?.job_id;
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
urlParams.set("bisection", jobId);
|
|
||||||
window.location.search = urlParams;
|
|
||||||
|
|
||||||
if (!jobId) {
|
if (!jobId) {
|
||||||
fatal("Received invalid response", data);
|
fatal("Received invalid response", data);
|
||||||
return;
|
return;
|
||||||
|
|
@ -212,18 +152,12 @@ impl<T> Struct<T> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchAndUpdate(jobId);
|
async function tryFetch() {
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAndUpdate(jobId) {
|
|
||||||
htmlStatus.innerHTML = `Waiting for result, job id=${jobId}`;
|
|
||||||
htmlStatus.classList.remove("hidden");
|
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
const fetched = await fetch(`${BASE_URL}bisect/${jobId}`);
|
const fetched = await fetch(`${BASE_URL}bisect/${jobId}`);
|
||||||
if (!fetched.ok) {
|
if (!fetched.ok) {
|
||||||
fatal("Failed to fetch bisection.", fetched);
|
fatal("Failed to fetch", fetched);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
response = await fetched.json();
|
response = await fetched.json();
|
||||||
|
|
@ -232,15 +166,6 @@ impl<T> Struct<T> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
console.log("Waiting for bisection", response.status.status);
|
|
||||||
const timeout = setTimeout(() => fetchAndUpdate(jobId), 3000);
|
|
||||||
fetches.push(timeout);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = response.status;
|
|
||||||
|
|
||||||
if (response.status.status !== "InProgress") {
|
if (response.status.status !== "InProgress") {
|
||||||
bisecting = false;
|
bisecting = false;
|
||||||
console.log(response.status.output);
|
console.log(response.status.output);
|
||||||
|
|
@ -252,11 +177,13 @@ impl<T> Struct<T> {
|
||||||
htmlStatus.innerHTML = `Bisected job ${jobId}`;
|
htmlStatus.innerHTML = `Bisected job ${jobId}`;
|
||||||
} else {
|
} else {
|
||||||
console.log("Waiting for bisection", response.status.status);
|
console.log("Waiting for bisection", response.status.status);
|
||||||
const timeout = setTimeout(() => fetchAndUpdate(jobId), 3000);
|
setTimeout(tryFetch, 3000);
|
||||||
fetches.push(timeout);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
tryFetch();
|
||||||
|
|
||||||
|
htmlStatus.innerHTML = `Waiting for result, job id=${jobId}`;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
|
|
||||||
packages = with pkgs; [ rustup ];
|
|
||||||
}
|
|
||||||
|
|
@ -107,8 +107,6 @@ pub fn process_job(job: Job, conn: &Connection) -> Result<()> {
|
||||||
|
|
||||||
trace!(?bisect, "Finished bisection job");
|
trace!(?bisect, "Finished bisection job");
|
||||||
|
|
||||||
crate::toolchain::clean_toolchains()?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
41
src/main.rs
41
src/main.rs
|
|
@ -2,14 +2,9 @@
|
||||||
|
|
||||||
mod bisect;
|
mod bisect;
|
||||||
mod db;
|
mod db;
|
||||||
mod toolchain;
|
|
||||||
|
|
||||||
use std::{
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
future,
|
|
||||||
sync::{mpsc, Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::bisect::Job;
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, Query},
|
extract::{Path, Query},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
|
@ -17,17 +12,16 @@ use axum::{
|
||||||
routing::{get, post, Router},
|
routing::{get, post, Router},
|
||||||
Extension, Json,
|
Extension, Json,
|
||||||
};
|
};
|
||||||
|
use bisect::Job;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
use std::env;
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
use tracing::{error, info, metadata::LevelFilter};
|
use tracing::{error, info, metadata::LevelFilter};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
type SendChannel = Arc<Mutex<mpsc::SyncSender<Job>>>;
|
type SendChannel = Arc<Mutex<mpsc::Sender<Job>>>;
|
||||||
type Conn = Arc<Mutex<Connection>>;
|
type Conn = Arc<Mutex<Connection>>;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -42,23 +36,7 @@ async fn main() -> color_eyre::Result<()> {
|
||||||
)
|
)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let metrics_app = async {
|
let (job_queue_send, job_queue_recv) = mpsc::channel(); // FIXME: make this a sync_channel because bounds are cool
|
||||||
let prom_handle = setup_metrics();
|
|
||||||
let router: Router =
|
|
||||||
Router::new().route("/metrics", get(move || future::ready(prom_handle.render())));
|
|
||||||
info!("Starting up metrics server on port 4001");
|
|
||||||
axum::Server::bind(&"0.0.0.0:4001".parse().unwrap())
|
|
||||||
.serve(router.into_make_service())
|
|
||||||
.await
|
|
||||||
.wrap_err("failed to start server")
|
|
||||||
};
|
|
||||||
|
|
||||||
tokio::try_join!(metrics_app, main_server())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn main_server() -> color_eyre::Result<()> {
|
|
||||||
let (job_queue_send, job_queue_recv) = mpsc::sync_channel(10);
|
|
||||||
|
|
||||||
let sqlite_db = env::var("SQLITE_DB").unwrap_or_else(|_| "bisect.sqlite".to_string());
|
let sqlite_db = env::var("SQLITE_DB").unwrap_or_else(|_| "bisect.sqlite".to_string());
|
||||||
|
|
||||||
|
|
@ -71,8 +49,6 @@ async fn main_server() -> color_eyre::Result<()> {
|
||||||
|
|
||||||
db::setup(&worker_conn).wrap_err("db setup")?;
|
db::setup(&worker_conn).wrap_err("db setup")?;
|
||||||
|
|
||||||
toolchain::clean_toolchains()?;
|
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(|| async { index_html() }))
|
.route("/", get(|| async { index_html() }))
|
||||||
.route("/bisect/:id", get(get_bisection))
|
.route("/bisect/:id", get(get_bisection))
|
||||||
|
|
@ -80,12 +56,11 @@ async fn main_server() -> color_eyre::Result<()> {
|
||||||
.route("/bisect", post(do_bisection))
|
.route("/bisect", post(do_bisection))
|
||||||
// this is really stupid and hacky
|
// this is really stupid and hacky
|
||||||
.layer(Extension(Arc::new(Mutex::new(job_queue_send))))
|
.layer(Extension(Arc::new(Mutex::new(job_queue_send))))
|
||||||
.layer(Extension(main_conn))
|
.layer(Extension(main_conn));
|
||||||
.layer(TraceLayer::new_for_http());
|
|
||||||
|
|
||||||
std::thread::spawn(|| bisect::bisect_worker(job_queue_recv, worker_conn));
|
std::thread::spawn(|| bisect::bisect_worker(job_queue_recv, worker_conn));
|
||||||
|
|
||||||
info!("Starting up server on port 4000");
|
info!("Starting up server");
|
||||||
|
|
||||||
axum::Server::bind(&"0.0.0.0:4000".parse().unwrap())
|
axum::Server::bind(&"0.0.0.0:4000".parse().unwrap())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
|
|
@ -135,7 +110,6 @@ async fn do_bisection(
|
||||||
body: String,
|
body: String,
|
||||||
send_channel: Extension<SendChannel>,
|
send_channel: Extension<SendChannel>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
metrics::counter!("bisections").increment(1);
|
|
||||||
let job_id = Uuid::new_v4();
|
let job_id = Uuid::new_v4();
|
||||||
|
|
||||||
let job = Job::new(job_id, body, options.0);
|
let job = Job::new(job_id, body, options.0);
|
||||||
|
|
@ -151,6 +125,3 @@ async fn do_bisection(
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn setup_metrics() -> PrometheusHandle {
|
|
||||||
PrometheusBuilder::new().install_recorder().unwrap()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use color_eyre::{
|
|
||||||
eyre::{eyre, Context},
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
const MAX_BISECTOR_TOOLCHAINS: usize = 15;
|
|
||||||
|
|
||||||
#[tracing::instrument]
|
|
||||||
pub fn clean_toolchains() -> Result<()> {
|
|
||||||
let toolchains = get_toolchains()?;
|
|
||||||
let for_removal = filter_toolchain_for_removal(toolchains);
|
|
||||||
if !for_removal.is_empty() {
|
|
||||||
remove_toolchains(&for_removal)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter_toolchain_for_removal(mut toolchains: Vec<String>) -> Vec<String> {
|
|
||||||
toolchains.retain(|toolchain| toolchain.starts_with("bisector-"));
|
|
||||||
|
|
||||||
let amount = toolchains.len();
|
|
||||||
if amount <= MAX_BISECTOR_TOOLCHAINS {
|
|
||||||
debug!(%amount, "No toolchains removed");
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let to_remove = amount - MAX_BISECTOR_TOOLCHAINS;
|
|
||||||
|
|
||||||
toolchains.into_iter().take(to_remove).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_toolchains() -> Result<Vec<String>> {
|
|
||||||
let mut command = Command::new("rustup");
|
|
||||||
command.args(["toolchain", "list"]);
|
|
||||||
|
|
||||||
let output = command
|
|
||||||
.output()
|
|
||||||
.wrap_err("running `rustup toolchain list`")?;
|
|
||||||
|
|
||||||
if output.status.success() {
|
|
||||||
let stdout =
|
|
||||||
String::from_utf8(output.stdout).wrap_err("rustup returned non-utf-8 bytes")?;
|
|
||||||
|
|
||||||
let toolchains = stdout.lines().map(ToOwned::to_owned).collect();
|
|
||||||
|
|
||||||
Ok(toolchains)
|
|
||||||
} else {
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
|
||||||
Err(eyre!("`rustup toolchain list` failed").wrap_err(stderr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_toolchains(toolchains: &[String]) -> Result<()> {
|
|
||||||
debug!(?toolchains, "Removing toolchains");
|
|
||||||
let mut command = Command::new("rustup");
|
|
||||||
command.args(["toolchain", "remove"]);
|
|
||||||
command.args(toolchains);
|
|
||||||
|
|
||||||
let output = command
|
|
||||||
.output()
|
|
||||||
.wrap_err("running `rustup toolchain remove`")?;
|
|
||||||
|
|
||||||
if output.status.success() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
|
||||||
Err(eyre!("`rustup toolchain remove` failed").wrap_err(stderr))
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue