diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23d3e71424b..2770bded30a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,11 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 2 + - name: "checkout the `master` branch for tidy" + uses: actions/checkout@v3 + with: + ref: master + fetch-depth: 1 - name: configure the PR in which the error message will be posted run: "echo \"[CI_PR_NUMBER=$num]\"" env: @@ -485,6 +490,11 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 2 + - name: "checkout the `master` branch for tidy" + uses: actions/checkout@v3 + with: + ref: master + fetch-depth: 1 - name: configure the PR in which the error message will be posted run: "echo \"[CI_PR_NUMBER=$num]\"" env: @@ -600,6 +610,11 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 2 + - name: "checkout the `master` branch for tidy" + uses: actions/checkout@v3 + with: + ref: master + fetch-depth: 1 - name: configure the PR in which the error message will be posted run: "echo \"[CI_PR_NUMBER=$num]\"" env: diff --git a/Cargo.lock b/Cargo.lock index 9581099c210..79c70ee31c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,10 @@ dependencies = [ "toml", ] +[[package]] +name = "build_helper" +version = "0.1.0" + [[package]] name = "bump-stage0" version = "0.1.0" @@ -5304,6 +5308,7 @@ dependencies = [ name = "tidy" version = "0.1.0" dependencies = [ + "build_helper", "cargo_metadata 0.14.0", "ignore", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 1cec4a84480..ce08d4edb56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "library/std", "library/test", "src/rustdoc-json-types", + "src/tools/build_helper", "src/tools/cargotest", "src/tools/clippy", "src/tools/clippy/clippy_dev", diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index efe8ae3169f..4a0ba592577 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ name = "bootstrap" version = "0.0.0" dependencies = [ + "build_helper", "cc", "cmake", "fd-lock", @@ -70,6 +71,10 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "build_helper" +version = "0.1.0" + [[package]] name = "cc" version = "1.0.73" diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index fafe82a9c12..22ceeca941e 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -30,6 +30,7 @@ path = "bin/sccache-plus-cl.rs" test = false [dependencies] +build_helper = { path = "../tools/build_helper" } cmake = "0.1.38" fd-lock = "3.0.8" filetime = "0.2" diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index 1d57c6ecbbb..64ae3fe0db9 100644 --- a/src/bootstrap/format.rs +++ b/src/bootstrap/format.rs @@ -2,6 +2,7 @@ use crate::builder::Builder; use crate::util::{output, program_out_of_date, t}; +use build_helper::git::get_rust_lang_rust_remote; use ignore::WalkBuilder; use std::collections::VecDeque; use std::path::{Path, PathBuf}; @@ -100,35 +101,6 @@ fn get_modified_rs_files(build: &Builder<'_>) -> Option> { ) } -/// Finds the remote for rust-lang/rust. -/// For example for these remotes it will return `upstream`. -/// ```text -/// origin https://github.com/Nilstrieb/rust.git (fetch) -/// origin https://github.com/Nilstrieb/rust.git (push) -/// upstream https://github.com/rust-lang/rust (fetch) -/// upstream https://github.com/rust-lang/rust (push) -/// ``` -fn get_rust_lang_rust_remote() -> Result { - let mut git = Command::new("git"); - git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]); - - let output = git.output().map_err(|err| format!("{err:?}"))?; - if !output.status.success() { - return Err("failed to execute git config command".to_owned()); - } - - let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?; - - let rust_lang_remote = stdout - .lines() - .find(|remote| remote.contains("rust-lang")) - .ok_or_else(|| "rust-lang/rust remote not found".to_owned())?; - - let remote_name = - rust_lang_remote.split('.').nth(1).ok_or_else(|| "remote name not found".to_owned())?; - Ok(remote_name.into()) -} - #[derive(serde::Deserialize)] struct RustfmtConfig { ignore: Vec, diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 5ea41d10bc8..d44b96cfb99 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -113,6 +113,7 @@ use std::process::Command; use std::str; +use build_helper::ci::CiEnv; use channel::GitInfo; use config::{DryRun, Target}; use filetime::FileTime; @@ -121,7 +122,7 @@ use crate::builder::Kind; use crate::config::{LlvmLibunwind, TargetSelection}; use crate::util::{ - exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed, CiEnv, + exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed, }; mod bolt; diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs index 4e503dfe864..bdc81b07b8d 100644 --- a/src/bootstrap/native.rs +++ b/src/bootstrap/native.rs @@ -24,6 +24,8 @@ use crate::util::{self, exe, output, t, up_to_date}; use crate::{CLang, GitRepo}; +use build_helper::ci::CiEnv; + #[derive(Clone)] pub struct LlvmResult { /// Path to llvm-config binary. @@ -217,7 +219,7 @@ pub(crate) fn is_ci_llvm_available(config: &Config, asserts: bool) -> bool { return false; } - if crate::util::CiEnv::is_ci() { + if CiEnv::is_ci() { // We assume we have access to git, so it's okay to unconditionally pass // `true` here. let llvm_sha = detect_llvm_sha(config, true); diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs index 58220783228..8ce9a9ce38c 100644 --- a/src/bootstrap/util.rs +++ b/src/bootstrap/util.rs @@ -255,35 +255,6 @@ pub enum CiEnv { GitHubActions, } -impl CiEnv { - /// Obtains the current CI environment. - pub fn current() -> CiEnv { - if env::var("TF_BUILD").map_or(false, |e| e == "True") { - CiEnv::AzurePipelines - } else if env::var("GITHUB_ACTIONS").map_or(false, |e| e == "true") { - CiEnv::GitHubActions - } else { - CiEnv::None - } - } - - pub fn is_ci() -> bool { - Self::current() != CiEnv::None - } - - /// If in a CI environment, forces the command to run with colors. - pub fn force_coloring_in_ci(self, cmd: &mut Command) { - if self != CiEnv::None { - // Due to use of stamp/docker, the output stream of rustbuild is not - // a TTY in CI, so coloring is by-default turned off. - // The explicit `TERM=xterm` environment is needed for - // `--color always` to actually work. This env var was lost when - // compiling through the Makefile. Very strange. - cmd.env("TERM", "xterm").args(&["--color", "always"]); - } - } -} - pub fn forcing_clang_based_tests() -> bool { if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") { match &var.to_string_lossy().to_lowercase()[..] { diff --git a/src/ci/github-actions/ci.yml b/src/ci/github-actions/ci.yml index d33396dcc80..d3b07419486 100644 --- a/src/ci/github-actions/ci.yml +++ b/src/ci/github-actions/ci.yml @@ -103,6 +103,12 @@ x--expand-yaml-anchors--remove: with: fetch-depth: 2 + - name: checkout the `master` branch for tidy + uses: actions/checkout@v3 + with: + ref: master + fetch-depth: 1 + # Rust Log Analyzer can't currently detect the PR number of a GitHub # Actions build on its own, so a hint in the log message is needed to # point it in the right direction. diff --git a/src/tools/build_helper/Cargo.toml b/src/tools/build_helper/Cargo.toml new file mode 100644 index 00000000000..99f6fea2ecf --- /dev/null +++ b/src/tools/build_helper/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "build_helper" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/build_helper/src/ci.rs b/src/tools/build_helper/src/ci.rs new file mode 100644 index 00000000000..9f113c72b93 --- /dev/null +++ b/src/tools/build_helper/src/ci.rs @@ -0,0 +1,40 @@ +use std::process::Command; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum CiEnv { + /// Not a CI environment. + None, + /// The Azure Pipelines environment, for Linux (including Docker), Windows, and macOS builds. + AzurePipelines, + /// The GitHub Actions environment, for Linux (including Docker), Windows and macOS builds. + GitHubActions, +} + +impl CiEnv { + /// Obtains the current CI environment. + pub fn current() -> CiEnv { + if std::env::var("TF_BUILD").map_or(false, |e| e == "True") { + CiEnv::AzurePipelines + } else if std::env::var("GITHUB_ACTIONS").map_or(false, |e| e == "true") { + CiEnv::GitHubActions + } else { + CiEnv::None + } + } + + pub fn is_ci() -> bool { + Self::current() != CiEnv::None + } + + /// If in a CI environment, forces the command to run with colors. + pub fn force_coloring_in_ci(self, cmd: &mut Command) { + if self != CiEnv::None { + // Due to use of stamp/docker, the output stream of rustbuild is not + // a TTY in CI, so coloring is by-default turned off. + // The explicit `TERM=xterm` environment is needed for + // `--color always` to actually work. This env var was lost when + // compiling through the Makefile. Very strange. + cmd.env("TERM", "xterm").args(&["--color", "always"]); + } + } +} diff --git a/src/tools/build_helper/src/git.rs b/src/tools/build_helper/src/git.rs new file mode 100644 index 00000000000..e9638206e67 --- /dev/null +++ b/src/tools/build_helper/src/git.rs @@ -0,0 +1,30 @@ +use std::process::Command; + +/// Finds the remote for rust-lang/rust. +/// For example for these remotes it will return `upstream`. +/// ```text +/// origin https://github.com/Nilstrieb/rust.git (fetch) +/// origin https://github.com/Nilstrieb/rust.git (push) +/// upstream https://github.com/rust-lang/rust (fetch) +/// upstream https://github.com/rust-lang/rust (push) +/// ``` +pub fn get_rust_lang_rust_remote() -> Result { + let mut git = Command::new("git"); + git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]); + + let output = git.output().map_err(|err| format!("{err:?}"))?; + if !output.status.success() { + return Err("failed to execute git config command".to_owned()); + } + + let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?; + + let rust_lang_remote = stdout + .lines() + .find(|remote| remote.contains("rust-lang")) + .ok_or_else(|| "rust-lang/rust remote not found".to_owned())?; + + let remote_name = + rust_lang_remote.split('.').nth(1).ok_or_else(|| "remote name not found".to_owned())?; + Ok(remote_name.into()) +} diff --git a/src/tools/build_helper/src/lib.rs b/src/tools/build_helper/src/lib.rs new file mode 100644 index 00000000000..d3d2323db85 --- /dev/null +++ b/src/tools/build_helper/src/lib.rs @@ -0,0 +1,2 @@ +pub mod ci; +pub mod git; diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml index 97d038da702..ffedb4d5eb8 100644 --- a/src/tools/tidy/Cargo.toml +++ b/src/tools/tidy/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" autobins = false [dependencies] +build_helper = { path = "../build_helper" } cargo_metadata = "0.14" regex = "1" miropt-test-tools = { path = "../miropt-test-tools" } diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index 698e4850bea..186cd9bb11b 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -48,6 +48,7 @@ macro_rules! tidy_error { pub mod extdeps; pub mod features; pub mod mir_opt_tests; +pub mod no_merge; pub mod pal; pub mod primitive_docs; pub mod style; diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs index 6714c63ee62..65f0fe9743f 100644 --- a/src/tools/tidy/src/main.rs +++ b/src/tools/tidy/src/main.rs @@ -107,6 +107,8 @@ macro_rules! check { check!(alphabetical, &compiler_path); check!(alphabetical, &library_path); + check!(no_merge, ()); + let collected = { drain_handles(&mut handles); diff --git a/src/tools/tidy/src/no_merge.rs b/src/tools/tidy/src/no_merge.rs new file mode 100644 index 00000000000..362d6f6f4d7 --- /dev/null +++ b/src/tools/tidy/src/no_merge.rs @@ -0,0 +1,72 @@ +//! This check makes sure that no accidental merge commits are introduced to the repository. +//! It forbids all merge commits that are not caused by rollups/bors or subtree syncs. + +use std::process::Command; + +use build_helper::ci::CiEnv; +use build_helper::git::get_rust_lang_rust_remote; + +macro_rules! try_unwrap_in_ci { + ($expr:expr) => { + match $expr { + Ok(value) => value, + Err(err) if CiEnv::is_ci() => { + panic!("Encountered error while testing Git status: {:?}", err) + } + Err(_) => return, + } + }; +} + +pub fn check(_: (), bad: &mut bool) { + let remote = try_unwrap_in_ci!(get_rust_lang_rust_remote()); + let merge_commits = try_unwrap_in_ci!(find_merge_commits(&remote)); + + let mut bad_merge_commits = merge_commits.lines().filter(|commit| { + !( + // Bors is the ruler of merge commits. + commit.starts_with("Auto merge of") || commit.starts_with("Rollup merge of") + ) + }); + + if let Some(merge) = bad_merge_commits.next() { + tidy_error!( + bad, + "found a merge commit in the history: `{merge}`. +To resolve the issue, see this: https://rustc-dev-guide.rust-lang.org/git.html#i-made-a-merge-commit-by-accident. +If you're doing a subtree sync, add your tool to the list in the code that emitted this error." + ); + } +} + +/// Runs `git log --merges --format=%s $REMOTE/master..HEAD` and returns all commits +fn find_merge_commits(remote: &str) -> Result { + let mut git = Command::new("git"); + git.args([ + "log", + "--merges", + "--format=%s", + &format!("{remote}/master..HEAD"), + // Ignore subtree syncs. Add your new subtrees here. + ":!src/tools/miri", + ":!src/tools/rust-analyzer", + ":!compiler/rustc_smir", + ":!library/portable-simd", + ":!compiler/rustc_codegen_gcc", + ":!src/tools/rustfmt", + ":!compiler/rustc_codegen_cranelift", + ":!src/tools/clippy", + ]); + + let output = git.output().map_err(|err| format!("{err:?}"))?; + if !output.status.success() { + return Err(format!( + "failed to execute git log command: {}", + String::from_utf8_lossy(&output.stderr) + )); + } + + let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?; + + Ok(stdout) +}