From 393d62c726573a0e6c351f422dbef4b17a5944bf Mon Sep 17 00:00:00 2001 From: Kevin Song Date: Wed, 4 May 2022 15:40:44 -0500 Subject: [PATCH] feat(rust): Display toolchain names (#3414) This is an actualization of PR #559 as originally envisioned by qryxip. Adds the ability to display toolchain versions, either as extracted from environment/settings files or by getting the host triple. As part of this, several other major changes were needed: - Many of the smaller functions within the code have been fused, moved, or dropped. - The Rustup environmental info is now initialized lazily using OnceCells. This will hopefully lead to a performance increase. - New configuration variables (`toolchain` and `numver`) have been added to allow finer-grained configuration. - Override information is no longer read from `rustup` output. Instead, it is parsed from the same files that rustup would use to determine this info. Co-authored-by: qryxip Co-authored-by: qryxip --- Cargo.lock | 274 ++++++-- Cargo.toml | 2 + docs/config/README.md | 12 +- src/modules/rust.rs | 1528 +++++++++++++++++++++++++---------------- 4 files changed, 1148 insertions(+), 668 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1e4d383..5c7d900b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,19 +33,20 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "async-broadcast" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b" +checksum = "1bbd92a9bd0e9c1298118ecf8a2f825e86b12c3ec9e411573e34aaf3a0c03cdd" dependencies = [ "easy-parallel", "event-listener", "futures-core", + "parking_lot", ] [[package]] @@ -321,9 +322,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.3" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" dependencies = [ "bytes", "memchr", @@ -340,9 +341,9 @@ dependencies = [ [[package]] name = "const_format" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22bc6cd49b0ec407b680c3e380182b6ac63b73991cb7602de350352fc309b614" +checksum = "0936ffe6d0c8d6a51b3b0a73b2acbe925d786f346cf45bfddc8341d79fb7dc8a" dependencies = [ "const_format_proc_macros", ] @@ -559,9 +560,9 @@ checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" [[package]] name = "dyn-clone" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "easy-parallel" @@ -577,9 +578,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", "serde", @@ -596,6 +597,27 @@ dependencies = [ "syn", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -659,6 +681,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9d758e60b45e8d749c89c1b389ad8aee550f86aa12e2b9298b546dda7a82ab1" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures-core" version = "0.3.21" @@ -765,9 +793,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "5e77a14ffc6ba4ad5188d6cf428894c4fcfda725326b37558f35bb677e712cec" dependencies = [ "bitflags", "libc", @@ -776,6 +804,18 @@ dependencies = [ "url", ] +[[package]] +name = "guess_host_triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35a8ce923c7490629d84e12fa2f75e1733f1ec692a47c264f9b7fd632855afc" +dependencies = [ + "errno", + "libc", + "log", + "winapi", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -784,9 +824,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" dependencies = [ "ahash", ] @@ -821,6 +861,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi", +] + [[package]] name = "idna" version = "0.2.3" @@ -896,15 +945,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.3+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de" dependencies = [ "cc", "libc", @@ -914,9 +963,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859" +checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e" dependencies = [ "cc", "libc", @@ -937,10 +986,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6a104730949fbc4c78e4fa98ed769ca0faa02e9818936b61032d2d77526afa9" [[package]] -name = "log" -version = "0.4.16" +name = "lock_api" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -990,9 +1049,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmem" @@ -1119,9 +1178,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1129,9 +1188,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -1228,7 +1287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" dependencies = [ "dlv-list", - "hashbrown 0.12.0", + "hashbrown 0.12.1", ] [[package]] @@ -1264,6 +1323,31 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "path-slash" version = "0.1.4" @@ -1375,9 +1459,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1476,9 +1560,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] @@ -1505,13 +1589,26 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.7.3" @@ -1557,6 +1654,21 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -1617,6 +1729,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1778,9 +1899,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ "proc-macro2", "quote", @@ -1893,9 +2014,15 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" @@ -1921,6 +2048,8 @@ dependencies = [ "dunce", "gethostname", "git2", + "guess_host_triple", + "home", "indexmap", "local_ipaddress", "log", @@ -2015,9 +2144,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -2034,6 +2163,16 @@ dependencies = [ "libc", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -2138,18 +2277,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2179,9 +2318,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -2225,6 +2364,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uds_windows" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486992108df0fe0160680af1941fe856c521be931d5a5ecccefe0de86dc47e4a" +dependencies = [ + "tempdir", + "winapi", +] + [[package]] name = "unicase" version = "2.6.0" @@ -2236,9 +2385,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-normalization" @@ -2263,9 +2412,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "uom" @@ -2566,9 +2715,9 @@ dependencies = [ [[package]] name = "zbus" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb86f3d4592e26a48b2719742aec94f8ae6238ebde20d98183ee185d1275e9a" +checksum = "53819092b9db813b2c6168b097b4b13ad284d81c9f2b0165a0a1b190e505a1f3" dependencies = [ "async-broadcast", "async-channel", @@ -2595,6 +2744,7 @@ dependencies = [ "serde_repr", "sha1", "static_assertions", + "uds_windows", "winapi", "zbus_macros", "zbus_names", @@ -2603,9 +2753,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36823cc10fddc3c6b19f048903262dacaf8274170e9a255784bdd8b4570a8040" +checksum = "c7174ebe6722c280d6d132d694bb5664ce50a788cb70eeb518e7fc1ca095a114" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2627,9 +2777,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.1.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ea5dc38b2058fae6a5b79009388143dadce1e91c26a67f984a0fc0381c8033" +checksum = "e18ba99d71e03af262953f476071607da0c44e225236cf9b5b9f7f11f1d0b6b0" dependencies = [ "byteorder", "enumflags2", @@ -2641,9 +2791,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.1.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2cecc5a61c2a053f7f653a24cd15b3b0195d7f7ddb5042c837fb32e161fb7a" +checksum = "9042892ebdca35261951a83d17bcbfd4d3d528cb3bde828498a9b306b50d05c0" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index ca46148b..c1f954a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,8 @@ yaml-rust = "0.4.5" process_control = { version = "3.4.0", features = ["crossbeam-channel"] } +guess_host_triple = "0.1.3" +home = "0.5.3" shell-words = "1.1.0" [dependencies.schemars] diff --git a/docs/config/README.md b/docs/config/README.md index 2e011535..3d9be60d 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -2991,11 +2991,13 @@ The module will be shown if any of the following conditions are met: ### Variables -| Variable | Example | Description | -| -------- | ----------------- | ------------------------------------ | -| version | `v1.43.0-nightly` | The version of `rustc` | -| symbol | | Mirrors the value of option `symbol` | -| style\* | | Mirrors the value of option `style` | +| Variable | Example | Description | +| --------- | ----------------- | -------------------------------------------- | +| version | `v1.43.0-nightly` | The version of `rustc` | +| numver | `1.51.0` | The numeric component of the `rustc` version | +| toolchain | `beta` | The toolchain version | +| symbol | | Mirrors the value of option `symbol` | +| style\* | | Mirrors the value of option `style` | *: This variable can only be used as a part of a style string diff --git a/src/modules/rust.rs b/src/modules/rust.rs index dddf3075..5ccc54af 100644 --- a/src/modules/rust.rs +++ b/src/modules/rust.rs @@ -1,601 +1,927 @@ -use std::fs; -use std::path::Path; -#[cfg(windows)] -use std::path::PathBuf; -use std::process::Output; - -use serde::Deserialize; - -use super::{Context, Module, ModuleConfig}; - -use crate::configs::rust::RustConfig; -use crate::formatter::{StringFormatter, VersionFormatter}; -use crate::utils::create_command; - -/// Creates a module with the current Rust version -pub fn module<'a>(context: &'a Context) -> Option> { - let mut module = context.new_module("rust"); - let config = RustConfig::try_load(module.config); - - let is_rs_project = context - .try_begin_scan()? - .set_files(&config.detect_files) - .set_extensions(&config.detect_extensions) - .set_folders(&config.detect_folders) - .is_match(); - - if !is_rs_project { - return None; - } - - let parsed = StringFormatter::new(config.format).and_then(|formatter| { - formatter - .map_meta(|var, _| match var { - "symbol" => Some(config.symbol), - _ => None, - }) - .map_style(|variable| match variable { - "style" => Some(Ok(config.style)), - _ => None, - }) - .map(|variable| match variable { - // This may result in multiple calls to `get_module_version` when a user have - // multiple `$version` variables defined in `format`. - "version" => get_module_version(context, &config).map(Ok), - _ => None, - }) - .parse(None, Some(context)) - }); - - module.set_segments(match parsed { - Ok(segments) => segments, - Err(error) => { - log::warn!("Error in module `rust`:\n{}", error); - return None; - } - }); - - Some(module) -} - -fn get_module_version(context: &Context, config: &RustConfig) -> Option { - // `$CARGO_HOME/bin/rustc(.exe) --version` may attempt installing a rustup toolchain. - // https://github.com/starship/starship/issues/417 - // - // To display appropriate versions preventing `rustc` from downloading toolchains, we have to - // check - // 1. `$RUSTUP_TOOLCHAIN` - // 2. `rustup override list` - // 3. `rust-toolchain` or `rust-toolchain.toml` in `.` or parent directories - // 4. `rustup default` - // as `rustup` does. - // https://github.com/rust-lang/rustup.rs/tree/eb694fcada7becc5d9d160bf7c623abe84f8971d#override-precedence - // - // Probably we have no other way to know whether any toolchain override is specified for the - // current directory. The following commands also cause toolchain installations. - // - `rustup show` - // - `rustup show active-toolchain` - // - `rustup which` - if let Some(toolchain) = env_rustup_toolchain(context) - .or_else(|| execute_rustup_override_list(context)) - .or_else(|| find_rust_toolchain_file(context)) - .or_else(|| execute_rustup_default(context)) - { - match execute_rustup_run_rustc_version(context, &toolchain) { - RustupRunRustcVersionOutcome::RustcVersion(rustc_version) => { - format_rustc_version(&rustc_version, config.version_format) - } - RustupRunRustcVersionOutcome::ToolchainName(toolchain) => Some(toolchain), - RustupRunRustcVersionOutcome::RustupNotWorking => { - // If `rustup` is not in `$PATH` or cannot be executed for other reasons, we can - // safely execute `rustc --version`. - format_rustc_version(&execute_rustc_version(context)?, config.version_format) - } - RustupRunRustcVersionOutcome::Err => None, - } - } else { - format_rustc_version(&execute_rustc_version(context)?, config.version_format) - } -} - -fn env_rustup_toolchain(context: &Context) -> Option { - let val = context.get_env("RUSTUP_TOOLCHAIN")?; - Some(val.trim().to_owned()) -} - -fn execute_rustup_override_list(context: &Context) -> Option { - extract_toolchain_from_rustup_override_list( - &context.exec_cmd("rustup", &["override", "list"])?.stdout, - &context.current_dir, - ) -} - -fn execute_rustup_default(context: &Context) -> Option { - // `rustup default` output is: - // stable-x86_64-apple-darwin (default) - context - .exec_cmd("rustup", &["default"])? - .stdout - .split_whitespace() - .next() - .map(str::to_owned) -} - -fn extract_toolchain_from_rustup_override_list(stdout: &str, cwd: &Path) -> Option { - if stdout == "no overrides\n" { - return None; - } - - #[cfg(windows)] - let cwd = { - // use display version of path, also allows stripping \\?\ - let cwd = cwd.to_string_lossy(); - // rustup strips \\?\ prefix, - // so we do the same and convert back to a `Path` - PathBuf::from(cwd.strip_prefix(r"\\?\").unwrap_or(&cwd)) - }; - - stdout - .lines() - .filter_map(|line| { - let (dir, toolchain) = line.split_once('\t')?; - Some((dir.trim(), toolchain.trim())) - }) - // find most specific match - .filter(|(dir, _)| cwd.starts_with(dir)) - .max_by_key(|(dir, _)| dir.len()) - .map(|(_, toolchain)| toolchain.to_owned()) -} - -fn find_rust_toolchain_file(context: &Context) -> Option { - // Look for 'rust-toolchain' or 'rust-toolchain.toml' as rustup does. - // for more information: - // https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file - // for the implementation in 'rustup': - // https://github.com/rust-lang/rustup/blob/a45e4cd21748b04472fce51ba29999ee4b62bdec/src/config.rs#L631 - - #[derive(Deserialize)] - struct OverrideFile { - toolchain: ToolchainSection, - } - - #[derive(Deserialize)] - struct ToolchainSection { - channel: Option, - } - - fn read_channel(path: &Path, only_toml: bool) -> Option { - let contents = fs::read_to_string(path).ok()?; - - match contents.lines().count() { - 0 => None, - 1 if !only_toml => Some(contents), - _ => { - toml::from_str::(&contents) - .ok()? - .toolchain - .channel - } - } - .filter(|c| !c.trim().is_empty()) - .map(|c| c.trim().to_owned()) - } - - if context - .dir_contents() - .map_or(false, |dir| dir.has_file("rust-toolchain")) - { - if let Some(toolchain) = read_channel(Path::new("rust-toolchain"), false) { - return Some(toolchain); - } - } - - if context - .dir_contents() - .map_or(false, |dir| dir.has_file("rust-toolchain.toml")) - { - if let Some(toolchain) = read_channel(Path::new("rust-toolchain.toml"), true) { - return Some(toolchain); - } - } - - let mut dir = &*context.current_dir; - loop { - if let Some(toolchain) = read_channel(&dir.join("rust-toolchain"), false) { - return Some(toolchain); - } - if let Some(toolchain) = read_channel(&dir.join("rust-toolchain.toml"), true) { - return Some(toolchain); - } - dir = dir.parent()?; - } -} - -fn execute_rustup_run_rustc_version( - context: &Context, - toolchain: &str, -) -> RustupRunRustcVersionOutcome { - create_command("rustup") - .and_then(|mut cmd| { - cmd.args(&["run", toolchain, "rustc", "--version"]) - .current_dir(&context.current_dir) - .output() - }) - .map(extract_toolchain_from_rustup_run_rustc_version) - .unwrap_or(RustupRunRustcVersionOutcome::RustupNotWorking) -} - -fn extract_toolchain_from_rustup_run_rustc_version(output: Output) -> RustupRunRustcVersionOutcome { - if output.status.success() { - if let Ok(output) = String::from_utf8(output.stdout) { - return RustupRunRustcVersionOutcome::RustcVersion(output); - } - } else if let Ok(stderr) = String::from_utf8(output.stderr) { - if stderr.starts_with("error: toolchain '") && stderr.ends_with("' is not installed\n") { - let stderr = stderr - ["error: toolchain '".len()..stderr.len() - "' is not installed\n".len()] - .to_owned(); - return RustupRunRustcVersionOutcome::ToolchainName(stderr); - } - } - RustupRunRustcVersionOutcome::Err -} - -fn execute_rustc_version(context: &Context) -> Option { - context - .exec_cmd("rustc", &["--version"]) - .map(|o| o.stdout) - .filter(|s| !s.is_empty()) -} - -fn format_rustc_version(rustc_version: &str, version_format: &str) -> Option { - let version = rustc_version - // split into ["rustc", "1.34.0", ...] - .split_whitespace() - // get down to "1.34.0" - .nth(1)?; - - match VersionFormatter::format_version(version, version_format) { - Ok(formatted) => Some(formatted), - Err(error) => { - log::warn!("Error formatting `rust` version:\n{}", error); - Some(format!("v{}", version)) - } - } -} - -#[derive(Debug, PartialEq)] -enum RustupRunRustcVersionOutcome { - RustcVersion(String), - ToolchainName(String), - RustupNotWorking, - Err, -} - -#[cfg(test)] -mod tests { - use crate::context::{Shell, Target}; - use once_cell::sync::Lazy; - use std::io; - use std::process::{ExitStatus, Output}; - - use super::*; - - #[test] - fn test_extract_toolchain_from_rustup_override_list() { - static NO_OVERRIDES_INPUT: &str = "no overrides\n"; - static NO_OVERRIDES_CWD: &str = ""; - assert_eq!( - extract_toolchain_from_rustup_override_list( - NO_OVERRIDES_INPUT, - NO_OVERRIDES_CWD.as_ref(), - ), - None, - ); - - static OVERRIDES_INPUT: &str = - "/home/user/src/a \t beta-x86_64-unknown-linux-gnu\n\ - /home/user/src/b \t nightly-x86_64-unknown-linux-gnu\n\ - /home/user/src/b/d c \t stable-x86_64-pc-windows-msvc\n"; - static OVERRIDES_CWD_A: &str = "/home/user/src/a/src"; - static OVERRIDES_CWD_B: &str = "/home/user/src/b/tests"; - static OVERRIDES_CWD_C: &str = "/home/user/src/c/examples"; - static OVERRIDES_CWD_D: &str = "/home/user/src/b/d c/spaces"; - static OVERRIDES_CWD_E: &str = "/home/user/src/b_and_more"; - static OVERRIDES_CWD_F: &str = "/home/user/src/b"; - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_A.as_ref()), - Some("beta-x86_64-unknown-linux-gnu".to_owned()), - ); - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_B.as_ref()), - Some("nightly-x86_64-unknown-linux-gnu".to_owned()), - ); - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_C.as_ref()), - None, - ); - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_D.as_ref()), - Some("stable-x86_64-pc-windows-msvc".to_owned()), - ); - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_E.as_ref()), - None, - ); - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_F.as_ref()), - Some("nightly-x86_64-unknown-linux-gnu".to_owned()), - ); - } - - #[test] - #[cfg(windows)] - fn test_extract_toolchain_from_rustup_override_list_win() { - static OVERRIDES_INPUT: &str = - "C:\\src \t beta-x86_64-unknown-linux-gnu\n"; - static OVERRIDES_CWD_A: &str = r"\\?\C:\src"; - static OVERRIDES_CWD_B: &str = r"C:\src"; - - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_A.as_ref()), - Some("beta-x86_64-unknown-linux-gnu".to_owned()), - ); - - assert_eq!( - extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_B.as_ref()), - Some("beta-x86_64-unknown-linux-gnu".to_owned()), - ); - } - - #[cfg(any(unix, windows))] - #[test] - fn test_extract_toolchain_from_rustup_run_rustc_version() { - #[cfg(unix)] - use std::os::unix::process::ExitStatusExt as _; - #[cfg(windows)] - use std::os::windows::process::ExitStatusExt as _; - - static RUSTC_VERSION: Lazy = Lazy::new(|| Output { - status: ExitStatus::from_raw(0), - stdout: b"rustc 1.34.0\n"[..].to_owned(), - stderr: vec![], - }); - assert_eq!( - extract_toolchain_from_rustup_run_rustc_version(RUSTC_VERSION.clone()), - RustupRunRustcVersionOutcome::RustcVersion("rustc 1.34.0\n".to_owned()), - ); - - static TOOLCHAIN_NAME: Lazy = Lazy::new(|| Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: b"error: toolchain 'channel-triple' is not installed\n"[..].to_owned(), - }); - assert_eq!( - extract_toolchain_from_rustup_run_rustc_version(TOOLCHAIN_NAME.clone()), - RustupRunRustcVersionOutcome::ToolchainName("channel-triple".to_owned()), - ); - - static INVALID_STDOUT: Lazy = Lazy::new(|| Output { - status: ExitStatus::from_raw(0), - stdout: b"\xc3\x28"[..].to_owned(), - stderr: vec![], - }); - assert_eq!( - extract_toolchain_from_rustup_run_rustc_version(INVALID_STDOUT.clone()), - RustupRunRustcVersionOutcome::Err, - ); - - static INVALID_STDERR: Lazy = Lazy::new(|| Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: b"\xc3\x28"[..].to_owned(), - }); - assert_eq!( - extract_toolchain_from_rustup_run_rustc_version(INVALID_STDERR.clone()), - RustupRunRustcVersionOutcome::Err, - ); - - static UNEXPECTED_FORMAT_OF_ERROR: Lazy = Lazy::new(|| Output { - status: ExitStatus::from_raw(1), - stdout: vec![], - stderr: b"error:"[..].to_owned(), - }); - assert_eq!( - extract_toolchain_from_rustup_run_rustc_version(UNEXPECTED_FORMAT_OF_ERROR.clone()), - RustupRunRustcVersionOutcome::Err, - ); - } - - #[test] - fn test_format_rustc_version() { - let config = RustConfig::default(); - let rustc_stable = "rustc 1.34.0 (91856ed52 2019-04-10)"; - let rustc_beta = "rustc 1.34.0-beta.1 (2bc1d406d 2019-04-10)"; - let rustc_nightly = "rustc 1.34.0-nightly (b139669f3 2019-04-10)"; - assert_eq!( - format_rustc_version(rustc_nightly, config.version_format), - Some("v1.34.0-nightly".to_string()) - ); - assert_eq!( - format_rustc_version(rustc_beta, config.version_format), - Some("v1.34.0-beta.1".to_string()) - ); - assert_eq!( - format_rustc_version(rustc_stable, config.version_format), - Some("v1.34.0".to_string()) - ); - assert_eq!( - format_rustc_version("rustc 1.34.0", config.version_format), - Some("v1.34.0".to_string()) - ); - } - - #[test] - fn test_find_rust_toolchain_file() -> io::Result<()> { - // `rust-toolchain` with toolchain in one line - let dir = tempfile::tempdir()?; - fs::write(dir.path().join("rust-toolchain"), "1.34.0")?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - dir.path().into(), - dir.path().into(), - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close()?; - - // `rust-toolchain` in toml format - let dir = tempfile::tempdir()?; - fs::write( - dir.path().join("rust-toolchain"), - "[toolchain]\nchannel = \"1.34.0\"", - )?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - dir.path().into(), - dir.path().into(), - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close()?; - - // `rust-toolchain` in toml format with new lines - let dir = tempfile::tempdir()?; - fs::write( - dir.path().join("rust-toolchain"), - "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", - )?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - dir.path().into(), - dir.path().into(), - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close()?; - - // `rust-toolchain` in parent directory. - let dir = tempfile::tempdir()?; - let child_dir_path = dir.path().join("child"); - fs::create_dir(&child_dir_path)?; - fs::write( - dir.path().join("rust-toolchain"), - "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", - )?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - child_dir_path.clone(), - child_dir_path, - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close()?; - - // `rust-toolchain.toml` with toolchain in one line - // This should not work! - // See https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file - let dir = tempfile::tempdir()?; - fs::write(dir.path().join("rust-toolchain.toml"), "1.34.0")?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - dir.path().into(), - dir.path().into(), - ); - - assert_eq!(find_rust_toolchain_file(&context), None); - dir.close()?; - - // `rust-toolchain.toml` in toml format - let dir = tempfile::tempdir()?; - fs::write( - dir.path().join("rust-toolchain.toml"), - "[toolchain]\nchannel = \"1.34.0\"", - )?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - dir.path().into(), - dir.path().into(), - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close()?; - - // `rust-toolchain.toml` in toml format with new lines - let dir = tempfile::tempdir()?; - fs::write( - dir.path().join("rust-toolchain.toml"), - "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", - )?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - dir.path().into(), - dir.path().into(), - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close()?; - - // `rust-toolchain.toml` in parent directory. - let dir = tempfile::tempdir()?; - let child_dir_path = dir.path().join("child"); - fs::create_dir(&child_dir_path)?; - fs::write( - dir.path().join("rust-toolchain.toml"), - "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", - )?; - - let context = Context::new_with_shell_and_path( - Default::default(), - Shell::Unknown, - Target::Main, - child_dir_path.clone(), - child_dir_path, - ); - - assert_eq!( - find_rust_toolchain_file(&context), - Some("1.34.0".to_owned()) - ); - dir.close() - } -} +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Output; + +use serde::Deserialize; +use std::collections::HashMap; + +use super::{Context, Module, ModuleConfig}; + +use crate::configs::rust::RustConfig; +use crate::formatter::{StringFormatter, VersionFormatter}; +use crate::utils::create_command; +use home::rustup_home; + +use once_cell::sync::OnceCell; + +use guess_host_triple::guess_host_triple; + +type VersionString = String; +type ToolchainString = String; + +/// A struct to cache the output of any commands that need to be run. +struct RustToolingEnvironmentInfo { + /// Rustup settings parsed from $HOME/.rustup/settings.toml + rustup_settings: OnceCell, + /// Rustc toolchain overrides as contained in the environment or files + env_toolchain_override: OnceCell>, + /// The output of `rustup rustc --version` with a fixed toolchain + rustup_rustc_output: OnceCell, + /// The output of running rustc -vV. Only called if rustup rustc fails or + /// is unavailable. + rustc_verbose_output: OnceCell>, +} + +impl RustToolingEnvironmentInfo { + fn new() -> Self { + Self { + rustup_settings: OnceCell::new(), + env_toolchain_override: OnceCell::new(), + rustup_rustc_output: OnceCell::new(), + rustc_verbose_output: OnceCell::new(), + } + } + + fn get_rustup_settings(&self, context: &Context) -> &RustupSettings { + self.rustup_settings + .get_or_init(|| RustupSettings::load(context).unwrap_or_default()) + } + + /// Gets any environmental toolchain overrides without downloading cargo toolchains + fn get_env_toolchain_override(&self, context: &Context) -> Option<&str> { + // `$CARGO_HOME/bin/rustc(.exe) --version` may attempt installing a rustup toolchain. + // https://github.com/starship/starship/issues/417 + // + // To display appropriate versions preventing `rustc` from downloading toolchains, we have to + // check + // 1. `$RUSTUP_TOOLCHAIN` + // 2. `rustup override list` + // 3. `rust-toolchain` or `rust-toolchain.toml` in `.` or parent directories + // 4. `rustup default` + // as `rustup` does. + // https://github.com/rust-lang/rustup.rs/tree/eb694fcada7becc5d9d160bf7c623abe84f8971d#override-precedence + // + // Probably we have no other way to know whether any toolchain override is specified for the + // current directory. The following commands also cause toolchain installations. + // - `rustup show` + // - `rustup show active-toolchain` + // - `rustup which` + self.env_toolchain_override + .get_or_init(|| { + let out = env_rustup_toolchain(context) + .or_else(|| { + self.get_rustup_settings(context) + .lookup_override(context.current_dir.as_path()) + }) + .or_else(|| find_rust_toolchain_file(context)) + .or_else(|| execute_rustup_default(context)); + + log::debug!("Environmental toolchain override is {:?}", out); + out + }) + .as_deref() + } + + /// Gets the output of running `rustup rustc --version` with a toolchain + /// specified by self.get_env_toolchain_override() + fn get_rustup_rustc_version(&self, context: &Context) -> &RustupRunRustcVersionOutcome { + self.rustup_rustc_output.get_or_init(|| { + let out = if let Some(toolchain) = self.get_env_toolchain_override(context) { + create_command("rustup") + .and_then(|mut cmd| { + cmd.args(&["run", toolchain, "rustc", "--version"]) + .current_dir(&context.current_dir) + .output() + }) + .map(extract_toolchain_from_rustup_run_rustc_version) + .unwrap_or(RustupRunRustcVersionOutcome::RustupNotWorking) + } else { + RustupRunRustcVersionOutcome::ToolchainUnknown + }; + + log::debug!("Rustup rustc version is {:?}", out); + out + }) + } + + /// Gets the (version, toolchain) string as returned by `rustc -vV` + fn get_rustc_verbose_version(&self, context: &Context) -> Option<(&str, &str)> { + let toolchain = self.get_rustup_settings(context).default_toolchain(); + + self.rustc_verbose_output + .get_or_init(|| { + let Output { status, stdout, .. } = create_command("rustc") + .and_then(|mut cmd| { + cmd.args(&["-Vv"]) + .current_dir(&context.current_dir) + .output() + }) + .ok()?; + if !status.success() { + return None; + } + let out = + format_rustc_version_verbose(std::str::from_utf8(&stdout).ok()?, toolchain); + + log::debug!("Rustup verbose version is {:?}", out); + out + }) + .as_ref() + .map(|(x, y)| (&x[..], &y[..])) + } +} + +/// Creates a module with the current Rust version +pub fn module<'a>(context: &'a Context) -> Option> { + let mut module = context.new_module("rust"); + let config = RustConfig::try_load(module.config); + + let is_rs_project = context + .try_begin_scan()? + .set_files(&config.detect_files) + .set_extensions(&config.detect_extensions) + .set_folders(&config.detect_folders) + .is_match(); + + if !is_rs_project { + return None; + } + + let rust_env_info = RustToolingEnvironmentInfo::new(); + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|var, _| match var { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => get_module_version(context, &config, &rust_env_info).map(Ok), + "numver" => get_module_numeric_version(context, &config, &rust_env_info).map(Ok), + "toolchain" => get_toolchain_version(context, &config, &rust_env_info).map(Ok), + _ => None, + }) + .parse(None, Some(context)) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `rust`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn get_module_version( + context: &Context, + config: &RustConfig, + rust_env_info: &RustToolingEnvironmentInfo, +) -> Option { + type Outcome = RustupRunRustcVersionOutcome; + + match rust_env_info.get_rustup_rustc_version(context) { + Outcome::RustcVersion(rustc_version) => { + format_rustc_version(rustc_version, config.version_format) + } + Outcome::RustupNotWorking | Outcome::ToolchainUnknown => { + // If `rustup` can't be executed, or there is no environmental toolchain, we can + // execute `rustc --version` without triggering a toolchain download + format_rustc_version(&execute_rustc_version(context)?, config.version_format) + } + Outcome::ToolchainNotInstalled(name) => Some(name.to_string()), + Outcome::Err => None, + } +} + +fn get_module_numeric_version( + context: &Context, + _config: &RustConfig, + rust_env_info: &RustToolingEnvironmentInfo, +) -> Option { + type Outcome = RustupRunRustcVersionOutcome; + + match rust_env_info.get_rustup_rustc_version(context) { + Outcome::RustcVersion(version) => { + let release = version.split_whitespace().nth(1).unwrap_or(version); + Some(format_semver(release)) + } + Outcome::RustupNotWorking | Outcome::ToolchainUnknown => { + let (numver, _toolchain) = rust_env_info.get_rustc_verbose_version(context)?; + Some(numver.to_string()) + } + Outcome::ToolchainNotInstalled(_) | RustupRunRustcVersionOutcome::Err => None, + } +} + +fn get_toolchain_version( + context: &Context, + _config: &RustConfig, + rust_env_info: &RustToolingEnvironmentInfo, +) -> Option { + type Outcome = RustupRunRustcVersionOutcome; + + let settings_host_triple = rust_env_info + .get_rustup_settings(context) + .default_host_triple(); + let default_host_triple = if settings_host_triple.is_none() { + guess_host_triple() + } else { + settings_host_triple + }; + + match rust_env_info.get_rustup_rustc_version(context) { + Outcome::RustcVersion(_) | Outcome::ToolchainNotInstalled(_) => { + let toolchain_override = rust_env_info + .get_env_toolchain_override(context) + // This match arm should only trigger if the toolchain override + // is not None because of how get_rustup_rustc_version works + .expect("Toolchain override was None: programming error."); + Some(format_toolchain(toolchain_override, default_host_triple)) + } + Outcome::RustupNotWorking | Outcome::ToolchainUnknown => { + let (_numver, toolchain) = rust_env_info.get_rustc_verbose_version(context)?; + Some(format_toolchain(toolchain, default_host_triple)) + } + Outcome::Err => None, + } +} + +fn env_rustup_toolchain(context: &Context) -> Option { + log::trace!("Searching for rustup toolchain in environment."); + let val = context.get_env("RUSTUP_TOOLCHAIN")?; + Some(val.trim().to_owned()) +} + +fn execute_rustup_default(context: &Context) -> Option { + log::trace!("Searching for toolchain with rustup default"); + // `rustup default` output is: + // stable-x86_64-apple-darwin (default) + context + .exec_cmd("rustup", &["default"])? + .stdout + .split_whitespace() + .next() + .map(str::to_owned) +} + +fn find_rust_toolchain_file(context: &Context) -> Option { + log::trace!("Searching for toolchain in toolchain file"); + // Look for 'rust-toolchain' or 'rust-toolchain.toml' as rustup does. + // for more information: + // https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file + // for the implementation in 'rustup': + // https://github.com/rust-lang/rustup/blob/a45e4cd21748b04472fce51ba29999ee4b62bdec/src/config.rs#L631 + + #[derive(Deserialize)] + struct OverrideFile { + toolchain: ToolchainSection, + } + + #[derive(Deserialize)] + struct ToolchainSection { + channel: Option, + } + + fn read_channel(path: &Path, only_toml: bool) -> Option { + let contents = fs::read_to_string(path).ok()?; + + match contents.lines().count() { + 0 => None, + 1 if !only_toml => Some(contents), + _ => { + toml::from_str::(&contents) + .ok()? + .toolchain + .channel + } + } + .filter(|c| !c.trim().is_empty()) + .map(|c| c.trim().to_owned()) + } + + if context + .dir_contents() + .map_or(false, |dir| dir.has_file("rust-toolchain")) + { + if let Some(toolchain) = read_channel(Path::new("rust-toolchain"), false) { + return Some(toolchain); + } + } + + if context + .dir_contents() + .map_or(false, |dir| dir.has_file("rust-toolchain.toml")) + { + if let Some(toolchain) = read_channel(Path::new("rust-toolchain.toml"), true) { + return Some(toolchain); + } + } + + let mut dir = &*context.current_dir; + loop { + if let Some(toolchain) = read_channel(&dir.join("rust-toolchain"), false) { + return Some(toolchain); + } + if let Some(toolchain) = read_channel(&dir.join("rust-toolchain.toml"), true) { + return Some(toolchain); + } + dir = dir.parent()?; + } +} + +fn extract_toolchain_from_rustup_run_rustc_version(output: Output) -> RustupRunRustcVersionOutcome { + if output.status.success() { + if let Ok(output) = String::from_utf8(output.stdout) { + return RustupRunRustcVersionOutcome::RustcVersion(output); + } + } else if let Ok(stderr) = String::from_utf8(output.stderr) { + if stderr.starts_with("error: toolchain '") && stderr.ends_with("' is not installed\n") { + let stderr = stderr + ["error: toolchain '".len()..stderr.len() - "' is not installed\n".len()] + .to_owned(); + return RustupRunRustcVersionOutcome::ToolchainNotInstalled(stderr); + } + } + RustupRunRustcVersionOutcome::Err +} + +fn execute_rustc_version(context: &Context) -> Option { + context + .exec_cmd("rustc", &["--version"]) + .map(|o| o.stdout) + .filter(|s| !s.is_empty()) +} + +fn format_rustc_version(rustc_version: &str, version_format: &str) -> Option { + let version = rustc_version + // split into ["rustc", "1.34.0", ...] + .split_whitespace() + // get down to "1.34.0" + .nth(1)?; + + match VersionFormatter::format_version(version, version_format) { + Ok(formatted) => Some(formatted), + Err(error) => { + log::warn!("Error formatting `rust` version:\n{}", error); + Some(format!("v{}", version)) + } + } +} + +fn format_toolchain(toolchain: &str, default_host_triple: Option<&str>) -> String { + default_host_triple + .map(|triple| toolchain.trim_end_matches(&format!("-{}", triple))) + .unwrap_or(toolchain) + .to_owned() +} + +fn format_rustc_version_verbose(stdout: &str, toolchain: Option<&str>) -> Option<(String, String)> { + let (mut release, mut host) = (None, None); + for line in stdout.lines() { + if line.starts_with("release: ") { + release = Some(line.trim_start_matches("release: ")); + } + if line.starts_with("host: ") { + host = Some(line.trim_start_matches("host: ")); + } + } + let (release, host) = (release?, host?); + let version = format_semver(release); + let toolchain = toolchain + .map(ToOwned::to_owned) + .unwrap_or_else(|| host.to_string()); + Some((version, toolchain)) +} + +fn format_semver(semver: &str) -> String { + format!( + "v{}", + semver.find('-').map(|i| &semver[..i]).unwrap_or(semver) + ) +} + +#[derive(Debug, PartialEq)] +enum RustupRunRustcVersionOutcome { + RustcVersion(String), + ToolchainNotInstalled(String), + ToolchainUnknown, + RustupNotWorking, + Err, +} + +#[derive(Default, Debug, PartialEq, Deserialize)] +struct RustupSettings { + default_host_triple: Option, + default_toolchain: Option, + overrides: HashMap, + version: Option, +} + +#[inline] +#[cfg(windows)] +fn strip_dos_path(path: PathBuf) -> PathBuf { + // Use the display version of the path to strip \\?\ + let path = path.to_string_lossy(); + PathBuf::from(path.strip_prefix(r"\\?\").unwrap_or(&path)) +} + +#[inline] +#[cfg(not(windows))] +fn strip_dos_path(path: PathBuf) -> PathBuf { + path +} + +impl RustupSettings { + fn load(_context: &Context) -> Option { + let path = rustup_home().ok()?.join("settings.toml"); + Self::from_toml_str(&fs::read_to_string(path).ok()?) + } + + fn from_toml_str(toml_str: &str) -> Option { + let settings = toml::from_str::(toml_str).ok()?; + match settings.version.as_deref() { + Some("12") => Some(settings), + _ => { + log::warn!( + r#"Rustup settings version is {:?}, expected "12""#, + settings.version + ); + None + } + } + } + + fn default_host_triple(&self) -> Option<&str> { + self.default_host_triple.as_deref() + } + + fn default_toolchain(&self) -> Option<&str> { + self.default_toolchain.as_deref() + } + + fn lookup_override(&self, cwd: &Path) -> Option { + let cwd = strip_dos_path(cwd.to_owned()); + self.overrides + .iter() + .map(|(dir, toolchain)| (strip_dos_path(dir.to_owned()), toolchain)) + .filter(|(dir, _)| cwd.starts_with(dir)) + .max_by_key(|(dir, _)| dir.components().count()) + .map(|(_, name)| name.to_owned()) + } +} + +#[cfg(test)] +mod tests { + use crate::context::{Shell, Target}; + use once_cell::sync::Lazy; + use std::io; + use std::process::{ExitStatus, Output}; + + use super::*; + + #[test] + fn test_rustup_settings_from_toml_value() { + assert_eq!( + RustupSettings::from_toml_str( + r#" +default_host_triple = "x86_64-unknown-linux-gnu" +default_toolchain = "stable" +version = "12" + +[overrides] +"/home/user/src/starship" = "1.40.0-x86_64-unknown-linux-gnu" +"# + ), + Some(RustupSettings { + default_host_triple: Some("x86_64-unknown-linux-gnu".to_owned()), + default_toolchain: Some("stable".to_owned()), + overrides: vec![( + "/home/user/src/starship".into(), + "1.40.0-x86_64-unknown-linux-gnu".to_owned(), + )] + .into_iter() + .collect(), + version: Some("12".to_string()) + }), + ); + + // Invalid or missing version key causes a failure + assert_eq!( + RustupSettings::from_toml_str( + r#" + default_host_triple = "x86_64-unknown-linux-gnu" + default_toolchain = "stable" + + [overrides] + "/home/user/src/starship" = "1.39.0-x86_64-unknown-linux-gnu" + "# + ), + None, + ); + } + + #[test] + fn test_override_matches_correct_directories() { + let test_settings = RustupSettings::from_toml_str( + r#" +default_host_triple = "x86_64-unknown-linux-gnu" +default_toolchain = "stable" +version = "12" + +[overrides] +"/home/user/src/a" = "beta-x86_64-unknown-linux-gnu" +"/home/user/src/b" = "nightly-x86_64-unknown-linux-gnu" +"/home/user/src/b/d c" = "stable-x86_64-pc-windows-msvc" +"#, + ) + .unwrap(); + + static OVERRIDES_CWD_A: &str = "/home/user/src/a/src"; + static OVERRIDES_CWD_B: &str = "/home/user/src/b/tests"; + static OVERRIDES_CWD_C: &str = "/home/user/src/c/examples"; + static OVERRIDES_CWD_D: &str = "/home/user/src/b/d c/spaces"; + static OVERRIDES_CWD_E: &str = "/home/user/src/b_and_more"; + static OVERRIDES_CWD_F: &str = "/home/user/src/b"; + + static BETA_TOOLCHAIN: &str = "beta-x86_64-unknown-linux-gnu"; + static NIGHTLY_TOOLCHAIN: &str = "nightly-x86_64-unknown-linux-gnu"; + static STABLE_TOOLCHAIN: &str = "stable-x86_64-pc-windows-msvc"; + + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_A.as_ref()), + Some(BETA_TOOLCHAIN.to_string()) + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_B.as_ref()), + Some(NIGHTLY_TOOLCHAIN.to_string()) + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_C.as_ref()), + None + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_D.as_ref()), + Some(STABLE_TOOLCHAIN.to_string()) + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_E.as_ref()), + None + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_F.as_ref()), + Some(NIGHTLY_TOOLCHAIN.to_string()) + ); + } + + #[test] + #[cfg(windows)] + fn test_extract_toolchain_from_override_with_dospath() { + let test_settings = RustupSettings::from_toml_str( + r#" +default_host_triple = "x86_64-unknown-linux-gnu" +default_toolchain = "stable" +version = "12" + +[overrides] +"C:\\src1" = "beta-x86_64-unknown-linux-gnu" +"\\\\?\\C:\\src2" = "beta-x86_64-unknown-linux-gnu" +"#, + ) + .unwrap(); + static OVERRIDES_CWD_A: &str = r"\\?\C:\src1"; + static OVERRIDES_CWD_B: &str = r"C:\src1"; + static OVERRIDES_CWD_C: &str = r"\\?\C:\src2"; + static OVERRIDES_CWD_D: &str = r"C:\src2"; + + static BETA_TOOLCHAIN: &str = "beta-x86_64-unknown-linux-gnu"; + + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_A.as_ref()), + Some(BETA_TOOLCHAIN.to_string()) + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_B.as_ref()), + Some(BETA_TOOLCHAIN.to_string()) + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_C.as_ref()), + Some(BETA_TOOLCHAIN.to_string()) + ); + assert_eq!( + test_settings.lookup_override(OVERRIDES_CWD_D.as_ref()), + Some(BETA_TOOLCHAIN.to_string()) + ); + } + + #[cfg(any(unix, windows))] + #[test] + fn test_extract_toolchain_from_rustup_run_rustc_version() { + #[cfg(unix)] + use std::os::unix::process::ExitStatusExt as _; + #[cfg(windows)] + use std::os::windows::process::ExitStatusExt as _; + + static RUSTC_VERSION: Lazy = Lazy::new(|| Output { + status: ExitStatus::from_raw(0), + stdout: b"rustc 1.34.0\n"[..].to_owned(), + stderr: vec![], + }); + assert_eq!( + extract_toolchain_from_rustup_run_rustc_version(RUSTC_VERSION.clone()), + RustupRunRustcVersionOutcome::RustcVersion("rustc 1.34.0\n".to_owned()), + ); + + static TOOLCHAIN_NAME: Lazy = Lazy::new(|| Output { + status: ExitStatus::from_raw(1), + stdout: vec![], + stderr: b"error: toolchain 'channel-triple' is not installed\n"[..].to_owned(), + }); + assert_eq!( + extract_toolchain_from_rustup_run_rustc_version(TOOLCHAIN_NAME.clone()), + RustupRunRustcVersionOutcome::ToolchainNotInstalled("channel-triple".to_owned()), + ); + + static INVALID_STDOUT: Lazy = Lazy::new(|| Output { + status: ExitStatus::from_raw(0), + stdout: b"\xc3\x28"[..].to_owned(), + stderr: vec![], + }); + assert_eq!( + extract_toolchain_from_rustup_run_rustc_version(INVALID_STDOUT.clone()), + RustupRunRustcVersionOutcome::Err, + ); + + static INVALID_STDERR: Lazy = Lazy::new(|| Output { + status: ExitStatus::from_raw(1), + stdout: vec![], + stderr: b"\xc3\x28"[..].to_owned(), + }); + assert_eq!( + extract_toolchain_from_rustup_run_rustc_version(INVALID_STDERR.clone()), + RustupRunRustcVersionOutcome::Err, + ); + + static UNEXPECTED_FORMAT_OF_ERROR: Lazy = Lazy::new(|| Output { + status: ExitStatus::from_raw(1), + stdout: vec![], + stderr: b"error:"[..].to_owned(), + }); + assert_eq!( + extract_toolchain_from_rustup_run_rustc_version(UNEXPECTED_FORMAT_OF_ERROR.clone()), + RustupRunRustcVersionOutcome::Err, + ); + } + + #[test] + fn test_format_rustc_version() { + let config = RustConfig::default(); + let rustc_stable = "rustc 1.34.0 (91856ed52 2019-04-10)"; + let rustc_beta = "rustc 1.34.0-beta.1 (2bc1d406d 2019-04-10)"; + let rustc_nightly = "rustc 1.34.0-nightly (b139669f3 2019-04-10)"; + assert_eq!( + format_rustc_version(rustc_nightly, config.version_format), + Some("v1.34.0-nightly".to_string()) + ); + assert_eq!( + format_rustc_version(rustc_beta, config.version_format), + Some("v1.34.0-beta.1".to_string()) + ); + assert_eq!( + format_rustc_version(rustc_stable, config.version_format), + Some("v1.34.0".to_string()) + ); + assert_eq!( + format_rustc_version("rustc 1.34.0", config.version_format), + Some("v1.34.0".to_string()) + ); + } + + #[test] + fn test_find_rust_toolchain_file() -> io::Result<()> { + // `rust-toolchain` with toolchain in one line + let dir = tempfile::tempdir()?; + fs::write(dir.path().join("rust-toolchain"), "1.34.0")?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + dir.path().into(), + dir.path().into(), + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close()?; + + // `rust-toolchain` in toml format + let dir = tempfile::tempdir()?; + fs::write( + dir.path().join("rust-toolchain"), + "[toolchain]\nchannel = \"1.34.0\"", + )?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + dir.path().into(), + dir.path().into(), + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close()?; + + // `rust-toolchain` in toml format with new lines + let dir = tempfile::tempdir()?; + fs::write( + dir.path().join("rust-toolchain"), + "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", + )?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + dir.path().into(), + dir.path().into(), + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close()?; + + // `rust-toolchain` in parent directory. + let dir = tempfile::tempdir()?; + let child_dir_path = dir.path().join("child"); + fs::create_dir(&child_dir_path)?; + fs::write( + dir.path().join("rust-toolchain"), + "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", + )?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + child_dir_path.clone(), + child_dir_path, + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close()?; + + // `rust-toolchain.toml` with toolchain in one line + // This should not work! + // See https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file + let dir = tempfile::tempdir()?; + fs::write(dir.path().join("rust-toolchain.toml"), "1.34.0")?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + dir.path().into(), + dir.path().into(), + ); + + assert_eq!(find_rust_toolchain_file(&context), None); + dir.close()?; + + // `rust-toolchain.toml` in toml format + let dir = tempfile::tempdir()?; + fs::write( + dir.path().join("rust-toolchain.toml"), + "[toolchain]\nchannel = \"1.34.0\"", + )?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + dir.path().into(), + dir.path().into(), + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close()?; + + // `rust-toolchain.toml` in toml format with new lines + let dir = tempfile::tempdir()?; + fs::write( + dir.path().join("rust-toolchain.toml"), + "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", + )?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + dir.path().into(), + dir.path().into(), + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close()?; + + // `rust-toolchain.toml` in parent directory. + let dir = tempfile::tempdir()?; + let child_dir_path = dir.path().join("child"); + fs::create_dir(&child_dir_path)?; + fs::write( + dir.path().join("rust-toolchain.toml"), + "\n\n[toolchain]\n\n\nchannel = \"1.34.0\"", + )?; + + let context = Context::new_with_shell_and_path( + Default::default(), + Shell::Unknown, + Target::Main, + child_dir_path.clone(), + child_dir_path, + ); + + assert_eq!( + find_rust_toolchain_file(&context), + Some("1.34.0".to_owned()) + ); + dir.close() + } + + #[test] + fn test_format_rustc_version_verbose() { + macro_rules! test { + () => {}; + (($input:expr, $toolchain:expr) => $expected:expr $(,$($rest:tt)*)?) => { + assert_eq!( + format_rustc_version_verbose($input, $toolchain) + .as_ref() + .map(|(s1, s2)| (&**s1, &**s2)), + $expected, + ); + test!($($($rest)*)?); + }; + } + + static STABLE: &str = r#"rustc 1.40.0 (73528e339 2019-12-16) +binary: rustc +commit-hash: 73528e339aae0f17a15ffa49a8ac608f50c6cf14 +commit-date: 2019-12-16 +host: x86_64-unknown-linux-gnu +release: 1.40.0 +LLVM version: 9.0 +"#; + + static BETA: &str = r#"rustc 1.41.0-beta.1 (eb3f7c2d3 2019-12-17) +binary: rustc +commit-hash: eb3f7c2d3aec576f47eba854cfbd3c1187b8a2a0 +commit-date: 2019-12-17 +host: x86_64-unknown-linux-gnu +release: 1.41.0-beta.1 +LLVM version: 9.0 +"#; + + static NIGHTLY: &str = r#"rustc 1.42.0-nightly (da3629b05 2019-12-29) +binary: rustc +commit-hash: da3629b05f8f1b425a738bfe9fe9aedd47c5417a +commit-date: 2019-12-29 +host: x86_64-unknown-linux-gnu +release: 1.42.0-nightly +LLVM version: 9.0 +"#; + + test!( + (STABLE, None) => Some(("v1.40.0", "x86_64-unknown-linux-gnu")), + (STABLE, Some("stable")) => Some(("v1.40.0", "stable")), + (BETA, None) => Some(("v1.41.0", "x86_64-unknown-linux-gnu")), + (BETA, Some("beta")) => Some(("v1.41.0", "beta")), + (NIGHTLY, None) => Some(("v1.42.0", "x86_64-unknown-linux-gnu")), + (NIGHTLY, Some("nightly")) => Some(("v1.42.0", "nightly")), + ("", None) => None, + ("", Some("stable")) => None, + ); + } +}