Skip to content
This repository was archived by the owner on Dec 29, 2022. It is now read-only.

Commit 3de00a4

Browse files
authored
Merge pull request #409 from Xanewok/workspaces
Workspaces
2 parents d195862 + 5d35c95 commit 3de00a4

File tree

11 files changed

+274
-73
lines changed

11 files changed

+274
-73
lines changed

src/build/cargo.rs

Lines changed: 191 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
// except according to those terms.
1010

1111
use cargo::core::{PackageId, Shell, Workspace, Verbosity};
12-
use cargo::ops::{compile_with_exec, Executor, Context, CompileOptions, CompileMode, CompileFilter, Unit};
13-
use cargo::util::{Config as CargoConfig, ProcessBuilder, homedir, ConfigValue};
14-
use cargo::util::{CargoResult};
12+
use cargo::ops::{compile_with_exec, Executor, Context, Packages, CompileOptions, CompileMode, CompileFilter, Unit};
13+
use cargo::util::{Config as CargoConfig, ProcessBuilder, homedir, important_paths, ConfigValue, CargoResult};
1514

1615
use build::{Internals, BufWriter, BuildResult, CompilationContext};
1716
use config::Config;
17+
use super::rustc::convert_message_to_json_strings;
1818

19-
use std::collections::{HashMap, BTreeMap};
19+
use std::collections::{HashMap, HashSet, BTreeMap};
2020
use std::env;
2121
use std::ffi::OsString;
2222
use std::fs::{read_dir, remove_file};
@@ -25,11 +25,13 @@ use std::process::Command;
2525
use std::sync::{Arc, Mutex};
2626
use std::thread;
2727

28-
2928
impl Internals {
3029
// Runs an in-process instance of Cargo.
3130
pub fn cargo(&self) -> BuildResult {
32-
let exec = RlsExecutor::new(self.compilation_cx.clone(), self.config.clone());
31+
let workspace_mode = self.config.lock().unwrap().workspace_mode;
32+
let compiler_messages = Arc::new(Mutex::new(vec![]));
33+
34+
let exec = RlsExecutor::new(self.compilation_cx.clone(), self.config.clone(), compiler_messages.clone());
3335

3436
let out = Arc::new(Mutex::new(vec![]));
3537
let out_clone = out.clone();
@@ -46,6 +48,10 @@ impl Internals {
4648
let handle = thread::spawn(move || run_cargo(exec, rls_config, build_dir, out));
4749

4850
match handle.join() {
51+
Ok(_) if workspace_mode => {
52+
let diagnostics = Arc::try_unwrap(compiler_messages).unwrap().into_inner().unwrap();
53+
BuildResult::Success(diagnostics, None)
54+
},
4955
Ok(_) => BuildResult::Success(vec![], None),
5056
Err(_) => {
5157
info!("cargo stdout {}", String::from_utf8(out_clone.lock().unwrap().to_owned()).unwrap());
@@ -56,98 +62,132 @@ impl Internals {
5662
}
5763

5864
fn run_cargo(exec: RlsExecutor, rls_config: Arc<Mutex<Config>>, build_dir: PathBuf, out: Arc<Mutex<Vec<u8>>>) {
59-
let mut flags = "-Zunstable-options -Zsave-analysis --error-format=json \
60-
-Zcontinue-parse-after-error".to_owned();
65+
// Note that this may not be equal build_dir when inside a workspace member
66+
let manifest_path = important_paths::find_root_manifest_for_wd(None, &build_dir)
67+
.expect(&format!("Couldn't find a root manifest for cwd: {:?}", &build_dir));
68+
trace!("root manifest_path: {:?}", &manifest_path);
6169

6270
let mut shell = Shell::from_write(Box::new(BufWriter(out.clone())));
6371
shell.set_verbosity(Verbosity::Quiet);
64-
let mut manifest_path = build_dir;
65-
let config = make_cargo_config(&manifest_path, shell);
66-
manifest_path.push("Cargo.toml");
67-
trace!("manifest_path: {:?}", manifest_path);
68-
// TODO: Add support for virtual manifests and multiple packages
72+
73+
// Cargo constructs relative paths from the manifest dir, so we have to pop "Cargo.toml"
74+
let manifest_dir = manifest_path.parent().unwrap();
75+
let config = make_cargo_config(manifest_dir, shell);
76+
6977
let ws = Workspace::new(&manifest_path, &config).expect("could not create cargo workspace");
70-
let current_package = ws.current().unwrap();
71-
let targets = current_package.targets();
72-
let bins;
73-
let target_string;
7478

75-
let opts = {
79+
// TODO: It might be feasible to keep this CargoOptions structure cached and regenerate
80+
// it on every relevant configuration change
81+
let (opts, rustflags) = {
82+
// We mustn't lock configuration for the whole build process
7683
let rls_config = rls_config.lock().unwrap();
77-
if let Some(ref sysroot) = rls_config.sysroot {
78-
flags.push_str(&format!(" --sysroot {}", sysroot));
79-
}
80-
let rustflags = format!("{} {} {}",
81-
env::var("RUSTFLAGS").unwrap_or(String::new()),
82-
rls_config.rustflags.as_ref().unwrap_or(&String::new()),
83-
flags);
84-
let rustflags = dedup_flags(&rustflags);
85-
env::set_var("RUSTFLAGS", &rustflags);
86-
87-
bins = {
88-
if let Some(ref build_bin) = rls_config.build_bin {
89-
let mut bins = targets.iter().filter(|x| x.is_bin());
90-
let bin = bins.find(|x| x.name() == build_bin);
91-
match bin {
92-
Some(bin) => vec![bin.name().to_owned()],
93-
None => {
94-
debug!("cargo - couldn't find binary `{}` (specified in rls toml file)", build_bin);
95-
vec![]
96-
}
84+
85+
let opts = CargoOptions::new(&rls_config);
86+
trace!("Cargo compilation options:\n{:?}", opts);
87+
let rustflags = prepare_cargo_rustflags(&rls_config);
88+
89+
// Warn about invalid specified bin target or package depending on current mode
90+
// TODO: Return client notifications along with diagnostics to inform the user
91+
if !rls_config.workspace_mode {
92+
let cur_pkg_targets = ws.current().unwrap().targets();
93+
94+
if let Some(ref build_bin) = rls_config.build_bin {
95+
let mut bins = cur_pkg_targets.iter().filter(|x| x.is_bin());
96+
if let None = bins.find(|x| x.name() == build_bin) {
97+
warn!("cargo - couldn't find binary `{}` specified in `build_bin` configuration", build_bin);
98+
}
99+
}
100+
} else {
101+
for package in &opts.package {
102+
if let None = ws.members().find(|x| x.name() == package) {
103+
warn!("cargo - couldn't find member package `{}` specified in `analyze_package` configuration", package);
97104
}
98-
} else {
99-
vec![]
100105
}
101-
};
102-
103-
let mut opts = CompileOptions::default(&config, CompileMode::Check);
104-
if rls_config.build_lib {
105-
opts.filter = CompileFilter::new(true, &[], false, &[], false, &[], false, &[], false);
106-
} else if !bins.is_empty() {
107-
opts.filter = CompileFilter::new(false, &bins, false, &[], false, &[], false, &[], false);
108-
}
109-
if let Some(ref target) = rls_config.target {
110-
target_string = target.clone();
111-
opts.target = Some(&target_string);
112106
}
113-
opts
107+
108+
(opts, rustflags)
109+
};
110+
111+
let spec = Packages::from_flags(opts.all, &opts.exclude, &opts.package)
112+
.expect("Couldn't create Packages for Cargo");
113+
114+
let compile_opts = CompileOptions {
115+
target: opts.target.as_ref().map(|t| &t[..]),
116+
spec: spec,
117+
filter: CompileFilter::new(opts.lib,
118+
&opts.bin, opts.bins,
119+
// TODO: Support more crate target types
120+
&[], false, &[], false, &[], false),
121+
.. CompileOptions::default(&config, CompileMode::Check)
114122
};
115-
compile_with_exec(&ws, &opts, Arc::new(exec)).expect("could not run cargo");
123+
124+
env::set_var("RUSTFLAGS", rustflags);
125+
compile_with_exec(&ws, &compile_opts, Arc::new(exec)).expect("could not run cargo");
116126
}
117127

118128
struct RlsExecutor {
119129
compilation_cx: Arc<Mutex<CompilationContext>>,
120130
cur_package_id: Mutex<Option<PackageId>>,
121131
config: Arc<Mutex<Config>>,
132+
workspace_mode: bool,
133+
/// Packages which are directly a member of the workspace, for which
134+
/// analysis and diagnostics will be provided
135+
member_packages: Mutex<HashSet<PackageId>>,
136+
/// JSON compiler messages emitted for each primary compiled crate
137+
compiler_messages: Arc<Mutex<Vec<String>>>,
122138
}
123139

124140
impl RlsExecutor {
125141
fn new(compilation_cx: Arc<Mutex<CompilationContext>>,
126-
config: Arc<Mutex<Config>>) -> RlsExecutor {
142+
config: Arc<Mutex<Config>>,
143+
compiler_messages: Arc<Mutex<Vec<String>>>)
144+
-> RlsExecutor {
145+
let workspace_mode = config.lock().unwrap().workspace_mode;
127146
RlsExecutor {
128-
compilation_cx: compilation_cx,
147+
compilation_cx,
129148
cur_package_id: Mutex::new(None),
130149
config,
150+
workspace_mode,
151+
member_packages: Mutex::new(HashSet::new()),
152+
compiler_messages,
131153
}
132154
}
133155

134156
fn is_primary_crate(&self, id: &PackageId) -> bool {
135-
let cur_package_id = self.cur_package_id.lock().unwrap();
136-
id == cur_package_id.as_ref().expect("Executor has not been initialised")
157+
if self.workspace_mode {
158+
self.member_packages.lock().unwrap().contains(id)
159+
} else {
160+
let cur_package_id = self.cur_package_id.lock().unwrap();
161+
id == cur_package_id.as_ref().expect("Executor has not been initialised")
162+
}
137163
}
138164
}
139165

140166
impl Executor for RlsExecutor {
141167
fn init(&self, cx: &Context) {
142-
let mut cur_package_id = self.cur_package_id.lock().unwrap();
143-
*cur_package_id = Some(cx.ws
144-
.current_opt()
145-
.expect("No current package in Cargo")
146-
.package_id()
147-
.clone());
168+
if self.workspace_mode {
169+
*self.member_packages.lock().unwrap() = cx.ws
170+
.members()
171+
.map(|x| x.package_id().clone())
172+
.collect();
173+
} else {
174+
let mut cur_package_id = self.cur_package_id.lock().unwrap();
175+
*cur_package_id = Some(cx.ws
176+
.current_opt()
177+
.expect("No current package in Cargo")
178+
.package_id()
179+
.clone());
180+
};
148181
}
149182

150183
fn force_rebuild(&self, unit: &Unit) -> bool {
184+
// TODO: Currently workspace_mode doesn't use rustc, so it doesn't
185+
// need args. When we start using rustc, we might consider doing
186+
// force_rebuild to retrieve args for given package if they're stale/missing
187+
if self.workspace_mode {
188+
return false;
189+
}
190+
151191
// We only do a cargo build if we want to force rebuild the last
152192
// crate (e.g., because some args changed). Therefore we should
153193
// always force rebuild the primary crate.
@@ -235,7 +275,7 @@ impl Executor for RlsExecutor {
235275
for a in &args {
236276
// Emitting only dep-info is possible only for final crate type, as
237277
// as others may emit required metadata for dependent crate types
238-
if a.starts_with("--emit") && is_final_crate_type {
278+
if a.starts_with("--emit") && is_final_crate_type && !self.workspace_mode {
239279
cmd.arg("--emit=dep-info");
240280
} else {
241281
cmd.arg(a);
@@ -247,7 +287,13 @@ impl Executor for RlsExecutor {
247287
}
248288
}
249289

250-
cmd.status().expect("Couldn't execute rustc");
290+
if self.workspace_mode {
291+
let output = cmd.output().expect("Couldn't execute rustc");
292+
let mut stderr_json_msg = convert_message_to_json_strings(output.stderr);
293+
self.compiler_messages.lock().unwrap().append(&mut stderr_json_msg);
294+
} else {
295+
cmd.status().expect("Couldn't execute rustc");
296+
}
251297

252298
// Finally, store the modified cargo-generated args/envs for future rustc calls
253299
args.insert(0, rustc_exe);
@@ -259,10 +305,88 @@ impl Executor for RlsExecutor {
259305
}
260306
}
261307

308+
#[derive(Debug)]
309+
struct CargoOptions {
310+
package: Vec<String>,
311+
target: Option<String>,
312+
lib: bool,
313+
bin: Vec<String>,
314+
bins: bool,
315+
all: bool,
316+
exclude: Vec<String>,
317+
}
318+
319+
impl CargoOptions {
320+
fn default() -> CargoOptions {
321+
CargoOptions {
322+
package: vec![],
323+
target: None,
324+
lib: false,
325+
bin: vec![],
326+
bins: false,
327+
all: false,
328+
exclude: vec![],
329+
}
330+
}
331+
332+
fn new(config: &Config) -> CargoOptions {
333+
if config.workspace_mode {
334+
let (package, all) = match config.analyze_package {
335+
Some(ref pkg_name) => (vec![pkg_name.clone()], false),
336+
None => (vec![], true),
337+
};
338+
339+
CargoOptions {
340+
package,
341+
all,
342+
target: config.target.clone(),
343+
.. CargoOptions::default()
344+
}
345+
} else {
346+
// In single-crate mode we currently support only one crate target,
347+
// and if lib is set, then we ignore bin target config
348+
let (lib, bin) = match config.build_lib {
349+
true => (true, vec![]),
350+
false => {
351+
let bin = match config.build_bin {
352+
Some(ref bin) => vec![bin.clone()],
353+
None => vec![],
354+
};
355+
(false, bin)
356+
},
357+
};
358+
359+
CargoOptions {
360+
lib,
361+
bin,
362+
target: config.target.clone(),
363+
.. CargoOptions::default()
364+
}
365+
}
366+
}
367+
}
368+
369+
fn prepare_cargo_rustflags(config: &Config) -> String {
370+
let mut flags = "-Zunstable-options -Zsave-analysis --error-format=json \
371+
-Zcontinue-parse-after-error".to_owned();
372+
373+
if let Some(ref sysroot) = config.sysroot {
374+
flags.push_str(&format!(" --sysroot {}", sysroot));
375+
}
376+
377+
flags = format!("{} {} {}",
378+
env::var("RUSTFLAGS").unwrap_or(String::new()),
379+
config.rustflags.as_ref().unwrap_or(&String::new()),
380+
flags);
381+
382+
dedup_flags(&flags)
383+
}
384+
262385
fn make_cargo_config(build_dir: &Path, shell: Shell) -> CargoConfig {
263386
let config = CargoConfig::new(shell,
264-
// This is Cargo's cwd. We are using the actual cwd, but perhaps
265-
// we should use build_dir or something else?
387+
// This is Cargo's cwd. We're using the actual cwd,
388+
// because Cargo will generate relative paths based
389+
// on this to source files it wants to compile
266390
env::current_dir().unwrap(),
267391
homedir(&build_dir).unwrap());
268392

src/build/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ impl BuildQueue {
299299
if interupt {
300300
and_then(BuildResult::Squashed);
301301
continue;
302-
}
302+
}
303303
}
304304

305305
// Run the build.
@@ -375,10 +375,16 @@ impl Internals {
375375

376376
// Don't hold this lock when we run Cargo.
377377
let needs_to_run_cargo = self.compilation_cx.lock().unwrap().args.is_empty();
378-
if needs_to_run_cargo {
379-
if let BuildResult::Err = self.cargo() {
380-
return BuildResult::Err;
381-
}
378+
let workspace_mode = self.config.lock().unwrap().workspace_mode;
379+
380+
if workspace_mode || needs_to_run_cargo {
381+
let result = self.cargo();
382+
383+
match result {
384+
BuildResult::Err => return BuildResult::Err,
385+
_ if workspace_mode => return result,
386+
_ => {},
387+
};
382388
}
383389

384390
let compile_cx = self.compilation_cx.lock().unwrap();

src/build/rustc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl Drop for Environment {
199199
}
200200
}
201201

202-
fn convert_message_to_json_strings(input: Vec<u8>) -> Vec<String> {
202+
pub fn convert_message_to_json_strings(input: Vec<u8>) -> Vec<String> {
203203
let mut output = vec![];
204204

205205
// FIXME: this is *so gross* Trying to work around cargo not supporting json messages

0 commit comments

Comments
 (0)