diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-09 10:21:13 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-09 10:21:13 +0000 |
commit | d0a32627a741826502692f2c3de71512b7ec23cf (patch) | |
tree | ee3cef56bd9738f5c3b98caae9b8ce1b8dbf401f /crates/ra_lsp_server/src | |
parent | 34398a8756b56c323d3b4b2ef32fbca32d88a105 (diff) | |
parent | e91a46eb0c4a355af25656d77dead55c2e29258e (diff) |
Merge #767
767: Extract project model to separate crate r=matklad a=flodiebold
I'm looking into creating a separate crate that would allow getting a HIR db for a project for 'batch' analyses, and this seems to be an obvious first step. We'd probably want to change the error handling to not rely on failure, though, right?
Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_lsp_server/src')
-rw-r--r-- | crates/ra_lsp_server/src/project_model.rs | 42 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/project_model/cargo_workspace.rs | 171 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/project_model/sysroot.rs | 137 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/server_world.rs | 89 |
4 files changed, 10 insertions, 429 deletions
diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs index fd5875a0a..6800eb138 100644 --- a/crates/ra_lsp_server/src/project_model.rs +++ b/crates/ra_lsp_server/src/project_model.rs | |||
@@ -1,34 +1,13 @@ | |||
1 | mod cargo_workspace; | 1 | use std::path::PathBuf; |
2 | mod sysroot; | ||
3 | 2 | ||
4 | use std::path::{Path, PathBuf}; | ||
5 | |||
6 | use failure::bail; | ||
7 | use thread_worker::{WorkerHandle, Worker}; | 3 | use thread_worker::{WorkerHandle, Worker}; |
8 | 4 | ||
9 | use crate::Result; | 5 | use crate::Result; |
10 | 6 | ||
11 | pub use crate::project_model::{ | 7 | pub use ra_project_model::{ |
12 | cargo_workspace::{CargoWorkspace, Package, Target, TargetKind}, | 8 | ProjectWorkspace, CargoWorkspace, Package, Target, TargetKind, Sysroot, |
13 | sysroot::Sysroot, | ||
14 | }; | 9 | }; |
15 | 10 | ||
16 | #[derive(Debug, Clone)] | ||
17 | pub struct ProjectWorkspace { | ||
18 | pub(crate) cargo: CargoWorkspace, | ||
19 | pub(crate) sysroot: Sysroot, | ||
20 | } | ||
21 | |||
22 | impl ProjectWorkspace { | ||
23 | pub fn discover(path: &Path) -> Result<ProjectWorkspace> { | ||
24 | let cargo_toml = find_cargo_toml(path)?; | ||
25 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?; | ||
26 | let sysroot = Sysroot::discover(&cargo_toml)?; | ||
27 | let res = ProjectWorkspace { cargo, sysroot }; | ||
28 | Ok(res) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | pub fn workspace_loader() -> (Worker<PathBuf, Result<ProjectWorkspace>>, WorkerHandle) { | 11 | pub fn workspace_loader() -> (Worker<PathBuf, Result<ProjectWorkspace>>, WorkerHandle) { |
33 | thread_worker::spawn::<PathBuf, Result<ProjectWorkspace>, _>( | 12 | thread_worker::spawn::<PathBuf, Result<ProjectWorkspace>, _>( |
34 | "workspace loader", | 13 | "workspace loader", |
@@ -42,18 +21,3 @@ pub fn workspace_loader() -> (Worker<PathBuf, Result<ProjectWorkspace>>, WorkerH | |||
42 | }, | 21 | }, |
43 | ) | 22 | ) |
44 | } | 23 | } |
45 | |||
46 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | ||
47 | if path.ends_with("Cargo.toml") { | ||
48 | return Ok(path.to_path_buf()); | ||
49 | } | ||
50 | let mut curr = Some(path); | ||
51 | while let Some(path) = curr { | ||
52 | let candidate = path.join("Cargo.toml"); | ||
53 | if candidate.exists() { | ||
54 | return Ok(candidate); | ||
55 | } | ||
56 | curr = path.parent(); | ||
57 | } | ||
58 | bail!("can't find Cargo.toml at {}", path.display()) | ||
59 | } | ||
diff --git a/crates/ra_lsp_server/src/project_model/cargo_workspace.rs b/crates/ra_lsp_server/src/project_model/cargo_workspace.rs deleted file mode 100644 index 3b76389d2..000000000 --- a/crates/ra_lsp_server/src/project_model/cargo_workspace.rs +++ /dev/null | |||
@@ -1,171 +0,0 @@ | |||
1 | use std::path::{Path, PathBuf}; | ||
2 | |||
3 | use cargo_metadata::{MetadataCommand, CargoOpt}; | ||
4 | use ra_syntax::SmolStr; | ||
5 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | use failure::format_err; | ||
8 | |||
9 | use crate::Result; | ||
10 | |||
11 | /// `CargoWorksapce` represents the logical structure of, well, a Cargo | ||
12 | /// workspace. It pretty closely mirrors `cargo metadata` output. | ||
13 | /// | ||
14 | /// Note that internally, rust analyzer uses a differnet structure: | ||
15 | /// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, | ||
16 | /// while this knows about `Pacakges` & `Targets`: purely cargo-related | ||
17 | /// concepts. | ||
18 | #[derive(Debug, Clone)] | ||
19 | pub struct CargoWorkspace { | ||
20 | packages: Arena<Package, PackageData>, | ||
21 | targets: Arena<Target, TargetData>, | ||
22 | } | ||
23 | |||
24 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
25 | pub struct Package(RawId); | ||
26 | impl_arena_id!(Package); | ||
27 | |||
28 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
29 | pub struct Target(RawId); | ||
30 | impl_arena_id!(Target); | ||
31 | |||
32 | #[derive(Debug, Clone)] | ||
33 | struct PackageData { | ||
34 | name: SmolStr, | ||
35 | manifest: PathBuf, | ||
36 | targets: Vec<Target>, | ||
37 | is_member: bool, | ||
38 | dependencies: Vec<PackageDependency>, | ||
39 | } | ||
40 | |||
41 | #[derive(Debug, Clone)] | ||
42 | pub struct PackageDependency { | ||
43 | pub pkg: Package, | ||
44 | pub name: SmolStr, | ||
45 | } | ||
46 | |||
47 | #[derive(Debug, Clone)] | ||
48 | struct TargetData { | ||
49 | pkg: Package, | ||
50 | name: SmolStr, | ||
51 | root: PathBuf, | ||
52 | kind: TargetKind, | ||
53 | } | ||
54 | |||
55 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
56 | pub enum TargetKind { | ||
57 | Bin, | ||
58 | Lib, | ||
59 | Example, | ||
60 | Test, | ||
61 | Bench, | ||
62 | Other, | ||
63 | } | ||
64 | |||
65 | impl TargetKind { | ||
66 | fn new(kinds: &[String]) -> TargetKind { | ||
67 | for kind in kinds { | ||
68 | return match kind.as_str() { | ||
69 | "bin" => TargetKind::Bin, | ||
70 | "test" => TargetKind::Test, | ||
71 | "bench" => TargetKind::Bench, | ||
72 | "example" => TargetKind::Example, | ||
73 | _ if kind.contains("lib") => TargetKind::Lib, | ||
74 | _ => continue, | ||
75 | }; | ||
76 | } | ||
77 | TargetKind::Other | ||
78 | } | ||
79 | } | ||
80 | |||
81 | impl Package { | ||
82 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
83 | ws.packages[self].name.as_str() | ||
84 | } | ||
85 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
86 | ws.packages[self].manifest.parent().unwrap() | ||
87 | } | ||
88 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { | ||
89 | ws.packages[self].targets.iter().cloned() | ||
90 | } | ||
91 | #[allow(unused)] | ||
92 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { | ||
93 | ws.packages[self].is_member | ||
94 | } | ||
95 | pub fn dependencies<'a>( | ||
96 | self, | ||
97 | ws: &'a CargoWorkspace, | ||
98 | ) -> impl Iterator<Item = &'a PackageDependency> + 'a { | ||
99 | ws.packages[self].dependencies.iter() | ||
100 | } | ||
101 | } | ||
102 | |||
103 | impl Target { | ||
104 | pub fn package(self, ws: &CargoWorkspace) -> Package { | ||
105 | ws.targets[self].pkg | ||
106 | } | ||
107 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
108 | ws.targets[self].name.as_str() | ||
109 | } | ||
110 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
111 | ws.targets[self].root.as_path() | ||
112 | } | ||
113 | pub fn kind(self, ws: &CargoWorkspace) -> TargetKind { | ||
114 | ws.targets[self].kind | ||
115 | } | ||
116 | } | ||
117 | |||
118 | impl CargoWorkspace { | ||
119 | pub fn from_cargo_metadata(cargo_toml: &Path) -> Result<CargoWorkspace> { | ||
120 | let mut meta = MetadataCommand::new(); | ||
121 | meta.manifest_path(cargo_toml).features(CargoOpt::AllFeatures); | ||
122 | if let Some(parent) = cargo_toml.parent() { | ||
123 | meta.current_dir(parent); | ||
124 | } | ||
125 | let meta = meta.exec().map_err(|e| format_err!("cargo metadata failed: {}", e))?; | ||
126 | let mut pkg_by_id = FxHashMap::default(); | ||
127 | let mut packages = Arena::default(); | ||
128 | let mut targets = Arena::default(); | ||
129 | |||
130 | let ws_members = &meta.workspace_members; | ||
131 | |||
132 | for meta_pkg in meta.packages { | ||
133 | let is_member = ws_members.contains(&meta_pkg.id); | ||
134 | let pkg = packages.alloc(PackageData { | ||
135 | name: meta_pkg.name.into(), | ||
136 | manifest: meta_pkg.manifest_path.clone(), | ||
137 | targets: Vec::new(), | ||
138 | is_member, | ||
139 | dependencies: Vec::new(), | ||
140 | }); | ||
141 | let pkg_data = &mut packages[pkg]; | ||
142 | pkg_by_id.insert(meta_pkg.id.clone(), pkg); | ||
143 | for meta_tgt in meta_pkg.targets { | ||
144 | let tgt = targets.alloc(TargetData { | ||
145 | pkg, | ||
146 | name: meta_tgt.name.into(), | ||
147 | root: meta_tgt.src_path.clone(), | ||
148 | kind: TargetKind::new(meta_tgt.kind.as_slice()), | ||
149 | }); | ||
150 | pkg_data.targets.push(tgt); | ||
151 | } | ||
152 | } | ||
153 | let resolve = meta.resolve.expect("metadata executed with deps"); | ||
154 | for node in resolve.nodes { | ||
155 | let source = pkg_by_id[&node.id]; | ||
156 | for dep_node in node.deps { | ||
157 | let dep = | ||
158 | PackageDependency { name: dep_node.name.into(), pkg: pkg_by_id[&dep_node.pkg] }; | ||
159 | packages[source].dependencies.push(dep); | ||
160 | } | ||
161 | } | ||
162 | |||
163 | Ok(CargoWorkspace { packages, targets }) | ||
164 | } | ||
165 | pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { | ||
166 | self.packages.iter().map(|(id, _pkg)| id) | ||
167 | } | ||
168 | pub fn target_by_root(&self, root: &Path) -> Option<Target> { | ||
169 | self.packages().filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)).next() | ||
170 | } | ||
171 | } | ||
diff --git a/crates/ra_lsp_server/src/project_model/sysroot.rs b/crates/ra_lsp_server/src/project_model/sysroot.rs deleted file mode 100644 index 49210ac7a..000000000 --- a/crates/ra_lsp_server/src/project_model/sysroot.rs +++ /dev/null | |||
@@ -1,137 +0,0 @@ | |||
1 | use std::{ | ||
2 | path::{Path, PathBuf}, | ||
3 | process::Command, | ||
4 | }; | ||
5 | |||
6 | use ra_syntax::SmolStr; | ||
7 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
8 | |||
9 | use crate::Result; | ||
10 | |||
11 | #[derive(Debug, Clone)] | ||
12 | pub struct Sysroot { | ||
13 | crates: Arena<SysrootCrate, SysrootCrateData>, | ||
14 | } | ||
15 | |||
16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
17 | pub struct SysrootCrate(RawId); | ||
18 | impl_arena_id!(SysrootCrate); | ||
19 | |||
20 | #[derive(Debug, Clone)] | ||
21 | struct SysrootCrateData { | ||
22 | name: SmolStr, | ||
23 | root: PathBuf, | ||
24 | deps: Vec<SysrootCrate>, | ||
25 | } | ||
26 | |||
27 | impl Sysroot { | ||
28 | pub(crate) fn std(&self) -> Option<SysrootCrate> { | ||
29 | self.by_name("std") | ||
30 | } | ||
31 | |||
32 | pub(crate) fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + 'a { | ||
33 | self.crates.iter().map(|(id, _data)| id) | ||
34 | } | ||
35 | |||
36 | pub(super) fn discover(cargo_toml: &Path) -> Result<Sysroot> { | ||
37 | let rustc_output = Command::new("rustc") | ||
38 | .current_dir(cargo_toml.parent().unwrap()) | ||
39 | .args(&["--print", "sysroot"]) | ||
40 | .output()?; | ||
41 | if !rustc_output.status.success() { | ||
42 | failure::bail!("failed to locate sysroot") | ||
43 | } | ||
44 | let stdout = String::from_utf8(rustc_output.stdout)?; | ||
45 | let sysroot_path = Path::new(stdout.trim()); | ||
46 | let src = sysroot_path.join("lib/rustlib/src/rust/src"); | ||
47 | if !src.exists() { | ||
48 | failure::bail!( | ||
49 | "can't load standard library from sysroot\n\ | ||
50 | {:?}\n\ | ||
51 | try running `rustup component add rust-src`", | ||
52 | src, | ||
53 | ); | ||
54 | } | ||
55 | |||
56 | let mut sysroot = Sysroot { crates: Arena::default() }; | ||
57 | for name in SYSROOT_CRATES.trim().lines() { | ||
58 | let root = src.join(format!("lib{}", name)).join("lib.rs"); | ||
59 | if root.exists() { | ||
60 | sysroot.crates.alloc(SysrootCrateData { | ||
61 | name: name.into(), | ||
62 | root, | ||
63 | deps: Vec::new(), | ||
64 | }); | ||
65 | } | ||
66 | } | ||
67 | if let Some(std) = sysroot.std() { | ||
68 | for dep in STD_DEPS.trim().lines() { | ||
69 | if let Some(dep) = sysroot.by_name(dep) { | ||
70 | sysroot.crates[std].deps.push(dep) | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | Ok(sysroot) | ||
75 | } | ||
76 | |||
77 | fn by_name(&self, name: &str) -> Option<SysrootCrate> { | ||
78 | self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | impl SysrootCrate { | ||
83 | pub(crate) fn name(self, sysroot: &Sysroot) -> &SmolStr { | ||
84 | &sysroot.crates[self].name | ||
85 | } | ||
86 | pub(crate) fn root(self, sysroot: &Sysroot) -> &Path { | ||
87 | sysroot.crates[self].root.as_path() | ||
88 | } | ||
89 | pub(crate) fn root_dir(self, sysroot: &Sysroot) -> &Path { | ||
90 | self.root(sysroot).parent().unwrap() | ||
91 | } | ||
92 | pub(crate) fn deps<'a>(self, sysroot: &'a Sysroot) -> impl Iterator<Item = SysrootCrate> + 'a { | ||
93 | sysroot.crates[self].deps.iter().map(|&it| it) | ||
94 | } | ||
95 | } | ||
96 | |||
97 | const SYSROOT_CRATES: &str = " | ||
98 | std | ||
99 | core | ||
100 | alloc | ||
101 | collections | ||
102 | libc | ||
103 | panic_unwind | ||
104 | proc_macro | ||
105 | rustc_unicode | ||
106 | std_unicode | ||
107 | test | ||
108 | alloc_jemalloc | ||
109 | alloc_system | ||
110 | compiler_builtins | ||
111 | getopts | ||
112 | panic_unwind | ||
113 | panic_abort | ||
114 | rand | ||
115 | term | ||
116 | unwind | ||
117 | build_helper | ||
118 | rustc_asan | ||
119 | rustc_lsan | ||
120 | rustc_msan | ||
121 | rustc_tsan | ||
122 | syntax"; | ||
123 | |||
124 | const STD_DEPS: &str = " | ||
125 | alloc | ||
126 | alloc_jemalloc | ||
127 | alloc_system | ||
128 | core | ||
129 | panic_abort | ||
130 | rand | ||
131 | compiler_builtins | ||
132 | unwind | ||
133 | rustc_asan | ||
134 | rustc_lsan | ||
135 | rustc_msan | ||
136 | rustc_tsan | ||
137 | build_helper"; | ||
diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs index 02f2a37a8..f97d240fa 100644 --- a/crates/ra_lsp_server/src/server_world.rs +++ b/crates/ra_lsp_server/src/server_world.rs | |||
@@ -9,13 +9,12 @@ use ra_ide_api::{ | |||
9 | SourceRootId | 9 | SourceRootId |
10 | }; | 10 | }; |
11 | use ra_vfs::{Vfs, VfsChange, VfsFile, VfsRoot}; | 11 | use ra_vfs::{Vfs, VfsChange, VfsFile, VfsRoot}; |
12 | use rustc_hash::FxHashMap; | ||
13 | use relative_path::RelativePathBuf; | 12 | use relative_path::RelativePathBuf; |
14 | use parking_lot::RwLock; | 13 | use parking_lot::RwLock; |
15 | use failure::format_err; | 14 | use failure::format_err; |
16 | 15 | ||
17 | use crate::{ | 16 | use crate::{ |
18 | project_model::{ProjectWorkspace, TargetKind}, | 17 | project_model::ProjectWorkspace, |
19 | Result, | 18 | Result, |
20 | }; | 19 | }; |
21 | 20 | ||
@@ -57,88 +56,14 @@ impl ServerWorldState { | |||
57 | change.add_root(SourceRootId(r.0.into()), is_local); | 56 | change.add_root(SourceRootId(r.0.into()), is_local); |
58 | } | 57 | } |
59 | 58 | ||
59 | // Create crate graph from all the workspaces | ||
60 | let mut crate_graph = CrateGraph::default(); | 60 | let mut crate_graph = CrateGraph::default(); |
61 | let mut load = |path: &std::path::Path| { | ||
62 | let vfs_file = vfs.load(path); | ||
63 | vfs_file.map(|f| FileId(f.0.into())) | ||
64 | }; | ||
61 | for ws in workspaces.iter() { | 65 | for ws in workspaces.iter() { |
62 | // First, load std | 66 | crate_graph.extend(ws.to_crate_graph(&mut load)); |
63 | let mut sysroot_crates = FxHashMap::default(); | ||
64 | for krate in ws.sysroot.crates() { | ||
65 | if let Some(file_id) = vfs.load(krate.root(&ws.sysroot)) { | ||
66 | let file_id = FileId(file_id.0.into()); | ||
67 | sysroot_crates.insert(krate, crate_graph.add_crate_root(file_id)); | ||
68 | } | ||
69 | } | ||
70 | for from in ws.sysroot.crates() { | ||
71 | for to in from.deps(&ws.sysroot) { | ||
72 | let name = to.name(&ws.sysroot); | ||
73 | if let (Some(&from), Some(&to)) = | ||
74 | (sysroot_crates.get(&from), sysroot_crates.get(&to)) | ||
75 | { | ||
76 | if let Err(_) = crate_graph.add_dep(from, name.clone(), to) { | ||
77 | log::error!("cyclic dependency between sysroot crates") | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | |||
83 | let libstd = ws.sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); | ||
84 | |||
85 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
86 | let mut pkg_crates = FxHashMap::default(); | ||
87 | // Next, create crates for each package, target pair | ||
88 | for pkg in ws.cargo.packages() { | ||
89 | let mut lib_tgt = None; | ||
90 | for tgt in pkg.targets(&ws.cargo) { | ||
91 | let root = tgt.root(&ws.cargo); | ||
92 | if let Some(file_id) = vfs.load(root) { | ||
93 | let file_id = FileId(file_id.0.into()); | ||
94 | let crate_id = crate_graph.add_crate_root(file_id); | ||
95 | if tgt.kind(&ws.cargo) == TargetKind::Lib { | ||
96 | lib_tgt = Some(crate_id); | ||
97 | pkg_to_lib_crate.insert(pkg, crate_id); | ||
98 | } | ||
99 | pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); | ||
100 | } | ||
101 | } | ||
102 | |||
103 | // Set deps to the std and to the lib target of the current package | ||
104 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
105 | if let Some(to) = lib_tgt { | ||
106 | if to != from { | ||
107 | if let Err(_) = | ||
108 | crate_graph.add_dep(from, pkg.name(&ws.cargo).into(), to) | ||
109 | { | ||
110 | log::error!( | ||
111 | "cyclic dependency between targets of {}", | ||
112 | pkg.name(&ws.cargo) | ||
113 | ) | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | if let Some(std) = libstd { | ||
118 | if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { | ||
119 | log::error!("cyclic dependency on std for {}", pkg.name(&ws.cargo)) | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | |||
125 | // Now add a dep ednge from all targets of upstream to the lib | ||
126 | // target of downstream. | ||
127 | for pkg in ws.cargo.packages() { | ||
128 | for dep in pkg.dependencies(&ws.cargo) { | ||
129 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | ||
130 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
131 | if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) { | ||
132 | log::error!( | ||
133 | "cyclic dependency {} -> {}", | ||
134 | pkg.name(&ws.cargo), | ||
135 | dep.pkg.name(&ws.cargo) | ||
136 | ) | ||
137 | } | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | } | ||
142 | } | 67 | } |
143 | change.set_crate_graph(crate_graph); | 68 | change.set_crate_graph(crate_graph); |
144 | 69 | ||