about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-08-07 14:27:59 +0200
committerLukas Wirth <lukastw97@gmail.com>2024-08-07 14:27:59 +0200
commitdbf2c126a30508fabb75eb03c8147efccd3ba11d (patch)
tree37d3e8ee177d9ad3c276179e09e22030e3c3a83c /src
parent0713a4704508c86ff223910a415829e08031ae64 (diff)
downloadrust-dbf2c126a30508fabb75eb03c8147efccd3ba11d.tar.gz
rust-dbf2c126a30508fabb75eb03c8147efccd3ba11d.zip
Remove unnecessary CfgFlag definition in project-model
Diffstat (limited to 'src')
-rw-r--r--src/tools/rust-analyzer/crates/base-db/src/input.rs17
-rw-r--r--src/tools/rust-analyzer/crates/cfg/src/lib.rs8
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs (renamed from src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs)379
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cfg.rs100
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/lib.rs45
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/project_json.rs35
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs15
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/workspace.rs30
8 files changed, 310 insertions, 319 deletions
diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs
index 460581f4a6c..3616fa9fd86 100644
--- a/src/tools/rust-analyzer/crates/base-db/src/input.rs
+++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs
@@ -690,6 +690,14 @@ impl Env {
     pub fn extend_from_other(&mut self, other: &Env) {
         self.entries.extend(other.entries.iter().map(|(x, y)| (x.to_owned(), y.to_owned())));
     }
+
+    pub fn is_empty(&self) -> bool {
+        self.entries.is_empty()
+    }
+
+    pub fn insert(&mut self, k: impl Into<String>, v: impl Into<String>) -> Option<String> {
+        self.entries.insert(k.into(), v.into())
+    }
 }
 
 impl From<Env> for Vec<(String, String)> {
@@ -700,6 +708,15 @@ impl From<Env> for Vec<(String, String)> {
     }
 }
 
+impl<'a> IntoIterator for &'a Env {
+    type Item = (&'a String, &'a String);
+    type IntoIter = std::collections::hash_map::Iter<'a, String, String>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.entries.iter()
+    }
+}
+
 #[derive(Debug)]
 pub struct CyclicDependenciesError {
     path: Vec<(CrateId, Option<CrateDisplayName>)>,
diff --git a/src/tools/rust-analyzer/crates/cfg/src/lib.rs b/src/tools/rust-analyzer/crates/cfg/src/lib.rs
index 6d46dfb9994..e9daaf7de3c 100644
--- a/src/tools/rust-analyzer/crates/cfg/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/cfg/src/lib.rs
@@ -108,6 +108,14 @@ impl<'a> IntoIterator for &'a CfgOptions {
     }
 }
 
+impl FromIterator<CfgAtom> for CfgOptions {
+    fn from_iter<T: IntoIterator<Item = CfgAtom>>(iter: T) -> Self {
+        let mut options = CfgOptions::default();
+        options.extend(iter);
+        options
+    }
+}
+
 #[derive(Default, Clone, Debug, PartialEq, Eq)]
 pub struct CfgDiff {
     // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
index 40e66e00b68..e7a4b8f39f7 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs
@@ -1,14 +1,16 @@
-//! Workspace information we get from cargo consists of two pieces. The first is
-//! the output of `cargo metadata`. The second is the output of running
-//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc
-//! macro.
+//! Logic to invoke `cargo` for building build-dependencies (build scripts and proc-macros) as well as
+//! executing the build scripts to fetch required dependency information (`OUT_DIR` env var, extra
+//! cfg flags, etc).
 //!
-//! This module implements this second part. We use "build script" terminology
-//! here, but it covers procedural macros as well.
+//! In essence this just invokes `cargo` with the appropriate output format which we consume,
+//! but if enabled we will also use `RUSTC_WRAPPER` to only compile the build scripts and
+//! proc-macros and skip everything else.
 
-use std::{cell::RefCell, io, mem, path, process::Command};
+use std::{cell::RefCell, io, mem, process::Command};
 
+use base_db::Env;
 use cargo_metadata::{camino::Utf8Path, Message};
+use cfg::CfgAtom;
 use itertools::Itertools;
 use la_arena::ArenaMap;
 use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
@@ -17,7 +19,7 @@ use serde::Deserialize;
 use toolchain::Tool;
 
 use crate::{
-    cfg::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
+    utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
     InvocationStrategy, ManifestPath, Package, Sysroot, TargetKind,
 };
 
@@ -32,12 +34,12 @@ pub struct WorkspaceBuildScripts {
 #[derive(Debug, Clone, Default, PartialEq, Eq)]
 pub(crate) struct BuildScriptOutput {
     /// List of config flags defined by this package's build script.
-    pub(crate) cfgs: Vec<CfgFlag>,
+    pub(crate) cfgs: Vec<CfgAtom>,
     /// List of cargo-related environment variables with their value.
     ///
     /// If the package has a build script which defines environment variables,
     /// they can also be found here.
-    pub(crate) envs: Vec<(String, String)>,
+    pub(crate) envs: Env,
     /// Directory where a build script might place its output.
     pub(crate) out_dir: Option<AbsPathBuf>,
     /// Path to the proc-macro library file if this package exposes proc-macros.
@@ -45,7 +47,7 @@ pub(crate) struct BuildScriptOutput {
 }
 
 impl BuildScriptOutput {
-    fn is_unchanged(&self) -> bool {
+    fn is_empty(&self) -> bool {
         self.cfgs.is_empty()
             && self.envs.is_empty()
             && self.out_dir.is_none()
@@ -54,85 +56,6 @@ impl BuildScriptOutput {
 }
 
 impl WorkspaceBuildScripts {
-    fn build_command(
-        config: &CargoConfig,
-        allowed_features: &FxHashSet<String>,
-        manifest_path: &ManifestPath,
-        sysroot: &Sysroot,
-    ) -> io::Result<Command> {
-        let mut cmd = match config.run_build_script_command.as_deref() {
-            Some([program, args @ ..]) => {
-                let mut cmd = Command::new(program);
-                cmd.args(args);
-                cmd
-            }
-            _ => {
-                let mut cmd = sysroot.tool(Tool::Cargo);
-
-                cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
-                cmd.args(&config.extra_args);
-
-                cmd.arg("--manifest-path");
-                cmd.arg(manifest_path);
-
-                if let Some(target_dir) = &config.target_dir {
-                    cmd.arg("--target-dir").arg(target_dir);
-                }
-
-                // --all-targets includes tests, benches and examples in addition to the
-                // default lib and bins. This is an independent concept from the --target
-                // flag below.
-                if config.all_targets {
-                    cmd.arg("--all-targets");
-                }
-
-                if let Some(target) = &config.target {
-                    cmd.args(["--target", target]);
-                }
-
-                match &config.features {
-                    CargoFeatures::All => {
-                        cmd.arg("--all-features");
-                    }
-                    CargoFeatures::Selected { features, no_default_features } => {
-                        if *no_default_features {
-                            cmd.arg("--no-default-features");
-                        }
-                        if !features.is_empty() {
-                            cmd.arg("--features");
-                            cmd.arg(
-                                features
-                                    .iter()
-                                    .filter(|&feat| allowed_features.contains(feat))
-                                    .join(","),
-                            );
-                        }
-                    }
-                }
-
-                if manifest_path.is_rust_manifest() {
-                    cmd.arg("-Zscript");
-                }
-
-                cmd.arg("--keep-going");
-
-                cmd
-            }
-        };
-
-        cmd.envs(&config.extra_env);
-        if config.wrap_rustc_in_build_scripts {
-            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
-            // that to compile only proc macros and build scripts during the initial
-            // `cargo check`.
-            let myself = std::env::current_exe()?;
-            cmd.env("RUSTC_WRAPPER", myself);
-            cmd.env("RA_RUSTC_WRAPPER", "1");
-        }
-
-        Ok(cmd)
-    }
-
     /// Runs the build scripts for the given workspace
     pub(crate) fn run_for_workspace(
         config: &CargoConfig,
@@ -141,17 +64,19 @@ impl WorkspaceBuildScripts {
         sysroot: &Sysroot,
     ) -> io::Result<WorkspaceBuildScripts> {
         let current_dir = match &config.invocation_location {
-            InvocationLocation::Root(root) if config.run_build_script_command.is_some() => {
-                root.as_path()
-            }
+            InvocationLocation::Root(root) if config.run_build_script_command.is_some() => root,
             _ => workspace.workspace_root(),
-        }
-        .as_ref();
+        };
 
         let allowed_features = workspace.workspace_features();
-        let cmd =
-            Self::build_command(config, &allowed_features, workspace.manifest_path(), sysroot)?;
-        Self::run_per_ws(cmd, workspace, current_dir, progress)
+        let cmd = Self::build_command(
+            config,
+            &allowed_features,
+            workspace.manifest_path(),
+            current_dir,
+            sysroot,
+        )?;
+        Self::run_per_ws(cmd, workspace, progress)
     }
 
     /// Runs the build scripts by invoking the configured command *once*.
@@ -178,6 +103,7 @@ impl WorkspaceBuildScripts {
             &Default::default(),
             // This is not gonna be used anyways, so just construct a dummy here
             &ManifestPath::try_from(workspace_root.clone()).unwrap(),
+            current_dir,
             &Sysroot::empty(),
         )?;
         // NB: Cargo.toml could have been modified between `cargo metadata` and
@@ -206,7 +132,6 @@ impl WorkspaceBuildScripts {
 
         let errors = Self::run_command(
             cmd,
-            current_dir.as_path().as_ref(),
             |package, cb| {
                 if let Some(&(package, workspace)) = by_id.get(package) {
                     cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]);
@@ -225,7 +150,7 @@ impl WorkspaceBuildScripts {
             for (idx, workspace) in workspaces.iter().enumerate() {
                 for package in workspace.packages() {
                     let package_build_data = &mut res[idx].outputs[package];
-                    if !package_build_data.is_unchanged() {
+                    if !package_build_data.is_empty() {
                         tracing::info!(
                             "{}: {package_build_data:?}",
                             workspace[package].manifest.parent(),
@@ -238,10 +163,100 @@ impl WorkspaceBuildScripts {
         Ok(res)
     }
 
+    pub fn error(&self) -> Option<&str> {
+        self.error.as_deref()
+    }
+
+    pub(crate) fn get_output(&self, idx: Package) -> Option<&BuildScriptOutput> {
+        self.outputs.get(idx)
+    }
+
+    /// Assembles build script outputs for the rustc crates via `--print target-libdir`.
+    pub(crate) fn rustc_crates(
+        rustc: &CargoWorkspace,
+        current_dir: &AbsPath,
+        extra_env: &FxHashMap<String, String>,
+        sysroot: &Sysroot,
+    ) -> Self {
+        let mut bs = WorkspaceBuildScripts::default();
+        for p in rustc.packages() {
+            bs.outputs.insert(p, BuildScriptOutput::default());
+        }
+        let res = (|| {
+            let target_libdir = (|| {
+                let mut cargo_config = sysroot.tool(Tool::Cargo);
+                cargo_config.envs(extra_env);
+                cargo_config
+                    .current_dir(current_dir)
+                    .args(["rustc", "-Z", "unstable-options", "--print", "target-libdir"])
+                    .env("RUSTC_BOOTSTRAP", "1");
+                if let Ok(it) = utf8_stdout(cargo_config) {
+                    return Ok(it);
+                }
+                let mut cmd = sysroot.tool(Tool::Rustc);
+                cmd.envs(extra_env);
+                cmd.args(["--print", "target-libdir"]);
+                utf8_stdout(cmd)
+            })()?;
+
+            let target_libdir = AbsPathBuf::try_from(Utf8PathBuf::from(target_libdir))
+                .map_err(|_| anyhow::format_err!("target-libdir was not an absolute path"))?;
+            tracing::info!("Loading rustc proc-macro paths from {target_libdir}");
+
+            let proc_macro_dylibs: Vec<(String, AbsPathBuf)> = std::fs::read_dir(target_libdir)?
+                .filter_map(|entry| {
+                    let dir_entry = entry.ok()?;
+                    if dir_entry.file_type().ok()?.is_file() {
+                        let path = dir_entry.path();
+                        let extension = path.extension()?;
+                        if extension == std::env::consts::DLL_EXTENSION {
+                            let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned();
+                            let path = AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).ok()?)
+                                .ok()?;
+                            return Some((name, path));
+                        }
+                    }
+                    None
+                })
+                .collect();
+            for p in rustc.packages() {
+                let package = &rustc[p];
+                if package
+                    .targets
+                    .iter()
+                    .any(|&it| matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true }))
+                {
+                    if let Some((_, path)) = proc_macro_dylibs
+                        .iter()
+                        .find(|(name, _)| *name.trim_start_matches("lib") == package.name)
+                    {
+                        bs.outputs[p].proc_macro_dylib_path = Some(path.clone());
+                    }
+                }
+            }
+
+            if tracing::enabled!(tracing::Level::INFO) {
+                for package in rustc.packages() {
+                    let package_build_data = &bs.outputs[package];
+                    if !package_build_data.is_empty() {
+                        tracing::info!(
+                            "{}: {package_build_data:?}",
+                            rustc[package].manifest.parent(),
+                        );
+                    }
+                }
+            }
+            Ok(())
+        })();
+        if let Err::<_, anyhow::Error>(e) = res {
+            bs.error = Some(e.to_string());
+        }
+        bs
+    }
+
     fn run_per_ws(
         cmd: Command,
         workspace: &CargoWorkspace,
-        current_dir: &path::Path,
         progress: &dyn Fn(String),
     ) -> io::Result<WorkspaceBuildScripts> {
         let mut res = WorkspaceBuildScripts::default();
@@ -257,7 +272,6 @@ impl WorkspaceBuildScripts {
 
         res.error = Self::run_command(
             cmd,
-            current_dir,
             |package, cb| {
                 if let Some(&package) = by_id.get(package) {
                     cb(&workspace[package].name, &mut outputs[package]);
@@ -269,7 +283,7 @@ impl WorkspaceBuildScripts {
         if tracing::enabled!(tracing::Level::INFO) {
             for package in workspace.packages() {
                 let package_build_data = &outputs[package];
-                if !package_build_data.is_unchanged() {
+                if !package_build_data.is_empty() {
                     tracing::info!(
                         "{}: {package_build_data:?}",
                         workspace[package].manifest.parent(),
@@ -282,8 +296,7 @@ impl WorkspaceBuildScripts {
     }
 
     fn run_command(
-        mut cmd: Command,
-        current_dir: &path::Path,
+        cmd: Command,
         // ideally this would be something like:
         // with_output_for: impl FnMut(&str, dyn FnOnce(&mut BuildScriptOutput)),
         // but owned trait objects aren't a thing
@@ -297,8 +310,7 @@ impl WorkspaceBuildScripts {
             e.push('\n');
         };
 
-        tracing::info!("Running build scripts in {}: {:?}", current_dir.display(), cmd);
-        cmd.current_dir(current_dir);
+        tracing::info!("Running build scripts: {:?}", cmd);
         let output = stdx::process::spawn_with_streaming_output(
             cmd,
             &mut |line| {
@@ -316,7 +328,7 @@ impl WorkspaceBuildScripts {
                             let cfgs = {
                                 let mut acc = Vec::new();
                                 for cfg in &message.cfgs {
-                                    match cfg.parse::<CfgFlag>() {
+                                    match crate::parse_cfg(cfg) {
                                         Ok(it) => acc.push(it),
                                         Err(err) => {
                                             push_err(&format!(
@@ -328,16 +340,14 @@ impl WorkspaceBuildScripts {
                                 }
                                 acc
                             };
-                            if !message.env.is_empty() {
-                                data.envs = mem::take(&mut message.env);
-                            }
+                            data.envs.extend(message.env.drain(..));
                             // cargo_metadata crate returns default (empty) path for
                             // older cargos, which is not absolute, so work around that.
                             let out_dir = mem::take(&mut message.out_dir);
                             if !out_dir.as_str().is_empty() {
                                 let out_dir = AbsPathBuf::assert(out_dir);
                                 // inject_cargo_env(package, package_build_data);
-                                data.envs.push(("OUT_DIR".to_owned(), out_dir.as_str().to_owned()));
+                                data.envs.insert("OUT_DIR", out_dir.as_str());
                                 data.out_dir = Some(out_dir);
                                 data.cfgs = cfgs;
                             }
@@ -349,7 +359,7 @@ impl WorkspaceBuildScripts {
                             if message.target.kind.iter().any(|k| k == "proc-macro") {
                                 // Skip rmeta file
                                 if let Some(filename) =
-                                    message.filenames.iter().find(|name| is_dylib(name))
+                                    message.filenames.iter().find(|file| is_dylib(file))
                                 {
                                     let filename = AbsPath::assert(filename);
                                     data.proc_macro_dylib_path = Some(filename.to_owned());
@@ -383,94 +393,85 @@ impl WorkspaceBuildScripts {
         Ok(errors)
     }
 
-    pub fn error(&self) -> Option<&str> {
-        self.error.as_deref()
-    }
-
-    pub(crate) fn get_output(&self, idx: Package) -> Option<&BuildScriptOutput> {
-        self.outputs.get(idx)
-    }
-
-    pub(crate) fn rustc_crates(
-        rustc: &CargoWorkspace,
+    fn build_command(
+        config: &CargoConfig,
+        allowed_features: &FxHashSet<String>,
+        manifest_path: &ManifestPath,
         current_dir: &AbsPath,
-        extra_env: &FxHashMap<String, String>,
         sysroot: &Sysroot,
-    ) -> Self {
-        let mut bs = WorkspaceBuildScripts::default();
-        for p in rustc.packages() {
-            bs.outputs.insert(p, BuildScriptOutput::default());
-        }
-        let res = (|| {
-            let target_libdir = (|| {
-                let mut cargo_config = sysroot.tool(Tool::Cargo);
-                cargo_config.envs(extra_env);
-                cargo_config
-                    .current_dir(current_dir)
-                    .args(["rustc", "-Z", "unstable-options", "--print", "target-libdir"])
-                    .env("RUSTC_BOOTSTRAP", "1");
-                if let Ok(it) = utf8_stdout(cargo_config) {
-                    return Ok(it);
+    ) -> io::Result<Command> {
+        let mut cmd = match config.run_build_script_command.as_deref() {
+            Some([program, args @ ..]) => {
+                let mut cmd = Command::new(program);
+                cmd.args(args);
+                cmd
+            }
+            _ => {
+                let mut cmd = sysroot.tool(Tool::Cargo);
+
+                cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
+                cmd.args(&config.extra_args);
+
+                cmd.arg("--manifest-path");
+                cmd.arg(manifest_path);
+
+                if let Some(target_dir) = &config.target_dir {
+                    cmd.arg("--target-dir").arg(target_dir);
                 }
-                let mut cmd = sysroot.tool(Tool::Rustc);
-                cmd.envs(extra_env);
-                cmd.args(["--print", "target-libdir"]);
-                utf8_stdout(cmd)
-            })()?;
 
-            let target_libdir = AbsPathBuf::try_from(Utf8PathBuf::from(target_libdir))
-                .map_err(|_| anyhow::format_err!("target-libdir was not an absolute path"))?;
-            tracing::info!("Loading rustc proc-macro paths from {target_libdir}");
+                // --all-targets includes tests, benches and examples in addition to the
+                // default lib and bins. This is an independent concept from the --target
+                // flag below.
+                if config.all_targets {
+                    cmd.arg("--all-targets");
+                }
 
-            let proc_macro_dylibs: Vec<(String, AbsPathBuf)> = std::fs::read_dir(target_libdir)?
-                .filter_map(|entry| {
-                    let dir_entry = entry.ok()?;
-                    if dir_entry.file_type().ok()?.is_file() {
-                        let path = dir_entry.path();
-                        let extension = path.extension()?;
-                        if extension == std::env::consts::DLL_EXTENSION {
-                            let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned();
-                            let path = AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).ok()?)
-                                .ok()?;
-                            return Some((name, path));
-                        }
+                if let Some(target) = &config.target {
+                    cmd.args(["--target", target]);
+                }
+
+                match &config.features {
+                    CargoFeatures::All => {
+                        cmd.arg("--all-features");
                     }
-                    None
-                })
-                .collect();
-            for p in rustc.packages() {
-                let package = &rustc[p];
-                if package
-                    .targets
-                    .iter()
-                    .any(|&it| matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true }))
-                {
-                    if let Some((_, path)) = proc_macro_dylibs
-                        .iter()
-                        .find(|(name, _)| *name.trim_start_matches("lib") == package.name)
-                    {
-                        bs.outputs[p].proc_macro_dylib_path = Some(path.clone());
+                    CargoFeatures::Selected { features, no_default_features } => {
+                        if *no_default_features {
+                            cmd.arg("--no-default-features");
+                        }
+                        if !features.is_empty() {
+                            cmd.arg("--features");
+                            cmd.arg(
+                                features
+                                    .iter()
+                                    .filter(|&feat| allowed_features.contains(feat))
+                                    .join(","),
+                            );
+                        }
                     }
                 }
-            }
 
-            if tracing::enabled!(tracing::Level::INFO) {
-                for package in rustc.packages() {
-                    let package_build_data = &bs.outputs[package];
-                    if !package_build_data.is_unchanged() {
-                        tracing::info!(
-                            "{}: {package_build_data:?}",
-                            rustc[package].manifest.parent(),
-                        );
-                    }
+                if manifest_path.is_rust_manifest() {
+                    cmd.arg("-Zscript");
                 }
+
+                cmd.arg("--keep-going");
+
+                cmd
             }
-            Ok(())
-        })();
-        if let Err::<_, anyhow::Error>(e) = res {
-            bs.error = Some(e.to_string());
+        };
+
+        cmd.current_dir(current_dir);
+        cmd.envs(&config.extra_env);
+        if config.wrap_rustc_in_build_scripts {
+            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
+            // that to compile only proc macros and build scripts during the initial
+            // `cargo check`.
+            let myself = std::env::current_exe()?;
+            cmd.env("RUSTC_WRAPPER", myself);
+            cmd.env("RA_RUSTC_WRAPPER", "1");
         }
-        bs
+
+        Ok(cmd)
     }
 }
 
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/cfg.rs
deleted file mode 100644
index e921e3de722..00000000000
--- a/src/tools/rust-analyzer/crates/project-model/src/cfg.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-//! Parsing of CfgFlags as command line arguments, as in
-//!
-//! rustc main.rs --cfg foo --cfg 'feature="bar"'
-use std::{fmt, str::FromStr};
-
-use cfg::{CfgDiff, CfgOptions};
-use intern::Symbol;
-use rustc_hash::FxHashMap;
-use serde::Serialize;
-
-#[derive(Clone, Eq, PartialEq, Debug, Serialize)]
-pub enum CfgFlag {
-    Atom(String),
-    KeyValue { key: String, value: String },
-}
-
-impl FromStr for CfgFlag {
-    type Err = String;
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let res = match s.split_once('=') {
-            Some((key, value)) => {
-                if !(value.starts_with('"') && value.ends_with('"')) {
-                    return Err(format!("Invalid cfg ({s:?}), value should be in quotes"));
-                }
-                let key = key.to_owned();
-                let value = value[1..value.len() - 1].to_string();
-                CfgFlag::KeyValue { key, value }
-            }
-            None => CfgFlag::Atom(s.into()),
-        };
-        Ok(res)
-    }
-}
-
-impl<'de> serde::Deserialize<'de> for CfgFlag {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
-    }
-}
-
-impl Extend<CfgFlag> for CfgOptions {
-    fn extend<T: IntoIterator<Item = CfgFlag>>(&mut self, iter: T) {
-        for cfg_flag in iter {
-            match cfg_flag {
-                CfgFlag::Atom(it) => self.insert_atom(Symbol::intern(&it)),
-                CfgFlag::KeyValue { key, value } => {
-                    self.insert_key_value(Symbol::intern(&key), Symbol::intern(&value))
-                }
-            }
-        }
-    }
-}
-
-impl FromIterator<CfgFlag> for CfgOptions {
-    fn from_iter<T: IntoIterator<Item = CfgFlag>>(iter: T) -> Self {
-        let mut this = CfgOptions::default();
-        this.extend(iter);
-        this
-    }
-}
-
-impl fmt::Display for CfgFlag {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            CfgFlag::Atom(atom) => f.write_str(atom),
-            CfgFlag::KeyValue { key, value } => {
-                f.write_str(key)?;
-                f.write_str("=")?;
-                f.write_str(value)
-            }
-        }
-    }
-}
-
-/// A set of cfg-overrides per crate.
-#[derive(Default, Debug, Clone, Eq, PartialEq)]
-pub struct CfgOverrides {
-    /// A global set of overrides matching all crates.
-    pub global: CfgDiff,
-    /// A set of overrides matching specific crates.
-    pub selective: FxHashMap<String, CfgDiff>,
-}
-
-impl CfgOverrides {
-    pub fn len(&self) -> usize {
-        self.global.len() + self.selective.values().map(|it| it.len()).sum::<usize>()
-    }
-
-    pub fn apply(&self, cfg_options: &mut CfgOptions, name: &str) {
-        if !self.global.is_empty() {
-            cfg_options.apply_diff(self.global.clone());
-        };
-        if let Some(diff) = self.selective.get(name) {
-            cfg_options.apply_diff(diff.clone());
-        };
-    }
-}
diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs
index 32280d5c763..4fa70508bbd 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs
@@ -15,9 +15,8 @@
 //!   procedural macros).
 //! * Lowering of concrete model to a [`base_db::CrateGraph`]
 
-mod build_scripts;
+mod build_dependencies;
 mod cargo_workspace;
-mod cfg;
 mod env;
 mod manifest_path;
 pub mod project_json;
@@ -41,12 +40,11 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
 use rustc_hash::FxHashSet;
 
 pub use crate::{
-    build_scripts::WorkspaceBuildScripts,
+    build_dependencies::WorkspaceBuildScripts,
     cargo_workspace::{
         CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency,
         RustLibSource, Target, TargetData, TargetKind,
     },
-    cfg::CfgOverrides,
     manifest_path::ManifestPath,
     project_json::{ProjectJson, ProjectJsonData},
     sysroot::Sysroot,
@@ -201,3 +199,42 @@ pub enum InvocationLocation {
     #[default]
     Workspace,
 }
+
+/// A set of cfg-overrides per crate.
+#[derive(Default, Debug, Clone, Eq, PartialEq)]
+pub struct CfgOverrides {
+    /// A global set of overrides matching all crates.
+    pub global: cfg::CfgDiff,
+    /// A set of overrides matching specific crates.
+    pub selective: rustc_hash::FxHashMap<String, cfg::CfgDiff>,
+}
+
+impl CfgOverrides {
+    pub fn len(&self) -> usize {
+        self.global.len() + self.selective.values().map(|it| it.len()).sum::<usize>()
+    }
+
+    pub fn apply(&self, cfg_options: &mut cfg::CfgOptions, name: &str) {
+        if !self.global.is_empty() {
+            cfg_options.apply_diff(self.global.clone());
+        };
+        if let Some(diff) = self.selective.get(name) {
+            cfg_options.apply_diff(diff.clone());
+        };
+    }
+}
+
+fn parse_cfg(s: &str) -> Result<cfg::CfgAtom, String> {
+    let res = match s.split_once('=') {
+        Some((key, value)) => {
+            if !(value.starts_with('"') && value.ends_with('"')) {
+                return Err(format!("Invalid cfg ({s:?}), value should be in quotes"));
+            }
+            let key = intern::Symbol::intern(key);
+            let value = intern::Symbol::intern(&value[1..value.len() - 1]);
+            cfg::CfgAtom::KeyValue { key, value }
+        }
+        None => cfg::CfgAtom::Flag(intern::Symbol::intern(s)),
+    };
+    Ok(res)
+}
diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs
index cf0a6ad4025..1fb9cec8e26 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs
@@ -50,12 +50,13 @@
 //! rust-project.json over time via configuration request!)
 
 use base_db::{CrateDisplayName, CrateName};
+use cfg::CfgAtom;
 use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
 use rustc_hash::FxHashMap;
 use serde::{de, Deserialize, Serialize};
 use span::Edition;
 
-use crate::{cfg::CfgFlag, ManifestPath, TargetKind};
+use crate::{ManifestPath, TargetKind};
 
 /// Roots and crates that compose this Rust project.
 #[derive(Clone, Debug, Eq, PartialEq)]
@@ -82,7 +83,7 @@ pub struct Crate {
     pub(crate) edition: Edition,
     pub(crate) version: Option<String>,
     pub(crate) deps: Vec<Dep>,
-    pub(crate) cfg: Vec<CfgFlag>,
+    pub(crate) cfg: Vec<CfgAtom>,
     pub(crate) target: Option<String>,
     pub(crate) env: FxHashMap<String, String>,
     pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
@@ -319,7 +320,8 @@ struct CrateData {
     version: Option<semver::Version>,
     deps: Vec<Dep>,
     #[serde(default)]
-    cfg: Vec<CfgFlag>,
+    #[serde(with = "cfg_")]
+    cfg: Vec<CfgAtom>,
     target: Option<String>,
     #[serde(default)]
     env: FxHashMap<String, String>,
@@ -334,6 +336,33 @@ struct CrateData {
     build: Option<BuildData>,
 }
 
+mod cfg_ {
+    use cfg::CfgAtom;
+    use serde::{Deserialize, Serialize};
+
+    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Vec<CfgAtom>, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let cfg: Vec<String> = Vec::deserialize(deserializer)?;
+        cfg.into_iter().map(|it| crate::parse_cfg(&it).map_err(serde::de::Error::custom)).collect()
+    }
+    pub(super) fn serialize<S>(cfg: &[CfgAtom], serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        cfg.iter()
+            .map(|cfg| match cfg {
+                CfgAtom::Flag(flag) => flag.as_str().to_owned(),
+                CfgAtom::KeyValue { key, value } => {
+                    format!("{}=\"{}\"", key.as_str(), value.as_str())
+                }
+            })
+            .collect::<Vec<String>>()
+            .serialize(serializer)
+    }
+}
+
 #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
 #[serde(rename = "edition")]
 enum EditionData {
diff --git a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
index 599897f84a0..aa73ff89100 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
@@ -1,10 +1,12 @@
 //! Runs `rustc --print cfg` to get built-in cfg flags.
 
 use anyhow::Context;
+use cfg::CfgAtom;
+use intern::Symbol;
 use rustc_hash::FxHashMap;
 use toolchain::Tool;
 
-use crate::{cfg::CfgFlag, utf8_stdout, ManifestPath, Sysroot};
+use crate::{utf8_stdout, ManifestPath, Sysroot};
 
 /// Determines how `rustc --print cfg` is discovered and invoked.
 pub(crate) enum RustcCfgConfig<'a> {
@@ -20,15 +22,15 @@ pub(crate) fn get(
     target: Option<&str>,
     extra_env: &FxHashMap<String, String>,
     config: RustcCfgConfig<'_>,
-) -> Vec<CfgFlag> {
+) -> Vec<CfgAtom> {
     let _p = tracing::info_span!("rustc_cfg::get").entered();
-    let mut res = Vec::with_capacity(6 * 2 + 1);
+    let mut res: Vec<_> = Vec::with_capacity(6 * 2 + 1);
 
     // Some nightly-only cfgs, which are required for stdlib
-    res.push(CfgFlag::Atom("target_thread_local".into()));
+    res.push(CfgAtom::Flag(Symbol::intern("target_thread_local")));
     for ty in ["8", "16", "32", "64", "cas", "ptr"] {
         for key in ["target_has_atomic", "target_has_atomic_load_store"] {
-            res.push(CfgFlag::KeyValue { key: key.to_owned(), value: ty.into() });
+            res.push(CfgAtom::KeyValue { key: Symbol::intern(key), value: Symbol::intern(ty) });
         }
     }
 
@@ -42,8 +44,7 @@ pub(crate) fn get(
         }
     };
 
-    let rustc_cfgs =
-        rustc_cfgs.lines().map(|it| it.parse::<CfgFlag>()).collect::<Result<Vec<_>, _>>();
+    let rustc_cfgs = rustc_cfgs.lines().map(crate::parse_cfg).collect::<Result<Vec<_>, _>>();
 
     match rustc_cfgs {
         Ok(rustc_cfgs) => {
diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
index 9156c8da33a..5620dfade2d 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
@@ -20,16 +20,15 @@ use tracing::instrument;
 use triomphe::Arc;
 
 use crate::{
-    build_scripts::BuildScriptOutput,
+    build_dependencies::BuildScriptOutput,
     cargo_workspace::{DepKind, PackageData, RustLibSource},
-    cfg::{CfgFlag, CfgOverrides},
     env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env},
     project_json::{Crate, CrateArrayIdx},
     rustc_cfg::{self, RustcCfgConfig},
     sysroot::{SysrootCrate, SysrootMode},
     target_data_layout::{self, RustcDataLayoutConfig},
-    utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
-    ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
+    utf8_stdout, CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath,
+    Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
 };
 use tracing::{debug, error, info};
 
@@ -55,7 +54,7 @@ pub struct ProjectWorkspace {
     /// `rustc --print cfg`.
     // FIXME: make this a per-crate map, as, eg, build.rs might have a
     // different target.
-    pub rustc_cfg: Vec<CfgFlag>,
+    pub rustc_cfg: Vec<CfgAtom>,
     /// The toolchain version used by this workspace.
     pub toolchain: Option<Version>,
     /// The target data layout queried for workspace.
@@ -842,7 +841,7 @@ impl ProjectWorkspace {
 
 #[instrument(skip_all)]
 fn project_json_to_crate_graph(
-    rustc_cfg: Vec<CfgFlag>,
+    rustc_cfg: Vec<CfgAtom>,
     load: FileLoader<'_>,
     project: &ProjectJson,
     sysroot: &Sysroot,
@@ -854,8 +853,8 @@ fn project_json_to_crate_graph(
     let (public_deps, libproc_macro) =
         sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load);
 
-    let r_a_cfg_flag = CfgFlag::Atom("rust_analyzer".to_owned());
-    let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
+    let r_a_cfg_flag = CfgAtom::Flag(sym::rust_analyzer.clone());
+    let mut cfg_cache: FxHashMap<&str, Vec<CfgAtom>> = FxHashMap::default();
 
     let idx_to_crate_id: FxHashMap<CrateArrayIdx, CrateId> = project
         .crates()
@@ -962,7 +961,7 @@ fn cargo_to_crate_graph(
     rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
     cargo: &CargoWorkspace,
     sysroot: &Sysroot,
-    rustc_cfg: Vec<CfgFlag>,
+    rustc_cfg: Vec<CfgAtom>,
     override_cfg: &CfgOverrides,
     build_scripts: &WorkspaceBuildScripts,
 ) -> (CrateGraph, ProcMacroPaths) {
@@ -1145,7 +1144,7 @@ fn cargo_to_crate_graph(
 }
 
 fn detached_file_to_crate_graph(
-    rustc_cfg: Vec<CfgFlag>,
+    rustc_cfg: Vec<CfgAtom>,
     load: FileLoader<'_>,
     detached_file: &ManifestPath,
     sysroot: &Sysroot,
@@ -1308,11 +1307,10 @@ fn add_target_crate_root(
         None
     } else {
         let mut potential_cfg_options = cfg_options.clone();
-        potential_cfg_options.extend(
-            pkg.features
-                .iter()
-                .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
-        );
+        potential_cfg_options.extend(pkg.features.iter().map(|feat| CfgAtom::KeyValue {
+            key: sym::feature.clone(),
+            value: Symbol::intern(feat.0),
+        }));
         Some(potential_cfg_options)
     };
     let cfg_options = {
@@ -1378,7 +1376,7 @@ impl SysrootPublicDeps {
 fn sysroot_to_crate_graph(
     crate_graph: &mut CrateGraph,
     sysroot: &Sysroot,
-    rustc_cfg: Vec<CfgFlag>,
+    rustc_cfg: Vec<CfgAtom>,
     load: FileLoader<'_>,
 ) -> (SysrootPublicDeps, Option<CrateId>) {
     let _p = tracing::info_span!("sysroot_to_crate_graph").entered();