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

Commit 2e5ba48

Browse files
authored
Merge pull request #438 from Xanewok/infer-targets
Infer appropriate crate target from workspace
2 parents b92fc57 + 5f5e64a commit 2e5ba48

File tree

14 files changed

+330
-17
lines changed

14 files changed

+330
-17
lines changed

src/actions/mod.rs

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010

1111
mod compiler_message_parsing;
1212

13+
use cargo::CargoResult;
14+
use cargo::util::important_paths;
15+
use cargo::core::{Shell, Workspace};
16+
1317
use analysis::{AnalysisHost};
1418
use url::Url;
1519
use vfs::{Vfs, Change, FileContents};
1620
use racer;
1721
use rustfmt::{Input as FmtInput, format_input};
1822
use rustfmt::file_lines::{Range as RustfmtRange, FileLines};
19-
use config::{Config, FmtConfig};
23+
use config::{Config, FmtConfig, Inferrable};
2024
use serde::Deserialize;
2125
use serde::de::Error;
2226
use serde_json;
@@ -32,6 +36,7 @@ use jsonrpc_core::types::ErrorCode;
3236
use std::collections::HashMap;
3337
use std::panic;
3438
use std::path::{Path, PathBuf};
39+
use std::io::sink;
3540
use std::sync::{Arc, Mutex};
3641
use std::thread;
3742
use std::time::Duration;
@@ -92,6 +97,19 @@ impl ActionHandler {
9297
pub fn init<O: Output>(&self, init_options: InitializationOptions, out: O) {
9398
trace!("init: {:?}", init_options);
9499

100+
let project_dir = self.current_project.clone();
101+
let config = self.config.clone();
102+
// Spawn another thread since we're shelling out to Cargo and this can
103+
// cause a non-trivial amount of time due to disk access
104+
thread::spawn(move || {
105+
let mut config = config.lock().unwrap();
106+
if let Err(e) = infer_config_defaults(&project_dir, &mut *config) {
107+
debug!("Encountered an error while trying to infer config \
108+
defaults: {:?}", e);
109+
}
110+
111+
});
112+
95113
if !init_options.omit_init_build {
96114
self.build_current_project(BuildPriority::Cargo, out);
97115
}
@@ -748,7 +766,27 @@ impl ActionHandler {
748766

749767
{
750768
let mut config = self.config.lock().unwrap();
751-
*config = new_config;
769+
770+
// User may specify null (to be inferred) options, in which case
771+
// we schedule further inference on a separate thread not to block
772+
// the main thread
773+
let needs_inference = new_config.needs_inference();
774+
// In case of null options, we provide default values for now
775+
config.update(new_config);
776+
trace!("Updated config: {:?}", *config);
777+
778+
if needs_inference {
779+
let project_dir = self.current_project.clone();
780+
let config = self.config.clone();
781+
// Will lock and access Config just outside the current scope
782+
thread::spawn(move || {
783+
let mut config = config.lock().unwrap();
784+
if let Err(e) = infer_config_defaults(&project_dir, &mut *config) {
785+
debug!("Encountered an error while trying to infer config \
786+
defaults: {:?}", e);
787+
}
788+
});
789+
}
752790
}
753791
// We do a clean build so that if we've changed any relevant options
754792
// for Cargo, we'll notice them. But if nothing relevant changes
@@ -812,6 +850,76 @@ impl ActionHandler {
812850
}
813851
}
814852

853+
fn infer_config_defaults(project_dir: &Path, config: &mut Config) -> CargoResult<()> {
854+
// Note that this may not be equal build_dir when inside a workspace member
855+
let manifest_path = important_paths::find_root_manifest_for_wd(None, project_dir)?;
856+
trace!("root manifest_path: {:?}", &manifest_path);
857+
858+
// Cargo constructs relative paths from the manifest dir, so we have to pop "Cargo.toml"
859+
let manifest_dir = manifest_path.parent().unwrap();
860+
let shell = Shell::from_write(Box::new(sink()));
861+
let cargo_config = make_cargo_config(manifest_dir, shell);
862+
863+
let ws = Workspace::new(&manifest_path, &cargo_config)?;
864+
865+
// Auto-detect --lib/--bin switch if working under single package mode
866+
// or under workspace mode with `analyze_package` specified
867+
let package = match config.workspace_mode {
868+
true => {
869+
let package_name = match config.analyze_package {
870+
// No package specified, nothing to do
871+
None => { return Ok(()); },
872+
Some(ref package) => package,
873+
};
874+
875+
ws.members()
876+
.find(move |x| x.name() == package_name)
877+
.ok_or(
878+
format!("Couldn't find specified `{}` package via \
879+
`analyze_package` in the workspace", package_name)
880+
)?
881+
},
882+
false => ws.current()?,
883+
};
884+
885+
trace!("infer_config_defaults: Auto-detected `{}` package", package.name());
886+
887+
let targets = package.targets();
888+
let (lib, bin) = if targets.iter().any(|x| x.is_lib()) {
889+
(true, None)
890+
} else {
891+
let mut bins = targets.iter().filter(|x| x.is_bin());
892+
// No `lib` detected, but also can't find any `bin` target - there's
893+
// no sensible target here, so just Err out
894+
let first = bins.nth(0)
895+
.ok_or("No `bin` or `lib` targets in the package")?;
896+
897+
let mut bins = targets.iter().filter(|x| x.is_bin());
898+
let target = match bins.find(|x| x.src_path().ends_with("main.rs")) {
899+
Some(main_bin) => main_bin,
900+
None => first,
901+
};
902+
903+
(false, Some(target.name().to_owned()))
904+
};
905+
906+
trace!("infer_config_defaults: build_lib: {:?}, build_bin: {:?}", lib, bin);
907+
908+
// Unless crate target is explicitly specified, mark the values as
909+
// inferred, so they're not simply ovewritten on config change without
910+
// any specified value
911+
let (lib, bin) = match (&config.build_lib, &config.build_bin) {
912+
(&Inferrable::Specified(true), _) => (lib, None),
913+
(_, &Inferrable::Specified(Some(_))) => (false, bin),
914+
_ => (lib, bin),
915+
};
916+
917+
config.build_lib.infer(lib);
918+
config.build_bin.infer(bin);
919+
920+
Ok(())
921+
}
922+
815923
fn racer_coord(line: span::Row<span::OneIndexed>,
816924
column: span::Column<span::ZeroIndexed>)
817925
-> racer::Coordinate {

src/build/cargo.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ fn run_cargo(compilation_cx: Arc<Mutex<CompilationContext>>,
9797
let manifest_path = important_paths::find_root_manifest_for_wd(None, &build_dir)
9898
.expect(&format!("Couldn't find a root manifest for cwd: {:?}", &build_dir));
9999
trace!("root manifest_path: {:?}", &manifest_path);
100+
// Cargo constructs relative paths from the manifest dir, so we have to pop "Cargo.toml"
101+
let manifest_dir = manifest_path.parent().unwrap();
100102

101103
let mut shell = Shell::from_write(Box::new(BufWriter(out.clone())));
102104
shell.set_verbosity(Verbosity::Quiet);
103105

104-
// Cargo constructs relative paths from the manifest dir, so we have to pop "Cargo.toml"
105-
let manifest_dir = manifest_path.parent().unwrap();
106106
let config = make_cargo_config(manifest_dir, shell);
107107

108108
let ws = Workspace::new(&manifest_path, &config).expect("could not create cargo workspace");
@@ -128,7 +128,7 @@ fn run_cargo(compilation_cx: Arc<Mutex<CompilationContext>>,
128128
if !rls_config.workspace_mode {
129129
let cur_pkg_targets = ws.current().unwrap().targets();
130130

131-
if let Some(ref build_bin) = rls_config.build_bin {
131+
if let &Some(ref build_bin) = rls_config.build_bin.as_ref() {
132132
let mut bins = cur_pkg_targets.iter().filter(|x| x.is_bin());
133133
if let None = bins.find(|x| x.name() == build_bin) {
134134
warn!("cargo - couldn't find binary `{}` specified in `build_bin` configuration", build_bin);
@@ -329,7 +329,8 @@ impl Executor for RlsExecutor {
329329
// assume crate_type arg (i.e. in `cargo test` it isn't specified for --test targets)
330330
// and build test harness only for final crate type
331331
let crate_type = crate_type.expect("no crate-type in rustc command line");
332-
let is_final_crate_type = crate_type == "bin" || (crate_type == "lib" && config.build_lib);
332+
let build_lib = *config.build_lib.as_ref();
333+
let is_final_crate_type = crate_type == "bin" || (crate_type == "lib" && build_lib);
333334

334335
if config.cfg_test {
335336
// FIXME(#351) allow passing --test to lib crate-type when building a dependency
@@ -448,10 +449,10 @@ impl CargoOptions {
448449
} else {
449450
// In single-crate mode we currently support only one crate target,
450451
// and if lib is set, then we ignore bin target config
451-
let (lib, bin) = match config.build_lib {
452+
let (lib, bin) = match *config.build_lib.as_ref() {
452453
true => (true, vec![]),
453454
false => {
454-
let bin = match config.build_bin {
455+
let bin = match *config.build_bin.as_ref() {
455456
Some(ref bin) => vec![bin.clone()],
456457
None => vec![],
457458
};
@@ -485,7 +486,7 @@ fn prepare_cargo_rustflags(config: &Config) -> String {
485486
dedup_flags(&flags)
486487
}
487488

488-
fn make_cargo_config(build_dir: &Path, shell: Shell) -> CargoConfig {
489+
pub fn make_cargo_config(build_dir: &Path, shell: Shell) -> CargoConfig {
489490
let config = CargoConfig::new(shell,
490491
// This is Cargo's cwd. We're using the actual cwd,
491492
// because Cargo will generate relative paths based

src/build/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
pub use self::cargo::make_cargo_config;
12+
1113
use data::Analysis;
1214
use vfs::Vfs;
1315
use config::Config;

src/config.rs

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,92 @@
99
// except according to those terms.
1010

1111
use std::path::Path;
12+
use std::fmt::Debug;
13+
14+
use serde::de::{Deserialize, Deserializer};
1215

1316
use rustfmt::config::Config as RustfmtConfig;
1417
use rustfmt::config::WriteMode;
1518

1619
const DEFAULT_WAIT_TO_BUILD: u64 = 500;
1720

21+
/// Some values in the config can be inferred without an explicit value set by
22+
/// the user. There are no guarantees which values will or will not be passed
23+
/// to the server, so we treat deserialized values effectively as `Option<T>`
24+
/// and use `None` to mark the values as unspecified, otherwise we always use
25+
/// `Specified` variant for the deserialized values. For user-provided `None`
26+
/// values, they must be `Inferred` prior to usage (and can be further
27+
/// `Specified` by the user).
28+
#[derive(Clone, Debug, Serialize)]
29+
pub enum Inferrable<T> {
30+
/// Explicitly specified value by the user. Retrieved by deserializing a
31+
/// non-`null` value. Can replace every other variant.
32+
Specified(T),
33+
/// Value that's inferred by the server. Can't replace a `Specified` variant.
34+
Inferred(T),
35+
/// Marker value that's retrieved when deserializing a user-specified `null`
36+
/// value. Can't be used alone and has to be replaced by server-`Inferred`
37+
/// or user-`Specified` value.
38+
None
39+
}
40+
41+
// Deserialize as if it's `Option<T>` and use `None` variant if it's `None`,
42+
// otherwise use `Specified` variant for deserialized value.
43+
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Inferrable<T> {
44+
fn deserialize<D>(deserializer: D) -> Result<Inferrable<T>, D::Error>
45+
where D: Deserializer<'de>
46+
{
47+
let value = Option::<T>::deserialize(deserializer)?;
48+
Ok(match value {
49+
None => Inferrable::None,
50+
Some(value) => Inferrable::Specified(value),
51+
})
52+
}
53+
}
54+
55+
impl<T: Clone + Debug> Inferrable<T> {
56+
pub fn combine_with_default(&self, new: &Self, default: T) -> Self {
57+
match (self, new) {
58+
// Don't allow to update a Specified value with an Inferred one
59+
(&Inferrable::Specified(_), &Inferrable::Inferred(_)) => self.clone(),
60+
// When trying to update with a `None`, use Inferred variant with
61+
// a specified default value, as `None` value can't be used directly
62+
(_, &Inferrable::None) => Inferrable::Inferred(default),
63+
_ => new.clone(),
64+
}
65+
}
66+
67+
pub fn infer(&mut self, value: T) {
68+
if let &mut Inferrable::Specified(_) = self {
69+
trace!("Trying to infer {:?} on a {:?}", value, self);
70+
return;
71+
}
72+
73+
*self = Inferrable::Inferred(value);
74+
}
75+
}
76+
77+
impl<T> AsRef<T> for Inferrable<T> {
78+
fn as_ref(&self) -> &T {
79+
match *self {
80+
Inferrable::Inferred(ref value) |
81+
Inferrable::Specified(ref value) => value,
82+
// Default values should always be initialized as `Inferred` even
83+
// before actual inference takes place, `None` variant is only used
84+
// when deserializing and should not be read directly (via `as_ref`)
85+
Inferrable::None => unreachable!(),
86+
}
87+
}
88+
}
89+
1890
#[derive(Clone, Debug, Deserialize, Serialize)]
1991
#[serde(default)]
2092
pub struct Config {
2193
pub sysroot: Option<String>,
2294
pub target: Option<String>,
2395
pub rustflags: Option<String>,
24-
pub build_lib: bool,
25-
pub build_bin: Option<String>,
96+
pub build_lib: Inferrable<bool>,
97+
pub build_bin: Inferrable<Option<String>>,
2698
pub cfg_test: bool,
2799
pub unstable_features: bool,
28100
pub wait_to_build: u64,
@@ -43,8 +115,8 @@ impl Default for Config {
43115
sysroot: None,
44116
target: None,
45117
rustflags: None,
46-
build_lib: false,
47-
build_bin: None,
118+
build_lib: Inferrable::Inferred(false),
119+
build_bin: Inferrable::Inferred(None),
48120
cfg_test: false,
49121
unstable_features: false,
50122
wait_to_build: DEFAULT_WAIT_TO_BUILD,
@@ -59,6 +131,23 @@ impl Default for Config {
59131
}
60132
}
61133

134+
impl Config {
135+
pub fn update(&mut self, mut new: Config) {
136+
new.build_lib = self.build_lib.combine_with_default(&new.build_lib, false);
137+
new.build_bin = self.build_bin.combine_with_default(&new.build_bin, None);
138+
139+
*self = new;
140+
}
141+
142+
pub fn needs_inference(&self) -> bool {
143+
match (&self.build_lib, &self.build_bin) {
144+
(&Inferrable::None, _) |
145+
(_, &Inferrable::None) => true,
146+
_ => false,
147+
}
148+
}
149+
}
150+
62151
/// A rustfmt config (typically specified via rustfmt.toml)
63152
/// The FmtConfig is not an exact translation of the config
64153
/// rustfmt generates from the user's toml file, since when

0 commit comments

Comments
 (0)