]> git.lizzy.rs Git - rust.git/blobdiff - crates/project_model/src/workspace.rs
Rename `is_member` to `is_local`
[rust.git] / crates / project_model / src / workspace.rs
index 84990075f728e4d56be8d31d9156284d56cdf0aa..5dc2e35fdab86f32f0ec5494beb3e6119c6dd17c 100644 (file)
@@ -2,33 +2,59 @@
 //! metadata` or `rust-project.json`) into representation stored in the salsa
 //! database -- `CrateGraph`.
 
-use std::{collections::VecDeque, fmt, fs, path::Path, process::Command};
+use std::{collections::VecDeque, convert::TryFrom, fmt, fs, process::Command};
 
 use anyhow::{format_err, Context, Result};
 use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
-use cargo_workspace::DepKind;
-use cfg::CfgOptions;
+use cfg::{CfgDiff, CfgOptions};
 use paths::{AbsPath, AbsPathBuf};
-use proc_macro_api::ProcMacroClient;
 use rustc_hash::{FxHashMap, FxHashSet};
+use stdx::always;
 
 use crate::{
-    build_data::{BuildDataResult, PackageBuildData, WorkspaceBuildData},
-    cargo_workspace,
+    build_scripts::BuildScriptOutput,
+    cargo_workspace::{DepKind, PackageData, RustcSource},
     cfg_flag::CfgFlag,
     rustc_cfg,
     sysroot::SysrootCrate,
-    utf8_stdout, BuildDataCollector, CargoConfig, CargoWorkspace, ProjectJson, ProjectManifest,
-    Sysroot, TargetKind,
+    utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, ProjectJson, ProjectManifest, Sysroot,
+    TargetKind, WorkspaceBuildScripts,
 };
 
+/// A set of cfg-overrides per crate.
+///
+/// `Wildcard(..)` is useful e.g. disabling `#[cfg(test)]` on all crates,
+/// without having to first obtain a list of all crates.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum CfgOverrides {
+    /// A single global set of overrides matching all crates.
+    Wildcard(CfgDiff),
+    /// A set of overrides matching specific crates.
+    Selective(FxHashMap<String, CfgDiff>),
+}
+
+impl Default for CfgOverrides {
+    fn default() -> Self {
+        Self::Selective(FxHashMap::default())
+    }
+}
+
+impl CfgOverrides {
+    pub fn len(&self) -> usize {
+        match self {
+            CfgOverrides::Wildcard(_) => 1,
+            CfgOverrides::Selective(hash_map) => hash_map.len(),
+        }
+    }
+}
+
 /// `PackageRoot` describes a package root folder.
 /// Which may be an external dependency, or a member of
 /// the current workspace.
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct PackageRoot {
-    /// Is a member of the current workspace
-    pub is_member: bool,
+    /// Is from the local filesystem and may be edited
+    pub is_local: bool,
     pub include: Vec<AbsPathBuf>,
     pub exclude: Vec<AbsPathBuf>,
 }
@@ -38,7 +64,8 @@ pub enum ProjectWorkspace {
     /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
     Cargo {
         cargo: CargoWorkspace,
-        sysroot: Sysroot,
+        build_scripts: WorkspaceBuildScripts,
+        sysroot: Option<Sysroot>,
         rustc: Option<CargoWorkspace>,
         /// Holds cfg flags for the current target. We get those by running
         /// `rustc --print cfg`.
@@ -46,6 +73,7 @@ pub enum ProjectWorkspace {
         /// FIXME: make this a per-crate map, as, eg, build.rs might have a
         /// different target.
         rustc_cfg: Vec<CfgFlag>,
+        cfg_overrides: CfgOverrides,
     },
     /// Project workspace was manually specified using a `rust-project.json` file.
     Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
@@ -67,16 +95,24 @@ impl fmt::Debug for ProjectWorkspace {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         // Make sure this isn't too verbose.
         match self {
-            ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f
+            ProjectWorkspace::Cargo {
+                cargo,
+                build_scripts: _,
+                sysroot,
+                rustc,
+                rustc_cfg,
+                cfg_overrides,
+            } => f
                 .debug_struct("Cargo")
                 .field("root", &cargo.workspace_root().file_name())
                 .field("n_packages", &cargo.packages().len())
-                .field("n_sysroot_crates", &sysroot.crates().len())
+                .field("sysroot", &sysroot.is_some())
                 .field(
                     "n_rustc_compiler_crates",
                     &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
                 )
                 .field("n_rustc_cfg", &rustc_cfg.len())
+                .field("n_cfg_overrides", &cfg_overrides.len())
                 .finish(),
             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
                 let mut debug_struct = f.debug_struct("Json");
@@ -111,7 +147,7 @@ pub fn load(
                 let data = serde_json::from_str(&file).with_context(|| {
                     format!("Failed to deserialize json file {}", project_json.display())
                 })?;
-                let project_location = project_json.parent().unwrap().to_path_buf();
+                let project_location = project_json.parent().to_path_buf();
                 let project_json = ProjectJson::new(&project_location, data);
                 ProjectWorkspace::load_inline(project_json, config.target.as_deref())?
             }
@@ -122,7 +158,7 @@ pub fn load(
                     cmd
                 })?;
 
-                let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, config, progress)
+                let meta = CargoWorkspace::fetch_metadata(&cargo_toml, config, progress)
                     .with_context(|| {
                         format!(
                             "Failed to read Cargo metadata from Cargo.toml file {}, {}",
@@ -130,41 +166,47 @@ pub fn load(
                             cargo_version
                         )
                     })?;
+                let cargo = CargoWorkspace::new(meta);
 
                 let sysroot = if config.no_sysroot {
-                    Sysroot::default()
+                    None
                 } else {
-                    Sysroot::discover(&cargo_toml).with_context(|| {
+                    Some(Sysroot::discover(cargo_toml.parent()).with_context(|| {
                         format!(
                             "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
                             cargo_toml.display()
                         )
-                    })?
+                    })?)
                 };
 
-                let rustc_dir = if let Some(rustc_source) = &config.rustc_source {
-                    use cargo_workspace::RustcSource;
-                    match rustc_source {
-                        RustcSource::Path(path) => Some(path.clone()),
-                        RustcSource::Discover => Sysroot::discover_rustc(&cargo_toml),
-                    }
-                } else {
-                    None
+                let rustc_dir = match &config.rustc_source {
+                    Some(RustcSource::Path(path)) => ManifestPath::try_from(path.clone()).ok(),
+                    Some(RustcSource::Discover) => Sysroot::discover_rustc(&cargo_toml),
+                    None => None,
                 };
 
-                let rustc = if let Some(rustc_dir) = rustc_dir {
-                    Some(
-                        CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress)
+                let rustc = match rustc_dir {
+                    Some(rustc_dir) => Some({
+                        let meta = CargoWorkspace::fetch_metadata(&rustc_dir, config, progress)
                             .with_context(|| {
                                 format!("Failed to read Cargo metadata for Rust sources")
-                            })?,
-                    )
-                } else {
-                    None
+                            })?;
+                        CargoWorkspace::new(meta)
+                    }),
+                    None => None,
                 };
 
                 let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref());
-                ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg }
+
+                let cfg_overrides = config.cfg_overrides();
+                ProjectWorkspace::Cargo {
+                    cargo,
+                    build_scripts: WorkspaceBuildScripts::default(),
+                    sysroot,
+                    rustc,
+                    rustc_cfg,
+                    cfg_overrides,
+                }
             }
         };
 
@@ -176,7 +218,7 @@ pub fn load_inline(
         target: Option<&str>,
     ) -> Result<ProjectWorkspace> {
         let sysroot = match &project_json.sysroot_src {
-            Some(path) => Some(Sysroot::load(path)?),
+            Some(path) => Some(Sysroot::load(path.clone())?),
             None => None,
         };
         let rustc_cfg = rustc_cfg::get(None, target);
@@ -185,21 +227,48 @@ pub fn load_inline(
 
     pub fn load_detached_files(detached_files: Vec<AbsPathBuf>) -> Result<ProjectWorkspace> {
         let sysroot = Sysroot::discover(
-            &detached_files.first().ok_or_else(|| format_err!("No detached files to load"))?,
+            detached_files
+                .first()
+                .and_then(|it| it.parent())
+                .ok_or_else(|| format_err!("No detached files to load"))?,
         )?;
         let rustc_cfg = rustc_cfg::get(None, None);
         Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
     }
 
+    pub fn run_build_scripts(
+        &self,
+        config: &CargoConfig,
+        progress: &dyn Fn(String),
+    ) -> Result<WorkspaceBuildScripts> {
+        match self {
+            ProjectWorkspace::Cargo { cargo, .. } => {
+                WorkspaceBuildScripts::run(config, cargo, progress)
+            }
+            ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
+                Ok(WorkspaceBuildScripts::default())
+            }
+        }
+    }
+
+    pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
+        match self {
+            ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,
+            _ => {
+                always!(bs == WorkspaceBuildScripts::default());
+            }
+        }
+    }
+
     /// Returns the roots for the current `ProjectWorkspace`
     /// The return type contains the path and whether or not
     /// the root is a member of the current workspace
-    pub fn to_roots(&self, build_data: Option<&BuildDataResult>) -> Vec<PackageRoot> {
+    pub fn to_roots(&self) -> Vec<PackageRoot> {
         match self {
             ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
                 .crates()
                 .map(|(_, krate)| PackageRoot {
-                    is_member: krate.is_workspace_member,
+                    is_local: krate.is_workspace_member,
                     include: krate.include.clone(),
                     exclude: krate.exclude.clone(),
                 })
@@ -207,59 +276,82 @@ pub fn to_roots(&self, build_data: Option<&BuildDataResult>) -> Vec<PackageRoot>
                 .into_iter()
                 .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
                     sysroot.crates().map(move |krate| PackageRoot {
-                        is_member: false,
-                        include: vec![sysroot[krate].root_dir().to_path_buf()],
+                        is_local: false,
+                        include: vec![sysroot[krate].root.parent().to_path_buf()],
                         exclude: Vec::new(),
                     })
                 }))
                 .collect::<Vec<_>>(),
-            ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg: _ } => cargo
-                .packages()
-                .map(|pkg| {
-                    let is_member = cargo[pkg].is_member;
-                    let pkg_root = cargo[pkg].root().to_path_buf();
-
-                    let mut include = vec![pkg_root.clone()];
-                    include.extend(
-                        build_data
-                            .and_then(|it| it.get(cargo.workspace_root()))
-                            .and_then(|map| map.get(&cargo[pkg].id))
-                            .and_then(|it| it.out_dir.clone()),
-                    );
+            ProjectWorkspace::Cargo {
+                cargo,
+                sysroot,
+                rustc,
+                rustc_cfg: _,
+                cfg_overrides: _,
+                build_scripts,
+            } => {
+                cargo
+                    .packages()
+                    .map(|pkg| {
+                        let is_local = cargo[pkg].is_local;
+                        let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
+
+                        let mut include = vec![pkg_root.clone()];
+                        include.extend(
+                            build_scripts.outputs.get(pkg).and_then(|it| it.out_dir.clone()),
+                        );
 
-                    let mut exclude = vec![pkg_root.join(".git")];
-                    if is_member {
-                        exclude.push(pkg_root.join("target"));
-                    } else {
-                        exclude.push(pkg_root.join("tests"));
-                        exclude.push(pkg_root.join("examples"));
-                        exclude.push(pkg_root.join("benches"));
-                    }
-                    PackageRoot { is_member, include, exclude }
-                })
-                .chain(sysroot.crates().map(|krate| PackageRoot {
-                    is_member: false,
-                    include: vec![sysroot[krate].root_dir().to_path_buf()],
-                    exclude: Vec::new(),
-                }))
-                .chain(rustc.into_iter().flat_map(|rustc| {
-                    rustc.packages().map(move |krate| PackageRoot {
-                        is_member: false,
-                        include: vec![rustc[krate].root().to_path_buf()],
-                        exclude: Vec::new(),
+                        // In case target's path is manually set in Cargo.toml to be
+                        // outside the package root, add its parent as an extra include.
+                        // An example of this situation would look like this:
+                        //
+                        // ```toml
+                        // [lib]
+                        // path = "../../src/lib.rs"
+                        // ```
+                        let extra_targets = cargo[pkg]
+                            .targets
+                            .iter()
+                            .filter(|&&tgt| cargo[tgt].kind == TargetKind::Lib)
+                            .filter_map(|&tgt| cargo[tgt].root.parent())
+                            .map(|tgt| tgt.normalize().to_path_buf())
+                            .filter(|path| !path.starts_with(&pkg_root));
+                        include.extend(extra_targets);
+
+                        let mut exclude = vec![pkg_root.join(".git")];
+                        if is_local {
+                            exclude.push(pkg_root.join("target"));
+                        } else {
+                            exclude.push(pkg_root.join("tests"));
+                            exclude.push(pkg_root.join("examples"));
+                            exclude.push(pkg_root.join("benches"));
+                        }
+                        PackageRoot { is_local, include, exclude }
                     })
-                }))
-                .collect(),
+                    .chain(sysroot.into_iter().map(|sysroot| PackageRoot {
+                        is_local: false,
+                        include: vec![sysroot.root().to_path_buf()],
+                        exclude: Vec::new(),
+                    }))
+                    .chain(rustc.into_iter().flat_map(|rustc| {
+                        rustc.packages().map(move |krate| PackageRoot {
+                            is_local: false,
+                            include: vec![rustc[krate].manifest.parent().to_path_buf()],
+                            exclude: Vec::new(),
+                        })
+                    }))
+                    .collect()
+            }
             ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
                 .into_iter()
                 .map(|detached_file| PackageRoot {
-                    is_member: true,
+                    is_local: true,
                     include: vec![detached_file.clone()],
                     exclude: Vec::new(),
                 })
                 .chain(sysroot.crates().map(|krate| PackageRoot {
-                    is_member: false,
-                    include: vec![sysroot[krate].root_dir().to_path_buf()],
+                    is_local: false,
+                    include: vec![sysroot[krate].root.parent().to_path_buf()],
                     exclude: Vec::new(),
                 }))
                 .collect(),
@@ -270,8 +362,9 @@ pub fn n_packages(&self) -> usize {
         match self {
             ProjectWorkspace::Json { project, .. } => project.n_crates(),
             ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
-                let rustc_package_len = rustc.as_ref().map_or(0, |rc| rc.packages().len());
-                cargo.packages().len() + sysroot.crates().len() + rustc_package_len
+                let rustc_package_len = rustc.as_ref().map_or(0, |it| it.packages().len());
+                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
+                cargo.packages().len() + sysroot_package_len + rustc_package_len
             }
             ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
                 sysroot.crates().len() + files.len()
@@ -281,59 +374,52 @@ pub fn n_packages(&self) -> usize {
 
     pub fn to_crate_graph(
         &self,
-        build_data: Option<&BuildDataResult>,
-        proc_macro_client: Option<&ProcMacroClient>,
+        load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     ) -> CrateGraph {
         let _p = profile::span("ProjectWorkspace::to_crate_graph");
-        let proc_macro_loader = |path: &Path| match proc_macro_client {
-            Some(client) => client.by_dylib_path(path),
-            None => Vec::new(),
-        };
 
         let mut crate_graph = match self {
             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
                 rustc_cfg.clone(),
-                &proc_macro_loader,
+                load_proc_macro,
                 load,
                 project,
                 sysroot,
             ),
-            ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => cargo_to_crate_graph(
+            ProjectWorkspace::Cargo {
+                cargo,
+                sysroot,
+                rustc,
+                rustc_cfg,
+                cfg_overrides,
+                build_scripts,
+            } => cargo_to_crate_graph(
                 rustc_cfg.clone(),
-                &proc_macro_loader,
+                cfg_overrides,
+                load_proc_macro,
                 load,
                 cargo,
-                build_data.and_then(|it| it.get(cargo.workspace_root())),
-                sysroot,
+                build_scripts,
+                sysroot.as_ref(),
                 rustc,
-                rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())),
             ),
             ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
                 detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
             }
         };
         if crate_graph.patch_cfg_if() {
-            log::debug!("Patched std to depend on cfg-if")
+            tracing::debug!("Patched std to depend on cfg-if")
         } else {
-            log::debug!("Did not patch std to depend on cfg-if")
+            tracing::debug!("Did not patch std to depend on cfg-if")
         }
         crate_graph
     }
-
-    pub fn collect_build_data_configs(&self, collector: &mut BuildDataCollector) {
-        match self {
-            ProjectWorkspace::Cargo { cargo, .. } => {
-                collector.add_config(&cargo.workspace_root(), cargo.build_data_config().clone());
-            }
-            _ => {}
-        }
-    }
 }
 
 fn project_json_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
-    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
+    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     project: &ProjectJson,
     sysroot: &Option<Sysroot>,
@@ -348,12 +434,12 @@ fn project_json_to_crate_graph(
         .crates()
         .filter_map(|(crate_id, krate)| {
             let file_path = &krate.root_module;
-            let file_id = load(&file_path)?;
+            let file_id = load(file_path)?;
             Some((crate_id, krate, file_id))
         })
         .map(|(crate_id, krate, file_id)| {
             let env = krate.env.clone().into_iter().collect();
-            let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| proc_macro_loader(&it));
+            let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| load_proc_macro(&it));
 
             let target_cfgs = match krate.target.as_deref() {
                 Some(target) => {
@@ -370,6 +456,7 @@ fn project_json_to_crate_graph(
                     file_id,
                     krate.edition,
                     krate.display_name.clone(),
+                    cfg_options.clone(),
                     cfg_options,
                     env,
                     proc_macro.unwrap_or_default(),
@@ -380,10 +467,20 @@ fn project_json_to_crate_graph(
 
     for (from, krate) in project.crates() {
         if let Some(&from) = crates.get(&from) {
-            if let Some((public_deps, _proc_macro)) = &sysroot_deps {
+            if let Some((public_deps, libproc_macro)) = &sysroot_deps {
                 for (name, to) in public_deps.iter() {
                     add_dep(&mut crate_graph, from, name.clone(), *to)
                 }
+                if krate.is_proc_macro {
+                    if let Some(proc_macro) = libproc_macro {
+                        add_dep(
+                            &mut crate_graph,
+                            from,
+                            CrateName::new("proc_macro").unwrap(),
+                            *proc_macro,
+                        );
+                    }
+                }
             }
 
             for dep in &krate.deps {
@@ -398,18 +495,20 @@ fn project_json_to_crate_graph(
 
 fn cargo_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
-    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
+    override_cfg: &CfgOverrides,
+    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     cargo: &CargoWorkspace,
-    build_data_map: Option<&WorkspaceBuildData>,
-    sysroot: &Sysroot,
+    build_scripts: &WorkspaceBuildScripts,
+    sysroot: Option<&Sysroot>,
     rustc: &Option<CargoWorkspace>,
-    rustc_build_data_map: Option<&WorkspaceBuildData>,
 ) -> CrateGraph {
     let _p = profile::span("cargo_to_crate_graph");
     let mut crate_graph = CrateGraph::default();
-    let (public_deps, libproc_macro) =
-        sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
+    let (public_deps, libproc_macro) = match sysroot {
+        Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
+        None => (Vec::new(), None),
+    };
 
     let mut cfg_options = CfgOptions::default();
     cfg_options.extend(rustc_cfg);
@@ -425,6 +524,27 @@ fn cargo_to_crate_graph(
     let mut has_private = false;
     // Next, create crates for each package, target pair
     for pkg in cargo.packages() {
+        let mut cfg_options = &cfg_options;
+        let mut replaced_cfg_options;
+
+        let overrides = match override_cfg {
+            CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
+            CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
+        };
+
+        if let Some(overrides) = overrides {
+            // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
+            // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
+            // working on rust-lang/rust as that's the only time it appears outside sysroot).
+            //
+            // A more ideal solution might be to reanalyze crates based on where the cursor is and
+            // figure out the set of cfgs that would have to apply to make it active.
+
+            replaced_cfg_options = cfg_options.clone();
+            replaced_cfg_options.apply_diff(overrides.clone());
+            cfg_options = &replaced_cfg_options;
+        };
+
         has_private |= cargo[pkg].metadata.rustc_private;
         let mut lib_tgt = None;
         for &tgt in cargo[pkg].targets.iter() {
@@ -432,9 +552,9 @@ fn cargo_to_crate_graph(
                 let crate_id = add_target_crate_root(
                     &mut crate_graph,
                     &cargo[pkg],
-                    build_data_map.and_then(|it| it.get(&cargo[pkg].id)),
+                    build_scripts.outputs.get(pkg),
                     &cfg_options,
-                    proc_macro_loader,
+                    load_proc_macro,
                     file_id,
                     &cargo[tgt].name,
                 );
@@ -442,15 +562,13 @@ fn cargo_to_crate_graph(
                     lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
                     pkg_to_lib_crate.insert(pkg, crate_id);
                 }
-                if cargo[tgt].is_proc_macro {
-                    if let Some(proc_macro) = libproc_macro {
-                        add_dep(
-                            &mut crate_graph,
-                            crate_id,
-                            CrateName::new("proc_macro").unwrap(),
-                            proc_macro,
-                        );
-                    }
+                if let Some(proc_macro) = libproc_macro {
+                    add_dep(
+                        &mut crate_graph,
+                        crate_id,
+                        CrateName::new("proc_macro").unwrap(),
+                        proc_macro,
+                    );
                 }
 
                 pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
@@ -506,9 +624,8 @@ fn cargo_to_crate_graph(
                 rustc_workspace,
                 load,
                 &mut crate_graph,
-                rustc_build_data_map,
                 &cfg_options,
-                proc_macro_loader,
+                load_proc_macro,
                 &mut pkg_to_lib_crate,
                 &public_deps,
                 cargo,
@@ -534,10 +651,10 @@ fn detached_files_to_crate_graph(
     cfg_options.extend(rustc_cfg);
 
     for detached_file in detached_files {
-        let file_id = match load(&detached_file) {
+        let file_id = match load(detached_file) {
             Some(file_id) => file_id,
             None => {
-                log::error!("Failed to load detached file {:?}", detached_file);
+                tracing::error!("Failed to load detached file {:?}", detached_file);
                 continue;
             }
         };
@@ -547,9 +664,10 @@ fn detached_files_to_crate_graph(
             .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
         let detached_file_crate = crate_graph.add_crate_root(
             file_id,
-            Edition::Edition2018,
+            Edition::CURRENT,
             display_name,
             cfg_options.clone(),
+            cfg_options.clone(),
             Env::default(),
             Vec::new(),
         );
@@ -565,9 +683,8 @@ fn handle_rustc_crates(
     rustc_workspace: &CargoWorkspace,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     crate_graph: &mut CrateGraph,
-    rustc_build_data_map: Option<&WorkspaceBuildData>,
     cfg_options: &CfgOptions,
-    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
+    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
     pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
     public_deps: &[(CrateName, CrateId)],
     cargo: &CargoWorkspace,
@@ -601,9 +718,9 @@ fn handle_rustc_crates(
                     let crate_id = add_target_crate_root(
                         crate_graph,
                         &rustc_workspace[pkg],
-                        rustc_build_data_map.and_then(|it| it.get(&rustc_workspace[pkg].id)),
-                        &cfg_options,
-                        proc_macro_loader,
+                        None,
+                        cfg_options,
+                        load_proc_macro,
                         file_id,
                         &rustc_workspace[tgt].name,
                     );
@@ -656,10 +773,10 @@ fn handle_rustc_crates(
 
 fn add_target_crate_root(
     crate_graph: &mut CrateGraph,
-    pkg: &cargo_workspace::PackageData,
-    build_data: Option<&PackageBuildData>,
+    pkg: &PackageData,
+    build_data: Option<&BuildScriptOutput>,
     cfg_options: &CfgOptions,
-    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
+    load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
     file_id: FileId,
     cargo_name: &str,
 ) -> CrateId {
@@ -676,6 +793,8 @@ fn add_target_crate_root(
     };
 
     let mut env = Env::default();
+    inject_cargo_env(pkg, &mut env);
+
     if let Some(envs) = build_data.map(|it| &it.envs) {
         for (k, v) in envs {
             env.set(k, v.clone());
@@ -685,15 +804,23 @@ fn add_target_crate_root(
     let proc_macro = build_data
         .as_ref()
         .and_then(|it| it.proc_macro_dylib_path.as_ref())
-        .map(|it| proc_macro_loader(&it))
+        .map(|it| load_proc_macro(it))
         .unwrap_or_default();
 
     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
+    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() }),
+    );
+
     let crate_id = crate_graph.add_crate_root(
         file_id,
         edition,
         Some(display_name),
         cfg_options,
+        potential_cfg_options,
         env,
         proc_macro,
     );
@@ -720,9 +847,10 @@ fn sysroot_to_crate_graph(
             let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
             let crate_id = crate_graph.add_crate_root(
                 file_id,
-                Edition::Edition2018,
+                Edition::CURRENT,
                 Some(display_name),
                 cfg_options.clone(),
+                cfg_options.clone(),
                 env,
                 proc_macro,
             );
@@ -750,6 +878,42 @@ fn sysroot_to_crate_graph(
 
 fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
     if let Err(err) = graph.add_dep(from, name, to) {
-        log::error!("{}", err)
+        tracing::error!("{}", err)
     }
 }
+
+/// Recreates the compile-time environment variables that Cargo sets.
+///
+/// Should be synced with
+/// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
+///
+/// FIXME: ask Cargo to provide this data instead of re-deriving.
+fn inject_cargo_env(package: &PackageData, env: &mut Env) {
+    // FIXME: Missing variables:
+    // CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
+
+    let manifest_dir = package.manifest.parent();
+    env.set("CARGO_MANIFEST_DIR".into(), manifest_dir.as_os_str().to_string_lossy().into_owned());
+
+    // Not always right, but works for common cases.
+    env.set("CARGO".into(), "cargo".into());
+
+    env.set("CARGO_PKG_VERSION".into(), package.version.to_string());
+    env.set("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string());
+    env.set("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string());
+    env.set("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string());
+    env.set("CARGO_PKG_VERSION_PRE".into(), package.version.pre.to_string());
+
+    env.set("CARGO_PKG_AUTHORS".into(), String::new());
+
+    env.set("CARGO_PKG_NAME".into(), package.name.clone());
+    // FIXME: This isn't really correct (a package can have many crates with different names), but
+    // it's better than leaving the variable unset.
+    env.set("CARGO_CRATE_NAME".into(), CrateName::normalize_dashes(&package.name).to_string());
+    env.set("CARGO_PKG_DESCRIPTION".into(), String::new());
+    env.set("CARGO_PKG_HOMEPAGE".into(), String::new());
+    env.set("CARGO_PKG_REPOSITORY".into(), String::new());
+    env.set("CARGO_PKG_LICENSE".into(), String::new());
+
+    env.set("CARGO_PKG_LICENSE_FILE".into(), String::new());
+}