diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 1d270e7894..dbe0dc229a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ github: rustfoundation -custom: ["rust-lang.org/funding"] +custom: ["https://rust-lang.org/funding"] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..8144ba3056 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f79ec050ba..f57ca6d789 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -202,13 +202,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive dist s3://rustup-builds/${{ github.sha }}/dist env: @@ -401,13 +401,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive dist s3://rustup-builds/${{ github.sha }}/dist env: @@ -606,13 +606,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive dist s3://rustup-builds/${{ github.sha }}/dist env: @@ -776,13 +776,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: @@ -948,13 +948,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: @@ -1003,6 +1003,7 @@ jobs: - armv7-linux-androideabi # skip-pr skip-main - x86_64-linux-android # skip-pr skip-main - riscv64gc-unknown-linux-gnu # skip-pr skip-main + - riscv64gc-unknown-linux-musl # skip-pr skip-main - loongarch64-unknown-linux-gnu # skip-pr skip-main - loongarch64-unknown-linux-musl # skip-pr skip-main include: @@ -1146,13 +1147,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: @@ -1272,13 +1273,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: @@ -1403,13 +1404,13 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: diff --git a/Cargo.lock b/Cargo.lock index c4ab9ec825..08a6440acf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "anstyle-svg" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7ec5fa88fc0837393f6b110b0764aeb0a673952bc91300628641dd0fffbf85" +checksum = "ab68e0b71ea68eb6b399ffe1957f26330aeed91f5e578d442a1c2f3df69f6ec1" dependencies = [ "anstyle", "anstyle-lossy", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "as-slice" @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" dependencies = [ "compression-codecs", "compression-core", @@ -127,13 +127,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -150,9 +150,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", "zeroize", @@ -160,9 +160,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.42.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" dependencies = [ "cc", "cmake", @@ -194,9 +194,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -209,9 +209,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", "regex-automata", @@ -232,15 +232,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.66" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "jobserver", @@ -262,9 +262,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -308,9 +308,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -321,23 +321,23 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.7" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b" +checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -452,18 +452,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -532,13 +532,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -562,9 +562,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "encode_unicode" @@ -574,22 +574,22 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "enum-map" -version = "2.7.3" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +checksum = "d4532960034c15ba2f55e9eee631040ecc473c32e6c4b200828c759840f13a06" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" -version = "0.17.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +checksum = "7127272f64b3e098f7d81f350d672081b879bff896d219c42370dc858b1af9ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -630,9 +630,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "filetime" @@ -646,9 +646,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "flate2" @@ -713,9 +713,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -723,15 +723,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -740,38 +740,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-io", @@ -836,7 +836,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "time", ] @@ -907,15 +907,15 @@ dependencies = [ [[package]] name = "html-escape" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c1ff2d1cbf39efe5af0900ced8a069b5e61557a17544eb0c4a50239937389e" +checksum = "c9356095b4b41197bba32173600e1582792cda618f65d12f68e2e77d273413c5" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -933,9 +933,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -958,18 +958,18 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -1182,9 +1182,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "is_terminal_polyfill" @@ -1228,7 +1228,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -1243,7 +1243,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.119", ] [[package]] @@ -1262,24 +1262,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if 1.0.4", "futures-util", @@ -1294,9 +1294,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libz-sys" @@ -1350,9 +1350,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "miniz_oxide" @@ -1366,9 +1366,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -1498,7 +1498,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1515,9 +1515,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.4+3.5.4" +version = "300.6.1+3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" dependencies = [ "cc", ] @@ -1545,7 +1545,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.18", + "thiserror 2.0.20", "tracing", ] @@ -1575,7 +1575,7 @@ dependencies = [ "opentelemetry_sdk", "prost", "reqwest", - "thiserror 2.0.18", + "thiserror 2.0.20", "tokio", "tonic", "tonic-types", @@ -1606,8 +1606,8 @@ dependencies = [ "opentelemetry", "percent-encoding", "portable-atomic", - "rand 0.9.4", - "thiserror 2.0.18", + "rand 0.9.5", + "thiserror 2.0.20", "tokio", "tokio-stream", ] @@ -1635,7 +1635,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1652,15 +1652,15 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "platforms" -version = "3.12.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9245c6e7c5a6bcdd7977fdf6d1e1c67f4cc2d0d58c041df0ea5940953033e6ca" +checksum = "acff5dfd42135d89cd7ad74a8c52813bbec91bf62c018ddd89390127767caab5" [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "potential_utf" @@ -1688,9 +1688,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -1705,7 +1705,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.4", + "rand 0.9.5", "rand_chacha", "rand_xorshift", "regex-syntax", @@ -1734,7 +1734,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1765,9 +1765,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1786,9 +1786,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -1861,9 +1861,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -1873,9 +1873,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1951,7 +1951,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cab9bd343c737660e523ee69f788018f3db686d537d2fd0f99c9f747c1bda4f" dependencies = [ - "rand 0.9.4", + "rand 0.9.5", ] [[package]] @@ -2002,9 +2002,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -2029,9 +2029,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] @@ -2083,7 +2083,6 @@ dependencies = [ "anstyle", "anyhow", "cc", - "cfg-if 1.0.4", "chrono", "clap", "clap-cargo", @@ -2108,7 +2107,6 @@ dependencies = [ "libz-sys", "opener", "openssl", - "openssl-src", "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", @@ -2135,7 +2133,7 @@ dependencies = [ "strsim", "tar", "tempfile", - "thiserror 2.0.18", + "thiserror 2.0.20", "threadpool", "tokio", "tokio-retry", @@ -2159,9 +2157,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "rusty-fork" @@ -2230,9 +2228,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2240,29 +2238,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2308,15 +2306,15 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -2330,9 +2328,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" +checksum = "85ee016af5d736b69fc89e19254540fa4b5f5492853fb5503920f084011c78b6" dependencies = [ "bstr", ] @@ -2375,9 +2373,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2403,9 +2401,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -2429,7 +2438,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2487,11 +2496,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.20", ] [[package]] @@ -2502,25 +2511,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if 1.0.4", ] @@ -2536,9 +2545,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.53" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "num-conv", @@ -2556,9 +2565,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -2576,9 +2585,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -2591,13 +2600,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -2633,9 +2642,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -2644,22 +2653,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", @@ -2681,18 +2691,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tonic" @@ -2815,7 +2825,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3002,9 +3012,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if 1.0.4", "once_cell", @@ -3015,9 +3025,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -3025,9 +3035,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3035,22 +3045,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] @@ -3070,9 +3080,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -3090,9 +3100,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -3256,9 +3266,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "wit-bindgen" @@ -3310,28 +3320,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3351,7 +3361,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -3391,20 +3401,20 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zlib-rs" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 2110cb28d8..069ba4a801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,6 @@ anstream = "1" anstyle = "1.0.11" anyhow = "1.0.69" cc = "1" -cfg-if = "1.0" chrono = { version = "0.4", default-features = false, features = ["std"] } clap = { version = "4", features = ["derive", "wrap_help", "string"] } clap-cargo = "0.18.3" @@ -53,7 +52,7 @@ clap_complete = "4" console = "0.16" curl = { version = "0.4.44", optional = true } effective-limits = "0.5.5" -enum-map = "2.5.0" +enum-map = "3.0.0" env_proxy = { version = "0.4.1", optional = true } faster-hex = "0.10.0" flate2 = { version = "1.1.1", default-features = false, features = ["zlib-rs"] } @@ -61,6 +60,9 @@ fs_at = "0.2.1" futures-util = "0.3.31" git-testament = "0.2" home = "0.5.4" +http-body-util = "0.1.0" +hyper = { version = "1.0", default-features = false, features = ["server", "http1"] } +hyper-util = { version = "0.1.1", features = ["tokio"] } indicatif = "0.18" itertools = "0.15" libc = "0.2" @@ -68,13 +70,10 @@ opener = "0.8.0" # `openssl` is used by `curl` or `reqwest` backend although it isn't imported by rustup: this # allows controlling the vendoring status without exposing the presence of the download crate. openssl = { version = "0.10", optional = true } -# HACK: Temporarily pinned due to ppc64 ELFv1/v2 ABI issue in 300.5.5, to be -# removed when lands. -openssl-src = { version = "=300.5.4", optional = true } opentelemetry = { version = "0.32", optional = true } opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"], optional = true } opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"], optional = true } -platforms = "3.4" +platforms = "4" pulldown-cmark = { version = "0.13", default-features = false } rand = "0.10" rayon = "1.10.0" @@ -145,17 +144,14 @@ version = "0.61" libz-sys = "=1.1.24" [dev-dependencies] -enum-map = "2.5.0" -http-body-util = "0.1.0" -hyper = { version = "1.0", default-features = false, features = ["server", "http1"] } -hyper-util = { version = "0.1.1", features = ["tokio"] } +enum-map = "3.0.0" proptest = "1.1.0" rustls-webpki = { version = "0.103.3" } tokio-rustls = "0.26.4" webpki-root-certs = "1" [build-dependencies] -platforms = "3.4" +platforms = "4" [lints.rust] rust_2018_idioms = { level = "deny", priority = -1 } diff --git a/ci/actions-templates/linux-builds-template.yaml b/ci/actions-templates/linux-builds-template.yaml index 214d666fab..cf6730e66a 100644 --- a/ci/actions-templates/linux-builds-template.yaml +++ b/ci/actions-templates/linux-builds-template.yaml @@ -44,6 +44,7 @@ jobs: # skip-main skip-pr skip-stable - armv7-linux-androideabi # skip-pr skip-main - x86_64-linux-android # skip-pr skip-main - riscv64gc-unknown-linux-gnu # skip-pr skip-main + - riscv64gc-unknown-linux-musl # skip-pr skip-main - loongarch64-unknown-linux-gnu # skip-pr skip-main - loongarch64-unknown-linux-musl # skip-pr skip-main include: @@ -187,13 +188,13 @@ jobs: # skip-main skip-pr skip-stable AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: diff --git a/ci/actions-templates/macos-builds-template.yaml b/ci/actions-templates/macos-builds-template.yaml index f7738b8ba0..f6411a929b 100644 --- a/ci/actions-templates/macos-builds-template.yaml +++ b/ci/actions-templates/macos-builds-template.yaml @@ -115,13 +115,13 @@ jobs: # skip-x86_64 skip-aarch64 AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive deploy/ s3://rustup-builds/${{ github.sha }} env: diff --git a/ci/actions-templates/windows-builds-template.yaml b/ci/actions-templates/windows-builds-template.yaml index 60eed06a08..938dd01e12 100644 --- a/ci/actions-templates/windows-builds-template.yaml +++ b/ci/actions-templates/windows-builds-template.yaml @@ -193,13 +193,13 @@ jobs: # skip-main skip-pr skip-stable AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Configure AWS credentials - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::890664054962:role/ci--rust-lang--rustup aws-region: us-east-1 - name: Deploy build to rustup-builds bucket for release team - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.mode == 'release' + if: github.event_name == 'push' && matrix.mode == 'release' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/main') run: | aws --debug s3 cp --recursive dist s3://rustup-builds/${{ github.sha }}/dist env: diff --git a/ci/cloudfront-invalidation.txt b/ci/cloudfront-invalidation.txt index 9b86ac321c..ae1892448d 100644 --- a/ci/cloudfront-invalidation.txt +++ b/ci/cloudfront-invalidation.txt @@ -57,6 +57,10 @@ rustup/dist/powerpc64le-unknown-linux-gnu/rustup-init rustup/dist/powerpc64le-unknown-linux-gnu/rustup-init.sha256 rustup/dist/powerpc64le-unknown-linux-musl/rustup-init rustup/dist/powerpc64le-unknown-linux-musl/rustup-init.sha256 +rustup/dist/riscv64gc-unknown-linux-gnu/rustup-init +rustup/dist/riscv64gc-unknown-linux-gnu/rustup-init.sha256 +rustup/dist/riscv64gc-unknown-linux-musl/rustup-init +rustup/dist/riscv64gc-unknown-linux-musl/rustup-init.sha256 rustup/dist/s390x-unknown-linux-gnu/rustup-init rustup/dist/s390x-unknown-linux-gnu/rustup-init.sha256 rustup/dist/sparcv9-sun-solaris/rustup-init.exe diff --git a/ci/docker/riscv64gc-unknown-linux-musl/Dockerfile b/ci/docker/riscv64gc-unknown-linux-musl/Dockerfile new file mode 100644 index 0000000000..3219088f87 --- /dev/null +++ b/ci/docker/riscv64gc-unknown-linux-musl/Dockerfile @@ -0,0 +1,8 @@ +FROM rust-riscv64gc-unknown-linux-musl + +# Building `aws-lc-rs` for Linux depends on `gcc-multilib`, `libclang` and `bindgen`. +# See: https://aws.github.io/aws-lc-rs/requirements/linux +RUN apt-get update && apt-get install -qy gcc-multilib libclang-dev + +ENV CC_riscv64gc_unknown_linux_musl=riscv64-unknown-linux-musl-gcc \ + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_LINKER=riscv64-unknown-linux-musl-gcc diff --git a/ci/fetch-rust-docker.bash b/ci/fetch-rust-docker.bash index eb2f69f8e4..3a1c022309 100644 --- a/ci/fetch-rust-docker.bash +++ b/ci/fetch-rust-docker.bash @@ -45,7 +45,8 @@ case "$TARGET" in x86_64-unknown-illumos) image=dist-x86_64-illumos ;; x86_64-unknown-linux-gnu) image=dist-x86_64-linux ;; x86_64-unknown-netbsd) image=dist-x86_64-netbsd ;; - riscv64gc-unknown-linux-gnu) image=dist-riscv64-linux ;; + riscv64gc-unknown-linux-gnu) image=dist-riscv64-linux-gnu ;; + riscv64gc-unknown-linux-musl) image=dist-riscv64-linux-musl ;; loongarch64-unknown-linux-gnu) image=dist-loongarch64-linux ;; loongarch64-unknown-linux-musl) image=dist-loongarch64-musl ;; *) exit ;; diff --git a/ci/run.bash b/ci/run.bash index f9d28cd6e6..e7e61acc6b 100644 --- a/ci/run.bash +++ b/ci/run.bash @@ -20,10 +20,16 @@ if [ -n "$INSTALL_BINDGEN" ]; then fi -FEATURES=('--no-default-features' '--features' 'curl-backend,reqwest-native-tls') -case "$(uname -s)" in - *NT* ) ;; # Windows NT - * ) FEATURES+=('--features' 'vendored-openssl') ;; +FEATURES=('--no-default-features') +case "$TARGET" in + *-linux-android*) ;; + *) + FEATURES+=('--features' 'curl-backend,reqwest-native-tls') + case "$(uname -s)" in + *NT* ) ;; # Windows NT + * ) FEATURES+=('--features' 'vendored-openssl') ;; + esac + ;; esac case "$TARGET" in diff --git a/doc/dev-guide/src/SUMMARY.md b/doc/dev-guide/src/SUMMARY.md index 15e9b2b4b1..d95e347b52 100644 --- a/doc/dev-guide/src/SUMMARY.md +++ b/doc/dev-guide/src/SUMMARY.md @@ -1,9 +1,10 @@ # Summary - [Introduction](index.md) - - [Linting](linting.md) + - [Linting](linting.md) - [Coding standards](coding-standards.md) - [Version numbers](version-numbers.md) +- [Recipes](recipes.md) - [Release process](release-process.md) - [Tips and tricks](tips-and-tricks.md) -- [Tracing](tracing.md) \ No newline at end of file +- [Tracing](tracing.md) diff --git a/doc/dev-guide/src/index.md b/doc/dev-guide/src/index.md index 0541d4e8f5..3bd36e4faa 100644 --- a/doc/dev-guide/src/index.md +++ b/doc/dev-guide/src/index.md @@ -48,3 +48,32 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as in the README, without any additional terms or conditions. + +## AI policy + +Using AI (LLMs) as tools for coding is welcome. A high bar is held for all contributions to this +project. Moreover, the project maintainers remain responsible for any code that is published as +part of a release. Contributors are expected to be responsible for any code they publish. + +AI should not be used to generate comments when communicating with maintainers. Comments are +expected to be written by humans. Comments that are believed to be written by AI may be hidden +without notice. + +If you are opening an issue, you should be able to describe the problem in your own words. + +If you are opening a pull request, you are expected to be able to explain the proposed changes in +your own words. This includes the pull request body and responses to questions. Make sure you have +reviewed the PR yourself before submitting it for review to the maintainers. Do not copy responses +from the AI when replying to questions from maintainers. As an exception, issues marked as `E-easy` +are meant for new contributors as a learning opportunity; the use of an LLM when submitting PR for +such issues is disallowed except without explicit permission from the team. Failure to comply may +result in the PR being closed directly without further notice. + +If you wish to include context from an interaction with AI in your comments, it must be in a +quote block (using `>`) and disclosed as such. It must be accompanied by human commentary +explaining the relevance and implications of the context. Do not share long snippets. + +AI is useful when communicating as a non-native English speaker. If you are using AI to edit your +comments for this purpose, please take the time to ensure it reflects your own voice and ideas. +When using AI for translation, we recommend writing in your native language and including the AI +translation in a quote block. diff --git a/doc/dev-guide/src/recipes.md b/doc/dev-guide/src/recipes.md new file mode 100644 index 0000000000..7feebf3577 --- /dev/null +++ b/doc/dev-guide/src/recipes.md @@ -0,0 +1,56 @@ +# Recipes + +This section contains some recipes for common tasks that you may want to +perform when contributing to rustup. + +## Supporting a new tier 2 target with host tools + +In principle, all target tuples **with host tools** should be recognized by +rustup and have its official rustup builds. Thus, once a new target has been +promoted to tier 2 with host tools, it should also be explicitly supported by +rustup through the following steps: + +1. Informing rustup of the new target: + + At the moment of writing, GitHub Actions' runners natively support Linux, + Windows, and macOS. Tier 2 builds are mostly cross-compiled from Linux + (except for Windows and macOS targets, which are directly compiled from the + target platform), and thus we will be focusing on the Linux case below. + + You can refer to [rustup#4688] for a practical example for adding a new tier + 2 target via cross-builds from Linux, where you can find nearly all places + where you would need to mention your new target in the rustup codebase. + + Notably, you would need to add a line in + `ci/actions-templates/linux-builds-template.yaml` to include it in rustup's + CI, while disabling the build for this target in all scenarios. At the + moment of writing, this is done by appending the YAML comment + `# skip-pr skip-master skip-stable` + at the end of the line when mentioning your target in that file. + + On the other hand, if you need to support new Windows or macOS targets, + please don't hesitate to discuss this with the team in a dedicated [rustup + issue]. + + [rustup#4688]: https://github.com/rust-lang/rustup/pull/4688 + [rustup issue]: https://github.com/rust-lang/rustup/issues + +2. Stabilizing the target: + + When your new target reaches stable Rust, you can then enable the target in + certain CI scenarios, depending on the popularity of the target. At the + moment of writing, this is done by removing certain occurrences of `skip-*` + in the aforementioned YAML comment. In most cases, you would only need to + enable the target for the `stable` CI scenario by removing `skip-stable`. + + You can refer to [rustup#4816] for a practical example for this step. + + Do note that when creating the PR for this step, you will need to prove that + the target's CI is indeed working by removing `skip-pr` in a separate commit + to temporarily enable this target in this PR's CI. Once the CI is green, you + can send the link ([example][send-link]) to that CI run in the PR thread for + verification. After that, you can safely drop the temporary commit to get + the PR ready for merging. + + [rustup#4816]: https://github.com/rust-lang/rustup/pull/4816 + [send-link]: https://github.com/rust-lang/rustup/pull/4816#issuecomment-4263419604 diff --git a/doc/dev-guide/src/tracing.md b/doc/dev-guide/src/tracing.md index 75caca1efc..ed3ffe2b75 100644 --- a/doc/dev-guide/src/tracing.md +++ b/doc/dev-guide/src/tracing.md @@ -59,7 +59,7 @@ receive log lines only from `rustup` itself with a max verbosity of `DEBUG`. ## Opentelemetry tracing The feature `otel` can be used when building rustup to turn on Opentelemetry -tracing with an OLTP GRPC exporter. +tracing with an OTLP gRPC exporter. This can be very useful for diagnosing performance or correctness issues in more complicated scenarios. @@ -118,6 +118,6 @@ Some good general heuristics: as these tend to help figure the puzzle of what-is-happening - Default to not instrumenting thin shim functions (or at least, only instrument them temporarily while figuring out the shape of a problem) -- Be way of debug build timing - release optimisations make a huge difference, +- Be wary of debug build timing - release optimisations make a huge difference, though debug is a lot faster to iterate on. If something isn't a problem in release don't pay it too much heed in debug. diff --git a/doc/user-guide/src/concepts/components.md b/doc/user-guide/src/concepts/components.md index 2d9b74beeb..9dce622a9c 100644 --- a/doc/user-guide/src/concepts/components.md +++ b/doc/user-guide/src/concepts/components.md @@ -62,6 +62,8 @@ toolchains. The following is an overview of the different components: * `rustc-dev` --- This component contains the compiler as a library. Most users will not need this; it is only needed for development *of* tools that link to the compiler, such as making modifications to [Clippy]. +* `enzyme` --- This component enables the `std::autodiff` module in nightly Rust. + See [autodiff][enzyme] for more detail. ### Previous components @@ -101,3 +103,4 @@ details. [rustup components history]: https://rust-lang.github.io/rustup-components-history/ [profiles]: profiles.md [nightly availability]: channels.md#nightly-availability +[enzyme]: https://rustc-dev-guide.rust-lang.org/autodiff/installation.html diff --git a/doc/user-guide/src/installation/other.md b/doc/user-guide/src/installation/other.md index 22ac13df73..fc9b918378 100644 --- a/doc/user-guide/src/installation/other.md +++ b/doc/user-guide/src/installation/other.md @@ -163,6 +163,10 @@ You can manually download `rustup-init` for a given target from - [sha256 file](https://static.rust-lang.org/rustup/dist/powerpc64le-unknown-linux-gnu/rustup-init.sha256) - [powerpc64le-unknown-linux-musl](https://static.rust-lang.org/rustup/dist/powerpc64le-unknown-linux-musl/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/powerpc64le-unknown-linux-musl/rustup-init.sha256) +- [riscv64gc-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/riscv64gc-unknown-linux-gnu/rustup-init) + - [sha256 file](https://static.rust-lang.org/rustup/dist/riscv64gc-unknown-linux-gnu/rustup-init.sha256) +- [riscv64gc-unknown-linux-musl](https://static.rust-lang.org/rustup/dist/riscv64gc-unknown-linux-musl/rustup-init) + - [sha256 file](https://static.rust-lang.org/rustup/dist/riscv64gc-unknown-linux-musl/rustup-init.sha256) - [s390x-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/s390x-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/s390x-unknown-linux-gnu/rustup-init.sha256) - [sparcv9-sun-solaris](https://static.rust-lang.org/rustup/dist/sparcv9-sun-solaris/rustup-init) diff --git a/doc/user-guide/src/installation/windows.md b/doc/user-guide/src/installation/windows.md index 59ac3bde82..b9d5d52c40 100644 --- a/doc/user-guide/src/installation/windows.md +++ b/doc/user-guide/src/installation/windows.md @@ -25,13 +25,7 @@ target that tuple by default. You can change this behavior with `rustup set default-host` or during installation. -For example, to explicitly select the 32-bit MSVC host: - -```console -$ rustup set default-host i686-pc-windows-msvc -``` - -Or to choose the 64 bit GNU toolchain: +For example, to choose the 64 bit GNU toolchain: ```console $ rustup set default-host x86_64-pc-windows-gnu diff --git a/src/bin/rustup-init.rs b/src/bin/rustup-init.rs index e844ddc19e..a0d408abc8 100644 --- a/src/bin/rustup-init.rs +++ b/src/bin/rustup-init.rs @@ -16,7 +16,6 @@ use std::process::ExitCode; use anyhow::{Context, Result, anyhow}; -use cfg_if::cfg_if; // Public macros require availability of the internal symbols use rs_tracing::{ close_trace_file, close_trace_file_internal, open_trace_file, trace_to_file_internal, @@ -105,12 +104,9 @@ async fn run_rustup_inner( Some(n) if n.starts_with("rustup-gc-") => { // This is the final uninstallation stage on windows where // rustup deletes its own exe - cfg_if! { - if #[cfg(windows)] { - self_update::complete_windows_uninstall(process) - } else { - unreachable!("Attempted to use Windows-specific code on a non-Windows platform. Aborting.") - } + cfg_select! { + windows => self_update::complete_windows_uninstall(process), + _ => unreachable!("Attempted to use Windows-specific code on a non-Windows platform. Aborting."), } } Some(n) => { diff --git a/src/cli.rs b/src/cli.rs index 88a7586ce2..31807fa261 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,6 +2,7 @@ #[macro_use] pub mod log; pub mod common; +mod docs; pub mod errors; mod help; mod job; diff --git a/src/cli/common.rs b/src/cli/common.rs index 8ec17a13ec..e9d9ccd26f 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -25,7 +25,9 @@ use crate::{ utils::{self, ExitCode}, }; -pub(crate) const WARN_COMPLETE_PROFILE: &str = "downloading with complete profile isn't recommended unless you are a developer of the rust language"; +pub(crate) const WARN_COMPLETE_PROFILE: &str = "\ +downloading with the `complete` profile is deprecated +help: consider switching to the `default` profile with `rustup set profile default`"; pub(crate) fn confirm(question: &str, default: bool, process: &Process) -> Result { write!(process.stdout().lock(), "{question} ")?; @@ -173,7 +175,7 @@ fn show_channel_updates( // this is a bit strange: we don't supply the version we // presumably had (for Installed and Unchanged), so we query it // again. Perhaps we can do better. - let version = match Toolchain::new(cfg, name.into()) { + let version = match Toolchain::new(cfg, name.clone().into()) { Ok(t) => t.rustc_version(), Err(_) => String::from("(toolchain not installed)"), }; diff --git a/src/cli/docs.rs b/src/cli/docs.rs new file mode 100644 index 0000000000..081fe768bd --- /dev/null +++ b/src/cli/docs.rs @@ -0,0 +1,369 @@ +//! `rustup doc` and `rustup man`: opening toolchain documentation and man pages. +//! +//! `rustup doc --serve` serves the documentation over a local HTTP server +//! instead of opening it directly as a `file://` URL. Some browsers (e.g. +//! Snap/Flatpak builds of Firefox or Brave) run in a sandbox that can't +//! access `file://` URLs under `~/.rustup`; serving the same static files +//! over `http://127.0.0.1` sidesteps that restriction entirely. +//! +//! The server binds to `127.0.0.1` only (never exposed on the network) and +//! rejects any request path that would resolve outside the served directory. + +use std::{ + borrow::Cow, + convert::Infallible, + io::Write as _, + path::{Component, Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{Context, Result, anyhow}; +use clap::Args; +use http_body_util::Full; +use hyper::{ + Request, Response, StatusCode, + body::{Bytes, Incoming}, + header::{CONTENT_LENGTH, CONTENT_TYPE}, + server::conn::http1, + service::service_fn, +}; +use hyper_util::rt::TokioIo; +use tokio::net::TcpListener; +use tracing::info; + +use super::topical_doc; +use crate::{ + config::{ActiveSource, Cfg}, + dist::{PartialToolchainDesc, manifest::ComponentStatus}, + toolchain::DistributableToolchain, + utils::{self, ExitCode}, +}; + +macro_rules! docs_data { + ( + $( + $( #[$meta:meta] )* + ($ident:ident, $help:expr, $path:expr $(,)?) + ),+ $(,)? + ) => { + #[derive(Debug, Args)] + pub(crate) struct DocPage { + $( + #[doc = $help] + #[arg(long, group = "page")] + $( #[$meta] )* + $ident: bool, + )+ + } + + impl DocPage { + fn path_str(&self) -> Option<&'static str> { + $( if self.$ident { return Some($path); } )+ + None + } + } + }; +} + +docs_data![ + // flags can be used to open specific documents, e.g. `rustup doc --nomicon` + // tuple elements: document name used as flag, help message, document index path + ( + alloc, + "The Rust core allocation and collections library", + "alloc/index.html" + ), + ( + book, + "The Rust Programming Language book", + "book/index.html" + ), + (cargo, "The Cargo Book", "cargo/index.html"), + (clippy, "The Clippy Documentation", "clippy/index.html"), + (core, "The Rust Core Library", "core/index.html"), + ( + edition_guide, + "The Rust Edition Guide", + "edition-guide/index.html" + ), + ( + embedded_book, + "The Embedded Rust Book", + "embedded-book/index.html" + ), + ( + error_codes, + "The Rust Error Codes Index", + "error_codes/index.html" + ), + ( + nomicon, + "The Dark Arts of Advanced and Unsafe Rust Programming", + "nomicon/index.html" + ), + #[arg(long = "proc_macro")] + ( + proc_macro, + "A support library for macro authors when defining new macros", + "proc_macro/index.html" + ), + (reference, "The Rust Reference", "reference/index.html"), + (releases, "Rust Release Notes", "releases.html"), + ( + rust_by_example, + "A collection of runnable examples that illustrate various Rust concepts and standard libraries", + "rust-by-example/index.html" + ), + ( + rustc, + "The compiler for the Rust programming language", + "rustc/index.html" + ), + ( + rustc_docs, + "The API documentation for the Rust compiler and other toolchain components", + "rustc-docs/index.html" + ), + ( + rustdoc, + "Documentation generator for Rust projects", + "rustdoc/index.html" + ), + (std, "Standard library API documentation", "std/index.html"), + ( + style_guide, + "The Rust Style Guide", + "style-guide/index.html" + ), + ( + test, + "Support code for rustc's built in unit-test and micro-benchmarking framework", + "test/index.html" + ), + ( + unstable_book, + "The Unstable Book", + "unstable-book/index.html" + ), +]; + +impl DocPage { + fn path(&self) -> Option<&'static Path> { + self.path_str().map(Path::new) + } + + fn name(&self) -> Option<&'static str> { + Some(self.path_str()?.rsplit_once('/')?.0) + } + + fn resolve<'t>(&self, root: &Path, topic: &'t str) -> Option<(PathBuf, Option<&'t str>)> { + // Use `.parent()` to chop off the default top-level `index.html`. + let mut base = root.join(Path::new(self.path()?).parent()?); + base.extend(topic.split("::")); + let base_index_html = base.join("index.html"); + + if base_index_html.is_file() { + return Some((base_index_html, None)); + } + + let base_html = base.with_extension("html"); + if base_html.is_file() { + return Some((base_html, None)); + } + + let parent_html = base.parent()?.with_extension("html"); + if parent_html.is_file() { + return Some((parent_html, topic.rsplit_once("::").map(|(_, s)| s))); + } + + None + } +} + +pub(crate) async fn doc( + cfg: &Cfg<'_>, + path_only: bool, + serve: bool, + toolchain: Option, + mut topic: Option<&str>, + doc_page: &DocPage, +) -> Result { + let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); + let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; + + if let Ok(distributable) = DistributableToolchain::try_from(&toolchain) + && let [_] = distributable + .components()? + .into_iter() + .filter(|cstatus| cstatus.component.short_name() == "rust-docs" && !cstatus.installed) + .take(1) + .collect::>() + .as_slice() + { + info!( + "`rust-docs` not installed in toolchain `{}`\nhelp: run `rustup component add --toolchain {} rust-docs` to install it", + distributable.desc(), + distributable.desc() + ); + return Err(anyhow!( + "unable to view documentation which is not installed" + )); + }; + + let (doc_path, fragment) = match (topic, doc_page.name()) { + (Some(topic), Some(name)) => { + let (doc_path, fragment) = doc_page + .resolve(&toolchain.doc_path("")?, topic) + .context(format!("no document for {name} on {topic}"))?; + (Cow::Owned(doc_path), fragment) + } + (Some(topic), None) => { + let doc_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?; + (Cow::Owned(doc_path), None) + } + (None, name) => { + topic = name; + let doc_path = doc_page.path().unwrap_or_else(|| Path::new("index.html")); + (Cow::Borrowed(doc_path), None) + } + }; + + if path_only { + let doc_path = toolchain.doc_path(&doc_path)?; + writeln!(cfg.process.stdout().lock(), "{}", doc_path.display())?; + return Ok(ExitCode::SUCCESS); + } + + if serve { + let root = toolchain.doc_path("")?; + serve_and_open(root, &doc_path, fragment).await?; + return Ok(ExitCode::SUCCESS); + } + + if let Some(name) = topic { + info!("opening docs named `{name}` in your browser"); + } else { + info!("opening docs in your browser"); + } + toolchain.open_docs(&doc_path, fragment)?; + Ok(ExitCode::SUCCESS) +} + +#[cfg(not(windows))] +pub(crate) async fn man( + cfg: &Cfg<'_>, + command: &str, + toolchain: Option, +) -> Result { + let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); + let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; + let path = toolchain.man_path(); + utils::assert_is_directory(&path)?; + + let mut manpaths = std::ffi::OsString::from(path); + manpaths.push(":"); // prepend to the default MANPATH list + if let Some(path) = cfg.process.var_os("MANPATH") { + manpaths.push(path); + } + std::process::Command::new("man") + .env("MANPATH", manpaths) + .arg(command) + .status() + .expect("failed to open man page"); + Ok(ExitCode::SUCCESS) +} + +/// Blocks forever, accepting and serving connections until the process is +/// killed by Ctrl-C. +async fn serve_and_open(root: PathBuf, initial_path: &Path, fragment: Option<&str>) -> Result<()> { + let listener = TcpListener::bind(("127.0.0.1", 0)) + .await + .context("failed to bind local documentation server")?; + let addr = listener + .local_addr() + .context("failed to read local address")?; + let root = Arc::::from(root); + + let mut url = format!( + "http://{addr}/{}", + initial_path.to_string_lossy().replace('\\', "/") + ); + if let Some(fragment) = fragment { + url.push('#'); + url.push_str(fragment); + } + + info!("serving docs at {url} (press Ctrl-C to stop)"); + utils::open_browser(&url)?; + + loop { + let (stream, _) = match listener.accept().await { + Ok(accepted) => accepted, + Err(err) => { + tracing::warn!("doc server: failed to accept connection: {err}"); + continue; + } + }; + let io = TokioIo::new(stream); + let root = root.clone(); + let svc = service_fn(move |req| serve(req, root.clone())); + + tokio::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { + tracing::warn!("doc server: connection error: {err}"); + } + }); + } +} + +async fn serve( + req: Request, + root: Arc, +) -> Result>, Infallible> { + let request_path = req.uri().path().trim_start_matches('/'); + let mut path = root.to_path_buf(); + for segment in Path::new(request_path).components() { + match segment { + Component::Normal(part) => path.push(part), + _ => return Ok(not_found()), + } + } + if !path.starts_with(&root) { + return Ok(not_found()); + } + if path.is_dir() { + path.push("index.html"); + } + + let Ok(contents) = tokio::fs::read(&path).await else { + return Ok(not_found()); + }; + + let response = Response::builder() + .status(StatusCode::OK) + .header( + CONTENT_TYPE, + match path.extension().and_then(|ext| ext.to_str()) { + Some("html") => "text/html; charset=utf-8", + Some("css") => "text/css", + Some("js") => "text/javascript", + Some("svg") => "image/svg+xml", + Some("png") => "image/png", + Some("jpg" | "jpeg") => "image/jpeg", + Some("woff2") => "font/woff2", + Some("txt") => "text/plain; charset=utf-8", + _ => "application/octet-stream", + }, + ) + .header(CONTENT_LENGTH, contents.len()) + .body(Full::new(Bytes::from(contents))) + .expect("building a static response can't fail"); + Ok(response) +} + +fn not_found() -> Response> { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from_static(b"404 not found"))) + .expect("building a static response can't fail") +} diff --git a/src/cli/help.rs b/src/cli/help.rs index 015e847077..333cf047ac 100644 --- a/src/cli/help.rs +++ b/src/cli/help.rs @@ -212,7 +212,7 @@ pub(crate) fn run_help() -> String { {LITERAL}$ cargo +nightly build{LITERAL:#} - {LITERAL}$ rustup run nightly cargo build{LITERAL:#}" + {LITERAL}$ rustup run --install nightly cargo build{LITERAL:#}" ) } @@ -232,7 +232,11 @@ pub(crate) fn doc_help() -> String { the default browser. By default, it opens the documentation index. Use the various - flags to open specific pieces of documentation." + flags to open specific pieces of documentation. + + If your browser is sandboxed (e.g. installed via Snap or Flatpak) it + may be unable to open `file://` URLs under `~/.rustup`. Pass `--serve` + to serve the documentation over a local HTTP server instead." ) } diff --git a/src/cli/proxy_mode.rs b/src/cli/proxy_mode.rs index 0e480e920f..4b7ac55d49 100644 --- a/src/cli/proxy_mode.rs +++ b/src/cli/proxy_mode.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, process::ExitStatus}; +use std::{path::PathBuf, process::ExitStatus, str::FromStr}; use anyhow::Result; @@ -23,7 +23,7 @@ pub async fn main(arg0: &str, current_dir: PathBuf, process: &Process) -> Result .as_ref() .map(|arg| arg.to_string_lossy()) .filter(|arg| arg.starts_with('+')) - .map(|name| ResolvableLocalToolchainName::try_from(&name.as_ref()[1..])) + .map(|name| ResolvableLocalToolchainName::from_str(&name[1..])) .transpose()?; // Build command args now while we know whether or not to skip arg 1. diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 343eccd504..8dbe6dad5d 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -12,7 +12,7 @@ use std::{ use anstream::ColorChoice; use anstyle::Style; -use anyhow::{Context, Error, Result, anyhow}; +use anyhow::{Error, Result, anyhow}; use clap::{ Args, CommandFactory, Parser, Subcommand, ValueEnum, builder::{PossibleValue, ValueHint}, @@ -29,6 +29,7 @@ use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; use crate::{ cli::{ common::{self, PackageUpdate, update_console_filter}, + docs, errors::CliError, help::{ check_help, completions_help, default_help, doc_help, install_help, @@ -39,14 +40,13 @@ use crate::{ update_help, }, self_update::{self, SelfUpdateMode, check_rustup_update}, - topical_doc, }, command, component_for_bin, config::{ActiveSource, Cfg}, dist::{ AutoInstallMode, DistOptions, PartialToolchainDesc, Profile, TargetTuple, download::DownloadCfg, - manifest::{Component, ComponentStatus, ManifestWithHash}, + manifest::{Component, ManifestWithHash}, }, errors::{DEFAULT_STABLE_HINT, RustupError}, install::{InstallMethod, UpdateStatus}, @@ -111,7 +111,7 @@ struct Rustup { fn plus_toolchain_value_parser(s: &str) -> clap::error::Result { use clap::{Error, error::ErrorKind}; if let Some(stripped) = s.strip_prefix('+') { - ResolvableToolchainName::try_from(stripped) + ResolvableToolchainName::from_str(stripped) .map_err(|e| Error::raw(ErrorKind::InvalidValue, e)) } else { Err(Error::raw( @@ -252,6 +252,11 @@ enum RustupSubcmd { #[arg(long)] path: bool, + /// Serve the documentation over a local HTTP server instead of + /// opening it directly as a `file://` URL + #[arg(long, conflicts_with = "path")] + serve: bool, + #[arg(long, help = official_toolchain_arg_help())] toolchain: Option, @@ -259,7 +264,7 @@ enum RustupSubcmd { topic: Option, #[command(flatten)] - page: DocPage, + page: docs::DocPage, }, /// View the man page for a given command @@ -468,6 +473,10 @@ struct UpdateOpts { /// Set the installed toolchain as the override for the current directory #[arg(long)] r#override: bool, + + /// Set the installed toolchain as the default toolchain + #[arg(long)] + default: bool, } #[derive(Debug, Default, Args)] @@ -801,18 +810,19 @@ pub async fn main( RustupSubcmd::Which { command, toolchain } => which(cfg, &command, toolchain).await, RustupSubcmd::Doc { path, + serve, toolchain, topic, page, - } => doc(cfg, path, toolchain, topic.as_deref(), &page).await, + } => docs::doc(cfg, path, serve, toolchain, topic.as_deref(), &page).await, #[cfg(not(windows))] - RustupSubcmd::Man { command, toolchain } => man(cfg, &command, toolchain).await, + RustupSubcmd::Man { command, toolchain } => docs::man(cfg, &command, toolchain).await, RustupSubcmd::Self_ { subcmd } => match subcmd { SelfSubcmd::Update => self_update::update(cfg).await, SelfSubcmd::Uninstall { no_prompt, no_modify_path, - } => self_update::uninstall(no_prompt, no_modify_path, process), + } => self_update::uninstall(no_prompt, no_modify_path, cfg), SelfSubcmd::UpgradeData => cfg.upgrade_data().map(|_| ExitCode::SUCCESS), }, RustupSubcmd::Set { subcmd } => match subcmd { @@ -854,7 +864,7 @@ async fn default_( cfg.set_default(None)?; } MaybeResolvableToolchainName::Some(ResolvableToolchainName::Custom(toolchain_name)) => { - Toolchain::new(cfg, (&toolchain_name).into())?; + Toolchain::new(cfg, toolchain_name.clone().into())?; cfg.set_default(Some(&toolchain_name.into()))?; } MaybeResolvableToolchainName::Some(ResolvableToolchainName::Official(toolchain)) => { @@ -1040,7 +1050,6 @@ async fn update( let self_update_mode = SelfUpdateMode::from_cfg(cfg)?; let should_self_update = !opts.no_self_update; let force_non_host = opts.force_non_host; - let set_override = opts.r#override; cfg.profile_override = opts.profile; let cfg = &cfg; @@ -1099,11 +1108,13 @@ async fn update( Ok(status.clone()), )?; - if set_override { + if opts.r#override { cfg.make_override(&cfg.current_dir, &desc.clone().into())?; } - if cfg.get_default()?.is_none() && matches!(status, UpdateStatus::Installed) { + if opts.default + || (cfg.get_default()?.is_none() && matches!(status, UpdateStatus::Installed)) + { cfg.set_default(Some(&desc.into()))?; } } @@ -1717,236 +1728,6 @@ fn override_remove(cfg: &Cfg<'_>, path: Option<&Path>, nonexistent: bool) -> Res Ok(ExitCode::SUCCESS) } -macro_rules! docs_data { - ( - $( - $( #[$meta:meta] )* - ($ident:ident, $help:expr, $path:expr $(,)?) - ),+ $(,)? - ) => { - #[derive(Debug, Args)] - struct DocPage { - $( - #[doc = $help] - #[arg(long, group = "page")] - $( #[$meta] )* - $ident: bool, - )+ - } - - impl DocPage { - fn path_str(&self) -> Option<&'static str> { - $( if self.$ident { return Some($path); } )+ - None - } - } - }; -} - -docs_data![ - // flags can be used to open specific documents, e.g. `rustup doc --nomicon` - // tuple elements: document name used as flag, help message, document index path - ( - alloc, - "The Rust core allocation and collections library", - "alloc/index.html" - ), - ( - book, - "The Rust Programming Language book", - "book/index.html" - ), - (cargo, "The Cargo Book", "cargo/index.html"), - (clippy, "The Clippy Documentation", "clippy/index.html"), - (core, "The Rust Core Library", "core/index.html"), - ( - edition_guide, - "The Rust Edition Guide", - "edition-guide/index.html" - ), - ( - embedded_book, - "The Embedded Rust Book", - "embedded-book/index.html" - ), - ( - error_codes, - "The Rust Error Codes Index", - "error_codes/index.html" - ), - ( - nomicon, - "The Dark Arts of Advanced and Unsafe Rust Programming", - "nomicon/index.html" - ), - #[arg(long = "proc_macro")] - ( - proc_macro, - "A support library for macro authors when defining new macros", - "proc_macro/index.html" - ), - (reference, "The Rust Reference", "reference/index.html"), - (releases, "Rust Release Notes", "releases.html"), - ( - rust_by_example, - "A collection of runnable examples that illustrate various Rust concepts and standard libraries", - "rust-by-example/index.html" - ), - ( - rustc, - "The compiler for the Rust programming language", - "rustc/index.html" - ), - ( - rustc_docs, - "The API documentation for the Rust compiler and other toolchain components", - "rustc-docs/index.html" - ), - ( - rustdoc, - "Documentation generator for Rust projects", - "rustdoc/index.html" - ), - (std, "Standard library API documentation", "std/index.html"), - ( - style_guide, - "The Rust Style Guide", - "style-guide/index.html" - ), - ( - test, - "Support code for rustc's built in unit-test and micro-benchmarking framework", - "test/index.html" - ), - ( - unstable_book, - "The Unstable Book", - "unstable-book/index.html" - ), -]; - -impl DocPage { - fn path(&self) -> Option<&'static Path> { - self.path_str().map(Path::new) - } - - fn name(&self) -> Option<&'static str> { - Some(self.path_str()?.rsplit_once('/')?.0) - } - - fn resolve<'t>(&self, root: &Path, topic: &'t str) -> Option<(PathBuf, Option<&'t str>)> { - // Use `.parent()` to chop off the default top-level `index.html`. - let mut base = root.join(Path::new(self.path()?).parent()?); - base.extend(topic.split("::")); - let base_index_html = base.join("index.html"); - - if base_index_html.is_file() { - return Some((base_index_html, None)); - } - - let base_html = base.with_extension("html"); - if base_html.is_file() { - return Some((base_html, None)); - } - - let parent_html = base.parent()?.with_extension("html"); - if parent_html.is_file() { - return Some((parent_html, topic.rsplit_once("::").map(|(_, s)| s))); - } - - None - } -} - -async fn doc( - cfg: &Cfg<'_>, - path_only: bool, - toolchain: Option, - mut topic: Option<&str>, - doc_page: &DocPage, -) -> Result { - let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); - let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; - - if let Ok(distributable) = DistributableToolchain::try_from(&toolchain) - && let [_] = distributable - .components()? - .into_iter() - .filter(|cstatus| cstatus.component.short_name() == "rust-docs" && !cstatus.installed) - .take(1) - .collect::>() - .as_slice() - { - info!( - "`rust-docs` not installed in toolchain `{}`\nhelp: run `rustup component add --toolchain {} rust-docs` to install it", - distributable.desc(), - distributable.desc() - ); - return Err(anyhow!( - "unable to view documentation which is not installed" - )); - }; - - let (doc_path, fragment) = match (topic, doc_page.name()) { - (Some(topic), Some(name)) => { - let (doc_path, fragment) = doc_page - .resolve(&toolchain.doc_path("")?, topic) - .context(format!("no document for {name} on {topic}"))?; - (Cow::Owned(doc_path), fragment) - } - (Some(topic), None) => { - let doc_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?; - (Cow::Owned(doc_path), None) - } - (None, name) => { - topic = name; - let doc_path = doc_page.path().unwrap_or_else(|| Path::new("index.html")); - (Cow::Borrowed(doc_path), None) - } - }; - - if path_only { - let doc_path = toolchain.doc_path(&doc_path)?; - writeln!(cfg.process.stdout().lock(), "{}", doc_path.display())?; - return Ok(ExitCode::SUCCESS); - } - - if let Some(name) = topic { - writeln!( - cfg.process.stderr().lock(), - "Opening docs named `{name}` in your browser" - )?; - } else { - writeln!(cfg.process.stderr().lock(), "Opening docs in your browser")?; - } - toolchain.open_docs(&doc_path, fragment)?; - Ok(ExitCode::SUCCESS) -} - -#[cfg(not(windows))] -async fn man( - cfg: &Cfg<'_>, - command: &str, - toolchain: Option, -) -> Result { - let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); - let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; - let path = toolchain.man_path(); - utils::assert_is_directory(&path)?; - - let mut manpaths = std::ffi::OsString::from(path); - manpaths.push(":"); // prepend to the default MANPATH list - if let Some(path) = cfg.process.var_os("MANPATH") { - manpaths.push(path); - } - std::process::Command::new("man") - .env("MANPATH", manpaths) - .arg(command) - .status() - .expect("failed to open man page"); - Ok(ExitCode::SUCCESS) -} - fn set_auto_self_update(cfg: &Cfg<'_>, auto_self_update_mode: SelfUpdateMode) -> Result { if cfg!(feature = "no-self-update") { let mut args = cfg.process.args_os(); @@ -2041,7 +1822,7 @@ async fn display_version(current_dir: PathBuf, process: &Process) -> Result<()> cfg.toolchain_override = cfg .process .args() - .find_map(|arg| arg.strip_prefix('+').map(ResolvableToolchainName::try_from)) + .find_map(|arg| arg.strip_prefix('+').map(ResolvableToolchainName::from_str)) .transpose()?; match cfg.maybe_ensure_active_toolchain(None).await { diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index cfe7ef0ed5..88bc516f81 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -30,21 +30,21 @@ //! Deleting the running binary during uninstall is tricky //! and racy on Windows. -use std::borrow::Cow; -use std::env::{self, consts::EXE_SUFFIX}; #[cfg(not(windows))] use std::io; -use std::io::Write; -use std::path::{Component, MAIN_SEPARATOR, Path, PathBuf}; -use std::process::Command; -use std::str::FromStr; -use std::{fmt, fs}; +use std::{ + borrow::Cow, + env::{self, consts::EXE_SUFFIX}, + fmt, fs, + io::Write, + path::{Component, MAIN_SEPARATOR, Path, PathBuf}, + process::Command, + str::FromStr, +}; use anstyle::Style; use anyhow::{Context, Result, anyhow}; -use cfg_if::cfg_if; -use clap::ValueEnum; -use clap::builder::PossibleValue; +use clap::{ValueEnum, builder::PossibleValue}; use clap_cargo::style::{GOOD, WARN}; use itertools::Itertools; use same_file::Handle; @@ -58,7 +58,7 @@ use crate::{ errors::CliError, markdown::md, }, - config::Cfg, + config::{Cfg, default_host_tuple}, dist::{ DistOptions, PartialToolchainDesc, Profile, TargetTuple, ToolchainDesc, download::DownloadCfg, @@ -67,6 +67,7 @@ use crate::{ errors::RustupError, install::{InstallMethod, UpdateStatus}, process::Process, + settings::SettingsFile, toolchain::{ DistributableToolchain, MaybeOfficialToolchainName, ResolvableToolchainName, Toolchain, ToolchainName, @@ -74,6 +75,9 @@ use crate::{ utils::{self, ExitCode}, }; +#[macro_use] +mod msg; + #[cfg(unix)] mod shell; @@ -89,7 +93,7 @@ mod windows; #[cfg(windows)] pub use windows::complete_windows_uninstall; #[cfg(all(windows, feature = "test"))] -pub use windows::{RegistryGuard, RegistryValueId, USER_PATH, get_path}; +pub use windows::{RUSTUP_REGISTRY_TEST_ID, RegistryValueId, USER_PATH, get_path}; #[cfg(windows)] use windows::{delete_rustup_and_cargo_home, do_add_to_path, do_remove_from_path}; #[cfg(windows)] @@ -106,7 +110,193 @@ pub(crate) struct InstallOpts<'a> { } impl InstallOpts<'_> { - fn install(self, cfg: &mut Cfg<'_>) -> Result> { + /// Installs rustup to the system according to the current options. + /// + /// The installation process is composed of the following steps: + /// - Copying the running binary to the binary directory. + /// - Hard-linking the various Rust tools to that copied binary. + /// - Adding the binary directory to the `$PATH` unless `no_modify_path` is set. + pub(crate) async fn install( + mut self, + current_dir: PathBuf, + no_prompt: bool, + quiet: bool, + process: &Process, + ) -> Result { + #[cfg_attr(not(unix), allow(unused_mut))] + let mut exit_code = ExitCode::SUCCESS; + + self.validate(process).map_err(|e| { + anyhow!( + "Pre-checks for host and toolchain failed: {e}\n\ + If you are unsure of suitable values, the 'stable' toolchain is the default.\n\ + Valid host tuples look something like: {}", + TargetTuple::from_host_or_build(process) + ) + })?; + + if process + .var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS") + .is_none_or(|s| s != "yes") + { + check_existence_of_rustc_or_cargo_in_path(no_prompt, process)?; + check_existence_of_settings_file(process)?; + } + + #[cfg(unix)] + { + exit_code &= unix::do_anti_sudo_check(no_prompt, process)?; + } + + let mut term = process.stdout(); + + #[cfg(windows)] + windows::maybe_install_msvc(&mut term, no_prompt, quiet, &self, process).await?; + + if !no_prompt { + let msg = pre_install_msg(self.no_modify_path, process)?; + + md(&mut term, msg); + let mut customized_install = false; + loop { + md(&mut term, self.display(process)); + match common::confirm_advanced(customized_install, process)? { + Confirm::No => { + info!("aborting installation"); + return Ok(ExitCode::SUCCESS); + } + Confirm::Yes => break, + Confirm::Advanced => { + customized_install = true; + self.customize(process)?; + } + } + } + } + + let no_modify_path = self.no_modify_path; + if let Err(e) = self.install_rust(current_dir, quiet, process).await { + report_error(&e, process); + + // On windows, where installation happens in a console + // that may have opened just for this purpose, give + // the user an opportunity to see the error before the + // window closes. + #[cfg(windows)] + if !no_prompt { + windows::ensure_prompt(process)?; + } + + return Ok(ExitCode::FAILURE); + } + + let cargo_home = canonical_cargo_home(process)?; + #[cfg(windows)] + let cargo_home = cargo_home.replace('\\', r"\\"); + #[cfg(windows)] + let msg = if no_modify_path { + format!( + post_install_msg_win_no_modify_path!(), + cargo_home = cargo_home + ) + } else { + format!(post_install_msg_win!(), cargo_home = cargo_home) + }; + #[cfg(not(windows))] + let source_env_lines = shell::build_source_env_lines(process); + #[cfg(not(windows))] + let msg = if no_modify_path { + format!( + post_install_msg_unix_no_modify_path!(), + cargo_home = cargo_home, + source_env_lines = source_env_lines, + ) + } else { + format!( + post_install_msg_unix!(), + cargo_home = cargo_home, + source_env_lines = source_env_lines, + ) + }; + md(&mut term, msg); + + #[cfg(unix)] + warn_if_default_linker_missing(process); + + #[cfg(windows)] + if !no_prompt { + // On windows, where installation happens in a console + // that may have opened just for this purpose, require + // the user to press a key to continue. + windows::ensure_prompt(process)?; + } + + Ok(exit_code) + } + + /// Installs the rustup binary and proxies, and installs a toolchain if specified. + async fn install_rust( + self, + current_dir: PathBuf, + quiet: bool, + process: &Process, + ) -> Result<()> { + install_bins(process)?; + + #[cfg(unix)] + unix::do_write_env_files(process)?; + + if !self.no_modify_path { + do_add_to_path(process)?; + } + + // If RUSTUP_HOME is not set, make sure it exists + if process.var_os("RUSTUP_HOME").is_none() { + let home = process + .home_dir() + .map(|p| p.join(".rustup")) + .ok_or_else(|| anyhow::anyhow!("could not find home dir to put .rustup in"))?; + + fs::create_dir_all(home).context("unable to create ~/.rustup")?; + } + + let mut cfg = Cfg::from_env(current_dir, quiet, false, process)?; + + let (components, targets) = (self.components, self.targets); + let toolchain = self.select_toolchain(&mut cfg)?; + if let Some(desc) = toolchain { + let options = + DistOptions::new(components, targets, &desc, cfg.get_profile()?, true, &cfg)?; + let status = if Toolchain::exists(&cfg, &desc.clone().into())? { + warn!("Updating existing toolchain, profile choice will be ignored"); + // If we have a partial install we might not be able to read content here. We could: + // - fail and folk have to delete the partially present toolchain to recover + // - silently ignore it (and provide inconsistent metadata for reporting the install/update change) + // - delete the partial install and start over + // For now, we error. + let toolchain = DistributableToolchain::new(&cfg, desc.clone())?; + InstallMethod::Dist(options.for_update(&toolchain, false)) + .install(None) + .await? + } else { + DistributableToolchain::install(options).await?.status + }; + + check_proxy_sanity(cfg.process, components, &desc)?; + + cfg.set_default(Some(&desc.clone().into()))?; + writeln!(cfg.process.stdout().lock())?; + common::show_channel_update(&cfg, PackageUpdate::Toolchain(desc), Ok(status))?; + } + Ok(()) + } + + /// Selects the toolchain to install based on the user's intent. + /// + /// This function first initializes the default profile and default host tuple in the + /// configuration, then returns the toolchain that should be installed, or `None` if none is + /// specified by the user. + fn select_toolchain(self, cfg: &mut Cfg<'_>) -> Result> { let Self { default_host_tuple, default_toolchain, @@ -204,14 +394,16 @@ impl InstallOpts<'_> { process, )?); - self.default_toolchain = Some(MaybeOfficialToolchainName::try_from(common::question_str( - "Default toolchain? (stable/beta/nightly/none)", - &match &self.default_toolchain { - Some(name) => name.to_string(), - None => "stable".to_owned(), - }, - process, - )?)?); + self.default_toolchain = Some(MaybeOfficialToolchainName::from_str( + &common::question_str( + "Default toolchain? (stable/beta/nightly/none)", + &match &self.default_toolchain { + Some(name) => name.to_string(), + None => "stable".to_owned(), + }, + process, + )?, + )?); self.profile = ::from_str(&common::question_str( &format!( @@ -238,7 +430,7 @@ impl InstallOpts<'_> { .unwrap_or_else(|| TargetTuple::from_host_or_build(process)); let partial_channel = match &self.default_toolchain { None | Some(MaybeOfficialToolchainName::None) => { - ResolvableToolchainName::try_from("stable")? + ResolvableToolchainName::from_str("stable")? } Some(MaybeOfficialToolchainName::Some(s)) => s.into(), }; @@ -246,6 +438,28 @@ impl InstallOpts<'_> { trace!("Successfully resolved installation toolchain as: {resolved}"); Ok(()) } + + fn display(&self, process: &Process) -> String { + format!( + r"Current installation options: + +- ` `default host tuple: `{}` +- ` `default toolchain: `{}` +- ` `profile: `{}` +- modify PATH variable: `{}` +", + self.default_host_tuple.as_ref().map_or_else( + || TargetTuple::from_host_or_build(process), + TargetTuple::new, + ), + match &self.default_toolchain { + Some(name) => name.to_string(), + None => "stable (default)".to_owned(), + }, + self.profile, + if !self.no_modify_path { "yes" } else { "no" } + ) + } } #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -322,7 +536,7 @@ impl SelfUpdateMode { let setup_path = prepare_update(dl_cfg).await?; if let Some(setup_path) = &setup_path { - return run_update(setup_path); + return run_update(setup_path, dl_cfg.process); } else { // Try again in case we emitted "tool `{}` is already installed" last time. install_proxies(dl_cfg.process)?; @@ -369,157 +583,6 @@ impl fmt::Display for SelfUpdateMode { } } -// The big installation messages. These are macros because the first -// argument of format! needs to be a literal. - -macro_rules! pre_install_msg_template { - ($platform_msg:literal) => { - concat!( - r" -# Welcome to Rust! - -This will download and install the official compiler for the Rust -programming language, and its package manager, Cargo. - -Rustup metadata and toolchains will be installed into the Rustup -home directory, located at: - - {rustup_home} - -This can be modified with the RUSTUP_HOME environment variable. - -The Cargo home directory is located at: - - {cargo_home} - -This can be modified with the CARGO_HOME environment variable. - -The `cargo`, `rustc`, `rustup` and other commands will be added to -Cargo's bin directory, located at: - - {cargo_home_bin} - -", - $platform_msg, - r#" - -You can uninstall at any time with `rustup self uninstall` and -these changes will be reverted. -"# - ) - }; -} - -#[cfg(not(windows))] -macro_rules! pre_install_msg_unix { - () => { - pre_install_msg_template!( - "This path will then be added to your `PATH` environment variable by -modifying the profile file{plural} located at: - -{rcfiles}" - ) - }; -} - -#[cfg(windows)] -macro_rules! pre_install_msg_win { - () => { - pre_install_msg_template!( - r#"This path will then be added to your `PATH` environment variable by -modifying the `PATH` registry key at `HKEY_CURRENT_USER\Environment`."# - ) - }; -} - -macro_rules! pre_install_msg_no_modify_path { - () => { - pre_install_msg_template!( - "This path needs to be in your `PATH` environment variable, -but will not be added automatically." - ) - }; -} - -#[cfg(not(windows))] -macro_rules! post_install_msg_unix { - () => { - r"# Rust is installed now. Great! - -To get started you may need to restart your current shell. -This would reload your `PATH` environment variable to include -Cargo's bin directory ({cargo_home}/bin). - -To configure your current shell, you need to source the -corresponding `env` file under {cargo_home}. - -Consider running the right command for your shell (note the leading DOT): -{source_env_lines}" - }; -} - -#[cfg(windows)] -macro_rules! post_install_msg_win { - () => { - r"# Rust is installed now. Great! - - -To get started you may need to restart your current shell. -This would reload its `PATH` environment variable to include -Cargo's bin directory ({cargo_home}\\bin). -" - }; -} - -#[cfg(not(windows))] -macro_rules! post_install_msg_unix_no_modify_path { - () => { - r"# Rust is installed now. Great! - -To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH` -environment variable. This has not been done automatically. - -To configure your current shell, you need to source -the corresponding `env` file under {cargo_home}. - -Consider running the right command for your shell (note the leading DOT): -{source_env_lines}" - }; -} - -#[cfg(windows)] -macro_rules! post_install_msg_win_no_modify_path { - () => { - r"# Rust is installed now. Great! - -To get started you need Cargo's bin directory ({cargo_home}\\bin) in your `PATH` -environment variable. This has not been done automatically. -" - }; -} - -macro_rules! pre_uninstall_msg { - () => { - r"# Thanks for hacking in Rust! - -This will uninstall all Rust toolchains and data, and remove -`{cargo_home}/bin` from your `PATH` environment variable. - -" - }; -} - -macro_rules! pre_uninstall_msg_no_modify_path { - () => { - r"# Thanks for hacking in Rust! - -This will uninstall all Rust toolchains and data. -Your `PATH` environment variable will not be touched. - -" - }; -} - static DEFAULT_UPDATE_ROOT: &str = "https://static.rust-lang.org/rustup"; fn update_root(process: &Process) -> String { @@ -539,140 +602,15 @@ fn canonical_cargo_home(process: &Process) -> Result> { .unwrap_or_else(|| PathBuf::from(".")) .join(".cargo"); Ok(if default_cargo_home == path { - cfg_if! { - if #[cfg(windows)] { - r"%USERPROFILE%\.cargo".into() - } else { - "$HOME/.cargo".into() - } + cfg_select! { + windows => r"%USERPROFILE%\.cargo".into(), + _ => "$HOME/.cargo".into(), } } else { path.to_string_lossy().into_owned().into() }) } -/// Installing is a simple matter of copying the running binary to -/// `CARGO_HOME`/bin, hard-linking the various Rust tools to it, -/// and adding `CARGO_HOME`/bin to PATH. -pub(crate) async fn install( - no_prompt: bool, - mut opts: InstallOpts<'_>, - cfg: &mut Cfg<'_>, -) -> Result { - #[cfg_attr(not(unix), allow(unused_mut))] - let mut exit_code = ExitCode::SUCCESS; - - opts.validate(cfg.process).map_err(|e| { - anyhow!( - "Pre-checks for host and toolchain failed: {e}\n\ - If you are unsure of suitable values, the 'stable' toolchain is the default.\n\ - Valid host tuples look something like: {}", - TargetTuple::from_host_or_build(cfg.process) - ) - })?; - - if cfg - .process - .var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS") - .is_none_or(|s| s != "yes") - { - check_existence_of_rustc_or_cargo_in_path(no_prompt, cfg.process)?; - check_existence_of_settings_file(cfg)?; - } - - #[cfg(unix)] - { - exit_code &= unix::do_anti_sudo_check(no_prompt, cfg.process)?; - } - - let mut term = cfg.process.stdout(); - - #[cfg(windows)] - windows::maybe_install_msvc(&mut term, no_prompt, &opts, &*cfg).await?; - - if !no_prompt { - let msg = pre_install_msg(opts.no_modify_path, cfg.process)?; - - md(&mut term, msg); - let mut customized_install = false; - loop { - md(&mut term, current_install_opts(&opts, cfg.process)); - match common::confirm_advanced(customized_install, cfg.process)? { - Confirm::No => { - info!("aborting installation"); - return Ok(ExitCode::SUCCESS); - } - Confirm::Yes => { - break; - } - Confirm::Advanced => { - customized_install = true; - opts.customize(cfg.process)?; - } - } - } - } - - let no_modify_path = opts.no_modify_path; - if let Err(e) = maybe_install_rust(opts, cfg).await { - report_error(&e, cfg.process); - - // On windows, where installation happens in a console - // that may have opened just for this purpose, give - // the user an opportunity to see the error before the - // window closes. - #[cfg(windows)] - if !no_prompt { - windows::ensure_prompt(cfg.process)?; - } - - return Ok(ExitCode::FAILURE); - } - - let cargo_home = canonical_cargo_home(cfg.process)?; - #[cfg(windows)] - let cargo_home = cargo_home.replace('\\', r"\\"); - #[cfg(windows)] - let msg = if no_modify_path { - format!( - post_install_msg_win_no_modify_path!(), - cargo_home = cargo_home - ) - } else { - format!(post_install_msg_win!(), cargo_home = cargo_home) - }; - #[cfg(not(windows))] - let source_env_lines = shell::build_source_env_lines(cfg.process); - #[cfg(not(windows))] - let msg = if no_modify_path { - format!( - post_install_msg_unix_no_modify_path!(), - cargo_home = cargo_home, - source_env_lines = source_env_lines, - ) - } else { - format!( - post_install_msg_unix!(), - cargo_home = cargo_home, - source_env_lines = source_env_lines, - ) - }; - md(&mut term, msg); - - #[cfg(unix)] - warn_if_default_linker_missing(cfg.process); - - #[cfg(windows)] - if !no_prompt { - // On windows, where installation happens in a console - // that may have opened just for this purpose, require - // the user to press a key to continue. - windows::ensure_prompt(cfg.process)?; - } - - Ok(exit_code) -} - fn rustc_or_cargo_exists_in_path(process: &Process) -> Result<()> { // Ignore rustc and cargo if present in $HOME/.cargo/bin or a few other directories #[allow(clippy::ptr_arg)] @@ -721,23 +659,22 @@ fn check_existence_of_rustc_or_cargo_in_path(no_prompt: bool, process: &Process) Ok(()) } -fn check_existence_of_settings_file(cfg: &Cfg<'_>) -> Result<()> { - let rustup_dir = cfg.process.rustup_home()?; - let settings_file_path = rustup_dir.join("settings.toml"); - if !utils::path_exists(&settings_file_path) { +fn check_existence_of_settings_file(process: &Process) -> Result<()> { + let rustup_dir = process.rustup_home()?; + let settings_file = SettingsFile::new(rustup_dir.join("settings.toml")); + if !utils::path_exists(&settings_file.path) { return Ok(()); } - let settings_toolchain = cfg - .settings_file - .with(|s| Ok(s.default_toolchain.clone()))?; + let settings_toolchain = settings_file.with(|s| Ok(s.default_toolchain.clone()))?; // If there is already a non-empty `settings.toml` file (e.g., not a fresh install), // then we warn the user that there was an already configured default toolchain. let Some(default_toolchain) = settings_toolchain else { return Ok(()); }; warn!("it looks like you have an existing rustup settings file at:"); - warn!("{}", settings_file_path.display()); - let inferred = PartialToolchainDesc::from_str("stable")?.resolve(&cfg.default_host_tuple()?)?; + warn!("{}", settings_file.path.display()); + let default_host_tuple = settings_file.with(|s| Ok(default_host_tuple(s, process)))?; + let inferred = PartialToolchainDesc::from_str("stable")?.resolve(&default_host_tuple)?; if default_toolchain != inferred.to_string() { warn!("rustup will install the default toolchain as specified in the settings file,"); warn!("instead of the one inferred from the default host tuple."); @@ -786,28 +723,6 @@ fn pre_install_msg(no_modify_path: bool, process: &Process) -> Result { } } -fn current_install_opts(opts: &InstallOpts<'_>, process: &Process) -> String { - format!( - r"Current installation options: - -- ` `default host tuple: `{}` -- ` `default toolchain: `{}` -- ` `profile: `{}` -- modify PATH variable: `{}` -", - opts.default_host_tuple - .as_ref() - .map(TargetTuple::new) - .unwrap_or_else(|| TargetTuple::from_host_or_build(process)), - match &opts.default_toolchain { - Some(name) => name.to_string(), - None => "stable (default)".to_owned(), - }, - opts.profile, - if !opts.no_modify_path { "yes" } else { "no" } - ) -} - #[cfg(unix)] fn warn_if_default_linker_missing(process: &Process) { // Search for linker in PATH @@ -998,66 +913,22 @@ fn check_proxy_sanity(process: &Process, components: &[&str], desc: &ToolchainDe Ok(()) } -async fn maybe_install_rust(opts: InstallOpts<'_>, cfg: &mut Cfg<'_>) -> Result<()> { - install_bins(cfg.process)?; - - #[cfg(unix)] - unix::do_write_env_files(cfg.process)?; - - if !opts.no_modify_path { - do_add_to_path(cfg.process)?; - } - - // If RUSTUP_HOME is not set, make sure it exists - if cfg.process.var_os("RUSTUP_HOME").is_none() { - let home = cfg - .process - .home_dir() - .map(|p| p.join(".rustup")) - .ok_or_else(|| anyhow::anyhow!("could not find home dir to put .rustup in"))?; - - fs::create_dir_all(home).context("unable to create ~/.rustup")?; - } - - let (components, targets) = (opts.components, opts.targets); - let toolchain = opts.install(cfg)?; - if let Some(desc) = &toolchain { - let options = DistOptions::new(components, targets, desc, cfg.get_profile()?, true, cfg)?; - let status = if Toolchain::exists(cfg, &desc.into())? { - warn!("Updating existing toolchain, profile choice will be ignored"); - // If we have a partial install we might not be able to read content here. We could: - // - fail and folk have to delete the partially present toolchain to recover - // - silently ignore it (and provide inconsistent metadata for reporting the install/update change) - // - delete the partial install and start over - // For now, we error. - let toolchain = DistributableToolchain::new(cfg, desc.clone())?; - InstallMethod::Dist(options.for_update(&toolchain, false)) - .install(None) - .await? - } else { - DistributableToolchain::install(options).await?.status - }; - - check_proxy_sanity(cfg.process, components, desc)?; - - cfg.set_default(Some(&desc.clone().into()))?; - writeln!(cfg.process.stdout().lock())?; - common::show_channel_update(cfg, PackageUpdate::Toolchain(desc.clone()), Ok(status))?; - } - Ok(()) -} - -pub(crate) fn uninstall( - no_prompt: bool, - no_modify_path: bool, - process: &Process, -) -> Result { +/// Uninstall process: +/// 1. Remove all installed toolchains. +/// 2. Remove rustup home. +/// 3. Remove all entries in `$CARGO_HOME` except `bin`. +/// 4. Remove rustup tool links and binary. +/// 5. Try to remove $CARGO_HOME/bin directory if it's empty. +/// 6. Upon successfully removing $CARGO_HOME/bin, clean up $PATH. +/// 7. Try to remove $CARGO_HOME directory if it's empty. +pub(crate) fn uninstall(no_prompt: bool, no_modify_path: bool, cfg: &Cfg<'_>) -> Result { if cfg!(feature = "no-self-update") { error!("self-uninstall is disabled for this build of rustup"); error!("you should probably use your system package manager to uninstall rustup"); return Ok(ExitCode::FAILURE); } + let process = cfg.process; let cargo_home = process.cargo_home()?; if !cargo_home.join(format!("bin/rustup{EXE_SUFFIX}")).exists() { @@ -1081,6 +952,11 @@ pub(crate) fn uninstall( } } + info!("removing toolchains"); + for toolchain in cfg.list_toolchains()? { + Toolchain::ensure_removed(cfg, toolchain.into())?; + } + info!("removing rustup home"); // Delete RUSTUP_HOME @@ -1245,7 +1121,7 @@ pub(crate) async fn update(cfg: &Cfg<'_>) -> Result { PackageUpdate::Rustup, Ok(UpdateStatus::Updated(version)), ); - return run_update(&setup_path); + return run_update(&setup_path, cfg.process); } None => { let _ = common::show_channel_update( @@ -1481,7 +1357,7 @@ mod tests { .unwrap() .resolve(&cfg.default_host_tuple().unwrap()) .unwrap(), - opts.install(&mut cfg) + opts.select_toolchain(&mut cfg) .unwrap() // result .unwrap() // option ); diff --git a/src/cli/self_update/msg.rs b/src/cli/self_update/msg.rs new file mode 100644 index 0000000000..fecaafc695 --- /dev/null +++ b/src/cli/self_update/msg.rs @@ -0,0 +1,150 @@ +//! The big installation messages. These are macros because the first +//! argument of format! needs to be a literal. + +macro_rules! pre_install_msg_template { + ($platform_msg:literal) => { + concat!( + r" +# Welcome to Rust! + +This will download and install the official compiler for the Rust +programming language, and its package manager, Cargo. + +Rustup metadata and toolchains will be installed into the Rustup +home directory, located at: + + {rustup_home} + +This can be modified with the RUSTUP_HOME environment variable. + +The Cargo home directory is located at: + + {cargo_home} + +This can be modified with the CARGO_HOME environment variable. + +The `cargo`, `rustc`, `rustup` and other commands will be added to +Cargo's bin directory, located at: + + {cargo_home_bin} + +", + $platform_msg, + r#" + +You can uninstall at any time with `rustup self uninstall` and +these changes will be reverted. +"# + ) + }; +} + +#[cfg(not(windows))] +macro_rules! pre_install_msg_unix { + () => { + pre_install_msg_template!( + "This path will then be added to your `PATH` environment variable by +modifying the profile file{plural} located at: + +{rcfiles}" + ) + }; +} + +#[cfg(windows)] +macro_rules! pre_install_msg_win { + () => { + pre_install_msg_template!( + r#"This path will then be added to your `PATH` environment variable by +modifying the `PATH` registry key at `HKEY_CURRENT_USER\Environment`."# + ) + }; +} + +macro_rules! pre_install_msg_no_modify_path { + () => { + pre_install_msg_template!( + "This path needs to be in your `PATH` environment variable, +but will not be added automatically." + ) + }; +} + +#[cfg(not(windows))] +macro_rules! post_install_msg_unix { + () => { + r"# Rust is installed now. Great! + +To get started you may need to restart your current shell. +This would reload your `PATH` environment variable to include +Cargo's bin directory ({cargo_home}/bin). + +To configure your current shell, you need to source the +corresponding `env` file under {cargo_home}. + +Consider running the right command for your shell (note the leading DOT): +{source_env_lines}" + }; +} + +#[cfg(windows)] +macro_rules! post_install_msg_win { + () => { + r"# Rust is installed now. Great! + + +To get started you may need to restart your current shell. +This would reload its `PATH` environment variable to include +Cargo's bin directory ({cargo_home}\\bin). +" + }; +} + +#[cfg(not(windows))] +macro_rules! post_install_msg_unix_no_modify_path { + () => { + r"# Rust is installed now. Great! + +To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH` +environment variable. This has not been done automatically. + +To configure your current shell, you need to source +the corresponding `env` file under {cargo_home}. + +Consider running the right command for your shell (note the leading DOT): +{source_env_lines}" + }; +} + +#[cfg(windows)] +macro_rules! post_install_msg_win_no_modify_path { + () => { + r"# Rust is installed now. Great! + +To get started you need Cargo's bin directory ({cargo_home}\\bin) in your `PATH` +environment variable. This has not been done automatically. +" + }; +} + +macro_rules! pre_uninstall_msg { + () => { + r"# Thanks for hacking in Rust! + +This will uninstall all Rust toolchains and data, and remove +`{cargo_home}/bin` from your `PATH` environment variable. + +" + }; +} + +macro_rules! pre_uninstall_msg_no_modify_path { + () => { + r"# Thanks for hacking in Rust! + +This will uninstall all Rust toolchains and data. +Your `PATH` environment variable will not be touched. + +" + }; +} diff --git a/src/cli/self_update/unix.rs b/src/cli/self_update/unix.rs index e4e236e2bc..b099534354 100644 --- a/src/cli/self_update/unix.rs +++ b/src/cli/self_update/unix.rs @@ -61,10 +61,7 @@ pub(crate) fn do_remove_from_path(process: &Process) -> Result<()> { let file = utils::read_file("rcfile", rc)?; let file_bytes = file.into_bytes(); // FIXME: This is whitespace sensitive where it should not be. - if let Some(idx) = file_bytes - .windows(source_bytes.len()) - .position(|w| w == source_bytes.as_slice()) - { + if let Some(idx) = find_exact_line(&file_bytes, &source_bytes) { // Here we rewrite the file without the offending line. let mut new_bytes = file_bytes[..idx].to_vec(); new_bytes.extend(&file_bytes[idx + source_bytes.len()..]); @@ -125,7 +122,7 @@ pub(crate) fn do_write_env_files(process: &Process) -> Result<()> { /// Tell the upgrader to replace the rustup bins, then delete /// itself. -pub(crate) fn run_update(setup_path: &Path) -> Result { +pub(crate) fn run_update(setup_path: &Path, _process: &Process) -> Result { let status = Command::new(setup_path) .arg("--self-replace") .status() @@ -153,10 +150,7 @@ fn remove_legacy_source_command(source_cmd: String, process: &Process) -> Result let file = utils::read_file("rcfile", &rc)?; let file_bytes = file.into_bytes(); // FIXME: This is whitespace sensitive where it should not be. - if let Some(idx) = file_bytes - .windows(cmd_bytes.len()) - .position(|w| w == cmd_bytes.as_slice()) - { + if let Some(idx) = find_exact_line(&file_bytes, &cmd_bytes) { // Here we rewrite the file without the offending line. let mut new_bytes = file_bytes[..idx].to_vec(); new_bytes.extend(&file_bytes[idx + cmd_bytes.len()..]); @@ -167,6 +161,16 @@ fn remove_legacy_source_command(source_cmd: String, process: &Process) -> Result Ok(()) } +fn find_exact_line(file: &[u8], line: &[u8]) -> Option { + // The trailing newline enforces the end boundary; check the start boundary here. + assert!(line.ends_with(b"\n")); + file.windows(line.len()) + .enumerate() + .find_map(|(idx, candidate)| { + (candidate == line && (idx == 0 || file[idx - 1] == b'\n')).then_some(idx) + }) +} + fn remove_legacy_paths(process: &Process) -> Result<()> { // Before the work to support more kinds of shells, which was released in // version 1.23.0 of Rustup, we always inserted this line instead, which is diff --git a/src/cli/self_update/windows.rs b/src/cli/self_update/windows.rs index fc3f9d0648..51acf1f7e0 100644 --- a/src/cli/self_update/windows.rs +++ b/src/cli/self_update/windows.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::env::{consts::EXE_SUFFIX, split_paths}; use std::ffi::{OsStr, OsString}; use std::fmt; @@ -5,8 +6,6 @@ use std::io::Write; use std::os::windows::ffi::OsStrExt; use std::path::Path; use std::process::Command; -#[cfg(any(test, feature = "test"))] -use std::sync::{LockResult, Mutex, MutexGuard}; use anyhow::{Context, Result, anyhow}; use tracing::{info, warn}; @@ -20,9 +19,7 @@ use super::super::errors::CliError; use super::common; use super::{InstallOpts, install_bins, report_error}; use crate::cli::markdown::md; -use crate::config::Cfg; use crate::dist::TargetTuple; -use crate::dist::download::DownloadCfg; use crate::download::download_file; use crate::process::{ColorableTerminal, Process}; use crate::utils; @@ -92,35 +89,36 @@ pub(crate) fn choose_vs_install(process: &Process) -> Result, - cfg: &Cfg<'_>, + process: &Process, ) -> Result<()> { - let Some(plan) = do_msvc_check(opts, cfg.process) else { + let Some(plan) = do_msvc_check(opts, process) else { return Ok(()); }; if no_prompt { warn!("installing msvc toolchain without its prerequisites"); - } else if !cfg.quiet && plan == VsInstallPlan::Automatic { + } else if !quiet && plan == VsInstallPlan::Automatic { md(term, MSVC_AUTO_INSTALL_MESSAGE); - match choose_vs_install(cfg.process)? { + match choose_vs_install(process)? { Some(VsInstallPlan::Automatic) => { - match try_install_msvc(opts, cfg).await { + match try_install_msvc(opts, process).await { Err(e) => { // Make sure the console doesn't exit before the user can // see the error and give the option to continue anyway. - report_error(&e, cfg.process); - if !common::question_bool("\nContinue?", false, cfg.process)? { + report_error(&e, process); + if !common::question_bool("\nContinue?", false, process)? { info!("aborting installation"); } } - Ok(ContinueInstall::No) => ensure_prompt(cfg.process)?, + Ok(ContinueInstall::No) => ensure_prompt(process)?, _ => {} } } Some(VsInstallPlan::Manual) => { md(term, MSVC_MANUAL_INSTALL_MESSAGE); - if !common::question_bool("\nContinue?", false, cfg.process)? { + if !common::question_bool("\nContinue?", false, process)? { info!("aborting installation"); } } @@ -129,7 +127,7 @@ pub(super) async fn maybe_install_msvc( } else { md(term, MSVC_MESSAGE); md(term, MSVC_MANUAL_INSTALL_MESSAGE); - if !common::question_bool("\nContinue?", false, cfg.process)? { + if !common::question_bool("\nContinue?", false, process)? { info!("aborting installation"); } } @@ -260,7 +258,7 @@ pub(crate) enum ContinueInstall { /// but the rustup install should not be continued at this time. pub(crate) async fn try_install_msvc( opts: &InstallOpts<'_>, - cfg: &Cfg<'_>, + process: &Process, ) -> Result { // download the installer let visual_studio_url = utils::parse_url("https://aka.ms/vs/17/release/vs_community.exe")?; @@ -271,16 +269,8 @@ pub(crate) async fn try_install_msvc( .context("error creating temp directory")?; let visual_studio = tempdir.path().join("vs_setup.exe"); - let dl_cfg = DownloadCfg::new(cfg); info!("downloading Visual Studio installer"); - download_file( - &visual_studio_url, - &visual_studio, - None, - None, - dl_cfg.process, - ) - .await?; + download_file(&visual_studio_url, &visual_studio, None, None, process).await?; // Run the installer. Arguments are documented at: // https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio @@ -295,7 +285,7 @@ pub(crate) async fn try_install_msvc( // It's possible an earlier or later version of the Windows SDK has been // installed separately from Visual Studio so installing it can be skipped. - if !has_windows_sdk_libs(cfg.process) { + if !has_windows_sdk_libs(process) { cmd.args([ "--add", "Microsoft.VisualStudio.Component.Windows11SDK.26100", @@ -328,8 +318,8 @@ pub(crate) async fn try_install_msvc( // It's possible that the installer returned a non-zero exit code // even though the required components were successfully installed. // In that case we warn about the error but continue on. - let have_msvc = do_msvc_check(opts, cfg.process).is_none(); - let has_libs = has_windows_sdk_libs(cfg.process); + let have_msvc = do_msvc_check(opts, process).is_none(); + let has_libs = has_windows_sdk_libs(process); if have_msvc && has_libs { warn!("Visual Studio is installed but a problem occurred during installation"); warn!("{}", err); @@ -454,11 +444,11 @@ pub(crate) fn wait_for_parent() -> Result<()> { pub(crate) fn do_add_to_path(process: &Process) -> Result<()> { let new_path = _with_path_cargo_home_bin(_add_to_path, process)?; - _apply_new_path(new_path)?; - do_add_to_programs(process) + _apply_new_path(new_path, process)?; + add_uninstall_registry_entry(process) } -fn _apply_new_path(new_path: Option) -> Result<()> { +fn _apply_new_path(new_path: Option, process: &Process) -> Result<()> { use std::ptr; use windows_sys::Win32::Foundation::{LPARAM, WPARAM}; use windows_sys::Win32::UI::WindowsAndMessaging::{ @@ -469,7 +459,7 @@ fn _apply_new_path(new_path: Option) -> Result<()> { return Ok(()); // No need to set the path }; - let environment = CURRENT_USER.create("Environment")?; + let environment = process.registry_environment_key()?; if new_path.is_empty() { environment.remove_value("PATH")?; @@ -497,9 +487,9 @@ fn _apply_new_path(new_path: Option) -> Result<()> { // Get the windows PATH variable out of the registry as a String. If // this returns None then the PATH variable is not a string and we // should not mess with it. -fn get_windows_path_var() -> Result> { - let environment = CURRENT_USER - .create("Environment") +fn get_windows_path_var(process: &Process) -> Result> { + let environment = process + .registry_environment_key() .context("Failed opening Environment key")?; let reg_value = environment.get_hstring("PATH"); @@ -563,7 +553,7 @@ fn _with_path_cargo_home_bin(f: F, process: &Process) -> Result Option, { - let windows_path = get_windows_path_var()?; + let windows_path = get_windows_path_var(process)?; let mut path_str = process.cargo_home()?; path_str.push("bin"); Ok(windows_path.and_then(|old_path| f(old_path, HSTRING::from(path_str.as_path())))) @@ -571,28 +561,58 @@ where pub(crate) fn do_remove_from_path(process: &Process) -> Result<()> { let new_path = _with_path_cargo_home_bin(_remove_from_path, process)?; - _apply_new_path(new_path)?; - do_remove_from_programs() + _apply_new_path(new_path, process)?; + remove_uninstall_registry_entry(process) } const RUSTUP_UNINSTALL_ENTRY: &str = r"Software\Microsoft\Windows\CurrentVersion\Uninstall\Rustup"; -fn rustup_uninstall_reg_key() -> Result { - CURRENT_USER - .create(RUSTUP_UNINSTALL_ENTRY) +impl Process { + fn registry_sub_key_path<'a>(&self, sub_key: &'a str) -> Cow<'a, str> { + #[cfg(any(test, feature = "test"))] + if let Ok(test_id) = self.var(RUSTUP_REGISTRY_TEST_ID) { + return Cow::Owned(format!(r"RustupTest-{test_id}\{sub_key}")); + } + + Cow::Borrowed(sub_key) + } + + fn registry_key(&self, sub_key: &str, parent: &Key) -> windows_registry::Result { + let mut options = parent.options(); + options.read().write().create(); + + #[cfg(any(test, feature = "test"))] + if self.var(RUSTUP_REGISTRY_TEST_ID).is_ok() { + options.volatile(); + } + + options.open(self.registry_sub_key_path(sub_key)) + } + + fn registry_environment_key(&self) -> windows_registry::Result { + self.registry_key("Environment", CURRENT_USER) + } +} + +fn rustup_uninstall_registry_key(process: &Process) -> Result { + process + .registry_key(RUSTUP_UNINSTALL_ENTRY, CURRENT_USER) .context("Failed creating uninstall key") } -pub(crate) fn do_update_programs_display_version(version: &str) -> Result<()> { - rustup_uninstall_reg_key()? +pub(crate) fn update_uninstall_registry_display_version( + version: &str, + process: &Process, +) -> Result<()> { + rustup_uninstall_registry_key(process)? .set_string("DisplayVersion", version) .context("Failed to set `DisplayVersion`") } -pub(crate) fn do_add_to_programs(process: &Process) -> Result<()> { +pub(crate) fn add_uninstall_registry_entry(process: &Process) -> Result<()> { use std::path::PathBuf; - let key = rustup_uninstall_reg_key()?; + let key = rustup_uninstall_registry_key(process)?; // Don't overwrite registry if Rustup is already installed let prev = key.get_hstring("UninstallString"); @@ -614,20 +634,20 @@ pub(crate) fn do_add_to_programs(process: &Process) -> Result<()> { .context("Failed to set `UninstallString`")?; key.set_string("DisplayName", "Rustup: the Rust toolchain installer") .context("Failed to set `DisplayName`")?; - do_update_programs_display_version(env!("CARGO_PKG_VERSION"))?; + update_uninstall_registry_display_version(env!("CARGO_PKG_VERSION"), process)?; Ok(()) } -pub(crate) fn do_remove_from_programs() -> Result<()> { - match CURRENT_USER.remove_tree(RUSTUP_UNINSTALL_ENTRY) { +pub(crate) fn remove_uninstall_registry_entry(process: &Process) -> Result<()> { + match CURRENT_USER.remove_tree(process.registry_sub_key_path(RUSTUP_UNINSTALL_ENTRY)) { Ok(()) => Ok(()), Err(e) if e.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND) => Ok(()), Err(e) => Err(anyhow!(e)), } } -pub(crate) fn run_update(setup_path: &Path) -> Result { +pub(crate) fn run_update(setup_path: &Path, process: &Process) -> Result { Command::new(setup_path) .arg("--self-replace") .spawn() @@ -637,7 +657,7 @@ pub(crate) fn run_update(setup_path: &Path) -> Result { warn!("failed to get the new rustup version in order to update `DisplayVersion`"); return Ok(utils::ExitCode(1)); }; - do_update_programs_display_version(&version)?; + update_uninstall_registry_display_version(&version, process)?; Ok(utils::ExitCode(0)) } @@ -754,39 +774,15 @@ pub(crate) fn delete_rustup_and_cargo_home(process: &Process) -> Result<()> { Ok(()) } +/// Environment variable carrying the per-test registry ID. #[cfg(any(test, feature = "test"))] -pub fn get_path() -> Result> { - USER_PATH.get() -} +pub const RUSTUP_REGISTRY_TEST_ID: &str = "RUSTUP_REGISTRY_TEST_ID"; #[cfg(any(test, feature = "test"))] -pub struct RegistryGuard<'a> { - _locked: LockResult>, - id: &'static RegistryValueId, - prev: Option, +pub fn get_path(test_id: &str) -> Result> { + USER_PATH.get(test_id, CURRENT_USER) } -#[cfg(any(test, feature = "test"))] -impl RegistryGuard<'_> { - pub fn new(id: &'static RegistryValueId) -> Result { - Ok(Self { - _locked: REGISTRY_LOCK.lock(), - id, - prev: id.get()?, - }) - } -} - -#[cfg(any(test, feature = "test"))] -impl Drop for RegistryGuard<'_> { - fn drop(&mut self) { - self.id.set(self.prev.as_ref()).unwrap(); - } -} - -#[cfg(any(test, feature = "test"))] -static REGISTRY_LOCK: Mutex<()> = Mutex::new(()); - #[cfg(any(test, feature = "test"))] pub const USER_PATH: RegistryValueId = RegistryValueId { sub_key: "Environment", @@ -801,8 +797,10 @@ pub struct RegistryValueId { #[cfg(any(test, feature = "test"))] impl RegistryValueId { - pub fn get(&self) -> Result> { - let sub_key = CURRENT_USER.create(self.sub_key)?; + pub fn get(&self, test_id: &str, parent: &Key) -> Result> { + let mut options = parent.options(); + options.read().write().create().volatile(); + let sub_key = options.open(format!(r"RustupTest-{test_id}\{}", self.sub_key))?; match sub_key.get_value(self.value_name) { Ok(val) => Ok(Some(val)), Err(e) if e.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND) => Ok(None), @@ -810,23 +808,109 @@ impl RegistryValueId { } } - pub fn set(&self, new: Option<&Value>) -> Result<()> { - let sub_key = CURRENT_USER.create(self.sub_key)?; + pub fn set(&self, new: Option<&Value>, test_id: &str, parent: &Key) -> Result<()> { + let mut options = parent.options(); + options.read().write().create().volatile(); + let sub_key = options.open(format!(r"RustupTest-{test_id}\{}", self.sub_key))?; match new { Some(new) => Ok(sub_key.set_value(self.value_name, new)?), - None => Ok(sub_key.remove_value(self.value_name)?), + None => match sub_key.remove_value(self.value_name) { + Ok(()) => Ok(()), + Err(e) if e.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND) => Ok(()), + Err(e) => Err(e.into()), + }, } } } #[cfg(test)] mod tests { + use std::collections::HashMap; use std::os::windows::ffi::OsStringExt; use windows_registry::Type; use super::*; use crate::process::TestProcess; + use crate::test::test_id; + + fn test_process(test_id: &str) -> TestProcess { + let vars: HashMap = [ + ("HOME".to_string(), "/unused".to_string()), + (RUSTUP_REGISTRY_TEST_ID.to_string(), test_id.to_owned()), + ] + .into_iter() + .collect(); + TestProcess::with_vars(vars) + } + + fn clear_path(environment: &Key) { + match environment.remove_value("PATH") { + Ok(()) => {} + Err(e) if e.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND) => {} + Err(e) => panic!("failed to clear PATH: {e}"), + } + } + + #[test] + fn windows_registry_isolated_per_test_id() { + let first_id = test_id(); + let second_id = test_id(); + let first = test_process(&first_id); + let second = test_process(&second_id); + let first_value = Value::from("first"); + let second_value = Value::from("second"); + + USER_PATH + .set(Some(&first_value), &first_id, CURRENT_USER) + .unwrap(); + USER_PATH + .set(Some(&second_value), &second_id, CURRENT_USER) + .unwrap(); + + assert_eq!( + first + .process + .registry_environment_key() + .unwrap() + .get_hstring("PATH") + .unwrap(), + HSTRING::from("first") + ); + assert_eq!( + second + .process + .registry_environment_key() + .unwrap() + .get_hstring("PATH") + .unwrap(), + HSTRING::from("second") + ); + + let programs_version = RegistryValueId { + sub_key: RUSTUP_UNINSTALL_ENTRY, + value_name: "DisplayVersion", + }; + programs_version + .set(Some(&first_value), &first_id, CURRENT_USER) + .unwrap(); + assert_eq!( + programs_version.get(&first_id, CURRENT_USER).unwrap(), + Some(first_value) + ); + assert_eq!( + programs_version.get(&second_id, CURRENT_USER).unwrap(), + None + ); + } + + #[test] + fn windows_registry_cleanup_is_idempotent() { + let test_id = test_id(); + let tp = test_process(&test_id); + + remove_uninstall_registry_entry(&tp.process).unwrap(); + } #[test] fn windows_install_does_not_add_path_twice() { @@ -864,16 +948,20 @@ mod tests { #[test] fn windows_path_regkey_type() { // per issue #261, setting PATH should use REG_EXPAND_SZ. - let _guard = RegistryGuard::new(&USER_PATH); - let environment = CURRENT_USER.create("Environment").unwrap(); - environment.remove_value("PATH").unwrap(); + let test_id = test_id(); + let tp = test_process(&test_id); + let environment = tp.process.registry_environment_key().unwrap(); + clear_path(&environment); { // Can't compare the Results as Eq isn't derived; thanks error-chain. #![allow(clippy::unit_cmp)] - assert_eq!((), _apply_new_path(Some(HSTRING::from("foo"))).unwrap()); + assert_eq!( + (), + _apply_new_path(Some(HSTRING::from("foo")), &tp.process).unwrap() + ); } - let environment = CURRENT_USER.create("Environment").unwrap(); + let environment = tp.process.registry_environment_key().unwrap(); let path = environment.get_value("PATH").unwrap(); let path_hstring = environment.get_hstring("PATH").unwrap(); assert_eq!(path.ty(), Type::ExpandString); @@ -884,8 +972,9 @@ mod tests { fn windows_path_delete_key_when_empty() { // during uninstall the PATH key may end up empty; if so we should // delete it. - let _guard = RegistryGuard::new(&USER_PATH); - let environment = CURRENT_USER.create("Environment").unwrap(); + let test_id = test_id(); + let tp = test_process(&test_id); + let environment = tp.process.registry_environment_key().unwrap(); environment .set_expand_hstring("PATH", &HSTRING::from("foo")) .unwrap(); @@ -893,7 +982,10 @@ mod tests { { // Can't compare the Results as Eq isn't derived; thanks error-chain. #![allow(clippy::unit_cmp)] - assert_eq!((), _apply_new_path(Some(HSTRING::new())).unwrap()); + assert_eq!( + (), + _apply_new_path(Some(HSTRING::new()), &tp.process).unwrap() + ); } let reg_value = environment.get_value("PATH"); match reg_value { @@ -905,16 +997,11 @@ mod tests { #[test] fn windows_doesnt_mess_with_a_non_string_path() { + let test_id = test_id(); // This writes an error, so we want a sink for it. - let tp = TestProcess::with_vars( - [("HOME".to_string(), "/unused".to_string())] - .iter() - .cloned() - .collect(), - ); + let tp = test_process(&test_id); - let _guard = RegistryGuard::new(&USER_PATH); - let environment = CURRENT_USER.create("Environment").unwrap(); + let environment = tp.process.registry_environment_key().unwrap(); environment .set_bytes("PATH", Type::Bytes, &[0x12, 0x34]) .unwrap(); @@ -934,11 +1021,15 @@ mod tests { #[test] fn windows_treat_missing_path_as_empty() { // during install the PATH key may be missing; treat it as empty - let _guard = RegistryGuard::new(&USER_PATH); - let environment = CURRENT_USER.create("Environment").unwrap(); - environment.remove_value("PATH").unwrap(); + let test_id = test_id(); + let tp = test_process(&test_id); + let environment = tp.process.registry_environment_key().unwrap(); + clear_path(&environment); - assert_eq!(Some(HSTRING::new()), get_windows_path_var().unwrap()); + assert_eq!( + Some(HSTRING::new()), + get_windows_path_var(&tp.process).unwrap() + ); } #[test] diff --git a/src/cli/setup_mode.rs b/src/cli/setup_mode.rs index e0bc08df60..f0eeb4e6b8 100644 --- a/src/cli/setup_mode.rs +++ b/src/cli/setup_mode.rs @@ -10,7 +10,6 @@ use crate::{ common::{self, update_console_filter}, self_update::{self, InstallOpts}, }, - config::Cfg, dist::Profile, process::Process, toolchain::MaybeOfficialToolchainName, @@ -129,7 +128,5 @@ pub async fn main( components: &component.iter().map(|s| &**s).collect::>(), targets: &target.iter().map(|s| &**s).collect::>(), }; - - let mut cfg = Cfg::from_env(current_dir, quiet, true, process)?; - self_update::install(no_prompt, opts, &mut cfg).await + opts.install(current_dir, no_prompt, quiet, process).await } diff --git a/src/config.rs b/src/config.rs index 30c3b73e6b..511b6de7e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -197,7 +197,7 @@ impl OverrideCfg { fn from_file(cfg: &Cfg<'_>, file: OverrideFile) -> Result { let toolchain_name = match (file.toolchain.channel, file.toolchain.path) { (Some(name), None) => { - ResolvableToolchainName::try_from(name)?.resolve(&cfg.default_host_tuple()?)? + ResolvableToolchainName::from_str(&name)?.resolve(&cfg.default_host_tuple()?)? } (None, Some(path)) => { if file.toolchain.targets.is_some() @@ -253,7 +253,7 @@ impl OverrideCfg { match self { Self::PathBased(path_based_name) => path_based_name.into(), Self::Custom(custom_name) => custom_name.into(), - Self::Official { toolchain, .. } => (&toolchain).into(), + Self::Official { toolchain, .. } => toolchain.into(), } } } @@ -353,8 +353,8 @@ impl<'a> Cfg<'a> { // Figure out default_host_tuple before Config is populated let default_host = settings_file.with(|s| Ok(default_host_tuple(s, process)))?; // Environment override - let env_override = match process.var_opt("RUSTUP_TOOLCHAIN")? { - Some(tc) => Some(ResolvableLocalToolchainName::try_from(tc)?.resolve(&default_host)?), + let env_override = match &process.var_opt("RUSTUP_TOOLCHAIN")? { + Some(tc) => Some(ResolvableLocalToolchainName::from_str(tc)?.resolve(&default_host)?), None => None, }; @@ -382,7 +382,7 @@ impl<'a> Cfg<'a> { // Run some basic checks against the constructed configuration // For now, that means simply checking that 'stable' can resolve // for the current configuration. - ResolvableToolchainName::try_from("stable")?.resolve( + ResolvableToolchainName::from_str("stable")?.resolve( &cfg.default_host_tuple() .context("Unable parse configuration")?, )?; @@ -637,7 +637,7 @@ impl<'a> Cfg<'a> { // However, settings.toml could conceivably be hand edited to // have an unresolved name. I'm just preserving pre-existing // behaviour by choosing ResolvableToolchainName here. - let toolchain_name = ResolvableToolchainName::try_from(name)? + let toolchain_name = ResolvableToolchainName::from_str(&name)? .resolve(&default_host_tuple(settings, self.process))?; let override_cfg = toolchain_name.into(); return Ok(Some((override_cfg, source))); @@ -689,7 +689,7 @@ impl<'a> Cfg<'a> { } })?; if let Some(toolchain_name_str) = &override_file.toolchain.channel { - let toolchain_name = ResolvableToolchainName::try_from( + let toolchain_name = ResolvableToolchainName::from_str( toolchain_name_str.as_str(), ) .map_err(|_| { @@ -707,7 +707,7 @@ impl<'a> Cfg<'a> { // Permit fully qualified names IFF the toolchain is installed. TODO(robertc): consider // disabling this and backing out https://github.com/rust-lang/rustup/pull/2141 (but provide // the base name in the error to help users) - let resolved_name = &ToolchainName::try_from(toolchain_name_str.as_str())?; + let resolved_name = &ToolchainName::from_str(toolchain_name_str)?; if !self.list_toolchains()?.iter().any(|s| s == resolved_name) { return Err(anyhow!(format!("target tuple in channel name '{name}'"))); } @@ -907,7 +907,7 @@ impl<'a> Cfg<'a> { user_opt }?; toolchain_maybe_str - .map(ResolvableToolchainName::try_from) + .map(|s| ResolvableToolchainName::from_str(&s)) .transpose()? .map(|t| t.resolve(&self.default_host_tuple()?)) .transpose() @@ -928,7 +928,7 @@ impl<'a> Cfg<'a> { .filter_map(io::Result::ok) .filter(|e| e.file_type().map(|f| !f.is_file()).unwrap_or(false)) .filter_map(|e| e.file_name().into_string().ok()) - .filter_map(|n| ToolchainName::try_from(n).ok()) + .filter_map(|n| ToolchainName::from_str(&n).ok()) .collect(); toolchains.sort(); @@ -1051,7 +1051,7 @@ impl Debug for Cfg<'_> { } } -fn default_host_tuple(s: &Settings, process: &Process) -> TargetTuple { +pub(crate) fn default_host_tuple(s: &Settings, process: &Process) -> TargetTuple { s.default_host_tuple .as_ref() .map(TargetTuple::new) diff --git a/src/dist/manifestation.rs b/src/dist/manifestation.rs index 469d830910..9bb2467e52 100644 --- a/src/dist/manifestation.rs +++ b/src/dist/manifestation.rs @@ -29,14 +29,13 @@ use crate::{ config::Config, download::{DownloadCfg, DownloadStatus, File}, manifest::{Component, CompressionKind, HashedBinary, Manifest}, - prefix::InstallPrefix, + prefix::{DIST_MANIFEST, InstallPrefix}, temp, }, errors::RustupError, utils, }; -pub(crate) const DIST_MANIFEST: &str = "multirust-channel-manifest.toml"; pub(crate) const CONFIG_FILE: &str = "multirust-config.toml"; #[derive(Debug)] @@ -396,8 +395,7 @@ impl Manifestation { #[tracing::instrument(level = "trace")] pub fn load_manifest(&self) -> Result> { let prefix = self.installation.prefix(); - let old_manifest_path = prefix.manifest_file(DIST_MANIFEST); - if utils::path_exists(&old_manifest_path) { + if let Some(old_manifest_path) = prefix.dist_manifest() { let manifest_str = utils::read_file("installed manifest", &old_manifest_path)?; Ok(Some(Manifest::parse(&manifest_str).with_context(|| { RustupError::ParsingFile { diff --git a/src/dist/mod.rs b/src/dist/mod.rs index d74afa014c..090941cf9c 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -608,6 +608,7 @@ impl TargetTuple { (b"Linux", b"loongarch64") => Some(TUPLE_LOONGARCH64_UNKNOWN_LINUX), (b"Linux", b"ppc64") => Some(TUPLE_POWERPC64_UNKNOWN_LINUX), (b"Linux", b"ppc64le") => Some(TUPLE_POWERPC64LE_UNKNOWN_LINUX), + (b"Linux", b"riscv64") => Some(TUPLE_RISCV64GC_UNKNOWN_LINUX), (b"Darwin", b"x86_64") => Some("x86_64-apple-darwin"), (b"Darwin", b"i686") => Some("i686-apple-darwin"), (b"FreeBSD", b"x86_64") => Some("x86_64-unknown-freebsd"), @@ -712,6 +713,10 @@ const TUPLE_POWERPC64_UNKNOWN_LINUX: &str = "powerpc64-unknown-linux-musl"; const TUPLE_POWERPC64LE_UNKNOWN_LINUX: &str = "powerpc64le-unknown-linux-gnu"; #[cfg(all(not(windows), target_env = "musl"))] const TUPLE_POWERPC64LE_UNKNOWN_LINUX: &str = "powerpc64le-unknown-linux-musl"; +#[cfg(all(not(windows), not(target_env = "musl")))] +const TUPLE_RISCV64GC_UNKNOWN_LINUX: &str = "riscv64gc-unknown-linux-gnu"; +#[cfg(all(not(windows), target_env = "musl"))] +const TUPLE_RISCV64GC_UNKNOWN_LINUX: &str = "riscv64gc-unknown-linux-musl"; // MIPS platforms don't indicate endianness in uname, however binaries only // run on boxes with the same endianness, as expected. @@ -1125,10 +1130,9 @@ async fn try_update_from_dist_( } else { download .dl_v2_manifest( - // Even if manifest has not changed, we must continue to install requested components. - // So if components or targets is not empty, we skip passing `update_hash` so that - // we essentially degenerate to `rustup component add` / `rustup target add` - if components.is_empty() && targets.is_empty() { + // Skip the update hash when the installed manifest is missing or when components + // or targets were requested, since either case still requires the channel manifest. + if prefix.dist_manifest().is_some() && components.is_empty() && targets.is_empty() { Some(update_hash) } else { None diff --git a/src/dist/prefix.rs b/src/dist/prefix.rs index 9b71118017..c0217bfb86 100644 --- a/src/dist/prefix.rs +++ b/src/dist/prefix.rs @@ -11,6 +11,7 @@ const REL_MANIFEST_DIR: &str = match std::path::MAIN_SEPARATOR { }; static V1_COMMON_COMPONENT_LIST: &[&str] = &["cargo", "rustc", "rust-docs"]; +pub(crate) const DIST_MANIFEST: &str = "multirust-channel-manifest.toml"; #[derive(Clone, Debug)] pub struct InstallPrefix { @@ -37,6 +38,11 @@ impl InstallPrefix { path } + pub(crate) fn dist_manifest(&self) -> Option { + let path = self.manifest_file(DIST_MANIFEST); + utils::path_exists(&path).then_some(path) + } + pub(crate) fn rel_manifest_file(&self, name: &str) -> PathBuf { let mut path = PathBuf::from(REL_MANIFEST_DIR); path.push(name); diff --git a/src/dist/target_tuple/known.rs b/src/dist/target_tuple/known.rs index b034a77211..6f46fbfb02 100644 --- a/src/dist/target_tuple/known.rs +++ b/src/dist/target_tuple/known.rs @@ -54,6 +54,7 @@ pub static LIST_ARCHS: &[&str] = &[ "riscv32imac", "riscv32imafc", "riscv32imc", + "riscv32imfc", "riscv64", "riscv64a23", "riscv64gc", @@ -108,9 +109,11 @@ pub static LIST_OSES: &[&str] = &[ "nuttx-eabi", "nuttx-eabihf", "nvidia-cuda", + "oe-linux", "openwrt-linux", "pc-cygwin", "pc-nto", + "pc-qnx", "pc-solaris", "pc-windows", "risc0-zkvm", @@ -138,6 +141,7 @@ pub static LIST_OSES: &[&str] = &[ "unknown-nto", "unknown-nuttx", "unknown-openbsd", + "unknown-qnx", "unknown-qurt", "unknown-redox", "unknown-teeos", @@ -187,10 +191,10 @@ pub static LIST_ENVS: &[&str] = &[ "newlibeabihf", "none", "ohos", + "pauthtest", "qnx700", "qnx710", "qnx710_iosock", - "qnx800", "sgx", "sim", "softfloat", diff --git a/src/env_var.rs b/src/env_var.rs index 45b19c6389..5ec689843e 100644 --- a/src/env_var.rs +++ b/src/env_var.rs @@ -54,8 +54,6 @@ mod tests { use std::ffi::{OsStr, OsString}; use super::*; - #[cfg(windows)] - use crate::cli::self_update::{RegistryGuard, USER_PATH}; use crate::process::TestProcess; use crate::test::Env; @@ -67,8 +65,6 @@ mod tests { env::join_paths(["/home/a/.cargo/bin", "/home/b/.cargo/bin"].iter()).unwrap(), ); let tp = TestProcess::with_vars(vars); - #[cfg(windows)] - let _path_guard = RegistryGuard::new(&USER_PATH).unwrap(); let mut path_entries = vec![]; let mut cmd = Command::new("test"); @@ -115,8 +111,6 @@ mod tests { env::join_paths(["/home/a/.cargo/bin", "/home/b/.cargo/bin"].iter()).unwrap(), ); let tp = TestProcess::with_vars(vars); - #[cfg(windows)] - let _path_guard = RegistryGuard::new(&USER_PATH).unwrap(); #[track_caller] fn check(tp: &TestProcess, path_entries: Vec, append: &str, expected: &[&str]) { diff --git a/src/install.rs b/src/install.rs index b7f4df76b9..bb5e275c8b 100644 --- a/src/install.rs +++ b/src/install.rs @@ -126,11 +126,12 @@ impl InstallMethod<'_, '_> { fn local_name(&self) -> LocalToolchainName { match self { - InstallMethod::Copy { dest, .. } => (*dest).into(), - InstallMethod::Link { dest, .. } => (*dest).into(), + InstallMethod::Copy { dest, .. } | InstallMethod::Link { dest, .. } => { + (*dest).clone().into() + } InstallMethod::Dist(DistOptions { toolchain: desc, .. - }) => (*desc).into(), + }) => (*desc).clone().into(), } } @@ -140,13 +141,14 @@ impl InstallMethod<'_, '_> { fn dest_path(&self) -> PathBuf { match self { - InstallMethod::Copy { cfg, dest, .. } => cfg.toolchain_path(&(*dest).into()), - InstallMethod::Link { cfg, dest, .. } => cfg.toolchain_path(&(*dest).into()), + InstallMethod::Copy { cfg, dest, .. } | InstallMethod::Link { cfg, dest, .. } => { + cfg.toolchain_path(&(*dest).clone().into()) + } InstallMethod::Dist(DistOptions { cfg, toolchain: desc, .. - }) => cfg.toolchain_path(&(*desc).into()), + }) => cfg.toolchain_path(&(*desc).clone().into()), } } } diff --git a/src/settings.rs b/src/settings.rs index 42dc7ceebf..cc84e5e4b9 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -15,7 +15,7 @@ use crate::utils; #[derive(Clone, Debug, Eq, PartialEq)] pub struct SettingsFile { - path: PathBuf, + pub(crate) path: PathBuf, cache: RefCell>, } @@ -29,7 +29,7 @@ impl SettingsFile { fn write_settings(&self) -> Result<()> { let settings = self.cache.borrow(); - utils::write_file( + utils::write_locked_file( "settings", &self.path, &settings.as_ref().unwrap().stringify()?, @@ -38,25 +38,18 @@ impl SettingsFile { } fn read_settings(&self) -> Result<()> { - let mut needs_save = false; - { - let b = self.cache.borrow(); - if b.is_none() { - drop(b); - *self.cache.borrow_mut() = Some(if utils::is_file(&self.path) { - let content = utils::read_file("settings", &self.path)?; - Settings::parse(&content).with_context(|| RustupError::ParsingFile { - name: "settings", - path: self.path.clone(), - })? - } else { - needs_save = true; - Default::default() - }); - } - } - if needs_save { - self.write_settings()?; + let b = self.cache.borrow(); + if b.is_none() { + drop(b); + *self.cache.borrow_mut() = Some(if utils::is_file(&self.path) { + let content = utils::read_locked_file("settings", &self.path)?; + Settings::parse(&content).with_context(|| RustupError::ParsingFile { + name: "settings", + path: self.path.clone(), + })? + } else { + Settings::default() + }); } Ok(()) } diff --git a/src/test.rs b/src/test.rs index 023d73aea4..3cc7901db3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -23,8 +23,13 @@ use sha2::{Digest, Sha256}; use crate::dist::TargetTuple; use crate::process::TestProcess; +#[cfg(all(windows, any(test, feature = "test")))] +pub(crate) fn test_id() -> String { + crate::utils::raw::random_string(8) +} + #[cfg(windows)] -pub use crate::cli::self_update::{RegistryGuard, RegistryValueId, USER_PATH, get_path}; +pub use crate::cli::self_update::{RUSTUP_REGISTRY_TEST_ID, RegistryValueId, USER_PATH, get_path}; mod clitools; pub use clitools::{ diff --git a/src/test/clitools.rs b/src/test/clitools.rs index 2eb666ee92..0949da1e13 100644 --- a/src/test/clitools.rs +++ b/src/test/clitools.rs @@ -31,6 +31,9 @@ use crate::test::tempdir_in_with_prefix; use crate::test::this_host_tuple; use crate::utils; +#[cfg(windows)] +use crate::{cli::self_update::RUSTUP_REGISTRY_TEST_ID, test::test_id}; + use super::{ CROSS_ARCH1, CROSS_ARCH2, MULTI_ARCH1, dist::{MockDistServer, MockManifestVersion, Release, RlsStatus, change_channel_date}, @@ -62,6 +65,9 @@ pub struct Config { pub workdir: RefCell, /// This is the test root for keeping stuff together test_root_dir: PathBuf, + /// Per-test Windows registry ID. + #[cfg(windows)] + pub test_registry_id: String, } /// Helper type to simplify assertions of a command's output. @@ -322,6 +328,9 @@ impl Config { if let Some(root) = self.rustup_update_root.as_ref() { cmd.env("RUSTUP_UPDATE_ROOT", root); } + + #[cfg(windows)] + cmd.env(RUSTUP_REGISTRY_TEST_ID, &self.test_registry_id); } /// Returns an [`Assert`] object to check the output of running the command @@ -810,6 +819,8 @@ async fn setup_test_state(test_dist_dir: TempDir) -> (TempDir, Config) { rustup_update_root: None, workdir: RefCell::new(workdir), test_root_dir: test_dir.path().to_path_buf(), + #[cfg(windows)] + test_registry_id: test_id(), }; let build_path = built_exe_dir.join(format!("rustup-init{EXE_SUFFIX}")); diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index a76776fa07..aa06931ff6 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -58,7 +58,7 @@ impl<'a> DistributableToolchain<'a> { } pub(crate) fn new(cfg: &'a Cfg<'a>, desc: ToolchainDesc) -> Result { - Toolchain::new(cfg, (&desc).into()).map(|toolchain| Self { toolchain, desc }) + Toolchain::new(cfg, desc.clone().into()).map(|toolchain| Self { toolchain, desc }) } pub(crate) fn desc(&self) -> &ToolchainDesc { @@ -439,12 +439,14 @@ impl<'a> DistributableToolchain<'a> { } pub async fn fetch_dist_manifest(&self) -> anyhow::Result> { + let prefix = InstallPrefix::from(self.toolchain.path()); + let update_hash = if prefix.dist_manifest().is_some() { + Some(self.toolchain.cfg.get_hash_file(&self.desc, false)?) + } else { + None + }; DownloadCfg::new(self.toolchain.cfg) - .dl_v2_manifest( - Some(&self.toolchain.cfg.get_hash_file(&self.desc, false)?), - &self.desc, - self.toolchain.cfg, - ) + .dl_v2_manifest(update_hash.as_deref(), &self.desc, self.toolchain.cfg) .await } diff --git a/src/toolchain/names.rs b/src/toolchain/names.rs index 37d92b9912..f5181246b5 100644 --- a/src/toolchain/names.rs +++ b/src/toolchain/names.rs @@ -71,60 +71,6 @@ pub enum InvalidName { PlusPrefix(String), } -macro_rules! from_variant { - ($from:ident, $to:ident, $variant:expr) => { - impl From<$from> for $to { - fn from(value: $from) -> Self { - $variant(value) - } - } - }; -} - -macro_rules! try_from_str { - ($to:ident) => { - try_from_str!(&str, $to); - - impl TryFrom for $to { - type Error = InvalidName; - - fn try_from(value: String) -> std::result::Result { - $to::validate(&value) - } - } - - impl FromStr for $to { - type Err = InvalidName; - - fn from_str(value: &str) -> std::result::Result { - $to::validate(value) - } - } - }; - ($from:ty, $to:ident) => { - impl TryFrom<$from> for $to { - type Error = InvalidName; - - fn try_from(value: $from) -> std::result::Result { - $to::validate(value) - } - } - }; -} - -/// Common validate rules for all sorts of toolchain names -fn validate(candidate: &str) -> Result<&str, InvalidName> { - if let Some(without_plus) = candidate.strip_prefix('+') { - return Err(InvalidName::PlusPrefix(without_plus.to_string())); - } - let normalized_name = candidate.trim_end_matches('/'); - if normalized_name.is_empty() { - Err(InvalidName::ToolchainName(candidate.into())) - } else { - Ok(normalized_name) - } -} - /// A toolchain name from user input. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum ResolvableToolchainName { @@ -140,24 +86,26 @@ impl ResolvableToolchainName { Self::Official(desc) => ToolchainName::Official(desc.resolve(host)?), }) } +} - // If candidate could be resolved, return a ready to resolve version of it. +impl FromStr for ResolvableToolchainName { + type Err = InvalidName; + + // If value could be resolved, return a ready to resolve version of it. // Otherwise error. - fn validate(candidate: &str) -> Result { - let candidate = validate(candidate)?; + fn from_str(value: &str) -> Result { + let candidate = normalize_name(value)?; if let Ok(desc) = PartialToolchainDesc::from_str(candidate) { return Ok(Self::Official(desc)); } - match CustomToolchainName::try_from(candidate) { + match CustomToolchainName::from_str(candidate) { Ok(custom) => Ok(Self::Custom(custom)), Err(_) => Err(InvalidName::ToolchainName(candidate.into())), } } } -try_from_str!(ResolvableToolchainName); - impl From<&PartialToolchainDesc> for ResolvableToolchainName { fn from(value: &PartialToolchainDesc) -> Self { Self::Official(value.to_owned()) @@ -182,19 +130,19 @@ pub(crate) enum MaybeResolvableToolchainName { None, } -impl MaybeResolvableToolchainName { - // If candidate could be resolved, return a ready to resolve version of it. +impl FromStr for MaybeResolvableToolchainName { + type Err = InvalidName; + + // If value could be resolved, return a ready to resolve version of it. // Otherwise error. - fn validate(candidate: &str) -> Result { - Ok(match validate(candidate)? { + fn from_str(value: &str) -> Result { + Ok(match normalize_name(value)? { "none" => Self::None, - candidate => Self::Some(ResolvableToolchainName::validate(candidate)?), + candidate => Self::Some(ResolvableToolchainName::from_str(candidate)?), }) } } -try_from_str!(MaybeResolvableToolchainName); - impl Display for MaybeResolvableToolchainName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -212,9 +160,11 @@ pub(crate) enum MaybeOfficialToolchainName { Some(PartialToolchainDesc), } -impl MaybeOfficialToolchainName { - fn validate(candidate: &str) -> Result { - Ok(match validate(candidate)? { +impl FromStr for MaybeOfficialToolchainName { + type Err = InvalidName; + + fn from_str(value: &str) -> Result { + Ok(match normalize_name(value)? { "none" => Self::None, candidate => Self::Some( PartialToolchainDesc::from_str(candidate) @@ -224,8 +174,6 @@ impl MaybeOfficialToolchainName { } } -try_from_str!(MaybeOfficialToolchainName); - impl Display for MaybeOfficialToolchainName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -244,26 +192,35 @@ pub enum ToolchainName { Custom(CustomToolchainName), } -impl ToolchainName { +impl From for ToolchainName { + fn from(value: ToolchainDesc) -> Self { + Self::Official(value) + } +} + +impl From for ToolchainName { + fn from(value: CustomToolchainName) -> Self { + Self::Custom(value) + } +} + +impl FromStr for ToolchainName { + type Err = InvalidName; + /// If the string is already resolved, allow direct conversion - fn validate(candidate: &str) -> Result { - let candidate = validate(candidate)?; + fn from_str(value: &str) -> Result { + let candidate = normalize_name(value)?; if let Ok(desc) = ToolchainDesc::from_str(candidate) { return Ok(Self::Official(desc)); } - match CustomToolchainName::try_from(candidate) { + match CustomToolchainName::from_str(candidate) { Ok(custom) => Ok(Self::Custom(custom)), Err(_) => Err(InvalidName::ToolchainName(candidate.into())), } } } -from_variant!(ToolchainDesc, ToolchainName, ToolchainName::Official); -from_variant!(CustomToolchainName, ToolchainName, ToolchainName::Custom); - -try_from_str!(ToolchainName); - impl Display for ToolchainName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -290,11 +247,15 @@ impl ResolvableLocalToolchainName { Self::Path(t) => Ok(LocalToolchainName::Path(t)), } } +} + +impl FromStr for ResolvableLocalToolchainName { + type Err = InvalidName; - /// Validates if the string is a resolvable toolchain, or a path based toolchain. - fn validate(candidate: &str) -> Result { - let candidate = validate(candidate)?; - if let Ok(name) = ResolvableToolchainName::try_from(candidate) { + /// Parses a resolvable toolchain, or a path based toolchain. + fn from_str(value: &str) -> Result { + let candidate = normalize_name(value)?; + if let Ok(name) = ResolvableToolchainName::from_str(candidate) { return Ok(Self::Named(name)); } @@ -304,8 +265,6 @@ impl ResolvableLocalToolchainName { } } -try_from_str!(ResolvableLocalToolchainName); - impl Display for ResolvableLocalToolchainName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -326,15 +285,21 @@ pub(crate) enum LocalToolchainName { Path(PathBasedToolchainName), } -impl From<&ToolchainDesc> for LocalToolchainName { - fn from(value: &ToolchainDesc) -> Self { - ToolchainName::Official(value.to_owned()).into() +impl From for LocalToolchainName { + fn from(value: ToolchainName) -> Self { + Self::Named(value) } } -impl From<&CustomToolchainName> for LocalToolchainName { - fn from(value: &CustomToolchainName) -> Self { - ToolchainName::Custom(value.to_owned()).into() +impl From for LocalToolchainName { + fn from(value: PathBasedToolchainName) -> Self { + Self::Path(value) + } +} + +impl From for LocalToolchainName { + fn from(value: ToolchainDesc) -> Self { + ToolchainName::Official(value).into() } } @@ -344,13 +309,6 @@ impl From for LocalToolchainName { } } -from_variant!(ToolchainName, LocalToolchainName, LocalToolchainName::Named); -from_variant!( - PathBasedToolchainName, - LocalToolchainName, - LocalToolchainName::Path -); - impl PartialEq for LocalToolchainName { fn eq(&self, other: &ToolchainName) -> bool { match self { @@ -374,9 +332,19 @@ impl Display for LocalToolchainName { #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct CustomToolchainName(String); -impl CustomToolchainName { - fn validate(candidate: &str) -> Result { - let candidate = validate(candidate)?; +impl Deref for CustomToolchainName { + type Target = str; + + fn deref(&self) -> &str { + &self.0 + } +} + +impl FromStr for CustomToolchainName { + type Err = InvalidName; + + fn from_str(value: &str) -> Result { + let candidate = normalize_name(value)?; if candidate.parse::().is_ok() || candidate == "none" || candidate.contains('/') @@ -389,16 +357,6 @@ impl CustomToolchainName { } } -impl Deref for CustomToolchainName { - type Target = str; - - fn deref(&self) -> &str { - &self.0 - } -} - -try_from_str!(CustomToolchainName); - impl Display for CustomToolchainName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) @@ -460,6 +418,23 @@ impl Deref for PathBasedToolchainName { } } +/// Normalization shared by all sorts of toolchain names. +/// +/// Strips the trailing slashes a shell may have completed onto a toolchain +/// directory, and rejects the `+toolchain` argument form along with names that +/// are empty once normalized. +fn normalize_name(candidate: &str) -> Result<&str, InvalidName> { + if let Some(without_plus) = candidate.strip_prefix('+') { + return Err(InvalidName::PlusPrefix(without_plus.to_string())); + } + let normalized_name = candidate.trim_end_matches('/'); + if normalized_name.is_empty() { + Err(InvalidName::ToolchainName(candidate.into())) + } else { + Ok(normalized_name) + } +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -528,12 +503,12 @@ mod tests { #[test] fn test_parse_custom(name in arb_custom_name()) { - CustomToolchainName::try_from(name).unwrap(); + CustomToolchainName::from_str(&name).unwrap(); } #[test] fn test_parse_resolvable_name(name in arb_resolvable_name()) { - ResolvableToolchainName::try_from(name).unwrap(); + ResolvableToolchainName::from_str(&name).unwrap(); } // TODO: This needs some thought @@ -570,7 +545,7 @@ mod tests { "this.is.not-a+semver", ] .into_iter() - .map(|s| ToolchainName::try_from(s).unwrap()) + .map(|s| ToolchainName::from_str(s).unwrap()) .collect::>(); let mut v = vec![ @@ -595,7 +570,7 @@ mod tests { "the cake is a lie", ] .into_iter() - .map(|s| ToolchainName::try_from(s).unwrap()) + .map(|s| ToolchainName::from_str(s).unwrap()) .collect::>(); v.sort(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index fdb079ccaf..414a5687a4 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -87,6 +87,13 @@ pub fn read_file(name: &'static str, path: &Path) -> Result { }) } +pub(crate) fn read_locked_file(name: &'static str, path: &Path) -> Result { + raw::read_locked_file(path).with_context(|| RustupError::ReadingFile { + name, + path: PathBuf::from(path), + }) +} + pub fn write_file(name: &'static str, path: &Path, contents: &str) -> Result<()> { raw::write_file(path, contents).with_context(|| RustupError::WritingFile { name, @@ -94,6 +101,13 @@ pub fn write_file(name: &'static str, path: &Path, contents: &str) -> Result<()> }) } +pub(crate) fn write_locked_file(name: &'static str, path: &Path, contents: &str) -> Result<()> { + raw::write_locked_file(path, contents).with_context(|| RustupError::WritingFile { + name, + path: PathBuf::from(path), + }) +} + pub(crate) fn append_file(name: &'static str, path: &Path, line: &str) -> Result<()> { raw::append_file(path, line).with_context(|| RustupError::WritingFile { name, diff --git a/src/utils/raw.rs b/src/utils/raw.rs index ba439ae731..70f27f3d42 100644 --- a/src/utils/raw.rs +++ b/src/utils/raw.rs @@ -3,7 +3,7 @@ use std::env; use std::fs; use std::fs::File; use std::io; -use std::io::Write; +use std::io::{Read, Seek, SeekFrom, Write}; use std::path::Path; use std::str; @@ -60,6 +60,21 @@ pub fn path_exists>(path: P) -> bool { fs::metadata(path).is_ok() } +pub(crate) fn read_locked_file(path: &Path) -> io::Result { + let mut file = File::open(path)?; + file.lock_shared()?; + + let size = file + .metadata() + .map(|metadata| usize::try_from(metadata.len()).unwrap_or(usize::MAX)) + .ok(); + let mut contents = String::new(); + contents.try_reserve_exact(size.unwrap_or(0))?; + file.read_to_string(&mut contents)?; + + Ok(contents) +} + pub(crate) fn random_string(length: usize) -> String { const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789_"; let mut rng = rand::rng(); @@ -82,6 +97,24 @@ pub fn write_file(path: &Path, contents: &str) -> io::Result<()> { Ok(()) } +pub(crate) fn write_locked_file(path: &Path, contents: &str) -> io::Result<()> { + let mut file = fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + // Truncation must happen after the exclusive lock is held. + .truncate(false) + .open(path)?; + + file.lock()?; + file.set_len(0)?; + file.seek(SeekFrom::Start(0))?; + Write::write_all(&mut file, contents.as_bytes())?; + file.sync_data()?; + + Ok(()) +} + pub(crate) fn filter_file bool>( src: &Path, dest: &Path, @@ -350,3 +383,20 @@ pub(crate) mod windows { inner(s.as_ref()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn locked_file_round_trip_truncates_previous_contents() -> io::Result<()> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("state.toml"); + + write_locked_file(&path, "last_release_notified_secs = 123456\n")?; + write_locked_file(&path, "version = \"12\"\n")?; + + assert_eq!(read_locked_file(&path)?, "version = \"12\"\n"); + Ok(()) + } +} diff --git a/tests/suite/cli_inst_interactive.rs b/tests/suite/cli_inst_interactive.rs index 9b75e951c4..95c761f4bc 100644 --- a/tests/suite/cli_inst_interactive.rs +++ b/tests/suite/cli_inst_interactive.rs @@ -1,12 +1,11 @@ //! Tests of the interactive console installer use std::env::consts::EXE_SUFFIX; +use std::fs; use std::io::Write; use std::process::Stdio; use rustup::test::{Assert, CliTestContext, Config, SanitizedOutput, Scenario, this_host_tuple}; -#[cfg(windows)] -use rustup::test::{RegistryGuard, USER_PATH}; use rustup::utils::raw; fn run_input(config: &Config, args: &[&str], input: &str) -> Assert { @@ -44,8 +43,6 @@ fn run_input_with_env(config: &Config, args: &[&str], input: &str, env: &[(&str, #[tokio::test] async fn update() { let cx = CliTestContext::new(Scenario::SimpleV2).await; - #[cfg(windows)] - let _path_guard = RegistryGuard::new(&USER_PATH).unwrap(); run_input(&cx.config, &["rustup-init"], "\n\n"); run_input(&cx.config, &["rustup-init"], "\n\n").is_ok(); @@ -100,8 +97,6 @@ Rust is installed now. Great! #[tokio::test] async fn smoke_case_install_with_path_install() { let cx = CliTestContext::new(Scenario::SimpleV2).await; - #[cfg(windows)] - let _path_guard = RegistryGuard::new(&USER_PATH).unwrap(); run_input(&cx.config, &["rustup-init"], "\n\n") .is_ok() @@ -497,7 +492,8 @@ async fn test_warn_if_complete_profile_is_used() { .await .with_stderr(snapbox::str![[r#" ... -warn: downloading with complete profile isn't recommended unless you are a developer of the rust language +warn: downloading with the `complete` profile is deprecated +help: consider switching to the `default` profile with `rustup set profile default` ... "#]]) .is_ok(); @@ -685,3 +681,40 @@ warn: many Rust crates require a system C toolchain to build ... "#]]); } + +#[tokio::test] +async fn install_rejection_leaves_rustupdir_untouched() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + + let rustupdir = &cx.config.rustupdir.rustupdir; + let settings_file = &rustupdir.join("settings.toml"); + fs::remove_dir_all(rustupdir).unwrap(); + + run_input(&cx.config, &["rustup-init", "--no-modify-path"], "3\n") + .with_stdout(snapbox::str![[r#" +... +This path needs to be in your PATH environment variable, +but will not be added automatically. + +You can uninstall at any time with rustup self uninstall and +these changes will be reverted. + +Current installation options: + + + default host tuple: [HOST_TUPLE] + default toolchain: stable (default) + profile: default + modify PATH variable: no + +1) Proceed with standard installation (default - just press enter) +2) Customize installation +3) Cancel installation +> + +"#]]) + .is_ok(); + + assert!(!settings_file.exists()); + assert!(!rustupdir.exists()); +} diff --git a/tests/suite/cli_paths.rs b/tests/suite/cli_paths.rs index f102dd5234..654e25413a 100644 --- a/tests/suite/cli_paths.rs +++ b/tests/suite/cli_paths.rs @@ -348,6 +348,41 @@ error: could not amend shell profile[..] } } + #[tokio::test] + async fn install_preserves_guarded_legacy_source() { + let cx = CliTestContext::new(Scenario::Empty).await; + let profile = cx.config.homedir.join(".bash_profile"); + let guarded_source = "[ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n"; + raw::write_file(&profile, guarded_source).unwrap(); + + let mut cmd = cx.config.cmd("rustup-init", &INIT_NONE[1..]); + cmd.env_remove("CARGO_HOME"); + assert!(cmd.output().unwrap().status.success()); + + let new_profile = fs::read_to_string(&profile).unwrap(); + let expected = guarded_source.to_owned() + &source("$HOME/.cargo", POSIX_SH); + assert_eq!(new_profile, expected); + } + + #[tokio::test] + async fn uninstall_preserves_guarded_source() { + let cx = CliTestContext::new(Scenario::Empty).await; + let profile = cx.config.homedir.join(".bash_profile"); + let guarded_source = "[ -f \"$HOME/.cargo/env\" ] && . \"$HOME/.cargo/env\"\n"; + raw::write_file(&profile, guarded_source).unwrap(); + + let mut cmd = cx.config.cmd("rustup-init", &INIT_NONE[1..]); + cmd.env_remove("CARGO_HOME"); + assert!(cmd.output().unwrap().status.success()); + + let mut cmd = cx.config.cmd("rustup", ["self", "uninstall", "-y"]); + cmd.env_remove("CARGO_HOME"); + assert!(cmd.output().unwrap().status.success()); + + let new_profile = fs::read_to_string(&profile).unwrap(); + assert_eq!(new_profile, guarded_source); + } + #[tokio::test] async fn uninstall_cleans_up_legacy_paths() { let cx = CliTestContext::new(Scenario::Empty).await; @@ -434,46 +469,54 @@ error: could not amend shell profile[..] #[cfg(windows)] mod windows { + use retry::delay::{Fibonacci, jitter}; + use retry::{OperationResult, retry}; + use super::INIT_NONE; use rustup::test::{CliTestContext, Scenario}; - use rustup::test::{RegistryGuard, USER_PATH, get_path}; + use rustup::test::{USER_PATH, get_path}; - use windows_registry::{HSTRING, Value}; + use windows_registry::{CURRENT_USER, HSTRING, Value}; #[tokio::test] /// Smoke test for end-to-end code connectivity of the installer path mgmt on windows. async fn install_uninstall_affect_path() { let cx = CliTestContext::new(Scenario::Empty).await; - let _guard = RegistryGuard::new(&USER_PATH).unwrap(); + let test_id = &cx.config.test_registry_id; let cfg_path = cx.config.cargodir.join("bin").display().to_string(); - let get_path_ = || { - HSTRING::try_from(get_path().unwrap().unwrap()) - .unwrap() - .to_string() + let read_path = |test_id: &str| -> Option { + retry( + Fibonacci::from_millis(1).map(jitter).take(21), + || match get_path(test_id).unwrap() { + Some(v) => OperationResult::Ok(HSTRING::try_from(v).unwrap().to_string()), + None => OperationResult::Retry(()), + }, + ) + .ok() }; cx.config.expect(&INIT_NONE).await.is_ok(); + let after_install = read_path(test_id).unwrap_or_default(); assert!( - get_path_().contains(cfg_path.trim_matches('"')), - "`{}` not in `{}`", - cfg_path, - get_path_() + after_install.contains(cfg_path.trim_matches('"')), + "`{cfg_path}` not in `{after_install}`", ); cx.config .expect(&["rustup", "self", "uninstall", "-y"]) .await .is_ok(); - assert!(!get_path_().contains(&cfg_path)); + let after_uninstall = read_path(test_id).unwrap_or_default(); + assert!(!after_uninstall.contains(&cfg_path)); } #[tokio::test] async fn uninstall_doesnt_affect_path_with_no_modify_path() { let cx = CliTestContext::new(Scenario::Empty).await; - let _guard = RegistryGuard::new(&USER_PATH).unwrap(); + let test_id = &cx.config.test_registry_id; let cfg_path = cx.config.cargodir.join("bin").display().to_string(); let get_path_ = || { - HSTRING::try_from(get_path().unwrap().unwrap()) + HSTRING::try_from(get_path(test_id).unwrap().unwrap()) .unwrap() .to_string() }; @@ -496,10 +539,10 @@ mod windows { async fn install_uninstall_affect_path_with_non_unicode() { use std::os::windows::ffi::OsStrExt; - use windows_registry::{CURRENT_USER, Type}; + use windows_registry::Type; let cx = CliTestContext::new(Scenario::Empty).await; - let _guard = RegistryGuard::new(&USER_PATH).unwrap(); + let test_id = &cx.config.test_registry_id; // Set up a non unicode PATH let mut reg_value = Value::from([ 0x00, 0xD8, // leading surrogate @@ -507,10 +550,8 @@ mod windows { 0x00, 0x00, // null ]); reg_value.set_ty(Type::ExpandString); - CURRENT_USER - .create("Environment") - .unwrap() - .set_value("PATH", ®_value) + USER_PATH + .set(Some(®_value), test_id, CURRENT_USER) .unwrap(); // compute expected path after installation @@ -529,12 +570,12 @@ mod windows { expected.set_ty(Type::ExpandString); cx.config.expect(&INIT_NONE).await.is_ok(); - assert_eq!(get_path().unwrap().unwrap(), expected); + assert_eq!(get_path(test_id).unwrap().unwrap(), expected); cx.config .expect(&["rustup", "self", "uninstall", "-y"]) .await .is_ok(); - assert_eq!(get_path().unwrap().unwrap(), reg_value); + assert_eq!(get_path(test_id).unwrap().unwrap(), reg_value); } } diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 91c3891907..76ca060212 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -353,7 +353,7 @@ async fn add_target() { let cx = CliTestContext::new(Scenario::SimpleV2).await; let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH1 ); cx.config @@ -372,7 +372,7 @@ async fn remove_target() { let cx = CliTestContext::new(Scenario::SimpleV2).await; let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH1 ); cx.config @@ -404,13 +404,13 @@ async fn add_remove_multiple_targets() { .is_ok(); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH1 ); assert!(cx.config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH2 ); assert!(cx.config.rustupdir.has(path)); @@ -421,13 +421,13 @@ async fn add_remove_multiple_targets() { .is_ok(); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH1 ); assert!(!cx.config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH2 ); assert!(!cx.config.rustupdir.has(path)); @@ -474,7 +474,7 @@ async fn add_target_explicit() { let cx = CliTestContext::new(Scenario::SimpleV2).await; let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH1 ); cx.config @@ -500,7 +500,7 @@ async fn remove_target_explicit() { let cx = CliTestContext::new(Scenario::SimpleV2).await; let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", - &this_host_tuple(), + this_host_tuple(), CROSS_ARCH1 ); cx.config @@ -4173,14 +4173,7 @@ async fn missing_manifest_shows_reinstall_help() { .await .is_ok(); - let manifest_path = cx - .config - .rustupdir - .join("toolchains") - .join(format!("nightly-{}", this_host_tuple())) - .join("lib") - .join("rustlib") - .join("multirust-channel-manifest.toml"); + let manifest_path = nightly_manifest_path(&cx); fs::remove_file(&manifest_path).unwrap(); @@ -4195,3 +4188,51 @@ help: try reinstalling or updating the toolchain "#]]) .is_err(); } + +#[tokio::test] +async fn reinstall_restores_missing_manifest() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + + cx.config + .expect(["rustup", "toolchain", "install", "nightly"]) + .await + .is_ok(); + + let manifest_path = nightly_manifest_path(&cx); + + fs::remove_file(&manifest_path).unwrap(); + + cx.config + .expect(["rustup", "toolchain", "install", "nightly"]) + .await + .is_ok(); + + assert!(manifest_path.exists()); +} + +#[tokio::test] +async fn update_all_restores_missing_manifest() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + + cx.config + .expect(["rustup", "toolchain", "install", "nightly"]) + .await + .is_ok(); + + let manifest_path = nightly_manifest_path(&cx); + fs::remove_file(&manifest_path).unwrap(); + + cx.config.expect(["rustup", "update"]).await.is_ok(); + + assert!(manifest_path.exists()); +} + +fn nightly_manifest_path(cx: &CliTestContext) -> PathBuf { + cx.config + .rustupdir + .join("toolchains") + .join(format!("nightly-{}", this_host_tuple())) + .join("lib") + .join("rustlib") + .join("multirust-channel-manifest.toml") +} diff --git a/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg b/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg index 1a2571939b..479447b6cb 100644 --- a/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg +++ b/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg @@ -1,4 +1,4 @@ - +