From 3bcfa0f466ce2d977f83ff1b0da938dbf72b1fc0 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 15 Jul 2019 22:00:20 +0200 Subject: [PATCH] Implement out-of-process compilation --- Cargo.lock | 197 ++++++++++++++++++++++++++++++- Cargo.toml | 9 +- rls-ipc/.gitignore | 3 + rls-ipc/Cargo.toml | 21 ++++ rls-ipc/src/client.rs | 25 ++++ rls-ipc/src/lib.rs | 9 ++ rls-ipc/src/rpc.rs | 80 +++++++++++++ rls-ipc/src/server.rs | 3 + rls-rustc/Cargo.toml | 15 +++ rls-rustc/src/bin/rustc.rs | 4 +- rls-rustc/src/clippy.rs | 90 ++++++++++++++ rls-rustc/src/ipc.rs | 73 ++++++++++++ rls-rustc/src/lib.rs | 232 +++++++++++++++++++++++++++++++++---- rls/src/build/ipc.rs | 163 ++++++++++++++++++++++++++ rls/src/build/mod.rs | 2 + rls/src/build/plan.rs | 5 +- rls/src/build/rustc.rs | 165 ++++++++++++++++++++------ rls/src/config.rs | 11 ++ rls/src/main.rs | 6 +- 19 files changed, 1042 insertions(+), 71 deletions(-) create mode 100644 rls-ipc/.gitignore create mode 100644 rls-ipc/Cargo.toml create mode 100644 rls-ipc/src/client.rs create mode 100644 rls-ipc/src/lib.rs create mode 100644 rls-ipc/src/rpc.rs create mode 100644 rls-ipc/src/server.rs create mode 100644 rls-rustc/src/clippy.rs create mode 100644 rls-rustc/src/ipc.rs create mode 100644 rls/src/build/ipc.rs diff --git a/Cargo.lock b/Cargo.lock index 08ab3d2279b..5e8508e09fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,9 +851,26 @@ name = "json" version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "jsonrpc-client-transports" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-pubsub 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-server-utils 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-tokio-ipc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "jsonrpc-core" -version = "12.2.0" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", @@ -863,6 +880,65 @@ dependencies = [ "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "jsonrpc-core-client" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "jsonrpc-client-transports 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jsonrpc-derive" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jsonrpc-ipc-server" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-server-utils 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-tokio-ipc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jsonrpc-pubsub" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jsonrpc-server-utils" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -942,6 +1018,14 @@ dependencies = [ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lock_api" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.4.8" @@ -1144,6 +1228,23 @@ dependencies = [ "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-tokio-ipc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-named-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.7.1" @@ -1153,6 +1254,16 @@ dependencies = [ "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.4.0" @@ -1165,6 +1276,20 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1185,6 +1310,14 @@ name = "ppv-lite86" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro-crate" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -1472,7 +1605,7 @@ dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "home 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 12.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1485,6 +1618,7 @@ dependencies = [ "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rls-analysis 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", "rls-data 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rls-ipc 0.1.0", "rls-rustc 0.6.0", "rls-span 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "rls-vfs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1530,9 +1664,33 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rls-ipc" +version = "0.1.0" +dependencies = [ + "jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core-client 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-derive 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-ipc-server 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rls-data 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rls-rustc" version = "0.6.0" +dependencies = [ + "clippy_lints 0.0.212 (git+https://github.com/rust-lang/rust-clippy?rev=72da1015d6d918fe1b29170acbf486d30e0c2695)", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rls-data 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rls-ipc 0.1.0", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rls-span" @@ -2148,6 +2306,18 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-named-pipes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-process" version = "0.2.4" @@ -2184,6 +2354,14 @@ dependencies = [ "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-service" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-signal" version = "0.2.7" @@ -2555,7 +2733,13 @@ dependencies = [ "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum jobserver 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "f74e73053eaf95399bf926e48fc7a2a3ce50bd0eaaa2357d391e95b2dcdd4f10" "checksum json 0.11.15 (registry+https://github.com/rust-lang/crates.io-index)" = "92c245af8786f6ac35f95ca14feca9119e71339aaab41e878e7cdd655c97e9e5" -"checksum jsonrpc-core 12.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8df63798dccd2fc909485cc7a8979ab79f398a7cf788e552e17537e06f85d8e" +"checksum jsonrpc-client-transports 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bb6fd4acf48d1f17eb7b0e27ab7043c16f063ad0aa7020ec92a431648286c2f" +"checksum jsonrpc-core 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34d379861584fe4e3678f6ae9ee60b41726df2989578c1dc0f90190dfc92dbe0" +"checksum jsonrpc-core-client 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6b0a3dc76953d88cdb47f5fe4ae21abcabc8d7edf4951ebce42db5c722d6698" +"checksum jsonrpc-derive 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9e2d4475549bc0126690788ed5107573c8917f97db5298f0043fb73d46fc498" +"checksum jsonrpc-ipc-server 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8ec122d46ed08c2ccb42a9fe83b921a7be4f94902631684a4ea763a9059b861" +"checksum jsonrpc-pubsub 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "583f5930821dbc043236fe5d672d496ead7ff83d21351146598386c66fe8722a" +"checksum jsonrpc-server-utils 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04f18ca34046c249751fe90428e77e9570beaa03b33a108e74418a586063d07d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" @@ -2565,6 +2749,7 @@ dependencies = [ "checksum libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "126a1f4078368b163bfdee65fbab072af08a1b374a5551b21e87ade27b1fbf9d" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum lsp-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "169d737ad89cf8ddd82d1804d9122f54568c49377665157277cc90d747b1d31a" "checksum lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe3edefcd66dde1f7f1df706f46520a3c93adc5ca4bc5747da6621195e894efd" @@ -2588,12 +2773,16 @@ dependencies = [ "checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884" "checksum ordslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd20eec3dbe4376829cb7d80ae6ac45e0a766831dca50202ff2d40db46a8a024" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" +"checksum parity-tokio-ipc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8281bf4f1d6429573f89589bf68d89451c46750977a8264f8ea3edbabeba7947" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" +"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" "checksum pulldown-cmark 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "77043da1282374688ee212dc44b3f37ff929431de9c9adc3053bd3cee5630357" @@ -2691,8 +2880,10 @@ dependencies = [ "checksum tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" "checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" +"checksum tokio-named-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d282d483052288b2308ba5ee795f5673b159c9bdf63c385a05609da782a5eae" "checksum tokio-process 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afbd6ef1b8cc2bd2c2b580d882774d443ebb1c6ceefe35ba9ea4ab586c89dbe8" "checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" +"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" "checksum tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd6dc5276ea05ce379a16de90083ec80836440d5ef8a6a39545a3207373b8296" "checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" diff --git a/Cargo.toml b/Cargo.toml index 08d2c81f8af..6174e48b037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,15 +27,17 @@ rls-data = "0.19" rls-rustc = { version = "0.6.0", path = "rls-rustc" } rls-span = "0.5" rls-vfs = "0.8" +rls-ipc = { version = "0.1.0", path = "rls-ipc", optional = true } cargo = { git = "https://github.com/rust-lang/cargo", rev = "1f74bdf4494f4d51dbe3a6af5474e39c8d194ad6" } cargo_metadata = "0.8" clippy_lints = { git = "https://github.com/rust-lang/rust-clippy", rev = "72da1015d6d918fe1b29170acbf486d30e0c2695", optional = true } env_logger = "0.6" failure = "0.1.1" +futures = { version = "0.1", optional = true } home = "0.5" itertools = "0.8" -jsonrpc-core = "12" +jsonrpc-core = "13" lsp-types = { version = "0.60", features = ["proposed"] } lazy_static = "1" log = "0.4" @@ -50,6 +52,7 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" serde_ignored = "0.1" +tokio = { version = "0.1", optional = true } url = "2" walkdir = "2" regex = "1" @@ -76,4 +79,6 @@ tokio-timer = "0.2" rustc_tools_util = "0.2" [features] -clippy = ["clippy_lints"] +clippy = ["clippy_lints", "rls-rustc/clippy"] +ipc = ["tokio", "futures", "rls-rustc/ipc", "rls-ipc/server"] +default = [] diff --git a/rls-ipc/.gitignore b/rls-ipc/.gitignore new file mode 100644 index 00000000000..6aa106405a4 --- /dev/null +++ b/rls-ipc/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/rls-ipc/Cargo.toml b/rls-ipc/Cargo.toml new file mode 100644 index 00000000000..fbc4935136f --- /dev/null +++ b/rls-ipc/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rls-ipc" +version = "0.1.0" +authors = ["Igor Matuszewski "] +edition = "2018" +description = "Inter-process communication (IPC) layer between RLS and rustc" +license = "Apache-2.0/MIT" +repository = "https://github.com/rust-lang/rls" +categories = ["development-tools"] + +[dependencies] +jsonrpc-core = "13" +jsonrpc-core-client = "13" +jsonrpc-derive = "13" +jsonrpc-ipc-server = { version = "13", optional = true } +rls-data = "0.19" +serde = { version = "1.0", features = ["derive"] } + +[features] +client = ["jsonrpc-core-client/ipc"] +server = ["jsonrpc-ipc-server"] diff --git a/rls-ipc/src/client.rs b/rls-ipc/src/client.rs new file mode 100644 index 00000000000..0a2d4758fb0 --- /dev/null +++ b/rls-ipc/src/client.rs @@ -0,0 +1,25 @@ +//! Allows to connect to an IPC server. + +use crate::rpc::callbacks::gen_client::Client as CallbacksClient; +use crate::rpc::file_loader::gen_client::Client as FileLoaderClient; + +pub use jsonrpc_core_client::transports::ipc::connect; +pub use jsonrpc_core_client::{RpcChannel, RpcError}; + +/// Joint IPC client. +#[derive(Clone)] +pub struct Client { + /// File loader interface + pub file_loader: FileLoaderClient, + /// Callbacks interface + pub callbacks: CallbacksClient, +} + +impl From for Client { + fn from(channel: RpcChannel) -> Self { + Client { + file_loader: FileLoaderClient::from(channel.clone()), + callbacks: CallbacksClient::from(channel), + } + } +} diff --git a/rls-ipc/src/lib.rs b/rls-ipc/src/lib.rs new file mode 100644 index 00000000000..04e9f9ff40d --- /dev/null +++ b/rls-ipc/src/lib.rs @@ -0,0 +1,9 @@ +//! Inter-process communication (IPC) layer between RLS and rustc. + +#![deny(missing_docs)] + +#[cfg(feature = "client")] +pub mod client; +pub mod rpc; +#[cfg(feature = "server")] +pub mod server; diff --git a/rls-ipc/src/rpc.rs b/rls-ipc/src/rpc.rs new file mode 100644 index 00000000000..6541d03c621 --- /dev/null +++ b/rls-ipc/src/rpc.rs @@ -0,0 +1,80 @@ +//! Available remote procedure call (RPC) interfaces. + +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; + +use jsonrpc_derive::rpc; +use serde::{Deserialize, Serialize}; + +pub use jsonrpc_core::{Error, Result}; + +// Separated because #[rpc] macro generated a `gen_client` mod and so two +// interfaces cannot be derived in the same scope due to a generated name clash +/// RPC interface for an overriden file loader to be used inside `rustc`. +pub mod file_loader { + use super::*; + // Expanded via #[rpc] + pub use gen_client::Client; + pub use rpc_impl_Rpc::gen_server::Rpc as Server; + + #[rpc] + /// RPC interface for an overriden file loader to be used inside `rustc`. + pub trait Rpc { + /// Query the existence of a file. + #[rpc(name = "file_exists")] + fn file_exists(&self, path: PathBuf) -> Result; + + /// Returns an absolute path to a file, if possible. + #[rpc(name = "abs_path")] + fn abs_path(&self, path: PathBuf) -> Result>; + + /// Read the contents of an UTF-8 file into memory. + #[rpc(name = "read_file")] + fn read_file(&self, path: PathBuf) -> Result; + } +} + +// Separated because #[rpc] macro generated a `gen_client` mod and so two +// interfaces cannot be derived in the same scope due to a generated name clash +/// RPC interface to feed back data from `rustc` instances. +pub mod callbacks { + use super::*; + // Expanded via #[rpc] + pub use gen_client::Client; + pub use rpc_impl_Rpc::gen_server::Rpc as Server; + + #[rpc] + /// RPC interface to feed back data from `rustc` instances. + pub trait Rpc { + /// Hands back computed analysis data for the compiled crate + #[rpc(name = "complete_analysis")] + fn complete_analysis(&self, analysis: rls_data::Analysis) -> Result<()>; + + /// Hands back computed input files for the compiled crate + #[rpc(name = "input_files")] + fn input_files(&self, input_files: HashMap>) -> Result<()>; + } +} + +/// Build system-agnostic, basic compilation unit +#[derive(PartialEq, Eq, Hash, Debug, Clone, Deserialize, Serialize)] +pub struct Crate { + /// Crate name + pub name: String, + /// Optional path to a crate root + pub src_path: Option, + /// Edition in which a given crate is compiled + pub edition: Edition, + /// From rustc; mainly used to group other properties used to disambiguate a + /// given compilation unit. + pub disambiguator: (u64, u64), +} + +/// Rust edition +#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Copy, Clone, Deserialize, Serialize)] +pub enum Edition { + /// Rust 2015 + Edition2015, + /// Rust 2018 + Edition2018, +} diff --git a/rls-ipc/src/server.rs b/rls-ipc/src/server.rs new file mode 100644 index 00000000000..2e2ae7f231f --- /dev/null +++ b/rls-ipc/src/server.rs @@ -0,0 +1,3 @@ +//! Includes facility functions to start an IPC server. + +pub use jsonrpc_ipc_server::{CloseHandle, ServerBuilder}; diff --git a/rls-rustc/Cargo.toml b/rls-rustc/Cargo.toml index b12657bc661..2633c7f5ada 100644 --- a/rls-rustc/Cargo.toml +++ b/rls-rustc/Cargo.toml @@ -9,3 +9,18 @@ repository = "https://github.com/rust-lang/rls" categories = ["development-tools"] [dependencies] +env_logger = "0.6" +log = "0.4" +failure = "0.1" +rand = "0.6" +clippy_lints = { git = "https://github.com/rust-lang/rust-clippy", rev = "72da1015d6d918fe1b29170acbf486d30e0c2695", optional = true } +tokio = { version = "0.1", optional = true } +futures = { version = "0.1", optional = true } +serde = { version = "1", features = ["derive"], optional = true } +rls-data = { version = "0.19", optional = true } +rls-ipc = { path = "../rls-ipc", optional = true } + +[features] +clippy = ["clippy_lints"] +ipc = ["tokio", "futures", "serde", "rls-data", "rls-ipc/client"] +default = [] diff --git a/rls-rustc/src/bin/rustc.rs b/rls-rustc/src/bin/rustc.rs index ff2480aa288..5e5da62c825 100644 --- a/rls-rustc/src/bin/rustc.rs +++ b/rls-rustc/src/bin/rustc.rs @@ -1,3 +1,3 @@ -fn main() { - rls_rustc::run(); +fn main() -> Result<(), ()> { + rls_rustc::run() } diff --git a/rls-rustc/src/clippy.rs b/rls-rustc/src/clippy.rs new file mode 100644 index 00000000000..67388edc064 --- /dev/null +++ b/rls-rustc/src/clippy.rs @@ -0,0 +1,90 @@ +//! Copied from rls/src/config.rs + +use std::str::FromStr; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ClippyPreference { + /// Disable clippy. + Off, + /// Enable clippy, but `allow` clippy lints (i.e., require `warn` override). + OptIn, + /// Enable clippy. + On, +} + +pub fn preference() -> Option { + std::env::var("RLS_CLIPPY_PREFERENCE").ok().and_then(|pref| FromStr::from_str(&pref).ok()) +} + +/// Permissive deserialization for `ClippyPreference` +/// "opt-in", "Optin" -> `ClippyPreference::OptIn` +impl FromStr for ClippyPreference { + type Err = (); + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "off" => Ok(ClippyPreference::Off), + "optin" | "opt-in" => Ok(ClippyPreference::OptIn), + "on" => Ok(ClippyPreference::On), + _ => Err(()), + } + } +} + +pub fn adjust_args(args: Vec, preference: ClippyPreference) -> Vec { + if preference != ClippyPreference::Off { + // Allow feature gating in the same way as `cargo clippy` + let mut clippy_args = vec!["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]; + + if preference == ClippyPreference::OptIn { + // `OptIn`: Require explicit `#![warn(clippy::all)]` annotation in each workspace crate + clippy_args.push("-A".to_owned()); + clippy_args.push("clippy::all".to_owned()); + } + + args.iter().map(ToOwned::to_owned).chain(clippy_args).collect() + } else { + args.to_owned() + } +} + +#[cfg(feature = "clippy")] +pub fn after_parse_callback(compiler: &rustc_interface::interface::Compiler) { + use rustc_plugin::registry::Registry; + + let sess = compiler.session(); + let mut registry = Registry::new( + sess, + compiler + .parse() + .expect( + "at this compilation stage \ + the crate must be parsed", + ) + .peek() + .span, + ); + registry.args_hidden = Some(Vec::new()); + + let conf = clippy_lints::read_conf(®istry); + clippy_lints::register_plugins(&mut registry, &conf); + + let Registry { + early_lint_passes, late_lint_passes, lint_groups, llvm_passes, attributes, .. + } = registry; + let mut ls = sess.lint_store.borrow_mut(); + for pass in early_lint_passes { + ls.register_early_pass(Some(sess), true, false, pass); + } + for pass in late_lint_passes { + ls.register_late_pass(Some(sess), true, false, false, pass); + } + + for (name, (to, deprecated_name)) in lint_groups { + ls.register_group(Some(sess), true, name, deprecated_name, to); + } + clippy_lints::register_pre_expansion_lints(sess, &mut ls, &conf); + clippy_lints::register_renamed(&mut ls); + + sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes); + sess.plugin_attributes.borrow_mut().extend(attributes); +} diff --git a/rls-rustc/src/ipc.rs b/rls-rustc/src/ipc.rs new file mode 100644 index 00000000000..7a7a521875d --- /dev/null +++ b/rls-rustc/src/ipc.rs @@ -0,0 +1,73 @@ +use std::collections::{HashMap, HashSet}; +use std::io; +use std::path::{Path, PathBuf}; + +use failure::Fail; +use futures::Future; + +use rls_ipc::client::{Client as JointClient, RpcChannel, RpcError}; +use rls_ipc::rpc::callbacks::Client as CallbacksClient; +use rls_ipc::rpc::file_loader::Client as FileLoaderClient; + +pub use rls_ipc::client::connect; + +#[derive(Clone)] +pub struct Client(JointClient); + +impl From for Client { + fn from(channel: RpcChannel) -> Self { + Client(channel.into()) + } +} + +#[derive(Clone)] +pub struct IpcFileLoader(FileLoaderClient); + +impl IpcFileLoader { + pub fn into_boxed(self) -> Option> { + Some(Box::new(self)) + } +} + +impl syntax::source_map::FileLoader for IpcFileLoader { + fn file_exists(&self, path: &Path) -> bool { + self.0.file_exists(path.to_owned()).wait().unwrap() + } + + fn abs_path(&self, path: &Path) -> Option { + self.0.abs_path(path.to_owned()).wait().ok()? + } + + fn read_file(&self, path: &Path) -> io::Result { + self.0 + .read_file(path.to_owned()) + .wait() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.compat())) + } +} + +#[derive(Clone)] +pub struct IpcCallbacks(CallbacksClient); + +impl IpcCallbacks { + pub fn complete_analysis( + &self, + analysis: rls_data::Analysis, + ) -> impl Future { + self.0.complete_analysis(analysis) + } + + pub fn input_files( + &self, + input_files: HashMap>, + ) -> impl Future { + self.0.input_files(input_files) + } +} + +impl Client { + pub fn split(self) -> (IpcFileLoader, IpcCallbacks) { + let JointClient { file_loader, callbacks } = self.0; + (IpcFileLoader(file_loader), IpcCallbacks(callbacks)) + } +} diff --git a/rls-rustc/src/lib.rs b/rls-rustc/src/lib.rs index 99da93c6e0b..a3ff1057991 100644 --- a/rls-rustc/src/lib.rs +++ b/rls-rustc/src/lib.rs @@ -1,49 +1,235 @@ #![feature(rustc_private)] extern crate env_logger; -extern crate getopts; extern crate rustc; -extern crate rustc_codegen_utils; extern crate rustc_driver; -extern crate rustc_errors; extern crate rustc_interface; -extern crate rustc_metadata; +extern crate rustc_plugin; +extern crate rustc_save_analysis; extern crate syntax; use rustc::session::config::ErrorOutputType; use rustc::session::early_error; +#[cfg(any(feature = "clippy", feature = "ipc"))] +use rustc_driver::Compilation; use rustc_driver::{run_compiler, Callbacks}; use rustc_interface::interface; use std::env; -use std::process; - -pub fn run() { - env_logger::init(); - let result = rustc_driver::report_ices_to_stderr_if_any(|| { - let args = env::args_os() - .enumerate() - .map(|(i, arg)| { - arg.into_string().unwrap_or_else(|arg| { - early_error( - ErrorOutputType::default(), - &format!("Argument {} is not valid Unicode: {:?}", i, arg), - ) - }) +#[allow(unused_imports)] +use std::path::{Path, PathBuf}; + +#[cfg(feature = "clippy")] +mod clippy; +#[cfg(feature = "ipc")] +mod ipc; + +pub fn run() -> Result<(), ()> { + #[cfg(feature = "ipc")] + let mut rt = tokio::runtime::Runtime::new().unwrap(); + #[cfg(feature = "clippy")] + let clippy_preference = clippy::preference(); + + #[cfg(feature = "ipc")] + let (mut shim_calls, file_loader) = match std::env::var("RLS_IPC_ENDPOINT").ok() { + Some(endpoint) => { + #[allow(deprecated)] // Windows doesn't work with lazily-bound reactors + let reactor = rt.reactor().clone(); + let connection = + ipc::connect(endpoint, &reactor).expect("Couldn't connect to IPC endpoint"); + let client: ipc::Client = + rt.block_on(connection).expect("Couldn't connect to IPC endpoint"); + let (file_loader, callbacks) = client.split(); + + ( + ShimCalls { + callbacks: Some(callbacks), + #[cfg(feature = "clippy")] + clippy_preference, + }, + file_loader.into_boxed(), + ) + } + None => (ShimCalls::default(), None), + }; + #[cfg(not(feature = "ipc"))] + let (mut shim_calls, file_loader) = (ShimCalls::default(), None); + + let args = env::args_os() + .enumerate() + .map(|(i, arg)| { + arg.into_string().unwrap_or_else(|arg| { + early_error( + ErrorOutputType::default(), + &format!("Argument {} is not valid Unicode: {:?}", i, arg), + ) }) - .collect::>(); + }) + .collect::>(); - run_compiler(&args, &mut ShimCalls, None, None) + #[cfg(feature = "clippy")] + let args = match clippy_preference { + Some(preference) => clippy::adjust_args(args, preference), + None => args, + }; + + rustc_driver::report_ices_to_stderr_if_any(|| { + run_compiler(&args, &mut shim_calls, file_loader, None) }) - .and_then(|result| result); - process::exit(result.is_err() as i32); + .map(|_| ()) + .map_err(|_| ()) } -struct ShimCalls; +#[derive(Default)] +struct ShimCalls { + #[cfg(feature = "ipc")] + callbacks: Option, + #[cfg(feature = "clippy")] + clippy_preference: Option, +} impl Callbacks for ShimCalls { fn config(&mut self, config: &mut interface::Config) { config.opts.debugging_opts.continue_parse_after_error = true; config.opts.debugging_opts.save_analysis = true; } + + #[cfg(feature = "clippy")] + fn after_parsing(&mut self, compiler: &interface::Compiler) -> Compilation { + match self.clippy_preference { + Some(preference) if preference != clippy::ClippyPreference::Off => { + clippy::after_parse_callback(compiler); + } + _ => {} + } + + Compilation::Continue + } + + #[cfg(feature = "ipc")] + fn after_expansion(&mut self, compiler: &interface::Compiler) -> Compilation { + use rustc::session::config::Input; + + use futures::future::Future; + use rls_ipc::rpc::{Crate, Edition}; + use std::collections::{HashMap, HashSet}; + + let callbacks = match self.callbacks.as_ref() { + Some(callbacks) => callbacks, + None => return Compilation::Continue, + }; + + let sess = compiler.session(); + let input = compiler.input(); + let crate_name = compiler.crate_name().unwrap().peek().clone(); + + let cwd = &sess.working_dir.0; + + let src_path = match input { + Input::File(ref name) => Some(name.to_path_buf()), + Input::Str { .. } => None, + } + .and_then(|path| src_path(Some(cwd), path)); + + let krate = Crate { + name: crate_name.to_owned(), + src_path, + disambiguator: sess.local_crate_disambiguator().to_fingerprint().as_value(), + edition: match sess.edition() { + syntax::edition::Edition::Edition2015 => Edition::Edition2015, + syntax::edition::Edition::Edition2018 => Edition::Edition2018, + }, + }; + + let mut input_files: HashMap> = HashMap::new(); + for file in fetch_input_files(sess) { + input_files.entry(file).or_default().insert(krate.clone()); + } + + if let Err(e) = callbacks.input_files(input_files).wait() { + log::error!("Can't send input files as part of a compilation callback: {:?}", e); + } + + Compilation::Continue + } + + #[cfg(feature = "ipc")] + fn after_analysis(&mut self, compiler: &interface::Compiler) -> Compilation { + use futures::future::Future; + + let callbacks = match self.callbacks.as_ref() { + Some(callbacks) => callbacks, + None => return Compilation::Continue, + }; + + use rustc_save_analysis::CallbackHandler; + + let input = compiler.input(); + let crate_name = compiler.crate_name().unwrap().peek().clone(); + + // Guaranteed to not be dropped yet in the pipeline thanks to the + // `config.opts.debugging_opts.save_analysis` value being set to `true`. + let expanded_crate = &compiler.expansion().unwrap().peek().0; + compiler.global_ctxt().unwrap().peek_mut().enter(|tcx| { + // There are two ways to move the data from rustc to the RLS, either + // directly or by serialising and deserialising. We only want to do + // the latter when there are compatibility issues between crates. + + // This version passes via JSON, it is more easily backwards compatible. + // save::process_crate(state.tcx.unwrap(), + // state.expanded_crate.unwrap(), + // state.analysis.unwrap(), + // state.crate_name.unwrap(), + // state.input, + // None, + // save::DumpHandler::new(state.out_dir, + // state.crate_name.unwrap())); + // This version passes directly, it is more efficient. + rustc_save_analysis::process_crate( + tcx, + &expanded_crate, + &crate_name, + &input, + None, + CallbackHandler { + callback: &mut |a| { + let analysis = unsafe { ::std::mem::transmute(a.clone()) }; + if let Err(e) = callbacks.complete_analysis(analysis).wait() { + log::error!( + "Can't send analysis as part of a compilation callback: {:?}", + e + ); + } + }, + }, + ); + }); + + Compilation::Continue + } +} + +#[cfg(feature = "ipc")] +fn fetch_input_files(sess: &rustc::session::Session) -> Vec { + let cwd = &sess.working_dir.0; + + sess.source_map() + .files() + .iter() + .filter(|fmap| fmap.is_real_file()) + .filter(|fmap| !fmap.is_imported()) + .map(|fmap| fmap.name.to_string()) + .map(|fmap| src_path(Some(cwd), fmap).unwrap()) + .collect() +} + +#[cfg(feature = "ipc")] +fn src_path(cwd: Option<&Path>, path: impl AsRef) -> Option { + let path = path.as_ref(); + + Some(match (cwd, path.is_absolute()) { + (_, true) => path.to_owned(), + (Some(cwd), _) => cwd.join(path), + (None, _) => std::env::current_dir().ok()?.join(path), + }) } diff --git a/rls/src/build/ipc.rs b/rls/src/build/ipc.rs new file mode 100644 index 00000000000..772f3c152cd --- /dev/null +++ b/rls/src/build/ipc.rs @@ -0,0 +1,163 @@ +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::sync::Mutex; +use std::time::Duration; +use std::{env, fs}; + +use jsonrpc_core::{ErrorCode, IoHandler}; + +use crate::build::plan::Crate; + +use rls_ipc::rpc::{self, Error, Result as RpcResult}; +use rls_ipc::server::{CloseHandle, ServerBuilder}; + +/// An IPC server spawned on a different thread. +pub struct Server { + endpoint: PathBuf, + join_handle: std::thread::JoinHandle<()>, + close_handle: CloseHandle, +} + +impl Server { + /// Returns an endpoint on which the server is listening. + pub fn endpoint(&self) -> &Path { + &self.endpoint + } + + /// Shuts down the IPC server and waits on the thread it was spawned on. + pub fn close(self) { + self.close_handle.close(); + let _ = self.join_handle.join(); + } +} + +/// Starts an IPC server in the background supporting both VFS requests and data +/// callbacks used by rustc for the out-of-process compilation. +pub fn start_with_all( + changed_files: HashMap, + analysis: Arc>>, + input_files: Arc>>>, +) -> Result { + use rls_ipc::rpc::callbacks::Server as _; + use rls_ipc::rpc::file_loader::Server as _; + + let mut io = IoHandler::new(); + io.extend_with(ChangedFiles(changed_files).to_delegate()); + io.extend_with(callbacks::CallbackHandler { analysis, input_files }.to_delegate()); + + self::start_with_handler(io) +} + +/// Spins up an IPC server in the background. +pub fn start_with_handler(io: IoHandler) -> Result { + let endpoint_path = gen_endpoint_path(); + let (tx, rx) = std::sync::mpsc::channel(); + let join_handle = std::thread::spawn({ + let endpoint_path = endpoint_path.clone(); + move || { + log::trace!("Attempting to spin up IPC server at {}", endpoint_path); + let runtime = tokio::runtime::Builder::new().core_threads(1).build().unwrap(); + #[allow(deprecated)] // Windows won't work with lazily bound reactor + let (reactor, executor) = (runtime.reactor(), runtime.executor()); + + let server = ServerBuilder::new(io) + .event_loop_executor(executor) + .event_loop_reactor(reactor.clone()) + .start(&endpoint_path) + .map_err(|_| log::warn!("Couldn't open socket")) + .unwrap(); + log::trace!("Started the IPC server at {}", endpoint_path); + + tx.send(server.close_handle()).unwrap(); + server.wait(); + } + }); + + rx.recv_timeout(Duration::from_secs(5)) + .map(|close_handle| Server { endpoint: endpoint_path.into(), join_handle, close_handle }) + .map_err(|_| ()) +} + +fn gen_endpoint_path() -> String { + let num: u64 = rand::Rng::gen(&mut rand::thread_rng()); + if cfg!(windows) { + format!(r"\\.\pipe\ipc-pipe-{}", num) + } else { + format!(r"/tmp/ipc-uds-{}", num) + } +} + +fn rpc_error(msg: &str) -> Error { + Error { code: ErrorCode::InternalError, message: msg.to_owned(), data: None } +} + +mod callbacks { + use super::PathBuf; + use super::{rpc, RpcResult}; + use super::{Arc, Mutex}; + use super::{HashMap, HashSet}; + + impl From for crate::build::plan::Crate { + fn from(krate: rls_ipc::rpc::Crate) -> Self { + Self { + name: krate.name, + src_path: krate.src_path, + edition: match krate.edition { + rls_ipc::rpc::Edition::Edition2015 => crate::build::plan::Edition::Edition2015, + rls_ipc::rpc::Edition::Edition2018 => crate::build::plan::Edition::Edition2018, + }, + disambiguator: krate.disambiguator, + } + } + } + + pub struct CallbackHandler { + pub analysis: Arc>>, + pub input_files: Arc>>>, + } + + impl rpc::callbacks::Rpc for CallbackHandler { + fn complete_analysis(&self, analysis: rls_data::Analysis) -> RpcResult<()> { + *self.analysis.lock().unwrap() = Some(analysis); + Ok(()) + } + + fn input_files( + &self, + input_files: HashMap>, + ) -> RpcResult<()> { + let mut current_files = self.input_files.lock().unwrap(); + for (file, crates) in input_files { + current_files.entry(file).or_default().extend(crates.into_iter().map(From::from)); + } + Ok(()) + } + } +} + +pub struct ChangedFiles(HashMap); + +impl rpc::file_loader::Rpc for ChangedFiles { + fn file_exists(&self, path: PathBuf) -> RpcResult { + // Copied from syntax::source_map::RealFileLoader + Ok(fs::metadata(path).is_ok()) + } + fn abs_path(&self, path: PathBuf) -> RpcResult> { + // Copied from syntax::source_map::RealFileLoader + Ok(if path.is_absolute() { + Some(path.to_path_buf()) + } else { + env::current_dir().ok().map(|cwd| cwd.join(path)) + }) + } + fn read_file(&self, path: PathBuf) -> RpcResult { + if let Some(abs_path) = self.abs_path(path.clone()).ok().and_then(|x| x) { + if self.0.contains_key(&abs_path) { + return Ok(self.0[&abs_path].clone()); + } + } + + fs::read_to_string(path).map_err(|e| rpc_error(&e.to_string())) + } +} diff --git a/rls/src/build/mod.rs b/rls/src/build/mod.rs index 88be633e5dc..17c55ff39ac 100644 --- a/rls/src/build/mod.rs +++ b/rls/src/build/mod.rs @@ -27,6 +27,8 @@ mod cargo; mod cargo_plan; pub mod environment; mod external; +#[cfg(feature = "ipc")] +mod ipc; mod plan; mod rustc; diff --git a/rls/src/build/plan.rs b/rls/src/build/plan.rs index c4df0a3ac0e..549b85b2e17 100644 --- a/rls/src/build/plan.rs +++ b/rls/src/build/plan.rs @@ -14,6 +14,7 @@ use std::sync::Arc; use cargo::util::ProcessBuilder; use log::trace; +use serde::{Deserialize, Serialize}; use crate::actions::progress::ProgressUpdate; use crate::build::cargo_plan::CargoPlan; @@ -220,7 +221,7 @@ impl JobQueue { } /// Build system-agnostic, basic compilation unit -#[derive(PartialEq, Eq, Hash, Debug, Clone)] +#[derive(PartialEq, Eq, Hash, Debug, Clone, Deserialize, Serialize)] pub struct Crate { pub name: String, pub src_path: Option, @@ -231,7 +232,7 @@ pub struct Crate { } // Temporary, until Edition from rustfmt is available -#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Copy, Clone)] +#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Copy, Clone, Deserialize, Serialize)] pub enum Edition { Edition2015, Edition2018, diff --git a/rls/src/build/rustc.rs b/rls/src/build/rustc.rs index 51e4585861d..1c8e45c0637 100644 --- a/rls/src/build/rustc.rs +++ b/rls/src/build/rustc.rs @@ -25,6 +25,7 @@ use std::collections::{HashMap, HashSet}; use std::env; use std::ffi::OsString; use std::io; +use std::mem; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::{Arc, Mutex}; @@ -46,7 +47,7 @@ use crate::build::plan::{Crate, Edition}; use crate::build::{BufWriter, BuildResult}; use crate::config::{ClippyPreference, Config}; -// Runs a single instance of Rustc (in-process). +// Runs a single instance of Rustc. pub(crate) fn rustc( vfs: &Vfs, args: &[String], @@ -66,22 +67,114 @@ pub(crate) fn rustc( let changed = vfs.get_cached_files(); - let mut local_envs = envs.clone(); + let mut envs = envs.clone(); let clippy_preference = { let config = rls_config.lock().unwrap(); if config.clear_env_rust_log { - local_envs.insert(String::from("RUST_LOG"), None); + envs.insert(String::from("RUST_LOG"), None); } config.clippy_preference }; - let (guard, _) = env_lock.lock(); - let restore_env = Environment::push_with_lock(&local_envs, cwd, guard); + let lock_environment = |envs, cwd| { + let (guard, _) = env_lock.lock(); + Environment::push_with_lock(envs, cwd, guard) + }; + + let CompilationResult { result, stderr, analysis, input_files } = match std::env::var( + "RLS_OUT_OF_PROCESS", + ) { + #[cfg(feature = "ipc")] + Ok(..) => run_out_of_process(changed.clone(), &args, &envs, clippy_preference) + .unwrap_or_else(|_| { + run_in_process(changed, &args, clippy_preference, lock_environment(&envs, cwd)) + }), + #[cfg(not(feature = "ipc"))] + Ok(..) => { + log::warn!("Support for out-of-process compilation was not compiled. Rebuild with 'ipc' feature enabled"); + run_in_process(changed, &args, clippy_preference, lock_environment(&envs, cwd)) + } + Err(..) => run_in_process(changed, &args, clippy_preference, lock_environment(&envs, cwd)), + }; + + let stderr = String::from_utf8(stderr).unwrap(); + log::debug!("rustc - stderr: {}", &stderr); + let stderr_json_msgs: Vec<_> = stderr.lines().map(String::from).collect(); + + let analysis = analysis.map(|analysis| vec![analysis]).unwrap_or_else(Vec::new); + log::debug!("rustc: analysis read successfully?: {}", !analysis.is_empty()); + + let cwd = cwd.unwrap_or_else(|| Path::new(".")).to_path_buf(); + + BuildResult::Success(cwd, stderr_json_msgs, analysis, input_files, result.is_ok()) +} + +/// Resulting data from compiling a crate (in the rustc sense) +pub struct CompilationResult { + /// Whether compilation was succesful + result: Result<(), ()>, + stderr: Vec, + analysis: Option, + // TODO: Move to Vec + input_files: HashMap>, +} + +#[cfg(feature = "ipc")] +fn run_out_of_process( + changed: HashMap, + args: &[String], + envs: &HashMap>, + clippy_preference: ClippyPreference, +) -> Result { + let analysis = Arc::default(); + let input_files = Arc::default(); + + let ipc_server = + super::ipc::start_with_all(changed, Arc::clone(&analysis), Arc::clone(&input_files))?; + + // Compiling out of process is only supported by our own shim + let rustc_shim = env::current_exe() + .ok() + .and_then(|x| x.to_str().map(String::from)) + .expect("Couldn't set executable for RLS rustc shim"); + + let output = Command::new(rustc_shim) + .env(crate::RUSTC_SHIM_ENV_VAR_NAME, "1") + .env("RLS_IPC_ENDPOINT", ipc_server.endpoint()) + .env("RLS_CLIPPY_PREFERENCE", clippy_preference.to_string()) + .args(args.iter().skip(1)) + .envs(envs.iter().filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))) + .output() + .map_err(|_| ()); + + let result = match &output { + Ok(output) if output.status.code() == Some(0) => Ok(()), + _ => Err(()), + }; + // NOTE: Make sure that we pass JSON error format + let stderr = output.map(|out| out.stderr).unwrap_or_default(); + + ipc_server.close(); + + let input_files = unwrap_shared(input_files, "Other ref dropped by closed IPC server"); + let analysis = unwrap_shared(analysis, "Other ref dropped by closed IPC server"); + // FIXME(#25): given that we are running the compiler directly, there is no need + // to serialize the error messages -- we should pass them in memory. + Ok(CompilationResult { result, stderr, analysis, input_files }) +} + +fn run_in_process( + changed: HashMap, + args: &[String], + clippy_preference: ClippyPreference, + environment_lock: Environment<'_>, +) -> CompilationResult { + let mut callbacks = RlsRustcCalls { clippy_preference, ..Default::default() }; + let input_files = Arc::clone(&callbacks.input_files); + let analysis = Arc::clone(&callbacks.analysis); - let buf = Arc::new(Mutex::new(vec![])); - let err_buf = Arc::clone(&buf); let args: Vec<_> = if cfg!(feature = "clippy") && clippy_preference != ClippyPreference::Off { // Allow feature gating in the same way as `cargo clippy` let mut clippy_args = vec!["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]; @@ -97,41 +190,35 @@ pub(crate) fn rustc( args.to_owned() }; - let mut callbacks = RlsRustcCalls { clippy_preference, ..Default::default() }; - let analysis = Arc::clone(&callbacks.analysis); - let input_files = Arc::clone(&callbacks.input_files); - // rustc explicitly panics in `run_compiler()` on compile failure, regardless // of whether it encounters an ICE (internal compiler error) or not. // TODO: Change librustc_driver behaviour to distinguish between ICEs and // regular compilation failure with errors? - let result = ::std::panic::catch_unwind(|| { - rustc_driver::report_ices_to_stderr_if_any(move || { - // Replace stderr so we catch most errors. - run_compiler( - &args, - &mut callbacks, - Some(Box::new(ReplacedFileLoader::new(changed))), - Some(Box::new(BufWriter(buf))), - ) - }) - }); - - // FIXME(#25): given that we are running the compiler directly, there is no need - // to serialize the error messages -- we should pass them in memory. - let err_buf = Arc::try_unwrap(err_buf).unwrap().into_inner().unwrap(); - let err_buf = String::from_utf8(err_buf).unwrap(); - let stderr_json_msgs: Vec<_> = err_buf.lines().map(String::from).collect(); - - let analysis = analysis.lock().unwrap().clone(); - let analysis = analysis.map(|analysis| vec![analysis]).unwrap_or_else(Vec::new); - log::debug!("rustc: analysis read successfully?: {}", !analysis.is_empty()); - - let input_files = Arc::try_unwrap(input_files).unwrap().into_inner().unwrap(); + let stderr = Arc::default(); + let result = std::panic::catch_unwind({ + let stderr = Arc::clone(&stderr); + || { + rustc_driver::report_ices_to_stderr_if_any(move || { + // Replace stderr so we catch most errors. + run_compiler( + &args, + &mut callbacks, + Some(Box::new(ReplacedFileLoader::new(changed))), + Some(Box::new(BufWriter(stderr))), + ) + }) + } + }) + .map(|_| ()) + .map_err(|_| ()); + // Explicitly drop the global environment lock + mem::drop(environment_lock); - let cwd = cwd.unwrap_or_else(|| restore_env.get_old_cwd()).to_path_buf(); + let stderr = unwrap_shared(stderr, "Other ref dropped by scoped compilation"); + let input_files = unwrap_shared(input_files, "Other ref dropped by scoped compilation"); + let analysis = unwrap_shared(analysis, "Other ref dropped by scoped compilation"); - BuildResult::Success(cwd, stderr_json_msgs, analysis, input_files, result.is_ok()) + CompilationResult { result, stderr, analysis, input_files } } // Our compiler controller. We mostly delegate to the default rustc @@ -226,7 +313,7 @@ impl rustc_driver::Callbacks for RlsRustcCalls { CallbackHandler { callback: &mut |a| { let mut analysis = self.analysis.lock().unwrap(); - let a = unsafe { ::std::mem::transmute(a.clone()) }; + let a = unsafe { mem::transmute(a.clone()) }; *analysis = Some(a); }, }, @@ -352,3 +439,7 @@ pub fn src_path(cwd: Option<&Path>, path: impl AsRef) -> Option { (None, _) => std::env::current_dir().ok()?.join(path), }) } + +fn unwrap_shared(shared: Arc>, msg: &'static str) -> T { + Arc::try_unwrap(shared).expect(msg).into_inner().unwrap() +} diff --git a/rls/src/config.rs b/rls/src/config.rs index b500f73fe6f..be2aeef2a46 100644 --- a/rls/src/config.rs +++ b/rls/src/config.rs @@ -383,6 +383,17 @@ impl FromStr for ClippyPreference { } } +impl ToString for ClippyPreference { + fn to_string(&self) -> String { + match self { + ClippyPreference::Off => "off", + ClippyPreference::OptIn => "optin", + ClippyPreference::On => "on", + } + .to_string() + } +} + /// Permissive custom deserialization for `ClippyPreference` using `FromStr`. fn deserialize_clippy_preference<'de, T, D>(deserializer: D) -> Result where diff --git a/rls/src/main.rs b/rls/src/main.rs index cf7dcf24937..c0192e4b5a1 100644 --- a/rls/src/main.rs +++ b/rls/src/main.rs @@ -39,8 +39,10 @@ fn main_inner() -> i32 { } if env::var(rls::RUSTC_SHIM_ENV_VAR_NAME).ok().map_or(false, |v| v != "0") { - rustc_shim::run(); - return 0; + match rustc_shim::run() { + Ok(..) => return 0, + Err(..) => return 101, + } } if let Some(first_arg) = env::args().nth(1) {