aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-03-30 13:45:49 +0100
committerGitHub <[email protected]>2020-03-30 13:45:49 +0100
commit671926ac93f0ff921758a919eaf87c056979189f (patch)
tree111c2cc751cb7fcca38eb7518e1d39af394ee243 /crates
parent9e12b9e6fdc03ea6bc35a88cfb5d5d6751672ec8 (diff)
parent4c897d8d2dd047e0906d585318866c9ae7a21610 (diff)
Merge #3666
3666: Reload part of the server configuration without restarts r=matklad a=SomeoneToIgnore Partially addresses https://github.com/rust-analyzer/rust-analyzer/issues/2857 Closes #3751 Reloads all server configuration that's not related to VFS without restarts. The VFS-related parameters are not considered, since VFS is planned to be rewritten/replaced in the future and I have a suspicion that with the current code, swapping the VFS and the file watchers on the fly will cause big troubles. I have to store and process the config request id separately, since the `workspace/configuration` response returns `any[]` (https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration), if there's a better way to handle those responses, let me know. Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_cargo_watch/src/lib.rs10
-rw-r--r--crates/ra_ide/src/lib.rs5
-rw-r--r--crates/ra_ide_db/src/lib.rs12
-rw-r--r--crates/rust-analyzer/src/main_loop.rs168
-rw-r--r--crates/rust-analyzer/src/req.rs2
-rw-r--r--crates/rust-analyzer/src/world.rs46
6 files changed, 162 insertions, 81 deletions
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index c67ec39d4..2692c1bf5 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -95,6 +95,9 @@ impl CheckWatcherThread {
95 } 95 }
96 96
97 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) { 97 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
98 // If we rerun the thread, we need to discard the previous check results first
99 self.clean_previous_results(task_send);
100
98 loop { 101 loop {
99 select! { 102 select! {
100 recv(&cmd_recv) -> cmd => match cmd { 103 recv(&cmd_recv) -> cmd => match cmd {
@@ -127,6 +130,13 @@ impl CheckWatcherThread {
127 } 130 }
128 } 131 }
129 132
133 fn clean_previous_results(&self, task_send: &Sender<CheckTask>) {
134 task_send.send(CheckTask::ClearDiagnostics).unwrap();
135 task_send
136 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None })))
137 .unwrap();
138 }
139
130 fn should_recheck(&mut self) -> bool { 140 fn should_recheck(&mut self) -> bool {
131 if let Some(_last_update_req) = &self.last_update_req { 141 if let Some(_last_update_req) = &self.last_update_req {
132 // We currently only request an update on save, as we need up to 142 // We currently only request an update on save, as we need up to
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index e43414985..937c9caa5 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -138,6 +138,11 @@ impl AnalysisHost {
138 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { 138 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
139 AnalysisHost { db: RootDatabase::new(lru_capacity) } 139 AnalysisHost { db: RootDatabase::new(lru_capacity) }
140 } 140 }
141
142 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
143 self.db.update_lru_capacity(lru_capacity);
144 }
145
141 /// Returns a snapshot of the current state, which you can query for 146 /// Returns a snapshot of the current state, which you can query for
142 /// semantic information. 147 /// semantic information.
143 pub fn analysis(&self) -> Analysis { 148 pub fn analysis(&self) -> Analysis {
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index 4faeefa8d..e6f2d36e9 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -115,12 +115,16 @@ impl RootDatabase {
115 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); 115 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
116 db.set_local_roots_with_durability(Default::default(), Durability::HIGH); 116 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
117 db.set_library_roots_with_durability(Default::default(), Durability::HIGH); 117 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
118 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); 118 db.update_lru_capacity(lru_capacity);
119 db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
120 db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
121 db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
122 db 119 db
123 } 120 }
121
122 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
123 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP);
124 self.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
125 self.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
126 self.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
127 }
124} 128}
125 129
126impl salsa::ParallelDatabase for RootDatabase { 130impl salsa::ParallelDatabase for RootDatabase {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c899ff677..79ea90cc9 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -17,8 +17,9 @@ use std::{
17use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ 19use lsp_types::{
20 ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, 20 ClientCapabilities, NumberOrString, TextDocumentClientCapabilities, WorkDoneProgress,
21 WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, 21 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
22 WorkDoneProgressReport,
22}; 23};
23use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; 24use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask};
24use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId}; 25use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId};
@@ -40,6 +41,7 @@ use crate::{
40 world::{Options, WorldSnapshot, WorldState}, 41 world::{Options, WorldSnapshot, WorldState},
41 Result, ServerConfig, 42 Result, ServerConfig,
42}; 43};
44use req::ConfigurationParams;
43 45
44#[derive(Debug)] 46#[derive(Debug)]
45pub struct LspError { 47pub struct LspError {
@@ -63,6 +65,53 @@ impl fmt::Display for LspError {
63 65
64impl Error for LspError {} 66impl Error for LspError {}
65 67
68fn get_feature_flags(config: &ServerConfig, connection: &Connection) -> FeatureFlags {
69 let mut ff = FeatureFlags::default();
70 for (flag, &value) in &config.feature_flags {
71 if ff.set(flag.as_str(), value).is_err() {
72 log::error!("unknown feature flag: {:?}", flag);
73 show_message(
74 req::MessageType::Error,
75 format!("unknown feature flag: {:?}", flag),
76 &connection.sender,
77 );
78 }
79 }
80 log::info!("feature_flags: {:#?}", ff);
81 ff
82}
83
84fn get_options(
85 config: &ServerConfig,
86 text_document_caps: Option<&TextDocumentClientCapabilities>,
87) -> Options {
88 Options {
89 publish_decorations: config.publish_decorations,
90 supports_location_link: text_document_caps
91 .and_then(|it| it.definition)
92 .and_then(|it| it.link_support)
93 .unwrap_or(false),
94 line_folding_only: text_document_caps
95 .and_then(|it| it.folding_range.as_ref())
96 .and_then(|it| it.line_folding_only)
97 .unwrap_or(false),
98 inlay_hints: InlayHintsOptions {
99 type_hints: config.inlay_hints_type,
100 parameter_hints: config.inlay_hints_parameter,
101 chaining_hints: config.inlay_hints_chaining,
102 max_length: config.inlay_hints_max_length,
103 },
104 cargo_watch: CheckOptions {
105 enable: config.cargo_watch_enable,
106 args: config.cargo_watch_args.clone(),
107 command: config.cargo_watch_command.clone(),
108 all_targets: config.cargo_watch_all_targets,
109 },
110 rustfmt_args: config.rustfmt_args.clone(),
111 vscode_lldb: config.vscode_lldb,
112 }
113}
114
66pub fn main_loop( 115pub fn main_loop(
67 ws_roots: Vec<PathBuf>, 116 ws_roots: Vec<PathBuf>,
68 client_caps: ClientCapabilities, 117 client_caps: ClientCapabilities,
@@ -90,23 +139,10 @@ pub fn main_loop(
90 SetThreadPriority(thread, thread_priority_above_normal); 139 SetThreadPriority(thread, thread_priority_above_normal);
91 } 140 }
92 141
142 let text_document_caps = client_caps.text_document.as_ref();
93 let mut loop_state = LoopState::default(); 143 let mut loop_state = LoopState::default();
94 let mut world_state = { 144 let mut world_state = {
95 let feature_flags = { 145 let feature_flags = get_feature_flags(&config, &connection);
96 let mut ff = FeatureFlags::default();
97 for (flag, value) in config.feature_flags {
98 if ff.set(flag.as_str(), value).is_err() {
99 log::error!("unknown feature flag: {:?}", flag);
100 show_message(
101 req::MessageType::Error,
102 format!("unknown feature flag: {:?}", flag),
103 &connection.sender,
104 );
105 }
106 }
107 ff
108 };
109 log::info!("feature_flags: {:#?}", feature_flags);
110 146
111 // FIXME: support dynamic workspace loading. 147 // FIXME: support dynamic workspace loading.
112 let workspaces = { 148 let workspaces = {
@@ -168,42 +204,13 @@ pub fn main_loop(
168 connection.sender.send(request.into()).unwrap(); 204 connection.sender.send(request.into()).unwrap();
169 } 205 }
170 206
171 let options = {
172 let text_document_caps = client_caps.text_document.as_ref();
173 Options {
174 publish_decorations: config.publish_decorations,
175 supports_location_link: text_document_caps
176 .and_then(|it| it.definition)
177 .and_then(|it| it.link_support)
178 .unwrap_or(false),
179 line_folding_only: text_document_caps
180 .and_then(|it| it.folding_range.as_ref())
181 .and_then(|it| it.line_folding_only)
182 .unwrap_or(false),
183 inlay_hints: InlayHintsOptions {
184 type_hints: config.inlay_hints_type,
185 parameter_hints: config.inlay_hints_parameter,
186 chaining_hints: config.inlay_hints_chaining,
187 max_length: config.inlay_hints_max_length,
188 },
189 cargo_watch: CheckOptions {
190 enable: config.cargo_watch_enable,
191 args: config.cargo_watch_args,
192 command: config.cargo_watch_command,
193 all_targets: config.cargo_watch_all_targets,
194 },
195 rustfmt_args: config.rustfmt_args,
196 vscode_lldb: config.vscode_lldb,
197 }
198 };
199
200 WorldState::new( 207 WorldState::new(
201 ws_roots, 208 ws_roots,
202 workspaces, 209 workspaces,
203 config.lru_capacity, 210 config.lru_capacity,
204 &globs, 211 &globs,
205 Watch(!config.use_client_watching), 212 Watch(!config.use_client_watching),
206 options, 213 get_options(&config, text_document_caps),
207 feature_flags, 214 feature_flags,
208 ) 215 )
209 }; 216 };
@@ -247,6 +254,7 @@ pub fn main_loop(
247 &task_sender, 254 &task_sender,
248 &libdata_sender, 255 &libdata_sender,
249 &connection, 256 &connection,
257 text_document_caps,
250 &mut world_state, 258 &mut world_state,
251 &mut loop_state, 259 &mut loop_state,
252 event, 260 event,
@@ -336,10 +344,10 @@ struct LoopState {
336 in_flight_libraries: usize, 344 in_flight_libraries: usize,
337 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, 345 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>,
338 workspace_loaded: bool, 346 workspace_loaded: bool,
339
340 roots_progress_reported: Option<usize>, 347 roots_progress_reported: Option<usize>,
341 roots_scanned: usize, 348 roots_scanned: usize,
342 roots_total: usize, 349 roots_total: usize,
350 configuration_request_id: Option<RequestId>,
343} 351}
344 352
345impl LoopState { 353impl LoopState {
@@ -357,6 +365,7 @@ fn loop_turn(
357 task_sender: &Sender<Task>, 365 task_sender: &Sender<Task>,
358 libdata_sender: &Sender<LibraryData>, 366 libdata_sender: &Sender<LibraryData>,
359 connection: &Connection, 367 connection: &Connection,
368 text_document_caps: Option<&TextDocumentClientCapabilities>,
360 world_state: &mut WorldState, 369 world_state: &mut WorldState,
361 loop_state: &mut LoopState, 370 loop_state: &mut LoopState,
362 event: Event, 371 event: Event,
@@ -397,19 +406,47 @@ fn loop_turn(
397 req, 406 req,
398 )?, 407 )?,
399 Message::Notification(not) => { 408 Message::Notification(not) => {
400 on_notification( 409 on_notification(&connection.sender, world_state, loop_state, not)?;
401 &connection.sender,
402 world_state,
403 &mut loop_state.pending_requests,
404 &mut loop_state.subscriptions,
405 not,
406 )?;
407 } 410 }
408 Message::Response(resp) => { 411 Message::Response(resp) => {
409 let removed = loop_state.pending_responses.remove(&resp.id); 412 let removed = loop_state.pending_responses.remove(&resp.id);
410 if !removed { 413 if !removed {
411 log::error!("unexpected response: {:?}", resp) 414 log::error!("unexpected response: {:?}", resp)
412 } 415 }
416
417 if Some(&resp.id) == loop_state.configuration_request_id.as_ref() {
418 loop_state.configuration_request_id = None;
419 log::debug!("config update response: '{:?}", resp);
420 let Response { error, result, .. } = resp;
421
422 match (
423 error,
424 result.map(|result| serde_json::from_value::<Vec<ServerConfig>>(result)),
425 ) {
426 (Some(err), _) => {
427 log::error!("failed to fetch the server settings: {:?}", err)
428 }
429 (None, Some(Ok(new_config))) => {
430 let new_config = new_config
431 .first()
432 .expect(
433 "the client is expected to always send a non-empty config data",
434 )
435 .to_owned();
436 world_state.update_configuration(
437 new_config.lru_capacity,
438 get_options(&new_config, text_document_caps),
439 get_feature_flags(&new_config, connection),
440 );
441 }
442 (None, Some(Err(e))) => {
443 log::error!("failed to parse client config response: {}", e)
444 }
445 (None, None) => {
446 log::error!("received empty server settings response from the client")
447 }
448 }
449 }
413 } 450 }
414 }, 451 },
415 }; 452 };
@@ -569,8 +606,7 @@ fn on_request(
569fn on_notification( 606fn on_notification(
570 msg_sender: &Sender<Message>, 607 msg_sender: &Sender<Message>,
571 state: &mut WorldState, 608 state: &mut WorldState,
572 pending_requests: &mut PendingRequests, 609 loop_state: &mut LoopState,
573 subs: &mut Subscriptions,
574 not: Notification, 610 not: Notification,
575) -> Result<()> { 611) -> Result<()> {
576 let not = match notification_cast::<req::Cancel>(not) { 612 let not = match notification_cast::<req::Cancel>(not) {
@@ -579,7 +615,7 @@ fn on_notification(
579 NumberOrString::Number(id) => id.into(), 615 NumberOrString::Number(id) => id.into(),
580 NumberOrString::String(id) => id.into(), 616 NumberOrString::String(id) => id.into(),
581 }; 617 };
582 if pending_requests.cancel(&id) { 618 if loop_state.pending_requests.cancel(&id) {
583 let response = Response::new_err( 619 let response = Response::new_err(
584 id, 620 id,
585 ErrorCode::RequestCanceled as i32, 621 ErrorCode::RequestCanceled as i32,
@@ -598,7 +634,7 @@ fn on_notification(
598 if let Some(file_id) = 634 if let Some(file_id) =
599 state.vfs.write().add_file_overlay(&path, params.text_document.text) 635 state.vfs.write().add_file_overlay(&path, params.text_document.text)
600 { 636 {
601 subs.add_sub(FileId(file_id.0)); 637 loop_state.subscriptions.add_sub(FileId(file_id.0));
602 } 638 }
603 return Ok(()); 639 return Ok(());
604 } 640 }
@@ -629,7 +665,7 @@ fn on_notification(
629 let uri = params.text_document.uri; 665 let uri = params.text_document.uri;
630 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; 666 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
631 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { 667 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) {
632 subs.remove_sub(FileId(file_id.0)); 668 loop_state.subscriptions.remove_sub(FileId(file_id.0));
633 } 669 }
634 let params = 670 let params =
635 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; 671 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None };
@@ -640,7 +676,17 @@ fn on_notification(
640 Err(not) => not, 676 Err(not) => not,
641 }; 677 };
642 let not = match notification_cast::<req::DidChangeConfiguration>(not) { 678 let not = match notification_cast::<req::DidChangeConfiguration>(not) {
643 Ok(_params) => { 679 Ok(_) => {
680 // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
681 // this notification's parameters should be ignored and the actual config queried separately.
682 let request_id = loop_state.next_request_id();
683 let request = request_new::<req::WorkspaceConfiguration>(
684 request_id.clone(),
685 ConfigurationParams::default(),
686 );
687 msg_sender.send(request.into())?;
688 loop_state.configuration_request_id = Some(request_id);
689
644 return Ok(()); 690 return Ok(());
645 } 691 }
646 Err(not) => not, 692 Err(not) => not,
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs
index 8557294f6..994f0ed61 100644
--- a/crates/rust-analyzer/src/req.rs
+++ b/crates/rust-analyzer/src/req.rs
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
6 6
7pub use lsp_types::{ 7pub use lsp_types::{
8 notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, 8 notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens,
9 CodeLensParams, CompletionParams, CompletionResponse, DiagnosticTag, 9 CodeLensParams, CompletionParams, CompletionResponse, ConfigurationParams, DiagnosticTag,
10 DidChangeConfigurationParams, DidChangeWatchedFilesParams, 10 DidChangeConfigurationParams, DidChangeWatchedFilesParams,
11 DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, 11 DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams,
12 DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, 12 DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType,
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index ca045f93c..ad096a1d8 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -31,6 +31,24 @@ use crate::{
31use ra_db::ExternSourceId; 31use ra_db::ExternSourceId;
32use rustc_hash::{FxHashMap, FxHashSet}; 32use rustc_hash::{FxHashMap, FxHashSet};
33 33
34fn create_watcher(workspaces: &[ProjectWorkspace], options: &Options) -> Option<CheckWatcher> {
35 // FIXME: Figure out the multi-workspace situation
36 workspaces
37 .iter()
38 .find_map(|w| match w {
39 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
40 ProjectWorkspace::Json { .. } => None,
41 })
42 .map(|cargo| {
43 let cargo_project_root = cargo.workspace_root().to_path_buf();
44 Some(CheckWatcher::new(&options.cargo_watch, cargo_project_root))
45 })
46 .unwrap_or_else(|| {
47 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
48 None
49 })
50}
51
34#[derive(Debug, Clone)] 52#[derive(Debug, Clone)]
35pub struct Options { 53pub struct Options {
36 pub publish_decorations: bool, 54 pub publish_decorations: bool,
@@ -167,21 +185,7 @@ impl WorldState {
167 }); 185 });
168 change.set_crate_graph(crate_graph); 186 change.set_crate_graph(crate_graph);
169 187
170 // FIXME: Figure out the multi-workspace situation 188 let check_watcher = create_watcher(&workspaces, &options);
171 let check_watcher = workspaces
172 .iter()
173 .find_map(|w| match w {
174 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
175 ProjectWorkspace::Json { .. } => None,
176 })
177 .map(|cargo| {
178 let cargo_project_root = cargo.workspace_root().to_path_buf();
179 Some(CheckWatcher::new(&options.cargo_watch, cargo_project_root))
180 })
181 .unwrap_or_else(|| {
182 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
183 None
184 });
185 189
186 let mut analysis_host = AnalysisHost::new(lru_capacity); 190 let mut analysis_host = AnalysisHost::new(lru_capacity);
187 analysis_host.apply_change(change); 191 analysis_host.apply_change(change);
@@ -199,6 +203,18 @@ impl WorldState {
199 } 203 }
200 } 204 }
201 205
206 pub fn update_configuration(
207 &mut self,
208 lru_capacity: Option<usize>,
209 options: Options,
210 feature_flags: FeatureFlags,
211 ) {
212 self.feature_flags = Arc::new(feature_flags);
213 self.analysis_host.update_lru_capacity(lru_capacity);
214 self.check_watcher = create_watcher(&self.workspaces, &options);
215 self.options = options;
216 }
217
202 /// Returns a vec of libraries 218 /// Returns a vec of libraries
203 /// FIXME: better API here 219 /// FIXME: better API here
204 pub fn process_changes( 220 pub fn process_changes(