pax_global_header00006660000000000000000000000064144132710520014511gustar00rootroot0000000000000052 comment=5af9b9484c1bb7a747700af5940ff9c479801dc8 rustup-1.26.0/000077500000000000000000000000001441327105200131415ustar00rootroot00000000000000rustup-1.26.0/.cargo/000077500000000000000000000000001441327105200143125ustar00rootroot00000000000000rustup-1.26.0/.cargo/config000066400000000000000000000000641441327105200155020ustar00rootroot00000000000000[alias] dev-install = "run -- --no-modify-path -y" rustup-1.26.0/.cirrus.yml000066400000000000000000000007501441327105200152530ustar00rootroot00000000000000# This is ci/actions-templates/linux-builds-template.yaml # Do not edit this file in .cirrus.yml task: name: FreeBSD freebsd_instance: image: freebsd-13-0-release-amd64 setup_script: | pkg install -y git gmake bash echo "=========" echo "create non-root user and log into it" pw group add -n tester pw user add -n tester -g tester -s `which bash` pw user mod tester -d `pwd` chown -R tester . sudo -u tester bash ci/cirrus-templates/script.bash rustup-1.26.0/.dockerignore000066400000000000000000000000141441327105200156100ustar00rootroot00000000000000.git target rustup-1.26.0/.envrc000066400000000000000000000000121441327105200142500ustar00rootroot00000000000000use flake rustup-1.26.0/.gitattributes000066400000000000000000000001111441327105200160250ustar00rootroot00000000000000*.rs text eol=lf *.lock text eol=lf *.txt text eol=lf *.toml text eol=lf rustup-1.26.0/.github/000077500000000000000000000000001441327105200145015ustar00rootroot00000000000000rustup-1.26.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001441327105200166645ustar00rootroot00000000000000rustup-1.26.0/.github/ISSUE_TEMPLATE/bug_report.yml000066400000000000000000000025011441327105200215550ustar00rootroot00000000000000name: Bug report description: Create a report to help us improve labels: [bug] body: - type: markdown attributes: value: > **Thanks for filing a 🐛 bug report 😄!** - type: textarea id: problem attributes: label: Problem description: > A clear and concise description of what the bug is, including what currently happens and what you expected to happen. validations: required: true - type: textarea id: steps attributes: label: Steps description: The steps to reproduce the bug. placeholder: | 1. 2. 3. validations: required: true - type: textarea id: solutions attributes: label: Possible Solution(s) description: > Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change. - type: textarea id: notes attributes: label: Notes - type: textarea id: version attributes: label: Rustup version description: Output of `rustup --version` render: console validations: required: true - type: textarea id: toolchains attributes: label: Installed toolchains description: Output of `rustup show` render: console validations: required: true rustup-1.26.0/.github/ISSUE_TEMPLATE/enhancement_request.yml000066400000000000000000000015411441327105200234450ustar00rootroot00000000000000name: Enhancement request description: Suggest an enhancement for this project labels: [enhancement] body: - type: markdown attributes: value: > **Thanks for filing an 🙋 enhancement request 😄!** - type: textarea id: problem attributes: label: Problem you are trying to solve description: > A clear and concise description of the problem this enhancement request is trying to solve. validations: required: true - type: textarea id: solution attributes: label: Solution you'd like description: > A clear and concise description of what you want to happen. validations: required: true - type: textarea id: notes attributes: label: Notes description: > Any additional context or information you feel may be relevant to the issue. rustup-1.26.0/.github/ISSUE_TEMPLATE/wsl_panic.yml000066400000000000000000000045501441327105200213720ustar00rootroot00000000000000name: I am experiencing a panic on WSL description: Report a panic when using Rustup on WSL labels: [bug] body: - type: markdown attributes: value: > Please read the following very carefully and decide if you actually need to file this bug... WSL, specifically WSL 1, has a limitation where glibc 2.31 and newer will panic, fundamentally the panic will look something like the following: thread 'main' panicked at 'assertion failed: `(left == right)` left: `22`, right: `4`', src/libstd/sys/unix/thread.rs:166:21 This is a bug, but it's a bug in WSL, not in Rust/Rustup and will affect other programs built with Rust too. Working around it with Rustup will not help you until WSL (or glibc) is fixed. This is known to affect: * Ubuntu 20.04 * Arch Linux But it may affect other versions of Linux on WSL1. You can find more information on the WSL bug report [here](https://github.com/microsoft/WSL/issues/4898). If you're CERTAIN that you're not reporting yet another duplicate of the above issue, then... **Thanks for filing a 🐛 bug report 😄!** - type: textarea id: problem attributes: label: Problem description: > A clear and concise description of what the bug is, including what currently happens and what you expected to happen. validations: required: true - type: textarea id: steps attributes: label: Steps description: The steps to reproduce the bug. placeholder: | 1. 2. 3. validations: required: true - type: textarea id: solutions attributes: label: Possible Solution(s) description: > Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change. - type: textarea id: notes attributes: label: Notes - type: textarea id: version attributes: label: Rustup version description: Output of `rustup --version` render: console validations: required: true - type: textarea id: toolchains attributes: label: Installed toolchains description: Output of `rustup show` render: console validations: required: true rustup-1.26.0/.github/renovate.json000066400000000000000000000006351441327105200172230ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" ], "labels": [ "dependencies" ], "lockFileMaintenance": { "enabled": true }, "prCreation": "not-pending", "rangeStrategy": "replace", "stabilityDays": 3, "github-actions": { "fileMatch": [ "^ci\\/.*/[^/]+\\.ya?ml$" ] } }rustup-1.26.0/.github/workflows/000077500000000000000000000000001441327105200165365ustar00rootroot00000000000000rustup-1.26.0/.github/workflows/centos-fmt-clippy-on-all.yaml000066400000000000000000000063101441327105200241570ustar00rootroot00000000000000# This is ci/actions-templates/centos-fmt-clippy.yaml # Do not edit this file in .github/workflows name: General Checks on: pull_request: branches: - "*" push: branches: - master - stable - renovate/* schedule: - cron: "30 0 * * 1" # Every Monday at half past midnight jobs: check: name: Checks runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ runner.os }}-cargo-clippy-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ runner.os }}-cargo-clippy-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Beta is up to date run: | if rustc +beta -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall beta fi rustup toolchain install --profile=minimal beta rustup default beta - name: Ensure we have the components we need run: | rustup component add rustfmt rustup component add clippy - name: Run the centos check within the docker image run: | docker run \ --volume "$PWD":/checkout:ro \ --workdir /checkout \ --tty \ --init \ --rm \ centos:7 \ sh ./ci/raw_init.sh - name: Run shell checks run: | shellcheck -x -s dash -- rustup-init.sh git ls-files -- '*.sh' | xargs shellcheck -x -s dash git ls-files -- '*.bash' | xargs shellcheck -x -s bash - name: Run formatting checks run: | cargo fmt --all --check - name: Run cargo check and clippy run: | cargo check --all --all-targets git ls-files -- '*.rs' | xargs touch cargo clippy --all --all-targets rustup-1.26.0/.github/workflows/deploy-docs.yaml000066400000000000000000000022641441327105200216500ustar00rootroot00000000000000name: Deploy Docs on: push: branches: - stable - master jobs: doc: name: Documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install mdbook run: | mkdir mdbook curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.6/mdbook-v0.4.6-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook echo "`pwd`/mdbook" >> $GITHUB_PATH - name: Build book run: | git checkout stable cd doc mdbook build mv book ${{ runner.temp }} git checkout master mdbook build mv book ${{ runner.temp }}/book/devel - name: Deploy to GitHub run: | cd ${{ runner.temp }}/book git init git config user.name "Deploy from CI" git config user.email "" git add . .nojekyll git commit -m "Deploy $GITHUB_REF $GITHUB_SHA to gh-pages" git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY git push --force --set-upstream origin master:gh-pages rustup-1.26.0/.github/workflows/linux-builds-on-master.yaml000066400000000000000000000142131441327105200237450ustar00rootroot00000000000000# This is ci/actions-templates/linux-builds-template.yaml # Do not edit this file in .github/workflows name: Linux (master) # skip-pr skip-stable on: push: # skip-pr branches: # skip-pr - master # skip-pr skip-stable schedule: # skip-pr skip-stable - cron: "30 0 * * 1" # Every Monday at half past midnight UTC skip-pr skip-stable jobs: build: name: Build runs-on: ubuntu-latest strategy: fail-fast: false matrix: mode: - dev - release target: - x86_64-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - aarch64-linux-android - aarch64-unknown-linux-gnu # skip-pr - powerpc64-unknown-linux-gnu # skip-pr - x86_64-unknown-linux-musl # skip-pr include: - target: x86_64-unknown-linux-gnu run_tests: YES #snap_arch: amd64 - target: aarch64-unknown-linux-gnu # skip-pr #snap_arch: arm64 # skip-pr - target: armv7-unknown-linux-gnueabihf #snap_arch: armhf steps: - name: Clone repo uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Stable is up to date run: | if rustc +stable -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall stable fi rustup toolchain install --profile=minimal stable - name: Ensure we have our goal target installed run: | rustup target install "$TARGET" - name: Determine which docker we need to run in run: | case "$TARGET" in *-linux-android*) DOCKER=android ;; # Android uses a local docker image *) DOCKER="$TARGET" ;; esac echo "DOCKER=$DOCKER" >> $GITHUB_ENV - name: Fetch the docker run: bash ci/fetch-rust-docker.bash "${TARGET}" - name: Maybe build a docker from there run: | if [ -f "ci/docker/$DOCKER/Dockerfile" ]; then docker build -t "$DOCKER" -f "ci/docker/${DOCKER}/Dockerfile" . fi - name: Run the build within the docker image env: BUILD_PROFILE: ${{ matrix.mode }} run: | mkdir -p "${PWD}/target" chown -R "$(id -u)":"$(id -g)" "${PWD}/target" docker run \ --entrypoint sh \ --env BUILD_PROFILE="${BUILD_PROFILE}" \ --env CARGO_HOME=/cargo \ --env CARGO_TARGET_DIR=/checkout/target \ --env LIBZ_SYS_STATIC=1 \ --env SKIP_TESTS="${SKIP_TESTS}" \ --env TARGET="${TARGET}" \ --init \ --rm \ --tty \ --user "$(id -u)":"$(id -g)" \ --volume "$(rustc --print sysroot)":/rustc-sysroot:ro \ --volume "${HOME}/.cargo:/cargo" \ --volume "${PWD}":/checkout:ro \ --volume "${PWD}"/target:/checkout/target \ --workdir /checkout \ "${DOCKER}" \ -c 'PATH="${PATH}":/rustc-sysroot/bin bash ci/run.bash' - name: Upload the built artifact uses: actions/upload-artifact@v3 if: matrix.mode == 'release' with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | pip3 install -U setuptools pip3 install awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | bash ci/prepare-deploy.bash - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws s3 cp --recursive deploy/ s3://dev-static-rust-lang-org/rustup/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/.github/workflows/linux-builds-on-pr.yaml000066400000000000000000000135501441327105200230760ustar00rootroot00000000000000# This is ci/actions-templates/linux-builds-template.yaml # Do not edit this file in .github/workflows name: Linux (PR) # skip-master skip-stable on: pull_request: # skip-master skip-stable branches: # skip-master skip-stable - "*" # skip-master skip-stable - renovate/* # skip-master skip-stable jobs: build: name: Build runs-on: ubuntu-latest strategy: fail-fast: false matrix: mode: - dev - release target: - x86_64-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - aarch64-linux-android include: - target: x86_64-unknown-linux-gnu run_tests: YES #snap_arch: amd64 - target: armv7-unknown-linux-gnueabihf #snap_arch: armhf steps: - name: Clone repo uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Stable is up to date run: | if rustc +stable -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall stable fi rustup toolchain install --profile=minimal stable - name: Ensure we have our goal target installed run: | rustup target install "$TARGET" - name: Determine which docker we need to run in run: | case "$TARGET" in *-linux-android*) DOCKER=android ;; # Android uses a local docker image *) DOCKER="$TARGET" ;; esac echo "DOCKER=$DOCKER" >> $GITHUB_ENV - name: Fetch the docker run: bash ci/fetch-rust-docker.bash "${TARGET}" - name: Maybe build a docker from there run: | if [ -f "ci/docker/$DOCKER/Dockerfile" ]; then docker build -t "$DOCKER" -f "ci/docker/${DOCKER}/Dockerfile" . fi - name: Run the build within the docker image env: BUILD_PROFILE: ${{ matrix.mode }} run: | mkdir -p "${PWD}/target" chown -R "$(id -u)":"$(id -g)" "${PWD}/target" docker run \ --entrypoint sh \ --env BUILD_PROFILE="${BUILD_PROFILE}" \ --env CARGO_HOME=/cargo \ --env CARGO_TARGET_DIR=/checkout/target \ --env LIBZ_SYS_STATIC=1 \ --env SKIP_TESTS="${SKIP_TESTS}" \ --env TARGET="${TARGET}" \ --init \ --rm \ --tty \ --user "$(id -u)":"$(id -g)" \ --volume "$(rustc --print sysroot)":/rustc-sysroot:ro \ --volume "${HOME}/.cargo:/cargo" \ --volume "${PWD}":/checkout:ro \ --volume "${PWD}"/target:/checkout/target \ --workdir /checkout \ "${DOCKER}" \ -c 'PATH="${PATH}":/rustc-sysroot/bin bash ci/run.bash' - name: Upload the built artifact uses: actions/upload-artifact@v3 if: matrix.mode == 'release' with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | pip3 install -U setuptools pip3 install awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | bash ci/prepare-deploy.bash - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws s3 cp --recursive deploy/ s3://dev-static-rust-lang-org/rustup/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/.github/workflows/linux-builds-on-stable.yaml000066400000000000000000000170761441327105200237360ustar00rootroot00000000000000# This is ci/actions-templates/linux-builds-template.yaml # Do not edit this file in .github/workflows name: Linux (stable) # skip-master skip-pr on: push: # skip-pr branches: # skip-pr - stable # skip-pr skip-master jobs: build: name: Build runs-on: ubuntu-latest strategy: fail-fast: false matrix: mode: - dev - release target: - x86_64-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - aarch64-linux-android - aarch64-unknown-linux-gnu # skip-pr - aarch64-unknown-linux-musl # skip-pr skip-master - powerpc64-unknown-linux-gnu # skip-pr - x86_64-unknown-linux-musl # skip-pr - i686-unknown-linux-gnu # skip-pr skip-master - arm-unknown-linux-gnueabi # skip-pr skip-master - arm-unknown-linux-gnueabihf # skip-pr skip-master - x86_64-unknown-freebsd # skip-pr skip-master - x86_64-unknown-netbsd # skip-pr skip-master - x86_64-unknown-illumos # skip-pr skip-master - powerpc-unknown-linux-gnu # skip-pr skip-master - powerpc64le-unknown-linux-gnu # skip-pr skip-master - mips-unknown-linux-gnu # skip-pr skip-master - mips64-unknown-linux-gnuabi64 # skip-pr skip-master - mipsel-unknown-linux-gnu # skip-pr skip-master - mips64el-unknown-linux-gnuabi64 # skip-pr skip-master - s390x-unknown-linux-gnu # skip-pr skip-master - arm-linux-androideabi # skip-pr skip-master - armv7-linux-androideabi # skip-pr skip-master - i686-linux-android # skip-pr skip-master - x86_64-linux-android # skip-pr skip-master - riscv64gc-unknown-linux-gnu # skip-pr skip-master include: - target: x86_64-unknown-linux-gnu run_tests: YES #snap_arch: amd64 - target: i686-unknown-linux-gnu # skip-pr skip-master #snap_arch: i386 # skip-pr skip-master - target: aarch64-unknown-linux-gnu # skip-pr #snap_arch: arm64 # skip-pr - target: armv7-unknown-linux-gnueabihf #snap_arch: armhf - target: powerpc64le-unknown-linux-gnu # skip-pr skip-master #snap_arch: ppc64el # skip-pr skip-master - target: s390x-unknown-linux-gnu # skip-pr skip-master #snap_arch: s390x # skip-pr skip-master steps: - name: Clone repo uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Stable is up to date run: | if rustc +stable -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall stable fi rustup toolchain install --profile=minimal stable - name: Ensure we have our goal target installed run: | rustup target install "$TARGET" - name: Determine which docker we need to run in run: | case "$TARGET" in *-linux-android*) DOCKER=android ;; # Android uses a local docker image *) DOCKER="$TARGET" ;; esac echo "DOCKER=$DOCKER" >> $GITHUB_ENV - name: Fetch the docker run: bash ci/fetch-rust-docker.bash "${TARGET}" - name: Maybe build a docker from there run: | if [ -f "ci/docker/$DOCKER/Dockerfile" ]; then docker build -t "$DOCKER" -f "ci/docker/${DOCKER}/Dockerfile" . fi - name: Run the build within the docker image env: BUILD_PROFILE: ${{ matrix.mode }} run: | mkdir -p "${PWD}/target" chown -R "$(id -u)":"$(id -g)" "${PWD}/target" docker run \ --entrypoint sh \ --env BUILD_PROFILE="${BUILD_PROFILE}" \ --env CARGO_HOME=/cargo \ --env CARGO_TARGET_DIR=/checkout/target \ --env LIBZ_SYS_STATIC=1 \ --env SKIP_TESTS="${SKIP_TESTS}" \ --env TARGET="${TARGET}" \ --init \ --rm \ --tty \ --user "$(id -u)":"$(id -g)" \ --volume "$(rustc --print sysroot)":/rustc-sysroot:ro \ --volume "${HOME}/.cargo:/cargo" \ --volume "${PWD}":/checkout:ro \ --volume "${PWD}"/target:/checkout/target \ --workdir /checkout \ "${DOCKER}" \ -c 'PATH="${PATH}":/rustc-sysroot/bin bash ci/run.bash' - name: Upload the built artifact uses: actions/upload-artifact@v3 if: matrix.mode == 'release' with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | pip3 install -U setuptools pip3 install awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | bash ci/prepare-deploy.bash - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws s3 cp --recursive deploy/ s3://dev-static-rust-lang-org/rustup/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/.github/workflows/macos-builds-on-all.yaml000066400000000000000000000122431441327105200231660ustar00rootroot00000000000000# This is ci/actions-templates/macos-builds-template.yaml # Do not edit this file in .github/workflows name: macOS on: pull_request: branches: - "*" push: branches: - master - stable - renovate/* schedule: - cron: "30 0 * * 1" # Every Monday at half past midnight UTC jobs: build: name: Build runs-on: macos-latest strategy: matrix: target: - x86_64-apple-darwin - aarch64-apple-darwin mode: - dev - release include: - target: x86_64-apple-darwin run_tests: YES steps: - uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV echo "SKIP_TESTS=" >> $GITHUB_ENV echo "LZMA_API_STATIC=1" >> $GITHUB_ENV - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Stable is up to date run: | if rustc +stable -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall stable fi rustup toolchain install --profile=minimal stable - name: aarch64-specific items run: | # Use nightly for now rustup toolchain install --profile=minimal nightly rustup default nightly if: matrix.target == 'aarch64-apple-darwin' - name: Ensure we have our goal target installed run: | rustup target install "$TARGET" - name: Run a full build and test env: BUILD_PROFILE: ${{ matrix.mode }} run: bash ci/run.bash - name: Dump dynamic link targets if: matrix.mode == 'release' run: | otool -L target/${TARGET}/release/rustup-init if otool -L target/${TARGET}/release/rustup-init | grep -q -F /usr/local/; then echo >&2 "Unfortunately there are /usr/local things in the link. Fail." exit 1 fi - name: Upload the built artifact if: matrix.mode == 'release' uses: actions/upload-artifact@v3 with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | pip3 install awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | bash ci/prepare-deploy.bash - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws s3 cp --recursive deploy/ s3://dev-static-rust-lang-org/rustup/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache - name: Flush cache # This is a workaround for a bug with GitHub Actions Cache that causes # corrupt cache entries (particularly in the target directory). See # https://github.com/actions/cache/issues/403 and # https://github.com/rust-lang/cargo/issues/8603. run: sudo /usr/sbin/purge rustup-1.26.0/.github/workflows/windows-builds-on-master.yaml000066400000000000000000000137351441327105200243100ustar00rootroot00000000000000# This is ci/actions-templates/windows-builds-template.yaml # Do not edit this file in .github/workflows name: Windows (master) # skip-pr skip-stable on: push: # skip-pr branches: # skip-pr - master # skip-pr skip-stable schedule: # skip-pr skip-stable - cron: "30 0 * * 1" # Every Monday at half past midnight UTC skip-pr skip-stable jobs: build: name: Build runs-on: windows-latest env: RUSTFLAGS: -Ctarget-feature=+crt-static strategy: fail-fast: false matrix: target: - x86_64-pc-windows-msvc - aarch64-pc-windows-msvc # skip-pr - x86_64-pc-windows-gnu # skip-pr mode: - dev - release include: - target: x86_64-pc-windows-msvc run_tests: YES - target: x86_64-pc-windows-gnu # skip-pr mingw: https://ci-mirrors.rust-lang.org/rustc/x86_64-6.3.0-release-posix-seh-rt_v5-rev2.7z # skip-pr mingwdir: mingw64 # skip-pr steps: - uses: actions/checkout@v3 # v2 defaults to a shallow checkout, but we need at least to the previous tag with: fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | New-Item "${env:USERPROFILE}\.cargo\registry" -ItemType Directory -Force New-Item "${env:USERPROFILE}\.cargo\git" -ItemType Directory -Force shell: powershell - name: Install mingw run: | # We retrieve mingw from the Rust CI buckets # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest ${{ matrix.mingw }} -OutFile mingw.7z 7z x -y mingw.7z -oC:\msys64 | Out-Null del mingw.7z echo "C:\msys64\usr\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "C:\msys64\${{ matrix.mingwdir }}\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 shell: powershell if: matrix.mingw != '' - name: Set PATH run: | echo "%USERPROFILE%\.cargo\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "TARGET=${{ matrix.target }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8 - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV shell: bash - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using win.rustup.rs run: | # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal del rustup-init.exe shell: powershell - name: Ensure stable toolchain is up to date run: rustup update stable shell: bash - name: Install the target run: | rustup target install ${{ matrix.target }} - name: Run a full build env: TARGET: ${{ matrix.target }} BUILD_PROFILE: ${{ matrix.mode }} run: bash ci/run.bash - name: Run cargo check and clippy if: matrix.mode != 'release' env: TARGET: ${{ matrix.target }} # os-specific code leads to lints escaping if we only run this in one target run: | cargo check --all --all-targets git ls-files -- '*.rs' | xargs touch cargo clippy --workspace --all-targets - name: Upload the built artifact if: matrix.mode == 'release' uses: actions/upload-artifact@v3 with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init.exe retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | choco upgrade awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | .\ci\prepare-deploy.ps1 shell: powershell - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws --debug s3 cp --recursive dist s3://dev-static-rust-lang-org/rustup/dist env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/.github/workflows/windows-builds-on-pr.yaml000066400000000000000000000132141441327105200234260ustar00rootroot00000000000000# This is ci/actions-templates/windows-builds-template.yaml # Do not edit this file in .github/workflows name: Windows (PR) # skip-master skip-stable on: pull_request: # skip-master skip-stable branches: # skip-master skip-stable - "*" # skip-master skip-stable - renovate/* # skip-master skip-stable jobs: build: name: Build runs-on: windows-latest env: RUSTFLAGS: -Ctarget-feature=+crt-static strategy: fail-fast: false matrix: target: - x86_64-pc-windows-msvc mode: - dev - release include: - target: x86_64-pc-windows-msvc run_tests: YES steps: - uses: actions/checkout@v3 # v2 defaults to a shallow checkout, but we need at least to the previous tag with: fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | New-Item "${env:USERPROFILE}\.cargo\registry" -ItemType Directory -Force New-Item "${env:USERPROFILE}\.cargo\git" -ItemType Directory -Force shell: powershell - name: Install mingw run: | # We retrieve mingw from the Rust CI buckets # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest ${{ matrix.mingw }} -OutFile mingw.7z 7z x -y mingw.7z -oC:\msys64 | Out-Null del mingw.7z echo "C:\msys64\usr\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "C:\msys64\${{ matrix.mingwdir }}\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 shell: powershell if: matrix.mingw != '' - name: Set PATH run: | echo "%USERPROFILE%\.cargo\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "TARGET=${{ matrix.target }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8 - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV shell: bash - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using win.rustup.rs run: | # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal del rustup-init.exe shell: powershell - name: Ensure stable toolchain is up to date run: rustup update stable shell: bash - name: Install the target run: | rustup target install ${{ matrix.target }} - name: Run a full build env: TARGET: ${{ matrix.target }} BUILD_PROFILE: ${{ matrix.mode }} run: bash ci/run.bash - name: Run cargo check and clippy if: matrix.mode != 'release' env: TARGET: ${{ matrix.target }} # os-specific code leads to lints escaping if we only run this in one target run: | cargo check --all --all-targets git ls-files -- '*.rs' | xargs touch cargo clippy --workspace --all-targets - name: Upload the built artifact if: matrix.mode == 'release' uses: actions/upload-artifact@v3 with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init.exe retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | choco upgrade awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | .\ci\prepare-deploy.ps1 shell: powershell - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws --debug s3 cp --recursive dist s3://dev-static-rust-lang-org/rustup/dist env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/.github/workflows/windows-builds-on-stable.yaml000066400000000000000000000143041441327105200242600ustar00rootroot00000000000000# This is ci/actions-templates/windows-builds-template.yaml # Do not edit this file in .github/workflows name: Windows (stable) # skip-master skip-pr on: push: # skip-pr branches: # skip-pr - stable # skip-pr skip-master jobs: build: name: Build runs-on: windows-latest env: RUSTFLAGS: -Ctarget-feature=+crt-static strategy: fail-fast: false matrix: target: - x86_64-pc-windows-msvc - i686-pc-windows-msvc # skip-pr skip-master - aarch64-pc-windows-msvc # skip-pr - x86_64-pc-windows-gnu # skip-pr - i686-pc-windows-gnu # skip-pr skip-master mode: - dev - release include: - target: x86_64-pc-windows-msvc run_tests: YES - target: x86_64-pc-windows-gnu # skip-pr mingw: https://ci-mirrors.rust-lang.org/rustc/x86_64-6.3.0-release-posix-seh-rt_v5-rev2.7z # skip-pr mingwdir: mingw64 # skip-pr - target: i686-pc-windows-gnu # skip-pr skip-master mingwdir: mingw32 # skip-pr skip-master mingw: https://ci-mirrors.rust-lang.org/rustc/i686-6.3.0-release-posix-dwarf-rt_v5-rev2.7z # skip-pr skip-master steps: - uses: actions/checkout@v3 # v2 defaults to a shallow checkout, but we need at least to the previous tag with: fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | New-Item "${env:USERPROFILE}\.cargo\registry" -ItemType Directory -Force New-Item "${env:USERPROFILE}\.cargo\git" -ItemType Directory -Force shell: powershell - name: Install mingw run: | # We retrieve mingw from the Rust CI buckets # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest ${{ matrix.mingw }} -OutFile mingw.7z 7z x -y mingw.7z -oC:\msys64 | Out-Null del mingw.7z echo "C:\msys64\usr\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "C:\msys64\${{ matrix.mingwdir }}\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 shell: powershell if: matrix.mingw != '' - name: Set PATH run: | echo "%USERPROFILE%\.cargo\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "TARGET=${{ matrix.target }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8 - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV shell: bash - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using win.rustup.rs run: | # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal del rustup-init.exe shell: powershell - name: Ensure stable toolchain is up to date run: rustup update stable shell: bash - name: Install the target run: | rustup target install ${{ matrix.target }} - name: Run a full build env: TARGET: ${{ matrix.target }} BUILD_PROFILE: ${{ matrix.mode }} run: bash ci/run.bash - name: Run cargo check and clippy if: matrix.mode != 'release' env: TARGET: ${{ matrix.target }} # os-specific code leads to lints escaping if we only run this in one target run: | cargo check --all --all-targets git ls-files -- '*.rs' | xargs touch cargo clippy --workspace --all-targets - name: Upload the built artifact if: matrix.mode == 'release' uses: actions/upload-artifact@v3 with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init.exe retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | choco upgrade awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | .\ci\prepare-deploy.ps1 shell: powershell - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws --debug s3 cp --recursive dist s3://dev-static-rust-lang-org/rustup/dist env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/.gitignore000066400000000000000000000001631441327105200151310ustar00rootroot00000000000000/.project /target /Cargo.lock /.settings /.idea/ /.vscode/ *~ /**/target /home /local-rustup /snapcraft flake.lock rustup-1.26.0/CHANGELOG.md000066400000000000000000002457671441327105200147770ustar00rootroot00000000000000# Changelog ## [1.26.0] - unreleased This version of Rustup involves a significant number of internal refactors, both in terms of the Rustup code and its tests. The headlines for this release are: 1. Add [rust-analyzer] as a [proxy] of rustup. Now you can call `rust-analyzer` and it will be proxied to the rust-analyzer component for the current toolchain. 2. Bump the [clap] dependency from 2.x to 3.x. It's a major version bump, so there are some help text changes, but the command line interface is unchanged. 3. Remove experimental GPG signature validation and the `rustup show keys` command. Due to its experimental status, validating the integrity of downloaded binaries did not rely on it, and there was no option to abort the installation if a signature mismatch happened. Multiple problems with its implementation were discovered in the recent months, which led to the decision to remove the experimental code. The team is working on the design of a new signature validation scheme, which will be implemented in the future. In addition to a lot of work on the codebase itself, due to the length of time since the last release this one has a record number of contributors and we thank you all for your efforts and time. Rather than list every single merged PR since the last release, we have pulled out a number of highlights to include in this changelog entry. For everything else, please review the repository. ### Added - Added `rust-analyzer` as a proxy of rustup [pr#3022] - Added DisplayVersion for rustup to registry on Windows [pr#3047] - Build Rustup for Windows arm64 on stable [pr#3232] - Added `up` as an alias of the `update` command [pr#3044] - Added details of each setting in the toolchain file in the documentation [pr#3067] - Added automatic resume flag when retrying download with curl [pr#3089] - Added UI tests for rustup [pr#3209] ### Changed - Bump the `clap` dependency from 2.x to 3.x [pr#3064] - Remove GPG signature support [pr#3277] - Don't add toolchain bin to PATH on Windows [pr#3178] - Remove use of hard links to symlinks on macOS [pr#3137] - Avoid deduplicate PATH entries added during build [pr#2848] - The toolchain name cannot be left blank [pr#2993] - Correctly propagate subshell failures in rustup-init.sh [pr#3012] - Enhanced warning message for Rust installation already present [pr#3038] - Improved error message when there is an error caused by override file [pr#3041] - Explain [proxy] in terminology documentation [pr#3091] - Recommend tracking `Cargo.lock` with rust-toolchain file [pr#3054] - Fix RUSTUP_PERMIT_COPY_RENAME condition so it is actually used [pr#3292] - Bump a lot of dependencies to their latest versions [pr#renovate-bot] [rust-analyzer]: https://github.com/rust-lang/rust-analyzer [proxy]: https://rust-lang.github.io/rustup/concepts/proxies.html [clap]: https://crates.io/crates/clap [pr#3022]: https://github.com/rust-lang/rustup/pull/3022 [pr#3047]: https://github.com/rust-lang/rustup/pull/3047 [pr#3232]: https://github.com/rust-lang/rustup/pull/3232 [pr#3044]: https://github.com/rust-lang/rustup/pull/3044 [pr#3067]: https://github.com/rust-lang/rustup/pull/3067 [pr#3089]: https://github.com/rust-lang/rustup/pull/3089 [pr#3209]: https://github.com/rust-lang/rustup/pull/3209 [pr#3064]: https://github.com/rust-lang/rustup/pull/3064 [pr#3277]: https://github.com/rust-lang/rustup/pull/3277 [pr#3178]: https://github.com/rust-lang/rustup/pull/3178 [pr#3137]: https://github.com/rust-lang/rustup/pull/3137 [pr#2848]: https://github.com/rust-lang/rustup/pull/2848 [pr#2993]: https://github.com/rust-lang/rustup/pull/2993 [pr#3012]: https://github.com/rust-lang/rustup/pull/3012 [pr#3038]: https://github.com/rust-lang/rustup/pull/3038 [pr#3041]: https://github.com/rust-lang/rustup/pull/3041 [pr#3091]: https://github.com/rust-lang/rustup/pull/3091 [pr#3054]: https://github.com/rust-lang/rustup/pull/3054 [pr#3292]: https://github.com/rust-lang/rustup/pull/3292 [pr#renovate-bot]: https://github.com/rust-lang/rustup/pulls?q=is:pr+author:app/renovate+is:merged Thanks go to: - Daniel Silverstone (kinnison) - Sabrina Jewson (SabrinaJewson) - Robert Collins (rbtcollins) - chansuke (chansuke) - Shamil (shamilsan) - Oli Lalonde (olalonde) - 二手掉包工程师 (hi-rustin) - Eric Huss (ehuss) - J Balint BIRO (jbalintbiro) - Easton Pillay (jedieaston) - zhaixiaojuan (zhaixiaojuan) - Chris Denton (ChrisDenton) - Martin Geisler (mgeisler) - Lucio Franco (LucioFranco) - Nicholas Bishop (nicholasbishop) - SADIK KUZU (sadikkuzu) - darkyshiny (darkyshiny) - René Dudfield (illume) - Noritada Kobayashi (noritada) - Mohammad AlSaleh (MoSal) - Dustin Martin (dmartin) - Ville Skyttä (scop) - Tshepang Mbambo (tshepang) - Illia Bobyr (ilya-bobyr) - Vincent Rischmann (vrischmann) - Alexander (Alovchin91) - Daniel Brotsky (brotskydotcom) - zohnannor (zohnannor) - Joshua Nelson (jyn514) - Prikshit Gautam (gautamprikshit1) - Dylan Thacker-Smith (dylanahsmith) - Jan David (jdno) - Aurora (lilith13666) - Pietro Albini (pietroalbini) - Renovate Bot (renovate-bot) ## [1.25.2] - 2023-02-01 This version of Rustup changes the signature verification code to continue accepting Rust's release signature key, which previously caused warnings due to a time-based check. Note that signature verification in Rustup is still an experimental feature, and there is intentionally no way to enforce signature verification due to the feature being incomplete. Thanks go to: - Pietro Albini - Daniel Silverstone [1.25.2]: https://github.com/rust-lang/rustup/release/tag/1.25.2 ## [1.25.1] - 2022-07-12 This version of Rustup reverts a single PR from 1.25.1 and tidies a couple of internal bits of code. In brief, it turns out that our optimisation for `RUSTC` and `RUSTDOC` cause problems with some tooling which runs under one `cargo` invocation, but expects to invoke either `cargo` or `rustc` without resetting the environment completely. As such, some particularly confusing error messages ensued, and we decided to revert this one optimisation while we wait to correct things in a future release. Thanks go to: - Joshua Nelson - Manish Goregaokar - Robert Collins [1.25.1]: https://github.com/rust-lang/rustup/releases/tag/1.25.1 ## [1.25.0] - 2022-07-11 This version of Rustup involves a significant number of internal cleanups, both in terms of the Rustup code and its documentation. In addition to a lot of work on the codebase itself, due to the length of time since the last release this one has a record number of contributors and we thank you all for your efforts and time. Rather than list every single merged PR since the last release, we have pulled out a number of highlights to include in this changelog entry. For everything else, please review the repository. ### Added - Added `rust-gdbgui` to the proxy list [pr#2811] - Support `rustup default none` as a way to unset the default toolchain [pr#2831] - Build Rustup for Windows arm64 [pr#2835] - Support Illumos/OpenIndiana platform check on website [pr#2839] - Add info message if self-update is disabled during update [pr#2845] - Added `RUSTC` and `RUSTDOC` environment variables for proxied child processes [pr#2958] - Added offer to auto-install VS 2022 [pr#2954] - Added `--verbose` mode for `rustup show` [pr#2992] - Added support for `--force-non-host` to more subcommands [pr#2968] ### Changed - Updated the `opener` crate used for `rustup-doc` [pr#2792] - Changed the recursion limit for tool/proxy invocation to 20 [pr#2812] - Updated to newer `effective-limits` crate to reduce "sysinfo not supported" errors [pr#2817] - Handle `-y` more robustly in `rustup-init.sh` [pr#2815] - Fix infinite recursion in bash completion when rustc not on PATH [pr#2833] - Update macOS aarch64 CI to newer xcode [pr#2877] - Update website to load TTF fonts more effectively [pr#2862] - Retry curl invocations in `rustup-init.sh` [pr#2869] - Better handle busybox's wget in `rustup-init.sh` [pr#2885] - Improve target matching to reduce spurious deprecation warnings [pr#2854] - Parse channel manifest only once to improve performance [pr#2898] - Remove trailing slashes from toolchain names [pr#2897] - Migrate OpenPGP support to Sequoia PGP [pr#2847] - We now send a user agent on http requests to improve compatibility with proxies [pr#2953] - We won't prepend `${CARGO_HOME}/bin` to `PATH` unless it's missing [pr#2978] Thanks go to: - 二手掉包工程师 (hi-rustin) - Brian Bowman (Seeker14491) - Jon Gjengset (jonho) - pierwill - Daniel Silverstone (kinnison) - Robert Collins (rbtcollins) - Alan Somers (asomers) - Brennan Vincent (umanwizard) - Joshua Nelson (jyn514) - Eric Huss (ehuss) - Will Bush (willbush) - Thad Guidry (thadguidry) - Alexander Lovchin (alovchin91) - zoodirector - Takayuki Nakata (giraffate) - Yusuke Abe (chansuke) - Wyatt Carss (wcarss) - Sondre Aasemoen (sondr3) - facklambda - Chad Dougherty (crd477) - Noritada Kobayashi (noritada) - Milan (mdaverde) - Pat Sier (pjsier) - Matt Keeter (mkeeter) - Alex Macleod (alexendoo) - Sathwik Matsa (sathwikmatsa) - Kushal Das (kushaldas) - Justus Winter (teythoon) - k900 - Nicolas Ambram (nico-abram) - Connor Slade (basicprogrammer10) - Yerkebulan Tulibergenov (yerke) - Caleb Cartwright (calebcartwright) - Matthias Beyer (matthiasbeyer) - spacemaniac - Alex Touchet (atouchet) - Guillaume Gomez (guillaumegomez) - Chris Denton (chrisdenton) - Thomas Orozco (krallin) - cui fliter (cuishuang) - Martin Nordholts (enselic) - Emil Gardström (emilgardis) - Arlo Siemsen (arlosi) [1.25.0]: https://github.com/rust-lang/rustup/releases/tag/1.25.0 [pr#2968]: https://github.com/rust-lang/rustup/pull/2968 [pr#2992]: https://github.com/rust-lang/rustup/pull/2992 [pr#2978]: https://github.com/rust-lang/rustup/pull/2978 [pr#2954]: https://github.com/rust-lang/rustup/pull/2954 [pr#2958]: https://github.com/rust-lang/rustup/pull/2958 [pr#2953]: https://github.com/rust-lang/rustup/pull/2953 [pr#2847]: https://github.com/rust-lang/rustup/pull/2847 [pr#2845]: https://github.com/rust-lang/rustup/pull/2845 [pr#2897]: https://github.com/rust-lang/rustup/pull/2897 [pr#2898]: https://github.com/rust-lang/rustup/pull/2898 [pr#2854]: https://github.com/rust-lang/rustup/pull/2854 [pr#2839]: https://github.com/rust-lang/rustup/pull/2839 [pr#2885]: https://github.com/rust-lang/rustup/pull/2885 [pr#2869]: https://github.com/rust-lang/rustup/pull/2869 [pr#2862]: https://github.com/rust-lang/rustup/pull/2862 [pr#2877]: https://github.com/rust-lang/rustup/pull/2877 [pr#2835]: https://github.com/rust-lang/rustup/pull/2835 [pr#2831]: https://github.com/rust-lang/rustup/pull/2831 [pr#2811]: https://github.com/rust-lang/rustup/pull/2811 [pr#2833]: https://github.com/rust-lang/rustup/pull/2833 [pr#2815]: https://github.com/rust-lang/rustup/pull/2815 [pr#2817]: https://github.com/rust-lang/rustup/pull/2817 [pr#2812]: https://github.com/rust-lang/rustup/pull/2812 [pr#2792]: https://github.com/rust-lang/rustup/pull/2792 ## [1.24.3] - 2021-05-31 This patch release focuses around resolving some regressions in behaviour in the 1.24.x series. One problem, related to accounting for the release of data blocks in the unpack slab allocator, fixed in [pr#2779], would manifest in the installer [hanging during installation][issue#2774]. A second, fixed in [pr#2781], manifested in very early Rust versions (1.0 through 1.7) [repeatedly having their checksums fetched][issue#2777] despite already being installed. Finally the heuristic which started warning that toolchains being installed may not work on the given host was improved in [pr#2782] to reduce false-positive rate and reduce worry among Windows users in particular. ### Added - Added the ability to configure the auto-self-update functionality. This will be of most use when people are testing unreleased versions of Rustup and wish to ensure they don't accidentally lose the test version, without having to remember to run with `--no-self-update` all the time. [pr#2763] ### Changed - We no longer delete the top level of `$RUSTUP_HOME/tmp` and `$RUSTUP_HOME/download` meaning that if you have these set up as symlinks to another place, or bind mounts, etc. things should work. [pr#2433] - We more gracefully handle outlier situations with unpack-RAM, panicing less often, clamping settings into viable ranges and warning instead. [pr#2780] Thanks go to: - Ian Jackson - Alexander (asv7c2) - pierwill - 二手掉包工程师 (hi-rustin) - Robert Collins - Daniel Silverstone [1.24.3]: https://github.com/rust-lang/rustup/releases/tag/1.24.3 [issue#2777]: https://github.com/rust-lang/rustup/issues/2777 [issue#2774]: https://github.com/rust-lang/rustup/issues/2774 [pr#2782]: https://github.com/rust-lang/rustup/pull/2782 [pr#2780]: https://github.com/rust-lang/rustup/pull/2780 [pr#2781]: https://github.com/rust-lang/rustup/pull/2781 [pr#2779]: https://github.com/rust-lang/rustup/pull/2779 [pr#2763]: https://github.com/rust-lang/rustup/pull/2763 [pr#2433]: https://github.com/rust-lang/rustup/pull/2433 ## [1.24.2] - 2021-05-05 This patch release primarily exists to work around a [problem discovered][issue#2748] on some Windows (and potentially other) systems where a combination of factors, including suspected allocator behaviour, led to Rustup failing to install certain toolchains. The symptom users observed was a failure to allocate 1677732 bytes: a chunk used for unpacking very large files. We hope this is fixed in a combination of [pr#2750][] and [pr#2756][]. In addition to that, we also: ### Added - SHA256 links on the download page so that you can verify your downloads if you want to be certain. [pr#2719][] - Added `--verbose` to `rustup show active-toolchain` to also display the version of the compiler for the toolchain. [pr#2710] - We now support `1.x` installation channel names for versions 1.0 through 1.8 by hardcoding `1.x.0` since they lack patch releases. [pr#2758][] ### Changed - Amended the behaviour of the 'missing components' code so that if the problem exists when _installing_ a toolchain (rather than updating it) the message is different and leads you to other remediations. [pr#2709][] - Amended the error message for a missing component so that when you're using a nightly toolchain and `rust-std` is missing for a given target, we lead you to `cargo build -Z build-std` as a remediation. [pr#2732][] - Improved the documentation around `settings.toml` locations. [pr#2698][] - Internal improvements around retrying removal of files. [pr#2752][] Thanks go to: - 二手掉包工程师 (hi-rustin) - Robert Collins - Daniel Silverstone - Joshua Nelson - João Marcos Bezerra - Carol (Nichols || Goulding) - Josh Rotenberg - Martijn Gribnau - pierwill [issue#2748]: https://github.com/rust-lang/rustup/issues/2748 [pr#2753]: https://github.com/rust-lang/rustup/pull/2753 [pr#2756]: https://github.com/rust-lang/rustup/pull/2756 [pr#2752]: https://github.com/rust-lang/rustup/pull/2752 [pr#2758]: https://github.com/rust-lang/rustup/pull/2758 [pr#2698]: https://github.com/rust-lang/rustup/pull/2698 [pr#2750]: https://github.com/rust-lang/rustup/pull/2750 [pr#2732]: https://github.com/rust-lang/rustup/pull/2732 [pr#2710]: https://github.com/rust-lang/rustup/pull/2710 [pr#2709]: https://github.com/rust-lang/rustup/pull/2709 [pr#2719]: https://github.com/rust-lang/rustup/pull/2719 [1.24.2]: https://github.com/rust-lang/rustup/releases/tag/1.24.2 ## [1.24.1] - 2021-04-27 This bugfix release [corrects an oversight][pr#2738] in the code we introduced to check for unknown proxy names. The original change accidentally omitted the `rustfmt` and `cargo-fmt` proxies due to a quirk of the fact those proxies were not originally part of a Rust component. We're sorry for pain this may have caused. [pr#2738]: https://github.com/rust-lang/rustup/pull/2738 [1.24.1]: https://github.com/rust-lang/rustup/releases/tag/1.24.1 ## [1.24.0] - 2021-04-27 This release is mostly a bugfix and quality of life improvement release. However the headlines for this release are: 1. Support of `rust-toolchain.toml` as a filename for specifying toolchains. 2. Streaming support for large files to better enable Rust on lower memory platforms such as some Raspberry Pi systems. When we introduced TOML support to `rust-toolchain` we expected to see some uptake but we saw a lot more than we had expected. Since Cargo is migrating to explicit `.toml` extensions on things like `.cargo/config.toml` it was considered sensible to also do this for `rust-toolchain` - at least the `toml` variant thereof. This release of `rustup` has seen a significant number of new contributors to the project, and we hope to see many of you again in the future. ### Added - Optional use of RUSTLS as TLS backend for Reqwest [pr#2517][] - We now support some corner cases in tarballs to permit unpacking early Rust versions [pr#2502][] - When running `rustup check` we now report possible `rustup` upgrades too. [pr#2615][] - We detect and warn if you try and install on an `x32` system since for now Rust isn't hostable on that. [pr#2622][] - We do, however, support `gnux32` as an environment label ready for future support [pr#2631][] - We now support managing `PATH`s on Windows which contain non-unicode values. [pr#2649][] - You can now name the TOML variant of `rust-toolchain` as `rust-toolchain.toml` [pr#2653][] - We prompt harder when checking for the MSVC tooling on Windows now. [pr#2529][] - _Experimental_ support for `zstd` compressed tarballs in channels. NOTE, this does not mean channels will magically gain `zstd` compressed component files any time soon. [pr#2676][] - Register `rustup` with the Windows installed programs list when installing. This is another experiment into whether this is useful for Windows users. [pr#2670][] - Added the ability to specify a `path` rather than a toolchain channel in the `rust-toolchain.toml` file. [pr#2678][] ### Changed - `rustup-init` now detects tls1.2 for cURL 7.73+ [pr#2604][] - Installation now indicates the defaults on all questions [pr#2605][] - We now support the Big Sur major OS version [pr#2607][] - You can now specify `profile` in `rust-toolchain`'s TOML form [pr#2586][] - We now use `.` instead of `source` to better support non-bash POSIX shells [pr#2616][] - We fixed a nasty corner case on wildcarded component installation/recognition [pr#2602][] - Our website now has a favicon [pr#2419][] - We no longer rely on a broken `mktemp` invocation, this should make `rustup-init.sh` more compatible [pr#2650][] - We now do a better job of reporting non-installable toolchains [pr#2562][] - We cope better when modifying RC files which lack a trailing newline [pr#2667][] - We are edging closer to requiring a specific force argument to install a toolchain whose host doesn't match the running system. This may break your CI in future so you should check carefully. The main use-case for this capability is the `rust-embedded/cross` project which we are working with to ensure this doesn't cause problems in the future. [pr#2672][] - Support streaming large files during unpack phase. [pr#2707][] - We report when you call `rustup` with an unsupported `arg0` -- for example if you make a symlink or hard link to the binary with a name other than one of the proxies. [pr#2716][] We also cleaned up a number of error message cases, including some on invalid toolchain name [pr#2613][], a better message when no toolchain is installed [pr#2657][], and some on component unavailability [pr#2619][]. ### Documented - Added notes about Powershell to proxies documentation [pr#2592][] - Various updates to the `rustup` manual build process including [pr#2628][] - Small fixes on how to build `rustup` documentation [pr#2641][] - We clarified the message around restarting the shell when installing [pr#2684][] Thanks go to: - SHA Miao - est31 - Andrew Norton - Gareth Hubball - 二手掉包工程师 (hi-rustin) - Tudor Brindus - Eduard Miller - Daniel Alley - наб (nabijaczleweli) - Eric Huss - chansuke - skim (sl4m) - Joshua Nelson - kellda - Alex Chan - Philipp Oppermann - Michael Cooper - Aloïs Micard - Gurkenglas - Vasili (3point2) - Jakub Stasiak - Robert Collins - Jubilee (workingjubilee) - Avery Harnish [pr#2716]: https://github.com/rust-lang/rustup/pull/2716 [pr#2718]: https://github.com/rust-lang/rustup/pull/2718 [pr#2707]: https://github.com/rust-lang/rustup/pull/2707 [pr#2678]: https://github.com/rust-lang/rustup/pull/2678 [pr#2695]: https://github.com/rust-lang/rustup/pull/2695 [pr#2670]: https://github.com/rust-lang/rustup/pull/2670 [pr#2684]: https://github.com/rust-lang/rustup/pull/2684 [pr#2676]: https://github.com/rust-lang/rustup/pull/2676 [pr#2529]: https://github.com/rust-lang/rustup/pull/2529 [pr#2667]: https://github.com/rust-lang/rustup/pull/2667 [pr#2653]: https://github.com/rust-lang/rustup/pull/2653 [pr#2657]: https://github.com/rust-lang/rustup/pull/2657 [pr#2562]: https://github.com/rust-lang/rustup/pull/2562 [pr#2649]: https://github.com/rust-lang/rustup/pull/2649 [pr#2650]: https://github.com/rust-lang/rustup/pull/2650 [pr#2641]: https://github.com/rust-lang/rustup/pull/2641 [pr#2419]: https://github.com/rust-lang/rustup/pull/2419 [pr#2631]: https://github.com/rust-lang/rustup/pull/2631 [pr#2628]: https://github.com/rust-lang/rustup/pull/2628 [pr#2622]: https://github.com/rust-lang/rustup/pull/2622 [pr#2619]: https://github.com/rust-lang/rustup/pull/2619 [pr#2615]: https://github.com/rust-lang/rustup/pull/2615 [pr#2602]: https://github.com/rust-lang/rustup/pull/2602 [pr#2502]: https://github.com/rust-lang/rustup/pull/2502 [pr#2616]: https://github.com/rust-lang/rustup/pull/2616 [pr#2613]: https://github.com/rust-lang/rustup/pull/2613 [pr#2586]: https://github.com/rust-lang/rustup/pull/2586 [pr#2607]: https://github.com/rust-lang/rustup/pull/2607 [pr#2605]: https://github.com/rust-lang/rustup/pull/2605 [pr#2604]: https://github.com/rust-lang/rustup/pull/2604 [pr#2517]: https://github.com/rust-lang/rustup/pull/2517 [pr#2592]: https://github.com/rust-lang/rustup/pull/2592 [1.24.0]: https://github.com/rust-lang/rustup/releases/tag/1.24.0 ## [1.23.1] - 2020-12-01 This point release is mostly to correct a problem where if you installed `rustup` with `--no-modify-path` then the `.cargo/env` file would not be created in some cases. In addition, we have rebuilt the macos binaries to correct an oversight which caused older Macs to be unable to run the new version. If you encountered a problem with `liblzma` on mac os 10.13 then this version should solve that for you. Finally, the illumos binary is now part of the release properly. Thanks go to: - Élie Roudninski - Jeroen Ooms - Jake Goulding - Joshua M. Clulow - Neil Mitchell - Richard Gomes [1.23.1]: https://github.com/rust-lang/rustup/releases/tag/1.23.1 ## [1.23.0] - 2020-11-27 The main points for this release are that `rustup` now supports a number of new host platforms, most importantly of which is `aarch64-apple-darwin` for the new Apple M1 based devices, and that we support a new structured format for the `rust-toolchain` file. You can find more information [in the new book format documentation][toolchain-file]. [toolchain-file]: https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file It is now also possible to install a particular release of the compiler as a two-part version number. If you do this, then the release channel will only update if there is a patch release of the compiler. For example, if you ran `rustup toolchain install 1.48` at the time of this release of `rustup` you would end up with a toolchain called `1.48` which contained `1.48.0`. If subsequently `1.48.1` were released, a `rustup update` would update your `1.48` from `1.48.0` to `1.48.1`. As always, there were more changes than described below, thanks to everyone who contributed to this release. Highlights for this release are detailed below, but you can always see the full list of changes via the Git repository. ### Added - Our documentation is now in "book" form. [pr#2448] - When you retrieve `rustup`'s version, you'll also be told the version of the compiler for your default toolchain, to disambiguate things a little. [pr#2465] - Support added for `aarch64-unknown-linux-musl` [pr#2493] - Support added for `aarch64-apple-darwin` [pr#2521] - Support added for `x86_64-unknown-illumos` [pr#2432] - You can now override the system-wide settings fallback path [pr#2545] - Support for `major.minor` channels [pr#2551] ### Changed - Significant updates to our handling of `PATH` updating on installation was made. Nominally this ought to have little external change visibility but it may make it more robust for some people. [pr#2387] - New support for toml-based `rust-toolchain` file format. This will be expanded upon going into the future to add new functionality, but for now the basics are in place, permitting you to select a channel, targets, and components which may be needed to build your applications. [pr#2438] - We now fall back to copying files when rename-in-place causes problems. This may improve matters in dockerised environments where `rustup` is preinstalled with a toolchain already. [pr#2410] - We do a better job of exiting gracefully in a number of circumstances. [pr#2427] - The `reqwest` backend (the default download backend) now supports socks5 proxies. [pr#2466] - If you use a proxy for a component which is not part of a custom toolchain you are using then we emit a message about trying to build that component. [pr#2487] - If you try and unpack super-large components which would previously be gracefully rejected, instead we _try_ and if we succeed then you get to have the component unpacked. Unfortunately this means if we fail you could end up with a broken toolchain install. [pr#2490] - We will recommend ways to recover if you can't update your toolchain due to components or targets going missing. [pr#2384] - If you choose to install a toolchain which is for a different target than you are running on, we will warn you and direct you toward `rustup target install` in case that's what you meant. [pr#2534] ### Thanks - Aaron Loucks - Aleksey Kladov - Aurelia Dolo - Camelid - Chansuke - Carol (Nichols || Goulding) - Daniel Silverstone - Dany Marcoux - Eduard Miller - Eduardo Broto - Eric Huss - Francesco Zardi - FR Bimo - Ivan Nejgebauer - Ivan Tham - Jake Goulding - Jens Reidel - Joshua M. Clulow - Joshua Nelson - Jubilee Young - Leigh McCulloch - Lzu Tao - Matthias Krüger - Matt Kraai - Matt McKay - Nick Ashley - Pascal Hertleif - Paul Lange - Pietro Albini - Robert Collins - Stephen Muss - Tom Eccles [pr#2534]: https://github.com/rust-lang/rustup/pull/2534 [pr#2384]: https://github.com/rust-lang/rustup/pull/2384 [pr#2545]: https://github.com/rust-lang/rustup/pull/2545 [pr#2432]: https://github.com/rust-lang/rustup/pull/2432 [pr#2521]: https://github.com/rust-lang/rustup/pull/2521 [pr#2493]: https://github.com/rust-lang/rustup/pull/2493 [pr#2499]: https://github.com/rust-lang/rustup/pull/2499 [pr#2487]: https://github.com/rust-lang/rustup/pull/2487 [pr#2466]: https://github.com/rust-lang/rustup/pull/2466 [pr#2465]: https://github.com/rust-lang/rustup/pull/2465 [pr#2448]: https://github.com/rust-lang/rustup/pull/2448 [pr#2427]: https://github.com/rust-lang/rustup/pull/2427 [pr#2410]: https://github.com/rust-lang/rustup/pull/2410 [pr#2438]: https://github.com/rust-lang/rustup/pull/2438 [pr#2387]: https://github.com/rust-lang/rustup/pull/2387 [pr#2551]: https://github.com/rust-lang/rustup/pull/2551 [1.23.0]: https://github.com/rust-lang/rustup/releases/tag/1.23.0 ## [1.22.1] - 2020-07-08 A regression in proxied behaviour slipped in due to a non-compatible change in `url` slipping in in 2.1 which [caused a misbehaviour][env_proxy#8] in `env_proxy`. which was [fixed][env_proxy@5591cc7] but not released to crates.io until after 1.22.0 was built. Fortunately, _inejge_ noticed and provided a fix for us by publishing a new `env_proxy` and providing us with [this fix][pr#2399]. We apologise for any inconvenience this caused. ### Changed - Update to `env_proxy` 0.4.1 - [#2399][pr#2399] - Fixed website copy button and copy space overflow - [#2398][pr#2398] ### Thanks - Ivan Nejgebauer - Ben Chen [env_proxy#8]: https://github.com/inejge/env_proxy/issues/8 [env_proxy@5591cc7]: https://github.com/inejge/env_proxy/commit/5591cc7 [pr#2399]: https://github.com/rust-lang/rustup/pull/2399 [pr#2398]: https://github.com/rust-lang/rustup/pull/2398 [1.22.1]: https://github.com/rust-lang/rustup/releases/tag/1.22.1 ## [1.22.0] - 2020-06-30 Alongside a significant amount of internal refactoring and code updates, the highlights of this release include: - We have switched to Github Actions to make our CI and release process more consistent. - We've invested time in the flow when you reinstall `rustup` atop an existing installation. - We've doubled down on discouraging the use of the internal-development-focussed `complete` profile. Please use `default` or `minimal` unless you're trying to test/develop the Rust tooling itself. - We've made a number of subtle quality-of-life improvements around the CLI. - Added a (provisionally unofficial) snap of `rustup` - We've worked hard to improve a lot of the messages (error and informational) in the tool. - We've increased internal timeouts and retries in an attempt to improve the situation for McAfee users. - While it's not a change, we've documented that `rust-toolchain` **must** be UTF8 encoded. While the changes spanned around 90 individual pull requests, here are the main changes and additions… ### Changed - Fixed various links to our repo and to the forge - [#2173][pr#2173] - Improved OS detection (particularly darwin) in `rustup-init.sh` - [#2042][pr#2042] - Fixed bug where i686 installer on x86_64 windows would intend to install 64-bit but would actually install 32-bit toolchains by default. - [#2186][pr#2186] - Increased width of copy box on rustup website - [#2208][pr#2208] - When updating a toolchain, indicate the version you updated _from_ as well. - [#2152][pr#2152] - When installing atop an existing `rustup` installation, we will now update the installed default toolchain, particularly we'll also try and install any additional targets or components specified - [#2201][pr#2201] and [#2339][pr#2339] - Fixed issue where `rustup doc` wouldn't work with custom toolchains - [#2235][pr#2235] - In low-memory situations, attempt to unpack more conservatively - [#2236][pr#2236] - Improved consistency in where `rustup` will auto-install a toolchain on use. - [#2252][pr#2252] - Try to force strong cipher suites in `rustup-init.sh` - [#2287][pr#2287] - When skipping a `nightly` indicate **all** the missing components - [#2316][pr#2316] - Increase timeout for rename retries - [#2348][pr#2348] - Increased 'sanity limit' to account for MIPS binary size increases - [#2363][pr#2363] - Fallback to non-threaded installation pathway on 1-CPU systems to improve chance that installation will succeed on Raspberry Pi - [#2372][pr#2372] ### Added - It is now possible to install `rustup` even when there's an existing `rustup.sh` installation, and we can install alongside `rustc` or `cargo` without necessarily forcing via `-y` by means of the `RUSTUP_INIT_SKIP_EXISTENCE_CHECKS` environment variable. - [#2214][pr#2214] - Added the concept of a _fallback_ settings file which will allow snaps, distro packages, etc. to provide a default toolchain for users who have not passed through the `rustup-init` managed one-time question set. - [#2244][pr#2244] - You can now specify multiple components in a single argument in the form `--component rls,rust-analysis,rust-src` when installing toolchains - [#2239][pr#2239] - It is now possible to `snap install --classic rustup` in theory (channel details may take some time to settle) - [#1898][pr#1898] - Added indication of why overrides are happening when running `rustup show` - [#2312][pr#2312] - Added `riscv64gc-unknown-linux-gnu` support (note: There is still work to be done on the compiler etc before this will necessarily work) - [#2313][pr#2313] ### Thanks - Alejandro Martinez Ruiz - Alexander D'hoore - Ben Chen - Chris Denton - Daniel Silverstone - Evan Weiler - Guillaume Gomez - Harry Sarson - Jacob Lifshay - James Yang - Joel Parker Henderson - John Titor - Jonas Platte - Josh Stone - Jubilee - Kellda - LeSeulArtichaut - Linus Färnstrand - LitoMore - LIU An (劉安) - Luciano Bestia - Lzu Tao - Manish Goregaokar - Mingye Wang - Montgomery Edwards - Per Lundberg - Pietro Albini - Robert Collins - Rudolf B. - Solomon Ucko - Stein Somers - Tetsuharu Ohzeki - Tom Eccles - Trevor Arjeski - Tshepang Lekhonkhobe [pr#2173]: https://github.com/rust-lang/rustup/pull/2173 [pr#2042]: https://github.com/rust-lang/rustup/pull/2042 [pr#2186]: https://github.com/rust-lang/rustup/pull/2186 [pr#2208]: https://github.com/rust-lang/rustup/pull/2208 [pr#2152]: https://github.com/rust-lang/rustup/pull/2152 [pr#2201]: https://github.com/rust-lang/rustup/pull/2201 [pr#2235]: https://github.com/rust-lang/rustup/pull/2235 [pr#2214]: https://github.com/rust-lang/rustup/pull/2214 [pr#2236]: https://github.com/rust-lang/rustup/pull/2236 [pr#2252]: https://github.com/rust-lang/rustup/pull/2252 [pr#2244]: https://github.com/rust-lang/rustup/pull/2244 [pr#2239]: https://github.com/rust-lang/rustup/pull/2239 [pr#2287]: https://github.com/rust-lang/rustup/pull/2287 [pr#2316]: https://github.com/rust-lang/rustup/pull/2316 [pr#1898]: https://github.com/rust-lang/rustup/pull/1898 [pr#2348]: https://github.com/rust-lang/rustup/pull/2348 [pr#2312]: https://github.com/rust-lang/rustup/pull/2312 [pr#2313]: https://github.com/rust-lang/rustup/pull/2313 [pr#2363]: https://github.com/rust-lang/rustup/pull/2363 [pr#2372]: https://github.com/rust-lang/rustup/pull/2372 [pr#2339]: https://github.com/rust-lang/rustup/pull/2339 [1.22.0]: https://github.com/rust-lang/rustup/releases/tag/1.22.0 ## [1.21.1] - 2019-12-19 A panic occurred if a `rustup update` was run with nothing to update and the download directory was missing. This was harmless but could have confused some automation jobs. [1.21.1]: https://github.com/rust-lang/rustup/releases/tag/1.21.1 ## [1.21.0] - 2019-12-19 In release 1.20.x profiles could incorrectly ascribe host-independent components to the host architecture, resulting in surprising behaviour with `rust-src`. We have [corrected this][pr#2087] and [added mitigations][pr#2115] which should mean that as of this release, such incorrect ascriptions are supported and also automatically corrected on toolchain update. Due to the large number of confusions around the `complete` profile, we have [introduced a warning][pr#2138] if you use it. It's really only meant for developers _of_ Rust, or those exploring particular issues in `nightly`. There are also a large number of other changes, the highlights of which are below. Thanks to everyone who helped work on this release. Even if your changes are not listed below, they are still greatly appreciated. ### Changed - [Download directory is cleaned up after successful full update.][pr#2046] - [Bad `.partial` downloads will be cleaned up for you][pr#1889] - [Force installation of toolchain if install is automatic][pr#2074] - [Switch to darker colours to improve terminal readability][pr#2083] - [Attempt to be less surprising wrt. default-host during installation][pr#2086] - [`rustup toolchain list --verbose` now correctly shows the paths][pr#2084] - [Fallback environment for non-cargo toolchains updated to match `rustc`][pr#2108] - [Made human-readable units slightly more comprehensible][pr#2043] - [Improved detection of armhf userland on aarch64 kernels][pr#2133] - [Improved error message when rustc is detected on installation][pr#2155] ### Added - [Added `--profile` support to `rustup toolchain install`][pr#2075] - [Added `+toolchain` support to `rustup` itself to match proxy functionality][pr#2031] - [Added ability to `rustup component add component-architecture`][pr#2088] - [Added clear report when `rustup doc` is run without `rust-docs` available][pr#2116] - [Added `keyword:`, `primitive:`, and `macro:` prefix support to `rustup doc FOO`][pr#2119] - [Added retry logic so that `rustup` will try and repeat interrupted downloads][pr#2121] - [Added `--allow-downgrade` support to `rustup toolchain install`][pr#2126] - [Added display of previous version when upgrading channels][pr#2143] - [Added support for local non-channel toolchains in rust-toolchain file][pr#2141] ### Thanks - Roman Frołow - Jean Simard - Lzu Tao - Benjamin Chen - Daniel Silverstone - Jon Hoo - Carlo Abelli - Filip Demski - Chris Tomlinson - Kane Green - Ralf Jung - Yves Dorfsman - Rudolf B - Pietro Albini - Takayuki Nakata - Justus K - Gilbert Röhrbein - Friedel Ziegelmayer - Robbie Clarken - Tetsuharu OHZEKI [pr#1889]: https://github.com/rust-lang/rustup/pull/1889 [pr#2031]: https://github.com/rust-lang/rustup/pull/2031 [pr#2043]: https://github.com/rust-lang/rustup/pull/2043 [pr#2046]: https://github.com/rust-lang/rustup/pull/2046 [pr#2074]: https://github.com/rust-lang/rustup/pull/2074 [pr#2075]: https://github.com/rust-lang/rustup/pull/2075 [pr#2083]: https://github.com/rust-lang/rustup/pull/2083 [pr#2084]: https://github.com/rust-lang/rustup/pull/2084 [pr#2086]: https://github.com/rust-lang/rustup/pull/2086 [pr#2087]: https://github.com/rust-lang/rustup/pull/2087 [pr#2108]: https://github.com/rust-lang/rustup/pull/2108 [pr#2088]: https://github.com/rust-lang/rustup/pull/2088 [pr#2115]: https://github.com/rust-lang/rustup/pull/2115 [pr#2116]: https://github.com/rust-lang/rustup/pull/2116 [pr#2119]: https://github.com/rust-lang/rustup/pull/2119 [pr#2121]: https://github.com/rust-lang/rustup/pull/2121 [pr#2126]: https://github.com/rust-lang/rustup/pull/2126 [pr#2133]: https://github.com/rust-lang/rustup/pull/2133 [pr#2138]: https://github.com/rust-lang/rustup/pull/2138 [pr#2141]: https://github.com/rust-lang/rustup/pull/2141 [pr#2143]: https://github.com/rust-lang/rustup/pull/2143 [pr#2155]: https://github.com/rust-lang/rustup/pull/2155 [1.21.0]: https://github.com/rust-lang/rustup/releases/tag/1.21.0 ## [1.20.2] - 2019-10-16 One final tweak was needed to the force-installation of toolchains because otherwise components would be marked as installed when they were not. Our apologies to anyone adversely affected by the 1.20.0/1 releases. [1.20.2]: https://github.com/rust-lang/rustup/releases/tag/1.20.2 ## [1.20.1] - 2019-10-16 This release was made to solve two problems spotted in `1.20.0` - Force installation of toolchain during `rustup-init` to improve handling on non-tier-one platforms - Assume the `default` profile if a profile is missing from configuration which will solve a problem where distro-provided `rustup` binaries did not upgrade the configuration properly [1.20.1]: https://github.com/rust-lang/rustup/releases/tag/1.20.1 ## [1.20.0] - 2019-10-15 ### Changed - [Toolchain listing now supports a verbose mode][pr#1988] - [Improve zsh completions for cargo][pr#1995] - [Updates/Installations of nightly now backtrack][pr#2002] - [Improve handling of Ctrl+C on Windows][pr#2014] - [`rustup which` now supports `--toolchain`][pr#2030] ### Added - [Added installation profiles][pr#1673] - [Added `rustup check`][pr#1980] - [Support for `--quiet` in most places][pr#1945] - [Support for adding components and targets during toolchain install][pr#2026] ### Thanks - Nick Cameron - Andy McCaffrey - Pietro Albini - Benjamin Chen - Artem Borisovskiy - Jon Gjengset - Lzu Tao - Daniel Silverstone - PicoJr - Mitchell Hynes - Matt Kantor [pr#1673]: https://github.com/rust-lang/rustup/pull/1673 [pr#1980]: https://github.com/rust-lang/rustup/pull/1980 [pr#1988]: https://github.com/rust-lang/rustup/pull/1988 [pr#1995]: https://github.com/rust-lang/rustup/pull/1995 [pr#2002]: https://github.com/rust-lang/rustup/pull/2002 [pr#2014]: https://github.com/rust-lang/rustup/pull/2014 [pr#1945]: https://github.com/rust-lang/rustup/pull/1945 [pr#2026]: https://github.com/rust-lang/rustup/pull/2026 [pr#2030]: https://github.com/rust-lang/rustup/pull/2030 [1.20.0]: https://github.com/rust-lang/rustup/releases/tag/1.20.0 ## [1.19.0] - 2019-09-09 ### Changed - [Fix race condition with some virus scanners][pr#1873] - [UI improvements for race condition fix][pr#1885] - [Improve home mismatch explanation][pr#1895] - [Enable fully threaded IO for installs][pr#1876] - [Improve look of rustup homepage][pr#1901] - [Improve messaging if shell profile cannot be updated][pr#1925] - [Improve messaging around directory names during install][pr#1914] - [Disregard unavailable targets][pr#1931] - [No longer provide non-panic backtraces by default][pr#1961] ### Added - [Add support for `rustup target add all`][pr#1868] - [Add `rustup show home`][pr#1933] - [Add NetBSD target to CI][pr#1978] - [Add x86_64 musl to CI][pr#1882] ### Thanks - Lzu Tao - Gonzalo Brito Gadeschi - Paul Oppenheimer - Robert Collins - KennyTM - Daniel Silverstone - Nicholas Parker - Caleb Cartwright - Josh Holland - Charlie Saunders - Wesley Van Melle - Jason Cooke - CrLF0710 - Brian Anderson - Bryan Dady - Fisher Darling - Bjorn3 - Iku Iwasa [pr#1873]: https://github.com/rust-lang/rustup/pull/1873 [pr#1885]: https://github.com/rust-lang/rustup/pull/1885 [pr#1882]: https://github.com/rust-lang/rustup/pull/1882 [pr#1895]: https://github.com/rust-lang/rustup/pull/1895 [pr#1876]: https://github.com/rust-lang/rustup/pull/1876 [pr#1901]: https://github.com/rust-lang/rustup/pull/1901 [pr#1925]: https://github.com/rust-lang/rustup/pull/1925 [pr#1914]: https://github.com/rust-lang/rustup/pull/1914 [pr#1931]: https://github.com/rust-lang/rustup/pull/1931 [pr#1961]: https://github.com/rust-lang/rustup/pull/1961 [pr#1868]: https://github.com/rust-lang/rustup/pull/1868 [pr#1933]: https://github.com/rust-lang/rustup/pull/1933 [pr#1978]: https://github.com/rust-lang/rustup/pull/1978 [1.19.0]: https://github.com/rust-lang/rustup/releases/tag/1.19.0 ## [1.18.3] - 2019-05-22 ### Changed - [Improve performance by only opening terminfo once][pr#1820] - [Use same webpage opening logic as cargo][pr#1830] - [Report download duration on completion][pr#1837] - [Reduce stat() usage in unpacking][pr#1839] - [Buffer reads from tarfile during unpacking][pr#1840] - [Buffer for hashing of dist content][pr#1845] - [Don't set mtime on unpacked toolchain files][pr#1847] - [UI consistency/improvement in download speeds][pr#1832] - [Avoid blocking on CloseHandle][pr#1850] ### Added - [Suggest possible components or targets if misspelled][pr#1824] ### Thanks - Robert Collins (who has tirelessly worked to improve the performance of Rustup, particularly on Windows) - Lucien Greathouse - Filip Demski - Peter Hrvola - Bogdan Kulbida - Srinivas Reddy Thatiparthy - Sunjay Varma - Lzu Tao (behind the scenes, lots of housekeeping and CI) [pr#1820]: https://github.com/rust-lang/rustup/pull/1820 [pr#1830]: https://github.com/rust-lang/rustup/pull/1830 [pr#1837]: https://github.com/rust-lang/rustup/pull/1837 [pr#1839]: https://github.com/rust-lang/rustup/pull/1839 [pr#1840]: https://github.com/rust-lang/rustup/pull/1840 [pr#1845]: https://github.com/rust-lang/rustup/pull/1845 [pr#1847]: https://github.com/rust-lang/rustup/pull/1847 [pr#1832]: https://github.com/rust-lang/rustup/pull/1832 [pr#1850]: https://github.com/rust-lang/rustup/pull/1850 [pr#1824]: https://github.com/rust-lang/rustup/pull/1824 [1.18.3]: https://github.com/rust-lang/rustup/releases/tag/1.18.3 ## [1.18.2] - 2019-05-02 ### Changed - [Fix local bash-completion directory path][pr#1809] - [Handle stray toolchain hashes during install][pr#1801] - [Update to env_proxy 0.3.1][pr#1819] - [Improvements to release process around Windows versions][pr#1822] ### Added - [Support listing installed targets only][pr#1808] - [Added CI of CentOS 6 support for rustup-init.sh][pr#1810] - [FAQ entry about not being able to update rustup on Windows][pr#1813] ### Thanks This release was made, in part, thanks to: - Brian Ericson - Onat Mercan - Lzu Tao - Takuto Ikuta - Jason Williams - Filip Demski - Michael Maclean - Daniel Silverstone [pr#1809]: https://github.com/rust-lang/rustup/pull/1809 [pr#1801]: https://github.com/rust-lang/rustup/pull/1801 [pr#1819]: https://github.com/rust-lang/rustup/pull/1819 [pr#1822]: https://github.com/rust-lang/rustup/pull/1822 [pr#1808]: https://github.com/rust-lang/rustup/pull/1808 [pr#1810]: https://github.com/rust-lang/rustup/pull/1810 [pr#1813]: https://github.com/rust-lang/rustup/pull/1813 [1.18.2]: https://github.com/rust-lang/rustup/releases/tag/1.18.2 ## [1.18.1] - 2019-04-25 ### Changed - [Fix panic when no default toolchain is installed][pr#1787] - [Remove repeated CLI subcommands][pr#1796] - [Detect s390x in rustup-init.sh][pr#1797] - [Fallback to less secure curl/wget invocation][pr#1803] [pr#1787]: https://github.com/rust-lang/rustup/pull/1787 [pr#1796]: https://github.com/rust-lang/rustup/pull/1796 [pr#1797]: https://github.com/rust-lang/rustup/pull/1797 [pr#1803]: https://github.com/rust-lang/rustup/pull/1803 [1.18.1]: https://github.com/rust-lang/rustup/releases/tag/1.18.1 ## [1.18.0] - 2019-04-22 ### Added - [Output shell completions for cargo by `rustup completions cargo`][pr#1646] - [Add `--embedded-book` flag to `rustup doc`][pr#1762] - [Add --path option to `rustup override set`][pr#1524] ### Changed - [`rustup default` now tells user if current directory is override][pr#1655] - [`rustup-init`: Force highest TLS version supported][pr#1716] - [Switch to git-testament rather than old `build.rs`][pr#1701] - [Less copying during dist installation][pr#1744] - [Improve error messages when missing nightly components][pr#1769] - [Improve `rustup install` error message][pr#1770] - [Update Visual C++ install instructions, to link to Visual Studio 2019][pr#1773] - [Use `DYLD_FALLBACK_LIBRARY_PATH` for `dylib_path_envvar` on macOS][pr#1752] - [Improved documentation for shell completion enabling][pr#1780] - [Added shellcheck and Travis folding][pr#1776] ### Fixed - [`rustup-init.sh`: Fix unset variable usage][pr#1683] - [Treat time in seconds as an integer for download times][pr#1699] - [Fix man proxy in FreeBSD][pr#1725] - [Fix networking failure after using socks5 proxy][pr#1746] - [Fix `rustup show` fails on terminal without color][pr#1747] - [Fix installation failed if `rustup-init` is owned by another user][pr#1767] - [Fix panics with "Broken pipe" when using in a shell pipeline][pr#1765] - [Document `--no-self-update` properly][pr#1763] - [Clear line properly in download progress][pr#1781] - [More download progress line clearing fixes][pr#1788] - [Fix a bunch of clippy warnings/errors][pr#1778] ### Removed - [Remove old `multirust` & compatibility code][pr#1715] [pr#1646]: https://github.com/rust-lang/rustup/pull/1646 [pr#1762]: https://github.com/rust-lang/rustup/pull/1762 [pr#1524]: https://github.com/rust-lang/rustup/pull/1524 [pr#1655]: https://github.com/rust-lang/rustup/pull/1655 [pr#1716]: https://github.com/rust-lang/rustup/pull/1716 [pr#1701]: https://github.com/rust-lang/rustup/pull/1701 [pr#1744]: https://github.com/rust-lang/rustup/pull/1744 [pr#1769]: https://github.com/rust-lang/rustup/pull/1769 [pr#1770]: https://github.com/rust-lang/rustup/pull/1770 [pr#1773]: https://github.com/rust-lang/rustup/pull/1773 [pr#1752]: https://github.com/rust-lang/rustup/pull/1752 [pr#1683]: https://github.com/rust-lang/rustup/pull/1683 [pr#1699]: https://github.com/rust-lang/rustup/pull/1699 [pr#1725]: https://github.com/rust-lang/rustup/pull/1725 [pr#1746]: https://github.com/rust-lang/rustup/pull/1746 [pr#1747]: https://github.com/rust-lang/rustup/pull/1747 [pr#1767]: https://github.com/rust-lang/rustup/pull/1767 [pr#1765]: https://github.com/rust-lang/rustup/pull/1765 [pr#1763]: https://github.com/rust-lang/rustup/pull/1763 [pr#1715]: https://github.com/rust-lang/rustup/pull/1715 [pr#1776]: https://github.com/rust-lang/rustup/pull/1776 [pr#1778]: https://github.com/rust-lang/rustup/pull/1778 [pr#1780]: https://github.com/rust-lang/rustup/pull/1780 [pr#1781]: https://github.com/rust-lang/rustup/pull/1781 [pr#1788]: https://github.com/rust-lang/rustup/pull/1788 [1.18.0]: https://github.com/rust-lang/rustup/releases/tag/1.18.0 ## [1.17.0] - 2019-03-05 - [Allow using inherited RUSTUP_UPDATE_ROOT variable in rustup-init.sh.][pr#1495] - [Fix `utils::copy_file` for symlink.][pr#1521] - [Improve formatting of longer download times in download tracker][pr#1547] - [Basic 2018 edition fix][pr#1583] - [Update rustup-init.sh for 32bit powerpc userland][pr#1587] - [Reformat the entire codebase using `cargo fmt`][pr#1585] - [Support to open more documents directly in `rustup doc`][pr#1597] - [Fix HumanReadable#fmt][pr#1603] - [Add more detail error messages when installing with some components has failed.][pr#1595] - [Fix a panic when a component is missing][pr#1576] - [Update to use `dirs::home_dir()`][pr#1588] - [Self update after updating a specific toolchain][pr#1605] - [Add miri to rustup][pr#1606] - [allow non-utf8 arguments to proxies][pr#1599] - [rustup-dist: Use Download notifications to track install][pr#1593] - [Deal cleanly with malformed default-host][pr#1578] - [Better error message for missing binary][pr#1619] - [Add tab completion instructions for PowerShell][pr#1623] - [Add tab completion test for PowerShell][pr#1629] - [When updating, show "removing old component" to avoid confusion][pr#1639] - [Upgrade to Rust 2018 edition idioms][pr#1643] - [Simplify host triplet passing code][pr#1645] - [Remove telemetry][pr#1642] - [Print default toolchain on `rustup default` without arguments][pr#1633] - [Bring output of `rustup show active-toolchain` and `rustup default` into line with rest of rustup][pr#1654] - [Deprecate cURL][pr#1660] - [Thread toolchain through to error message][pr#1616] - [Add Listing of Installed Components (`rustup component list --installed`)][pr#1659 ] - [Add `clippy-driver` as a proxy][pr#1679] - [Remove the `rustup-win-installer` directory][pr#1666] [pr#1495]: https://github.com/rust-lang/rustup/pull/1495 [pr#1521]: https://github.com/rust-lang/rustup/pull/1521 [pr#1547]: https://github.com/rust-lang/rustup/pull/1547 [pr#1583]: https://github.com/rust-lang/rustup/pull/1583 [pr#1587]: https://github.com/rust-lang/rustup/pull/1587 [pr#1585]: https://github.com/rust-lang/rustup/pull/1585 [pr#1597]: https://github.com/rust-lang/rustup/pull/1597 [pr#1603]: https://github.com/rust-lang/rustup/pull/1603 [pr#1595]: https://github.com/rust-lang/rustup/pull/1595 [pr#1576]: https://github.com/rust-lang/rustup/pull/1576 [pr#1588]: https://github.com/rust-lang/rustup/pull/1588 [pr#1605]: https://github.com/rust-lang/rustup/pull/1605 [pr#1606]: https://github.com/rust-lang/rustup/pull/1606 [pr#1599]: https://github.com/rust-lang/rustup/pull/1599 [pr#1593]: https://github.com/rust-lang/rustup/pull/1593 [pr#1578]: https://github.com/rust-lang/rustup/pull/1578 [pr#1619]: https://github.com/rust-lang/rustup/pull/1619 [pr#1623]: https://github.com/rust-lang/rustup/pull/1623 [pr#1629]: https://github.com/rust-lang/rustup/pull/1629 [pr#1639]: https://github.com/rust-lang/rustup/pull/1639 [pr#1643]: https://github.com/rust-lang/rustup/pull/1643 [pr#1645]: https://github.com/rust-lang/rustup/pull/1645 [pr#1642]: https://github.com/rust-lang/rustup/pull/1642 [pr#1633]: https://github.com/rust-lang/rustup/pull/1633 [pr#1654]: https://github.com/rust-lang/rustup/pull/1654 [pr#1660]: https://github.com/rust-lang/rustup/pull/1660 [pr#1616]: https://github.com/rust-lang/rustup/pull/1616 [pr#1659]: https://github.com/rust-lang/rustup/pull/1659 [pr#1679]: https://github.com/rust-lang/rustup/pull/1679 [pr#1666]: https://github.com/rust-lang/rustup/pull/1666 [1.17.0]: https://github.com/rust-lang/rustup/releases/tag/1.17.0 ## [1.16.0] - 2018-12-06 - [Fix rename_rls_remove test on Windows][pr#1561] [pr#1561]: https://github.com/rust-lang/rustup/pull/1561 [1.16.0]: https://github.com/rust-lang/rustup/releases/tag/1.16.0 ## [1.15.0] - 2018-11-27 - [More tweaks to renames][pr#1554] - [Return Ok status when trying to add required component][pr#1553] - [Use `renames` instead of `rename` to match the actual manifest][pr#1552] - [Size optimizations: Build with LTO and alloc_system][pr#1526] - [Use `openssl-src` from crates.io to link to OpenSSL][pr#1536] - [Change handling of renames][pr#1549] [pr#1554]: https://github.com/rust-lang/rustup/pull/1554 [pr#1553]: https://github.com/rust-lang/rustup/pull/1553 [pr#1552]: https://github.com/rust-lang/rustup/pull/1552 [pr#1526]: https://github.com/rust-lang/rustup/pull/1526 [pr#1536]: https://github.com/rust-lang/rustup/pull/1536 [pr#1549]: https://github.com/rust-lang/rustup/pull/1549 [1.15.0]: https://github.com/rust-lang/rustup/releases/tag/1.15.0 ## [1.14.0] - 2018-10-04 - [Fix Windows job management][pr#1511] - [Preserve symlinks when installing][pr#1504] - [Add `--toolchain` option to `rustup doc`][pr#1478] - [Fix removing toolchain fail when update-hash does not exist][pr#1472] - [Add note about installing the Windows SDK component][pr#1468] [pr#1511]: https://github.com/rust-lang/rustup/pull/1511 [pr#1504]: https://github.com/rust-lang/rustup/pull/1504 [pr#1478]: https://github.com/rust-lang/rustup/pull/1478 [pr#1472]: https://github.com/rust-lang/rustup/pull/1472 [pr#1468]: https://github.com/rust-lang/rustup/pull/1468 [1.14.0]: https://github.com/rust-lang/rustup/releases/tag/1.14.0 ## [1.13.0] - 2018-07-16 - [Add clippy to the tools list][pr1461] [pr1461]: https://github.com/rust-lang/rustup/pull/1461 [1.13.0]: https://github.com/rust-lang/rustup/releases/tag/1.13.0 Contributors: Jane Lusby ## [1.12.0] - 2018-07-07 - [Add --path flag to 'rustup doc'][pr1453] - [Add flag to "rustup show" for active-toolchain][pr1449] - [Bring rustup.js and markup into alignment with rust-www][pr1437] - [Add caret after first installation question][pr1435] - [Add "rustup doc --reference"][pr1430] - [Update Visual C++ Build Tools URL][pr1428] - [Fix download indicator on OSes with newer ncurses package][pr1422] - [Remove components if they don't exist anymore during update][pr1419] - [Make sure rustup uses `utils::rename*` consistently][pr1389] - [Do not try to get CWD if not required][pr1379] - [Give correct error message if user tries to install an unavailable toolchain][pr1380] - [Fall back to wget if curl is not installed][pr1373] - [Added a link to all installers to the homepage][pr1370] - [Display helpful advice even with -y][pr1290] - [Use browser in BROWSER env if present for `doc` command][pr1289] - [Update shebang to reflect bashisms][pr1269] [pr1453]: https://github.com/rust-lang/rustup/pull/1453 [pr1449]: https://github.com/rust-lang/rustup/pull/1449 [pr1437]: https://github.com/rust-lang/rustup/pull/1437 [pr1435]: https://github.com/rust-lang/rustup/pull/1435 [pr1430]: https://github.com/rust-lang/rustup/pull/1430 [pr1428]: https://github.com/rust-lang/rustup/pull/1428 [pr1422]: https://github.com/rust-lang/rustup/pull/1422 [pr1419]: https://github.com/rust-lang/rustup/pull/1419 [pr1389]: https://github.com/rust-lang/rustup/pull/1389 [pr1379]: https://github.com/rust-lang/rustup/pull/1379 [pr1380]: https://github.com/rust-lang/rustup/pull/1380 [pr1373]: https://github.com/rust-lang/rustup/pull/1373 [pr1370]: https://github.com/rust-lang/rustup/pull/1370 [pr1290]: https://github.com/rust-lang/rustup/pull/1290 [pr1289]: https://github.com/rust-lang/rustup/pull/1289 [pr1269]: https://github.com/rust-lang/rustup/pull/1269 [1.12.0]: https://github.com/rust-lang/rustup/releases/tag/1.12.0 Contributors: Andrew Pennebaker, Who? Me?!, Matteo Bertini, mog422, Kasper Møller Andersen, Thibault Delor, Justin Worthe, TitanSnow, aimileus, Antonio Murdaca, Cyryl Płotnicki, Nick Cameron, Alex Crichton, Kornel, Stuart Dootson, Pietro Albini, Diggory Blake, Yuji Nakao, Johannes Hofmann, CrLF0710, Aaron Lee, Brian Anderson, Mateusz Mikuła, Segev Finer, Dan Aloni, Joeri van Ruth ## [1.11.0] - 2018-02-13 - [windows: detect architecture on website, update to matching arch][pr1354] [pr1354]: https://github.com/rust-lang/rustup/pull/1354 [1.11.0]: https://github.com/rust-lang/rustup/releases/tag/1.11.0 Contributors: Steffen Butzer ## [1.10.0] - 2018-01-25 - [Warn when tools are missing and allow an override][pr1337] [pr1337]: https://github.com/rust-lang/rustup/pull/1337 [1.10.0]: https://github.com/rust-lang/rustup/releases/tag/1.10.0 Contributors: Nick Cameron, Steffen Butzer ## [1.9.0] - 2018-01-04 - [Fix self update errors filling in missing proxies][pr1326] [pr1326]: https://github.com/rust-lang/rustup/pull/1326 [1.9.0]: https://github.com/rust-lang/rustup/releases/tag/1.9.0 Contributors: Alex Crichton ## [1.8.0] - 2017-12-19 - [Add `rustup run --install`][pr1295] - [Prevent `rustup update` to a toolchain without `rustc` or `cargo`][pr1298] - [Add support for `rustfmt` shims][pr1294] [pr1295]: https://github.com/rust-lang/rustup/pull/1295 [pr1298]: https://github.com/rust-lang/rustup/pull/1298 [pr1294]: https://github.com/rust-lang/rustup/pull/1294 [1.8.0]: https://github.com/rust-lang/rustup/releases/tag/1.8.0 Contributors: Alex Crichton, kennytm, Nick Cameron, Simon Sapin, Who? Me?! ## [1.7.0] - 2017-10-30 - [Improve clarity of component errors][pr1255] - [Support `--default-toolchain none`][pr1257] - [Automatically install override toolchain when missing][pr1250] [pr1255]: https://github.com/rust-lang/rustup/pull/1255 [pr1257]: https://github.com/rust-lang/rustup/pull/1257 [pr1250]: https://github.com/rust-lang/rustup/pull/1250 [1.7.0]: https://github.com/rust-lang/rustup/releases/tag/1.7.0 Contributors: Aidan Hobson Sayers, Alan Du, Alex Crichton, Christoph Wurst, Jason Mobarak, Leon Isenberg, Simon Sapin, Vadim Petrochenkov ## [1.6.0] - 2017-08-30 - [Fix support for s390x][pr1228] - [Fix `show` so it displays helpful information if the active toolchain is not installed][pr1189] - [Fix uninstalling toolchains with stale symlinks][pr1201] - [Replace the hyper backend with a reqwest downloading backend][pr1222] - [Consistently give a toolchain argument in the help text][pr1212] - [Use `exec` on Unix where possible to help manage Unix signals][pr1242] [pr1228]: https://github.com/rust-lang/rustup/pull/1228 [pr1189]: https://github.com/rust-lang/rustup/pull/1189 [pr1201]: https://github.com/rust-lang/rustup/pull/1201 [pr1222]: https://github.com/rust-lang/rustup/pull/1222 [pr1212]: https://github.com/rust-lang/rustup/pull/1212 [pr1242]: https://github.com/rust-lang/rustup/pull/1242 [1.6.0]: https://github.com/rust-lang/rustup/releases/tag/1.6.0 Contributors: Alex Crichton, Chen Rotem Levy, Krishna Sundarram, Martin Geisler, Matt Brubeck, Matt Ickstadt, Michael Benfield, Michael Fletcher, Nick Cameron, Patrick Reisert, Ralf Jung, Sean McArthur, Steven Fackler ## [1.5.0] - 2017-06-24 - [Rename references to multirust to rustup where applicable](https://github.com/rust-lang/rustup/pull/1148) - [Update platform support in README](https://github.com/rust-lang/rustup/pull/1159) - [Allow rustup to handle unavailable packages](https://github.com/rust-lang/rustup/pull/1063) - [Update libz-sys and curl-sys](https://github.com/rust-lang/rustup/pull/1176) - [Teach rustup to override the toolchain from a version file](https://github.com/rust-lang/rustup/pull/1172) - [Update sha2 crate](https://github.com/rust-lang/rustup/pull/1162) - [Check for unexpected cargo/rustc before install](https://github.com/rust-lang/rustup/pull/705) - [Update PATH in .bash_profile](https://github.com/rust-lang/rustup/pull/1179) [1.5.0]: https://github.com/rust-lang/rustup/releases/tag/1.5.0 Contributors: Allen Welkie, bors, Brian Anderson, Diggory Blake, Erick Tryzelaar, Ricardo Martins, Артём Павлов [Artyom Pavlov] ## [1.4.0] - 2017-06-09 - [set_file_perms: if the file is already executable, keep it executable](https://github.com/rust-lang/rustup/pull/1141) - [Disable man support on Windows](https://github.com/rust-lang/rustup/pull/1139) - [VS 2017 updates](https://github.com/rust-lang/rustup/pull/1145) - [Show version of rust being installed](https://github.com/rust-lang/rustup/pull/1025) - [Detect MSVC 2017](https://github.com/rust-lang/rustup/pull/1136) - [Use same precision as rustc for commit sha](https://github.com/rust-lang/rustup/pull/1134) - [Fix prompt asking for msvc even though -y is provided](https://github.com/rust-lang/rustup/pull/1124) - [README: fix rust build dir](https://github.com/rust-lang/rustup/pull/1135) - [Add support for XZ-compressed packages](https://github.com/rust-lang/rustup/pull/1100) - [Add PATH in post-install message when not modifying PATH](https://github.com/rust-lang/rustup/pull/1126) - [Cleanup download-related code in the rustup_dist crate](https://github.com/rust-lang/rustup/pull/1131) - [Increase Rust detection timeout to 3 seconds](https://github.com/rust-lang/rustup/pull/1130) - [Suppress confusing NotADirectory error and show override missing](https://github.com/rust-lang/rustup/pull/1128) - [Don't try to update archive toolchains](https://github.com/rust-lang/rustup/pull/1121) - [Exit successfully on "update not yet available"](https://github.com/rust-lang/rustup/pull/1120) - [Add a message when removing a component](https://github.com/rust-lang/rustup/pull/1119) - [Use ShellExecute rather than start.exe to open docs on windows](https://github.com/rust-lang/rustup/pull/1117) - [Clarify that rustup update updates rustup itself](https://github.com/rust-lang/rustup/pull/1113) - [Ensure that intermediate directories exist when unpacking an entry](https://github.com/rust-lang/rustup/pull/1098) - [Add the rust lib dir (containing std-.dll) to the path on windows](https://github.com/rust-lang/rustup/pull/1093) - [Add x86_64-linux-android target](https://github.com/rust-lang/rustup/pull/1086) - [Fix for help.rs suggestion](https://github.com/rust-lang/rustup/pull/1107) - [Ignore remove_override_nonexistent on windows](https://github.com/rust-lang/rustup/pull/1105) - [Update proxy setting docs](https://github.com/rust-lang/rustup/pull/1088) - [Add sensible-browser to the browser list](https://github.com/rust-lang/rustup/pull/1087) - [Added help for `rustup toolchain link`](https://github.com/rust-lang/rustup/pull/1017) [1.4.0]: https://github.com/rust-lang/rustup/releases/tag/1.4.0 Contributors: Andrea Canciani, bors, Brian Anderson, CrazyMerlyn, Diggory Blake, Fabio B, James Elford, Jim McGrath, johnthagen, Josh Lee, Kim Christensen, Marco A L Barbosa, Mateusz Mikula, Matthew, Matt Ickstadt, Mikhail Modin, Patrick Deuster, pxdeu, Ralf Jung, Raphaël Huchet, Robert Vally, theindigamer, Tommy Ip, Xidorn Quan ## [1.3.0] - 2017-05-09 - [Add armv8l support](https://github.com/rust-lang/rustup/pull/1055) - [Update curl crate](https://github.com/rust-lang/rustup/pull/1101) - [Fix inadvertent dependency on bash](https://github.com/rust-lang/rustup/pull/1048) - [Update openssl-probe to 0.1.1](https://github.com/rust-lang/rustup/pull/1061) - [zsh completions cleanup](https://github.com/rust-lang/rustup/pull/1068) - [Alias 'rustup toolchain uninstall' to 'rustup uninstall'](https://github.com/rust-lang/rustup/pull/1073) - [Fix a typo in PowerShell completion script help](https://github.com/rust-lang/rustup/pull/1076) - [Enforce timeouts for reading rustc version](https://github.com/rust-lang/rustup/pull/1071) - [Fix OpenSSL linkage by using the final install-directory in the build](https://github.com/rust-lang/rustup/pull/1065) [1.3.0]: https://github.com/rust-lang/rustup/releases/tag/1.3.0 Contributors: bors, Brian Anderson, Diggory Blake, Greg Alexander, James Elford, Jordan Hiltunen, Justin Noah, Kang Seonghoon, Kevin K, Marco A L Barbosa ## [1.2.0] - 2017-04-08 - [Check ZDOTDIR when adding path to .zprofile](https://github.com/rust-lang/rustup/pull/1038) - [Update links and install page to include android support](https://github.com/rust-lang/rustup/pull/1037) - [Add bash completion guidance for macOS users](https://github.com/rust-lang/rustup/pull/1035) - [Support partial downloads](https://github.com/rust-lang/rustup/pull/1020) - [Don't crash if modifying multiple profile files](https://github.com/rust-lang/rustup/pull/1040) [1.2.0]: https://github.com/rust-lang/rustup/releases/tag/1.2.0 Contributors: Brian Anderson, James Elford, Jason Dreyzehner, Marco A L Barbosa, Wim Looman ## [1.1.0] - 2017-04-06 - [Fix browser detection for Linux ppc64 and NetBSD](https://github.com/rust-lang/rustup/pull/875) - [Update windows info](https://github.com/rust-lang/rustup/pull/879) - [Update to markdown 0.2](https://github.com/rust-lang/rustup/pull/896) - [Make running program extension case insensitive](https://github.com/rust-lang/rustup/pull/887) - [Add MIPS/s390x builders (with PPC64 compilation fixed)](https://github.com/rust-lang/rustup/pull/890) - [Fix two missing quotes of download error message](https://github.com/rust-lang/rustup/pull/867) - [www: MIPS support and cleanups](https://github.com/rust-lang/rustup/pull/866) - [Update release instructions](https://github.com/rust-lang/rustup/pull/863) - [Don't set low speed limits for curl](https://github.com/rust-lang/rustup/pull/914) - [Attempt to fix msi build. Pin appveyor nightlies](https://github.com/rust-lang/rustup/pull/910) - [Stop defaulting to \$PATH searches when the binary can't be found and causing infinite recursion](https://github.com/rust-lang/rustup/pull/917) - [Upgrade openssl](https://github.com/rust-lang/rustup/pull/934) - [Improve browser detection and install instructions](https://github.com/rust-lang/rustup/pull/936) - [Add android support to rustup-init.sh](https://github.com/rust-lang/rustup/pull/949) - [Add fallback to symlink if hardlink fails](https://github.com/rust-lang/rustup/pull/951) - [readme: add tmp dir hint to Contributing section](https://github.com/rust-lang/rustup/pull/985) - [Fixed link to the list of supported platforms](https://github.com/rust-lang/rustup/pull/970) - [Update job object code to match Cargo's](https://github.com/rust-lang/rustup/pull/984) - [Added argument-documentation to rustup-init.sh](https://github.com/rust-lang/rustup/pull/962) - [Add/remove multiple toolchains](https://github.com/rust-lang/rustup/pull/986) - [Remove curl usage from appveyor](https://github.com/rust-lang/rustup/pull/1001) - [Store downloaded files in a persistent directory until installation](https://github.com/rust-lang/rustup/pull/958) - [Add android build support](https://github.com/rust-lang/rustup/pull/1000) - [Fix up a bunch of things indicated by clippy](https://github.com/rust-lang/rustup/pull/1012) - [Ensure librssl compatibility](https://github.com/rust-lang/rustup/pull/1011) - [RLS support](https://github.com/rust-lang/rustup/pull/1005) - [Add 'docs' alias](https://github.com/rust-lang/rustup/pull/1010) - [Use correct name for undefined linked toolchain invocation](https://github.com/rust-lang/rustup/pull/1008) - [zsh install support](https://github.com/rust-lang/rustup/pull/1013) - [Add/remove multiple components+targets](https://github.com/rust-lang/rustup/pull/1016) - [Better error message when not running in a tty](https://github.com/rust-lang/rustup/pull/1026) - [Indent help text](https://github.com/rust-lang/rustup/pull/1019) - [Document installing to a custom location using CARGO_HOME and RUSTUP_HOME environment variables](https://github.com/rust-lang/rustup/pull/1024) - [Aggressive remove_dir_all](https://github.com/rust-lang/rustup/pull/1015) [1.1.0]: https://github.com/rust-lang/rustup/releases/tag/1.1.0 Contributors: Aarthi Janakiraman, Alex Burka, Alex Crichton, bors, Brian Anderson, Christian Muirhead, Christopher Armstrong, Daniel Lockyer, Diggory Blake, Evgenii Pashkin, Grissiom, James Elford, Luca Bruno, Lyuha, Manish Goregaokar, Marc-Antoine Perennou, Marco A L Barbosa, Mikhail Pak, Nick Cameron, polonez, Sam Marshall, Steve Klabnik, Tomáš Hübelbauer, topecongiro, Wang Xuerui ## [1.0.0] - 2016-12-15 - [Statically link MSVC CRT](https://github.com/rust-lang/rustup/pull/843) - [Upgrade ~/.multirust correctly from rustup-init](https://github.com/rust-lang/rustup/pull/858) [1.0.0]: https://github.com/rust-lang/rustup/releases/tag/1.0.0 Contributors: Alex Crichton, Andrew Koroluk, Arch, benaryorg, Benedikt Reinartz, Björn Steinbrink, bors, Boutin, Michael, Brian Anderson, Cam Swords, Chungmin Park, Corey Farwell, Daniel Keep, David Salter, Diggory Blake, Drew Fisher, Erick Tryzelaar, Florian Gilcher, geemili, Guillaume Fraux, Ivan Nejgebauer, Ivan Petkov, Jacob Shaffer, Jake Goldsborough, James Lucas, Jeremiah Peschka, jethrogb, Jian Zeng, Jimmy Cuadra, Joe Wilm, Jorge Aparicio, Josh Machol, Josh Stone, Julien Blanchard, Kai Noda, Kai Roßwag, Kamal Marhubi, Kevin K, Kevin Rauwolf, Kevin Yap, Knight, leonardo.yvens, llogiq, Marco A L Barbosa, Martin Pool, Matt Brubeck, mdinger, Michael DeWitt, Mika Attila, Nate Mara, NODA, Kai, Oliver Schneider, Patrick Reisert, Paul Padier, Ralph Giles, Raphael Cohn, Ri, Ricardo Martins, Ryan Havar, Ryan Kung, Severen Redwood, Tad Hardesty, Taylor Cramer, theindigamer, Tim Neumann, Tobias Bucher, trolleyman, Vadim Petrochenkov, Virgile Andreani, V Jackson, Vladimir, Wang Xuerui, Wayne Warren, Wesley Moore, Yasushi Abe, Y. T. Chung ## [0.7.0] - 2016-12-11 - [Correctly "detect" host endianness on MIPS](https://github.com/rust-lang/rustup/pull/802) - [Add powershell completions](https://github.com/rust-lang/rustup/pull/801) - [Update toolchain used to build rustup](https://github.com/rust-lang/rustup/pull/741) - [Support probing MIPS64 n64 targets](https://github.com/rust-lang/rustup/pull/815) - [Support MIPS architectures in rustup-init.sh](https://github.com/rust-lang/rustup/pull/825) - [Automatically detect NetBSD during standard install](https://github.com/rust-lang/rustup/pull/824) - [Fix symlink creation on windows](https://github.com/rust-lang/rustup/pull/823) - [Search PATH for binaries run by `rustup run`](https://github.com/rust-lang/rustup/pull/822) - [Recursive tool invocations should invoke the proxy, not the tool directly](https://github.com/rust-lang/rustup/pull/812) - [Upgrade error-chain](https://github.com/rust-lang/rustup/pull/841) - [Add FAQ entry for downloading Rust source](https://github.com/rust-lang/rustup/pull/840) - [Rename ~/.multirust to ~/.rustup](https://github.com/rust-lang/rustup/pull/830) - [Remove some codegen hacks](https://github.com/rust-lang/rustup/pull/850) - [Update libc for MIPS64 host builds](https://github.com/rust-lang/rustup/pull/847) - [Default to MSVC on Windows](https://github.com/rust-lang/rustup/pull/842) [0.7.0]: https://github.com/rust-lang/rustup/releases/tag/0.7.0 Contributors: Alex Crichton, Arch, bors, Brian Anderson, Diggory Blake, Kai Roßwag, Kevin K, Oliver Schneider, Ryan Havar, Tobias Bucher, Wang Xuerui ## [0.6.5] - 2016-11-04 - [Update bundled curl code](https://github.com/rust-lang/rustup/pull/790) - [Remove old zsh completions](https://github.com/rust-lang/rustup/pull/779) - [Fix two small typos in the error descriptions](https://github.com/rust-lang/rustup/pull/788) - [Update README](https://github.com/rust-lang/rustup/pull/782) - [Fix name of bash completion directory](https://github.com/rust-lang/rustup/pull/780) [0.6.5]: https://github.com/rust-lang/rustup/releases/tag/0.6.5 Contributors: Alex Crichton, Björn Steinbrink, Brian Anderson, Jian Zeng, Matt Brubeck ## [0.6.4] - 2016-10-24 - [making rustup prepend cargo bin to path instead of append](https://github.com/rust-lang/rustup/pull/707) - [Use released version of rustls dependency](https://github.com/rust-lang/rustup/pull/711) - [Update OpenSSL](https://github.com/rust-lang/rustup/pull/733) - [Made outputting of ANSI terminal escapes codes defensive](https://github.com/rust-lang/rustup/pull/725) - [Adjusted rustup-init.sh need_cmd to add uname and remove printf](https://github.com/rust-lang/rustup/pull/723) - [Update to error-chain 0.5.0 to allow optional backtrace](https://github.com/rust-lang/rustup/pull/591) - [Fix variable naming in rustup-init.sh](https://github.com/rust-lang/rustup/pull/737) - [Update clap to fix --help formatting](https://github.com/rust-lang/rustup/pull/738) - [Add an FAQ entry about troubles with antivirus](https://github.com/rust-lang/rustup/pull/739) - [Clarify how rustup toolchain installation works on Windows](https://github.com/rust-lang/rustup/pull/744) - [Do not interpret commas when using "rustup run"](https://github.com/rust-lang/rustup/pull/752) - [Fix local declarations for zsh completions](https://github.com/rust-lang/rustup/pull/753) - [Fix checksum failures](https://github.com/rust-lang/rustup/pull/759) - [Treat an empty `CARGO_HOME` the same as an unset `CARGO_HOME`](https://github.com/rust-lang/rustup/pull/767) - [Check stdout is a tty before using terminal features](https://github.com/rust-lang/rustup/pull/772) - [Add completion generation for zsh, bash and fish shells](https://github.com/rust-lang/rustup/pull/773) [0.6.4]: https://github.com/rust-lang/rustup/releases/tag/0.6.4 Contributors: Alex Crichton, Andrew Koroluk, Brian Anderson, Chungmin Park, Diggory Blake, Guillaume Fraux, Jake Goldsborough, jethrogb, Kamal Marhubi, Kevin K, Kevin Rauwolf, Raphael Cohn, Ricardo Martins ## [0.6.3] - 2016-08-28 - [Disable anti-sudo check](https://github.com/rust-lang/rustup/pull/698) - [Fixed CI toolchain pinning](https://github.com/rust-lang/rustup/pull/696) [0.6.3]: https://github.com/rust-lang/rustup/releases/tag/0.6.3 Contributors: Brian Anderson ## [0.6.2] - 2016-08-27 - [Add basic autocompletion for Zsh](https://github.com/rust-lang/rustup/pull/689) - [Sort toolchains by semantic version](https://github.com/rust-lang/rustup/pull/688) [0.6.2]: https://github.com/rust-lang/rustup/releases/tag/0.6.2 Contributors: Brian Anderson, Diggory Blake, Knight, Marco A L Barbosa ## [0.6.1] - 2016-08-24 - [Fix mysterious crash on OS X 10.10+](https://github.com/rust-lang/rustup/pull/684) - [Fix `component remove` command and add a test for it](https://github.com/rust-lang/rustup/pull/683) [0.6.1]: https://github.com/rust-lang/rustup/releases/tag/0.6.1 Contributors: Brian Anderson, Diggory Blake ## [0.6.0] - 2016-08-23 - [Print rustup version after update](https://github.com/rust-lang/rustup/pull/614) - [Don't spawn processes for copying](https://github.com/rust-lang/rustup/pull/630) - [Upgrade error-chain to 0.3](https://github.com/rust-lang/rustup/pull/636) - [Support telemetry with lots of output](https://github.com/rust-lang/rustup/pull/645) - [Remove empty directories after component uninstall](https://github.com/rust-lang/rustup/pull/634) - [Update rustup-init.sh for powerpc](https://github.com/rust-lang/rustup/pull/647) - [Switch builds to current nightly toolchain](https://github.com/rust-lang/rustup/pull/651) - [Add a WIP MSI installer](https://github.com/rust-lang/rustup/pull/635) - [Add `--path` and `--nonexistent` options to `rustup override unset`](https://github.com/rust-lang/rustup/pull/650) - [Add `component` subcommand](https://github.com/rust-lang/rustup/pull/659) [0.6.0]: https://github.com/rust-lang/rustup/releases/tag/0.6.0 Contributors: Alex Crichton, Brian Anderson, Diggory Blake, Ivan Nejgebauer Josh Machol, Julien Blanchard, Patrick Reisert, Ri, Tim Neumann ## [0.5.0] - 2016-07-30 - [List custom toolchains in `rustup show`](https://github.com/rust-lang/rustup/pull/620) - [Add a usage example for local builds](https://github.com/rust-lang/rustup/pull/622) - [Read/Write impl rework for rustls](https://github.com/rust-lang/rustup/pull/592) - [Introduce `+TOOLCHAIN` syntax for proxies](https://github.com/rust-lang/rustup/pull/615) - [Add `rustup man`](https://github.com/rust-lang/rustup/pull/616) - [Try detecting sudo when running `rustup-init`](https://github.com/rust-lang/rustup/pull/617) - [Handle active custom toolchain in `rustup show`](https://github.com/rust-lang/rustup/pull/621) [0.5.0]: https://github.com/rust-lang/rustup/releases/tag/0.5.0 Contributors: Brian Anderson, Cam Swords, Daniel Keep, Diggory Blake, Florian Gilcher, Ivan Nejgebauer, theindigamer ## [0.4.0] - 2016-07-22 - [Improve rustls CA certificate loading](https://github.com/rust-lang/rustup/pull/585) - [Detect ARMv7 CPUs without NEON extensions and treat as ARMv6](https://github.com/rust-lang/rustup/pull/593) - [Allow any toolchain to be specified as the default during rustup installation](https://github.com/rust-lang/rustup/pull/586) - [Add details about updating rustup to README](https://github.com/rust-lang/rustup/pull/590) - [Update libbacktrace to generate less filesystem thrashing on Windows](https://github.com/rust-lang/rustup/pull/604) - [Update gcc dep to fix building on MSVC](https://github.com/rust-lang/rustup/pull/605) - [Remove the multirust binary](https://github.com/rust-lang/rustup/pull/606) - [Use the env_proxy crate for proxy environment variable handling](https://github.com/rust-lang/rustup/pull/598) - [Set system-specific dynamic loader env var for command execution](https://github.com/rust-lang/rustup/pull/600) - [Hide telemetry command from top level help](https://github.com/rust-lang/rustup/pull/601) - [Add the "no-self-update" feature](https://github.com/rust-lang/rustup/pull/602) - [Update to error-chain 0.2.2](https://github.com/rust-lang/rustup/pull/609) - [Add HTTP proxy documentation to README](https://github.com/rust-lang/rustup/pull/610) [0.4.0]: https://github.com/rust-lang/rustup/releases/tag/0.4.0 Contributors: Alex Crichton, Brian Anderson, Ivan Nejgebauer, Jimmy Cuadra, Martin Pool, Wesley Moore ## [0.3.0] - 2016-07-14 - [Teach rustup to download manifests from the `/staging/` directory](https://github.com/rust-lang/rustup/pull/579). - [Treat all HTTP client errors the same](https://github.com/rust-lang/rustup/pull/578). - [Remove winapi replacement](https://github.com/rust-lang/rustup/pull/577). - [Remove toolchain directory if initial toolchain install fails](https://github.com/rust-lang/rustup/pull/574). - [Fallback to old download methods if server returns 403](https://github.com/rust-lang/rustup/pull/573). - [Add preliminary rustls support](https://github.com/rust-lang/rustup/pull/572). - [Add a hack to remediate checksum failure issues](https://github.com/rust-lang/rustup/pull/562). - [Move error-chain out of tree](https://github.com/rust-lang/rustup/pull/564). - [Remove uses of subcommand synonyms in the examples](https://github.com/rust-lang/rustup/pull/560). - [Add `--yes` as alias for `-y`](https://github.com/rust-lang/rustup/pull/563). - [Remove unavailable toolchains from `target list`](https://github.com/rust-lang/rustup/pull/553). - [Add powerpc builds](https://github.com/rust-lang/rustup/pull/534). - [Fix help text for `rustup update`](https://github.com/rust-lang/rustup/pull/552). - [Remove noisy "rustup is up to date" message](https://github.com/rust-lang/rustup/pull/550). - [Fix references to non-existent `.rustup` directory](https://github.com/rust-lang/rustup/pull/545). - [When listing toolchains only list directories](https://github.com/rust-lang/rustup/pull/544). - [rustup-init: remove dependency on `file` command](https://github.com/rust-lang/rustup/pull/543). - [Link to rustup-init.sh in README](https://github.com/rust-lang/rustup/pull/541). - [Improve docs for `set default-host`](https://github.com/rust-lang/rustup/pull/540). [0.3.0]: https://github.com/rust-lang/rustup/releases/tag/0.3.0 Contributors: Alex Crichton, Brian Anderson, Drew Fisher, geemili, Ivan Petkov, James Lucas, jethrogb, Kevin Yap, leonardo.yvens, Michael DeWitt, Nate Mara, Virgile Andreani ## [0.2.0] - 2016-06-21 - [Indicate correct path to remove in multirust upgrade instructions](https://github.com/rust-lang/rustup/pull/518). - [Bring back optional hyper with proxy support](https://github.com/rust-lang/rustup/pull/532). - ['default' and 'update' heuristics for bare triples](https://github.com/rust-lang/rustup/pull/516). - [Change upstream via \$RUSTUP_DIST_SERVER](https://github.com/rust-lang/rustup/pull/521). - [Fail with a nicer error message if /tmp is mounted noexec](https://github.com/rust-lang/rustup/pull/523). - [Remove printfs from ~/.cargo/env](https://github.com/rust-lang/rustup/pull/527). - [Reduce margin in installer text to 79 columns](https://github.com/rust-lang/rustup/pull/526). - [Fix typos](https://github.com/rust-lang/rustup/pull/519). - [Fix missing curly braces in error-chain docs](https://github.com/rust-lang/rustup/pull/522). - [Fix downloads of builds without v2 manifests](https://github.com/rust-lang/rustup/pull/515). - [Explain toolchains in `help install`](https://github.com/rust-lang/rustup/pull/496). - [Compile on stable Rust](https://github.com/rust-lang/rustup/pull/476). - [Fix spelling mistakes](https://github.com/rust-lang/rustup/pull/489). - [Fix the toolchain command synonyms](https://github.com/rust-lang/rustup/pull/477). - [Configurable host triples](https://github.com/rust-lang/rustup/pull/421). - [Use a .toml file to store settings](https://github.com/rust-lang/rustup/pull/420). - [Point PATH to toolchain/bin on Windows](https://github.com/rust-lang/rustup/pull/402). - [Remove extra '.' in docs](https://github.com/rust-lang/rustup/pull/472). [0.2.0]: https://github.com/rust-lang/rustup/releases/tag/0.2.0 Contributors: Alex Crichton, benaryorg, Benedikt Reinartz, Boutin, Michael, Brian Anderson, Diggory Blake, Erick Tryzelaar, Ivan Nejgebauer, Jeremiah Peschka, Josh Stone, Knight, mdinger, Ryan Kung, Tad Hardesty ## [0.1.12] - 2016-05-12 - [Don't install when multirust metadata exists](https://github.com/rust-lang/rustup/pull/456). [0.1.12]: https://github.com/rust-lang/rustup/releases/tag/0.1.12 ## [0.1.11] - 2016-05-12 - [Actually dispatch the `rustup install` command](https://github.com/rust-lang/rustup/pull/444). - [Migrate to libcurl instead of hyper](https://github.com/rust-lang/rustup/pull/434). - [Add error for downloading bogus versions](https://github.com/rust-lang/rustup/pull/428). [0.1.11]: https://github.com/rust-lang/rustup/releases/tag/0.1.11 ## [0.1.10] - 2016-05-09 - [Multiple cli improvements](https://github.com/rust-lang/rustup/pull/419). - [Support HTTP protocol again](https://github.com/rust-lang/rustup/pull/431). - [Improvements to welcome screen](https://github.com/rust-lang/rustup/pull/418). - [Don't try to update non-tracking channels](https://github.com/rust-lang/rustup/pull/425). - [Don't panic when NativeSslStream lock is poisoned](https://github.com/rust-lang/rustup/pull/429). - [Fix multiple issues in schannel bindings](https://github.com/sfackler/schannel-rs/pull/1) [0.1.10]: https://github.com/rust-lang/rustup/releases/tag/0.1.10 ## [0.1.9] - 2016-05-07 - [Do TLS hostname verification](https://github.com/rust-lang/rustup/pull/400). - [Expand `rustup show`](https://github.com/rust-lang/rustup/pull/406). - [Add `rustup doc`](https://github.com/rust-lang/rustup/pull/403). - [Refuse to install if it looks like other Rust installations are present](https://github.com/rust-lang/rustup/pull/408). - [Update www platform detection for FreeBSD](https://github.com/rust-lang/rustup/pull/399). - [Fix color display during telemetry capture](https://github.com/rust-lang/rustup/pull/394). - [Make it less of an error for the self-update hash to be wrong](https://github.com/rust-lang/rustup/pull/372). [0.1.9]: https://github.com/rust-lang/rustup/releases/tag/0.1.9 ## [0.1.8] - 2016-04-28 - [Initial telemetry implementation (disabled)](https://github.com/rust-lang/rustup/pull/289) - [Add hash to `--version`](https://github.com/rust-lang/rustup/pull/347) - [Improve download progress](https://github.com/rust-lang/rustup/pull/355) - [Completely overhaul error handling](https://github.com/rust-lang/rustup/pull/358) - [Add armv7l support to www](https://github.com/rust-lang/rustup/pull/359) - [Overhaul website](https://github.com/rust-lang/rustup/pull/363) [0.1.8]: https://github.com/rust-lang/rustup/releases/tag/0.1.8 ## [0.1.7] - 2016-04-17 - [Fix overrides for Windows root directories](https://github.com/rust-lang/rustup/pull/317). - [Remove 'multirust' binary and rename crates](https://github.com/rust-lang/rustup/pull/312). - [Pass rustup-setup.sh arguments to rustup-setup](https://github.com/rust-lang/rustup/pull/325). - [Don't open /dev/tty if passed -y](https://github.com/rust-lang/rustup/pull/334). - [Add interactive install, `--default-toolchain` argument](https://github.com/rust-lang/rustup/pull/293). - [Rename rustup-setup to rustu-init](https://github.com/rust-lang/rustup/pull/303). [0.1.7]: https://github.com/rust-lang/rustup/releases/tag/0.1.7 rustup-1.26.0/CONTRIBUTING.md000066400000000000000000000315641441327105200154030ustar00rootroot00000000000000# Contributing to rustup 1. Fork it! 2. Create your feature branch: `git checkout -b my-new-feature` 3. Test it: `cargo test` 4. Lint it: `cargo +beta clippy --all --all-targets -- -D warnings` > We use `cargo clippy` to ensure high-quality code and to enforce a set of best practices for Rust programming. However, not all lints provided by `cargo clippy` are relevant or applicable to our project. > We may choose to ignore some lints if they are unstable, experimental, or specific to our project. > If you are unsure about a lint, please ask us in the [rustup Discord channel](https://discord.com/channels/442252698964721669/463480252723888159). 5. Commit your changes: `git commit -am 'Add some feature'` 6. Push to the branch: `git push origin my-new-feature` 7. Submit a pull request :D For developing on `rustup` itself, you may want to install into a temporary directory, with a series of commands similar to this: ```bash cargo build mkdir home RUSTUP_HOME=home CARGO_HOME=home target/debug/rustup-init --no-modify-path -y ``` You can then try out `rustup` with your changes by running `home/bin/rustup`, without affecting any existing installation. Remember to keep those two environment variables set when running your compiled `rustup-init` or the toolchains it installs, but _unset_ when rebuilding `rustup` itself. If you wish to install your new build to try out longer term in your home directory then you can run `cargo dev-install` which is an alias in `.cargo/config` which runs `cargo run -- --no-modify-path -y` to install your build into your homedir. We use `rustfmt` to keep our codebase consistently formatted. Please ensure that you have correctly formatted your code (most editors will do this automatically when saving) or it may not pass the CI tests. 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. ## Non-machine-enforced coding standards These are requirements we have that we have not yet lifted to the level of automatic enforcement. ### Import grouping In each file the imports should be grouped into at most 4 groups in the following order: 1. stdlib 2. non-repository local crates 3. repository local other crates 4. this crate Separate each group with a blank line, and rustfmt will sort into a canonical order. Any file that is not grouped like this can be rearranged whenever the file is touched - we're not precious about having it done in a separate commit, though that is helpful. ### No direct use of process state outside rustup::currentprocess The `rustup::currentprocess` module abstracts the global state that is `std::env::args`, `std::env::vars`, `std::io::std*`, `std::process::id`, `std::env::current_dir` and `std::process::exit` permitting threaded tests of the CLI logic; use `process()` rather than those APIs directly. ### Clippy lints We do not enforce lint status in the checks done by GitHub Actions, because clippy is a moving target that can make it hard to merge for little benefit. We do ask that contributors keep the clippy status clean themselves. Minimally, run `cargo +nightly clippy --all --all-targets -- -D warnings` before submitting code. If possible, adding `--all-features` to the command is useful, but will require additional dependencies like `libcurl-dev`. Regular contributors or contributors to particularly OS-specific code should also make sure that their clippy checking is done on at least Linux and Windows, as OS-conditional code is a common source of unused imports and other small lints, which can build up over time. For developers using BSD/Linux/Mac OS, there are Windows VM's suitable for such development tasks for use with virtualbox and other hypervisors are downloadable from [Microsoft](https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/). Similarly, there are many Linux and Unix operating systems images available for developers whose usual operating system is Windows. Currently Rustup has no Mac OS specific code, so there should be no need to worry about Mac VM images. Clippy is also run in GitHub Actions, in the `General Checks / Checks` build task, but not currently run per-platform, which means there is no way to find out the status of clippy per platform without running it on that platform as a developer. ## Version numbers If you ever see a released version of rustup which has `::` in its version string then something went wrong with the CI and that needs to be addressed. We use `git-testament` to construct our version strings. This records, as a struct, details of the git commit, tag description, and also an indication of modifications to the working tree present when the binary was compiled. During normal development you may get information from invoking `rustup --version` which looks like `rustup-init 1.18.3+15 (a54051502 2019-05-26)` or even `rustup-init 1.18.3+15 (a54051502 2019-05-26) dirty 1 modification`. The first part is always the binary name as per `clap`'s normal operation. The version number is a combination of the most recent tag in the git repo, and the number of commits since that tag. The parenthesised information is, naturally, the SHA of the most recent commit and the date of that commit. If the indication of a dirty tree is present, the number of changes is indicated. This combines adds, deletes, modifies, and unknown entries. You can request further information of a `rustup` binary with the `rustup dump-testament` hidden command. It produces output of the form: ```shell $ rustup dump-testament Rustup version renders as: 1.18.3+15 (a54051502 2019-05-26) dirty 1 modification Current crate version: 1.18.3 Built from branch: kinnison/version-strings Commit info: 1.18.3+15 (a54051502 2019-05-26) Modified: CONTRIBUTING.md ``` This can be handy when you are testing development versions on your PC and cannot remember exactly which version you had installed, or if you have given a development copy (or instruction to build such) to a user, and wish to have them confirm _exactly_ what they are using. Finally, we tell `git-testament` that we trust the `stable` branch to carry releases. If the build is being performed when not on the `stable` branch, and the tag and `CARGO_PKG_VERSION` differ, then the short version string will include both, in the form `rustup-init 1.18.3 :: 1.18.2+99 (a54051502 2019-05-26)` which indicates the crate version before the rest of the commit. On the other hand, if the build was on the `stable` branch then regardless of the tag information, providing the commit was clean, the version is always replaced by the crate version. The `dump-testament` hidden command can reveal the truth however. ## Making a release Before making a release, ensure that `rustup-init.sh` is behaving correctly, and that you're satisfied that nothing in the ecosystem is breaking because of the update. A useful set of things to check includes verifying that real-world toolchains install okay, and that `rls-vscode` isn't broken by the release. While it's not our responsibility if they depend on non-stable APIs, we should behave well if we can. Producing the final release artifacts is a bit involved because of the way Rustup is distributed. The steps for a release are: 1. Update `Cargo.toml` and `download/Cargo.toml`to have the same new version (optionally make a commit) 2. Run `cargo build` and review `Cargo.lock` changes if all looks well, make a commit 3. Update `rustup-init.sh` version to match the commit details for `Cargo.lock` 4. Push this to the `stable` branch (git push origin HEAD:stable) 5. While you wait for green CI, double-check the `rustup-init.sh` functionality and `rustup-init` just in case. 6. Ensure all of CI is green on the `stable` branch. Once it is, check through a representative proportion of the builds looking for the reported version statements to ensure that we definitely built something cleanly which reports as the right version number when run `--version`. 7. Ping someone in the release team to perform the actual release. They can find instructions in `ci/sync-dist.py` Note: Some manual testing occurs here, so hopefully they'll catch anything egregious in which case abort the change and roll back. 8. Once the official release has happened, prepare and push a tag of that commit, and also push the content to master - `git tag -as $VER_NUM -m $VER_NUM` (optionally without -s if not GPG signing the tag) - `git push origin HEAD:master` - `git push origin $VER_NUM` ## Developer tips and tricks ### `RUSTUP_FORCE_ARG0` The environment variable `RUSTUP_FORCE_ARG0` can be used to get rustup to think it's a particular binary, rather than e.g. copying it, symlinking it or other tricks with exec. This is handy when testing particular code paths from cargo run. ```shell RUSTUP_FORCE_ARG0=rustup cargo run -- uninstall nightly ``` ### `RUSTUP_BACKTRACK_LIMIT` If it's necessary to alter the backtracking limit from the default of half a release cycle for some reason, you can set the `RUSTUP_BACKTRACK_LIMIT` environment variable. If this is unparseable as an `i32` or if it's absent then the default of 21 days (half a cycle) is used. If it parses and is less than 1, it is clamped to 1 at minimum. This is not meant for use by users, but can be suggested in diagnosing an issue should one arise with the backtrack limits. ### `RUSTUP_MAX_RETRIES` When downloading a file, rustup will retry the download a number of times. The default is 3 times, but if this variable is set to a valid usize then it is the max retry count. A value of `0` means no retries, thus the default of `3` will mean a download is tried a total of four times before failing out. ### `RUSTUP_BACKTRACE` By default while running tests, we unset some environment variables that will break our testing (like `RUSTUP_TOOLCHAIN`, `SHELL`, `ZDOTDIR`, `RUST_BACKTRACE`). But if you want to debug locally, you may need backtrace. `RUSTUP_BACKTRACE` is used like `RUST_BACKTRACE` to enable backtraces of failed tests. **NOTE**: This is a backtrace for the test, not for any rustup process running in the test ```bash $ RUSTUP_BACKTRACE=1 cargo test --release --test cli-v1 -- remove_toolchain_then_add_again Finished release [optimized] target(s) in 0.38s Running target\release\deps\cli_v1-1f29f824792f6dc1.exe running 1 test test remove_toolchain_then_add_again ... FAILED failures: ---- remove_toolchain_then_add_again stdout ---- thread 'remove_toolchain_then_add_again' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 1142, kind: Other, message: "An attempt was made to create more links on a file than the file system supports." }', src\libcore\result.rs:999:5 stack backtrace: 0: backtrace::backtrace::trace_unsynchronized at C:\Users\appveyor\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.29\src\backtrace\mod.rs:66 1: std::sys_common::backtrace::_print at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\sys_common\backtrace.rs:47 2: std::sys_common::backtrace::print at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\sys_common\backtrace.rs:36 3: std::panicking::default_hook::{{closure}} at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\panicking.rs:198 4: std::panicking::default_hook at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\panicking.rs:209 5: std::panicking::rust_panic_with_hook at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\panicking.rs:475 6: std::panicking::continue_panic_fmt at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\panicking.rs:382 7: std::panicking::rust_begin_panic at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libstd\panicking.rs:309 8: core::panicking::panic_fmt at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libcore\panicking.rs:85 9: core::result::unwrap_failed 10: cli_v1::mock::clitools::test 11: alloc::boxed::{{impl}}::call_once<(),FnOnce<()>> at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\src\liballoc\boxed.rs:746 12: panic_unwind::__rust_maybe_catch_panic at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libpanic_unwind\lib.rs:82 13: std::panicking::try at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\src\libstd\panicking.rs:273 14: std::panic::catch_unwind at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\src\libstd\panic.rs:388 15: test::run_test::run_test_inner::{{closure}} at /rustc/de02101e6d949c4a9040211e9ce8c488a997497e\/src\libtest\lib.rs:1466 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. failures: remove_toolchain_then_add_again test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 26 filtered out error: test failed, to rerun pass '--test cli-v1' ``` rustup-1.26.0/Cargo.lock000066400000000000000000001710721441327105200150560ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "aligned" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80a21b9440a626c7fc8573a9e3d3a06b75c7c97754c2949bc7857b90353ca655" dependencies = [ "as-slice", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "as-slice" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" dependencies = [ "stable_deref_trait", ] [[package]] name = "async-compression" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" dependencies = [ "flate2", "futures-core", "memchr", "pin-project-lite", "tokio", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] [[package]] name = "bstr" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", "once_cell", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", "num-integer", "num-traits", "time 0.1.45", "wasm-bindgen", "winapi", ] [[package]] name = "clap" version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", "clap_lex", "indexmap", "strsim", "termcolor", "terminal_size", "textwrap", ] [[package]] name = "clap_complete" version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" dependencies = [ "clap", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "codespan-reporting" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", "unicode-width", ] [[package]] name = "concolor" version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7b3e3c41e9488eeda196b6806dbf487742107d61b2e16485bcca6c25ed5755b" dependencies = [ "bitflags", "concolor-query", "is-terminal", ] [[package]] name = "concolor-query" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" [[package]] name = "content_inspector" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" dependencies = [ "memchr", ] [[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "crossbeam-channel" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "curl" version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", "socket2", "winapi", ] [[package]] name = "curl-sys" version = "0.4.60+curl-7.88.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "717abe2cb465a5da6ce06617388a3980c9a2844196734bec8ccb8e575250f13f" dependencies = [ "cc", "libc", "libz-sys", "openssl-sys", "pkg-config", "vcpkg", "winapi", ] [[package]] name = "cvt" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac344c7efccb80cd25bc61b2170aec26f2f693fd40e765a539a1243db48c71" dependencies = [ "cfg-if 0.1.10", ] [[package]] name = "cxx" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" dependencies = [ "cc", "cxxbridge-flags", "cxxbridge-macro", "link-cplusplus", ] [[package]] name = "cxx-build" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" dependencies = [ "cc", "codespan-reporting", "once_cell", "proc-macro2", "quote", "scratch", "syn", ] [[package]] name = "cxxbridge-flags" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" [[package]] name = "cxxbridge-macro" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "digest" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "download" version = "1.26.0" dependencies = [ "anyhow", "curl", "env_proxy", "hyper", "lazy_static", "reqwest", "tempfile", "thiserror", "tokio", "url", ] [[package]] name = "dunce" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "effective-limits" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37195f01a7464b2bc99ba33c5b2b61929bb294632bce96987f88e2ade8e29a07" dependencies = [ "cfg-if 0.1.10", "libc", "sys-info", "thiserror", "winapi", ] [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "enum-map" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50c25992259941eb7e57b936157961b217a4fc8597829ddef0596d6c3cd86e1a" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4da76b3b6116d758c7ba93f7ec6a35d2e2cf24feda76c6e38a375f4d5c59f2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "env_proxy" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a5019be18538406a43b5419a5501461f0c8b49ea7dfda0cfc32f4e51fc44be1" dependencies = [ "log", "url", ] [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "filetime" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "windows-sys 0.45.0", ] [[package]] name = "flate2" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ "percent-encoding", ] [[package]] name = "fs_at" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3dfc546714a30e1e1f2eb5e1d233a6c1254c4a3d25ac35c09743e824a39d35e" dependencies = [ "aligned", "cfg-if 1.0.0", "cvt", "libc", "nix", "smart-default", "windows-sys 0.45.0", ] [[package]] name = "futures-channel" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-io" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-sink" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", "futures-io", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "git-testament" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986bf57c808270f3a0a0652c3bfce0f5d667aa5f5b465616dc697c7f390834b1" dependencies = [ "git-testament-derive", "no-std-compat", ] [[package]] name = "git-testament-derive" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a782db5866c7ab75f3552dda4cbf34e3e257cc64c963c6ed5af1e12818e8ae6" dependencies = [ "log", "proc-macro2", "quote", "syn", "time 0.3.20", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "home" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" dependencies = [ "winapi", ] [[package]] name = "http" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", "pin-project-lite", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "humantime-serde" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ "humantime", "serde", ] [[package]] name = "hyper" version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", "want", ] [[package]] name = "hyper-rustls" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", "rustls", "tokio", "tokio-rustls", ] [[package]] name = "hyper-tls" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", "native-tls", "tokio", "tokio-native-tls", ] [[package]] name = "iana-time-zone" version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "winapi", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", ] [[package]] name = "idna" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "io-lifetimes" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", "windows-sys 0.45.0", ] [[package]] name = "ipnet" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", "windows-sys 0.45.0", ] [[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libz-sys" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "link-cplusplus" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] [[package]] name = "linux-raw-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "lzma-sys" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] [[package]] name = "native-tls" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if 1.0.0", "libc", "static_assertions", ] [[package]] name = "no-std-compat" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "normpath" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "972dec05f98e7c787ede35d7a9ea4735eb7788c299287352757b3def6cc1f7b5" dependencies = [ "windows-sys 0.45.0", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opener" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "293c15678e37254c15bd2f092314abb4e51d7fdde05c2021279c12631b54f005" dependencies = [ "bstr", "winapi", ] [[package]] name = "openssl" version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" version = "111.25.1+1.1.1t" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ef9a9cc6ea7d9d5e7c4a913dc4b48d0e359eddf01af1dfec96ba7064b4aba10" dependencies = [ "cc", ] [[package]] name = "openssl-sys" version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", "libc", "openssl-src", "pkg-config", "vcpkg", ] [[package]] name = "os_pipe" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a53dbb20faf34b16087a931834cba2d7a73cc74af2b7ef345a4c8324e2409a12" dependencies = [ "libc", "windows-sys 0.45.0", ] [[package]] name = "os_str_bytes" version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "pulldown-cmark" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" dependencies = [ "bitflags", "memchr", "unicase", ] [[package]] name = "quote" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7174320e07c29945955cedd70b865995b286847111c8308d349a1f3a9e3af555" dependencies = [ "aligned", "cfg-if 1.0.0", "cvt", "fs_at", "lazy_static", "libc", "normpath", "rayon", "windows-sys 0.45.0", ] [[package]] name = "reqwest" version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ "async-compression", "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "hyper", "hyper-rustls", "hyper-tls", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls", "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", "tokio-rustls", "tokio-socks", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "winreg 0.10.1", ] [[package]] name = "retry" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" dependencies = [ "rand", ] [[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", "once_cell", "spin", "untrusted", "web-sys", "winapi", ] [[package]] name = "rs_tracing" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b121670da627e1c0110e7972c9db150dd7f8704dc073cce32c3db9cb7861e0" dependencies = [ "serde", "serde_json", ] [[package]] name = "rustix" version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.45.0", ] [[package]] name = "rustls" version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", "sct", "webpki", ] [[package]] name = "rustls-native-certs" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ "base64", ] [[package]] name = "rustup" version = "1.26.0" dependencies = [ "anyhow", "cc", "cfg-if 1.0.0", "chrono", "clap", "clap_complete", "download", "effective-limits", "enum-map", "flate2", "git-testament", "home", "lazy_static", "libc", "num_cpus", "once_cell", "opener", "openssl", "pulldown-cmark", "rand", "regex", "remove_dir_all", "retry", "rs_tracing", "same-file", "scopeguard", "semver", "serde", "sha2", "sharded-slab", "strsim", "tar", "tempfile", "term", "thiserror", "threadpool", "toml", "trycmd", "url", "wait-timeout", "walkdir", "winapi", "winreg 0.11.0", "xz2", "zstd", ] [[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ "windows-sys 0.42.0", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", ] [[package]] name = "security-framework" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sha2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] [[package]] name = "shlex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "similar" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "slab" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "smart-default" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "snapbox" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389a6395e9925166f19d67b64874e526ec28a4b8455f3321b686c912299c3ea" dependencies = [ "concolor", "content_inspector", "dunce", "filetime", "libc", "normalize-line-endings", "os_pipe", "similar", "snapbox-macros", "tempfile", "wait-timeout", "walkdir", "windows-sys 0.45.0", "yansi", ] [[package]] name = "snapbox-macros" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "485e65c1203eb37244465e857d15a26d3a85a5410648ccb53b18bd44cb3a7336" [[package]] name = "socket2" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", ] [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sys-info" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" dependencies = [ "cc", "libc", ] [[package]] name = "tar" version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" dependencies = [ "filetime", "libc", "xattr", ] [[package]] name = "tempfile" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall", "rustix", "windows-sys 0.42.0", ] [[package]] name = "term" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" dependencies = [ "byteorder", "winapi", ] [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" dependencies = [ "rustix", "windows-sys 0.45.0", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" dependencies = [ "terminal_size", ] [[package]] name = "thiserror" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "threadpool" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ "num_cpus", ] [[package]] name = "time" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "time" version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "pin-project-lite", "socket2", "windows-sys 0.42.0", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", "tokio", "webpki", ] [[package]] name = "tokio-socks" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" dependencies = [ "either", "futures-util", "thiserror", "tokio", ] [[package]] name = "tokio-util" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "toml_datetime" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "trycmd" version = "0.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2311fe1144338119b5b9b31499286c7f60eaf00ce0dcacf5a445a12eb47aed29" dependencies = [ "glob", "humantime", "humantime-serde", "rayon", "serde", "shlex", "snapbox", "toml_edit", ] [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ "log", "try-lock", ] [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "winnow" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658" dependencies = [ "memchr", ] [[package]] name = "winreg" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] [[package]] name = "winreg" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" dependencies = [ "cfg-if 1.0.0", "winapi", ] [[package]] name = "xattr" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] [[package]] name = "xz2" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ "lzma-sys", ] [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "6.0.4+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7afb4b54b8910cf5447638cb54bf4e8a65cbedd783af98b98c62ffe91f185543" dependencies = [ "libc", "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.7+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" dependencies = [ "cc", "libc", "pkg-config", ] rustup-1.26.0/Cargo.toml000066400000000000000000000062041441327105200150730ustar00rootroot00000000000000[package] authors = [ "Daniel Silverstone ", "Diggory Blake ", ] build = "build.rs" description = "Manage multiple rust installations with ease" edition = "2021" homepage = "https://github.com/rust-lang/rustup" keywords = ["rustup", "multirust", "install", "proxy"] license = "MIT OR Apache-2.0" name = "rustup" readme = "README.md" repository = "https://github.com/rust-lang/rustup" version = "1.26.0" [features] curl-backend = ["download/curl-backend"] default = [ "curl-backend", "reqwest-backend", "reqwest-default-tls", "reqwest-rustls-tls", ] reqwest-backend = ["download/reqwest-backend"] vendored-openssl = ['openssl/vendored'] reqwest-default-tls = ["download/reqwest-default-tls"] reqwest-rustls-tls = ["download/reqwest-rustls-tls"] # Include in the default set to disable self-update and uninstall. no-self-update = [] # Sorted by alphabetic order [dependencies] anyhow.workspace = true cfg-if = "1.0" chrono = "0.4" clap = { version = "3", features = ["wrap_help"] } clap_complete = "3" download = { path = "download", default-features = false } effective-limits = "0.5.5" enum-map = "2.4.2" flate2 = "1" git-testament = "0.2" home = "0.5.4" lazy_static.workspace = true libc = "0.2" num_cpus = "1.15" opener = "0.5.2" # Used by `curl` or `reqwest` backend although it isn't imported by our rustup : # this allows controlling the vendoring status without exposing the presence of # the download crate. openssl = { version = "0.10", optional = true } pulldown-cmark = { version = "0.9", default-features = false } rand = "0.8" regex = "1" remove_dir_all = { version = "0.8.1", features = ["parallel"] } same-file = "1" scopeguard = "1" semver = "1.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10" sharded-slab = "0.1.1" strsim = "0.10" tar = "0.4.26" tempfile.workspace = true # FIXME(issue #1818, #1826, and friends) term = "=0.5.1" thiserror.workspace = true threadpool = "1" toml = "0.5" url.workspace = true wait-timeout = "0.2" xz2 = "0.1.3" zstd = "0.12" [dependencies.retry] default-features = false features = ["random"] version = "1.3.1" [dependencies.rs_tracing] features = ["rs_tracing"] version = "1.1.0" [target."cfg(windows)".dependencies] cc = "1" winreg = "0.11" [target."cfg(windows)".dependencies.winapi] features = [ "combaseapi", "errhandlingapi", "fileapi", "handleapi", "ioapiset", "jobapi", "jobapi2", "minwindef", "processthreadsapi", "psapi", "shlobj", "shtypes", "synchapi", "sysinfoapi", "tlhelp32", "userenv", "winbase", "winerror", "winioctl", "winnt", "winuser", ] version = "0.3" [dev-dependencies] enum-map = "2.4.2" once_cell = "1.17.1" trycmd = "0.14.13" walkdir = "2" [build-dependencies] lazy_static = "1" regex = "1" [workspace] members = ["download"] [workspace.dependencies] anyhow = "1.0.69" lazy_static = "1" tempfile = "3.4" thiserror = "1.0" url = "2.3" [lib] name = "rustup" path = "src/lib.rs" [profile.release] codegen-units = 1 lto = true # Reduce build time by setting proc-macro crates non optimized. [profile.release.build-override] opt-level = 0 rustup-1.26.0/LICENSE-APACHE000066400000000000000000000251421441327105200150710ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rustup-1.26.0/LICENSE-MIT000066400000000000000000000020571441327105200146010ustar00rootroot00000000000000Copyright (c) 2016 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rustup-1.26.0/README.md000066400000000000000000000032301441327105200144160ustar00rootroot00000000000000# Rustup: the Rust toolchain installer | Master CI | Build Status | |--------------|----------------------------------------------------------| | Windows | ![Windows builds][actions-windows-master] | | macOS | ![maOS builds][actions-macos-master] | | Linux Etc | ![Linux (etc) builds][actions-linux-master] | *Rustup* installs [The Rust Programming Language][rustlang] from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling simpler with binary builds of the standard library for common platforms. And it runs on all platforms Rust supports, including Windows. [rustlang]: https://www.rust-lang.org ## Documentation See [**The Rustup book**](https://rust-lang.github.io/rustup/) for documentation on installing and using Rustup. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for information on contributing to Rustup. ## License Copyright Diggory Blake, the Mozilla Corporation, and Rustup contributors. Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) at your option. [actions-windows-master]: https://github.com/rust-lang/rustup/workflows/Windows%20(master)/badge.svg [actions-macos-master]: https://github.com/rust-lang/rustup/workflows/macOS/badge.svg?branch=master [actions-linux-master]: https://github.com/rust-lang/rustup/workflows/Linux%20(master)/badge.svg rustup-1.26.0/build.rs000066400000000000000000000017561441327105200146170ustar00rootroot00000000000000use std::env; include!("src/dist/triple.rs"); fn from_build() -> Result { let triple = if let Ok(triple) = env::var("RUSTUP_OVERRIDE_BUILD_TRIPLE") { triple } else { env::var("TARGET").unwrap() }; PartialTargetTriple::new(&triple).ok_or(triple) } fn main() { println!("cargo:rerun-if-env-changed=RUSTUP_OVERRIDE_BUILD_TRIPLE"); println!("cargo:rerun-if-env-changed=TARGET"); match from_build() { Ok(triple) => eprintln!("Computed build based partial target triple: {triple:#?}"), Err(s) => { eprintln!("Unable to parse target '{s}' as a PartialTargetTriple"); eprintln!( "If you are attempting to bootstrap a new target you may need to adjust the\n\ permitted values found in src/dist/triple.rs" ); std::process::abort(); } } let target = env::var("TARGET").unwrap(); println!("cargo:rustc-env=TARGET={target}"); } rustup-1.26.0/ci/000077500000000000000000000000001441327105200135345ustar00rootroot00000000000000rustup-1.26.0/ci/actions-templates/000077500000000000000000000000001441327105200171705ustar00rootroot00000000000000rustup-1.26.0/ci/actions-templates/README.md000066400000000000000000000127031441327105200204520ustar00rootroot00000000000000# Rustup GitHub Actions Workflows This directory contains all the workflows we use in Rustup for GitHub Actions. ## Triggers for CI builds Rustup has five situations in which we perform CI builds: 1. On PR changes 2. On merge to master 3. Time-based rebuilds of master 4. Pushes to the stable branch 5. Renovate branches with dependency updates, tested before opening a PR or merging. They are assessed the same as a PR: if it would be good enough as a human proposed change, it's good enough as a robot proposed change. The goals for each of those situations are subtly different. For PR changes, we want to know as quickly as possible if the change is likely to be an issue. Once a change hits master, we want to know that all our targets build. Time based rebuilds of master are about determining if updates to the toolchain have caused us problems, and also to try and highlight if we have flaky tests. The stable branch is about making releases. Builds from that branch are uploaded to S3 so that we can then make a release of rustup. ## Targets we need to build We build for all the Tier one targets and a non-trivial number of the tier two targets of Rust. We do not even attempt tier three builds. We don't run the tests on all the targets because many are cross-built. If we cross-build we don't run the tests. All the builds which aren't mac or windows are built on an x86_64 system because that's the easiest way to get a performant system. | Target | Cross | Tier | On PR? | On master? | | ----------------------------- | ---------- | ----- | ------ | ---------- | | x86_64-unknown-linux-gnu | No | One | Yes | Yes | | armv7-unknown-linux-gnueabihf | Yes | Two | Yes | Yes | | aarch64-linux-android | Yes | Two | Yes | Yes | | aarch64-unknown-linux-gnu | Yes | Two | No | Yes | | aarch64-unknown-linux-musl | Yes | Two | No | Yes | | powerpc64-unknown-linux-gnu | Yes | Two | No | Yes | | x86_64-unknown-linux-musl | Yes | Two | No | Yes | | i686-unknown-linux-gnu | Yes | One | No | No | | arm-unknown-linux-gnueabi | Yes | Two | No | No | | arm-unknown-linux-gnueabihf | Yes | Two | No | No | | x86_64-unknown-freebsd | Yes | Two | No | No | | x86_64-unknown-netbsd | Yes | Two | No | No | | x86_64-unknown-illumos | Yes | Two | No | No | | powerpc-unknown-linux-gnu | Yes | Two | No | No | | powerpc64le-unknown-linux-gnu | Yes | Two | No | No | | mips-unknown-linux-gnu | Yes | Two | No | No | | mips64-unknown-linux-gnu | Yes | Two | No | No | | mipsel-unknown-linux-gnu | Yes | Two | No | No | | mips64el-unknown-linux-gnu | Yes | Two | No | No | | s390x-unknown-linux-gnu | Yes | Two | No | No | | arm-linux-androideabi | Yes | Two | No | No | | armv7-linux-androideabi | Yes | Two | No | No | | i686-linux-android | Yes | Two | No | No | | x86_64-linux-android | Yes | Two | No | No | | riscv64gc-unknown-linux-gnu | Yes | --- | No | No | | ----------------------------- | ---------- | ----- | ------ | ---------- | | aarch64-apple-darwin | Yes | Two | Yes | Yes | | x86_64-apple-darwin | No | One | Yes | Yes | | ----------------------------- | ---------- | ----- | ------ | ---------- | | x86_64-pc-windows-msvc | No | One | Yes | Yes | | x86_64-pc-windows-gnu | No | One | No | Yes | | i686-pc-windows-msvc | No | One | No | No | | i686-pc-windows-gnu | No | One | No | No | | aarch64-pc-windows-msvc | Yes | Two | No | Yes | We also have a clippy/shellcheck target which runs on x86_64 linux and is run in all cases. It does a `cargo fmt` check, a `cargo clippy` check on the beta toolchain, and also runs `rustup-init.sh` through to completion inside a centos 6 docker to ensure that we continue to work on there OK. ## Useful notes about how we run builds For the builds which run on x86_64 linux, we deliberately run inside a docker image which comes from `rust-lang/rust`'s CI so that we know we're linking against the same libc etc as the release of Rust. For the builds which run on Windows, we retrieve mingw from Rust's CI as well so that we're clearly using the right version of that. In all cases, we attempt to use the `rustup-init.sh` from the branch under test where at all possible, so that we spot errors in that ASAP. Given that, we prefer to not use a preinstalled rust/rustup at all if we can, so we start from as bare a VM as makes sense. For Windows builds, we use a Visual Studio 2017 image if we can. ## The workflows Due to limitations in how github workflows work, we have to create our workflows from template files and then commit them. The templates are in this directory, and the built workflows end up in the `.github/workflows` directory. `-all` always runs, `-on-pr` `-on-master` and `-on-stable` do the obvious. rustup-1.26.0/ci/actions-templates/centos-fmt-clippy-template.yaml000066400000000000000000000063101441327105200252420ustar00rootroot00000000000000# This is ci/actions-templates/centos-fmt-clippy.yaml # Do not edit this file in .github/workflows name: General Checks on: pull_request: branches: - "*" push: branches: - master - stable - renovate/* schedule: - cron: "30 0 * * 1" # Every Monday at half past midnight jobs: check: name: Checks runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ runner.os }}-cargo-clippy-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ runner.os }}-cargo-clippy-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Beta is up to date run: | if rustc +beta -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall beta fi rustup toolchain install --profile=minimal beta rustup default beta - name: Ensure we have the components we need run: | rustup component add rustfmt rustup component add clippy - name: Run the centos check within the docker image run: | docker run \ --volume "$PWD":/checkout:ro \ --workdir /checkout \ --tty \ --init \ --rm \ centos:7 \ sh ./ci/raw_init.sh - name: Run shell checks run: | shellcheck -x -s dash -- rustup-init.sh git ls-files -- '*.sh' | xargs shellcheck -x -s dash git ls-files -- '*.bash' | xargs shellcheck -x -s bash - name: Run formatting checks run: | cargo fmt --all --check - name: Run cargo check and clippy run: | cargo check --all --all-targets git ls-files -- '*.rs' | xargs touch cargo clippy --all --all-targets rustup-1.26.0/ci/actions-templates/gen-workflows.sh000077500000000000000000000011011441327105200223240ustar00rootroot00000000000000#!/bin/sh INPATH=$(dirname "$0") OUTPATH="${INPATH}/../../.github/workflows" gen_workflow () { grep -v "skip-$2" "$INPATH/$1-template.yaml" > "$OUTPATH/$1-on-$2.yaml" } mkdir -p "$OUTPATH" # macOS only has a single target so single flow gen_workflow macos-builds all gen_workflow windows-builds pr gen_workflow windows-builds master gen_workflow windows-builds stable gen_workflow linux-builds pr gen_workflow linux-builds master gen_workflow linux-builds stable # The clippy checks only have a single target and thus single flow gen_workflow centos-fmt-clippy all rustup-1.26.0/ci/actions-templates/linux-builds-template.yaml000066400000000000000000000177511441327105200243170ustar00rootroot00000000000000# This is ci/actions-templates/linux-builds-template.yaml # Do not edit this file in .github/workflows name: Linux (PR) # skip-master skip-stable name: Linux (master) # skip-pr skip-stable name: Linux (stable) # skip-master skip-pr on: pull_request: # skip-master skip-stable branches: # skip-master skip-stable - "*" # skip-master skip-stable push: # skip-pr branches: # skip-pr - master # skip-pr skip-stable - stable # skip-pr skip-master - renovate/* # skip-master skip-stable schedule: # skip-pr skip-stable - cron: "30 0 * * 1" # Every Monday at half past midnight UTC skip-pr skip-stable jobs: build: name: Build runs-on: ubuntu-latest strategy: fail-fast: false matrix: mode: - dev - release target: - x86_64-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - aarch64-linux-android - aarch64-unknown-linux-gnu # skip-pr - aarch64-unknown-linux-musl # skip-pr skip-master - powerpc64-unknown-linux-gnu # skip-pr - x86_64-unknown-linux-musl # skip-pr - i686-unknown-linux-gnu # skip-pr skip-master - arm-unknown-linux-gnueabi # skip-pr skip-master - arm-unknown-linux-gnueabihf # skip-pr skip-master - x86_64-unknown-freebsd # skip-pr skip-master - x86_64-unknown-netbsd # skip-pr skip-master - x86_64-unknown-illumos # skip-pr skip-master - powerpc-unknown-linux-gnu # skip-pr skip-master - powerpc64le-unknown-linux-gnu # skip-pr skip-master - mips-unknown-linux-gnu # skip-pr skip-master - mips64-unknown-linux-gnuabi64 # skip-pr skip-master - mipsel-unknown-linux-gnu # skip-pr skip-master - mips64el-unknown-linux-gnuabi64 # skip-pr skip-master - s390x-unknown-linux-gnu # skip-pr skip-master - arm-linux-androideabi # skip-pr skip-master - armv7-linux-androideabi # skip-pr skip-master - i686-linux-android # skip-pr skip-master - x86_64-linux-android # skip-pr skip-master - riscv64gc-unknown-linux-gnu # skip-pr skip-master include: - target: x86_64-unknown-linux-gnu run_tests: YES #snap_arch: amd64 - target: i686-unknown-linux-gnu # skip-pr skip-master #snap_arch: i386 # skip-pr skip-master - target: aarch64-unknown-linux-gnu # skip-pr #snap_arch: arm64 # skip-pr - target: armv7-unknown-linux-gnueabihf #snap_arch: armhf - target: powerpc64le-unknown-linux-gnu # skip-pr skip-master #snap_arch: ppc64el # skip-pr skip-master - target: s390x-unknown-linux-gnu # skip-pr skip-master #snap_arch: s390x # skip-pr skip-master steps: - name: Clone repo uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Stable is up to date run: | if rustc +stable -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall stable fi rustup toolchain install --profile=minimal stable - name: Ensure we have our goal target installed run: | rustup target install "$TARGET" - name: Determine which docker we need to run in run: | case "$TARGET" in *-linux-android*) DOCKER=android ;; # Android uses a local docker image *) DOCKER="$TARGET" ;; esac echo "DOCKER=$DOCKER" >> $GITHUB_ENV - name: Fetch the docker run: bash ci/fetch-rust-docker.bash "${TARGET}" - name: Maybe build a docker from there run: | if [ -f "ci/docker/$DOCKER/Dockerfile" ]; then docker build -t "$DOCKER" -f "ci/docker/${DOCKER}/Dockerfile" . fi - name: Run the build within the docker image env: BUILD_PROFILE: ${{ matrix.mode }} run: | mkdir -p "${PWD}/target" chown -R "$(id -u)":"$(id -g)" "${PWD}/target" docker run \ --entrypoint sh \ --env BUILD_PROFILE="${BUILD_PROFILE}" \ --env CARGO_HOME=/cargo \ --env CARGO_TARGET_DIR=/checkout/target \ --env LIBZ_SYS_STATIC=1 \ --env SKIP_TESTS="${SKIP_TESTS}" \ --env TARGET="${TARGET}" \ --init \ --rm \ --tty \ --user "$(id -u)":"$(id -g)" \ --volume "$(rustc --print sysroot)":/rustc-sysroot:ro \ --volume "${HOME}/.cargo:/cargo" \ --volume "${PWD}":/checkout:ro \ --volume "${PWD}"/target:/checkout/target \ --workdir /checkout \ "${DOCKER}" \ -c 'PATH="${PATH}":/rustc-sysroot/bin bash ci/run.bash' - name: Upload the built artifact uses: actions/upload-artifact@v3 if: matrix.mode == 'release' with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | pip3 install -U setuptools pip3 install awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | bash ci/prepare-deploy.bash - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws s3 cp --recursive deploy/ s3://dev-static-rust-lang-org/rustup/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/ci/actions-templates/macos-builds-template.yaml000066400000000000000000000122431441327105200242510ustar00rootroot00000000000000# This is ci/actions-templates/macos-builds-template.yaml # Do not edit this file in .github/workflows name: macOS on: pull_request: branches: - "*" push: branches: - master - stable - renovate/* schedule: - cron: "30 0 * * 1" # Every Monday at half past midnight UTC jobs: build: name: Build runs-on: macos-latest strategy: matrix: target: - x86_64-apple-darwin - aarch64-apple-darwin mode: - dev - release include: - target: x86_64-apple-darwin run_tests: YES steps: - uses: actions/checkout@v3 with: # v2 defaults to a shallow checkout, but we need at least to the previous tag fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | mkdir -p ~/.cargo/{registry,git} - name: Set environment variables appropriately for the build run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV echo "SKIP_TESTS=" >> $GITHUB_ENV echo "LZMA_API_STATIC=1" >> $GITHUB_ENV - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using ./rustup-init.sh run: | sh ./rustup-init.sh --default-toolchain=none --profile=minimal -y - name: Ensure Stable is up to date run: | if rustc +stable -vV >/dev/null 2>/dev/null; then rustup toolchain uninstall stable fi rustup toolchain install --profile=minimal stable - name: aarch64-specific items run: | # Use nightly for now rustup toolchain install --profile=minimal nightly rustup default nightly if: matrix.target == 'aarch64-apple-darwin' - name: Ensure we have our goal target installed run: | rustup target install "$TARGET" - name: Run a full build and test env: BUILD_PROFILE: ${{ matrix.mode }} run: bash ci/run.bash - name: Dump dynamic link targets if: matrix.mode == 'release' run: | otool -L target/${TARGET}/release/rustup-init if otool -L target/${TARGET}/release/rustup-init | grep -q -F /usr/local/; then echo >&2 "Unfortunately there are /usr/local things in the link. Fail." exit 1 fi - name: Upload the built artifact if: matrix.mode == 'release' uses: actions/upload-artifact@v3 with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | pip3 install awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | bash ci/prepare-deploy.bash - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws s3 cp --recursive deploy/ s3://dev-static-rust-lang-org/rustup/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache - name: Flush cache # This is a workaround for a bug with GitHub Actions Cache that causes # corrupt cache entries (particularly in the target directory). See # https://github.com/actions/cache/issues/403 and # https://github.com/rust-lang/cargo/issues/8603. run: sudo /usr/sbin/purge rustup-1.26.0/ci/actions-templates/windows-builds-template.yaml000066400000000000000000000151631441327105200246450ustar00rootroot00000000000000# This is ci/actions-templates/windows-builds-template.yaml # Do not edit this file in .github/workflows name: Windows (PR) # skip-master skip-stable name: Windows (master) # skip-pr skip-stable name: Windows (stable) # skip-master skip-pr on: pull_request: # skip-master skip-stable branches: # skip-master skip-stable - "*" # skip-master skip-stable push: # skip-pr branches: # skip-pr - master # skip-pr skip-stable - stable # skip-pr skip-master - renovate/* # skip-master skip-stable schedule: # skip-pr skip-stable - cron: "30 0 * * 1" # Every Monday at half past midnight UTC skip-pr skip-stable jobs: build: name: Build runs-on: windows-latest env: RUSTFLAGS: -Ctarget-feature=+crt-static strategy: fail-fast: false matrix: target: - x86_64-pc-windows-msvc - i686-pc-windows-msvc # skip-pr skip-master - aarch64-pc-windows-msvc # skip-pr - x86_64-pc-windows-gnu # skip-pr - i686-pc-windows-gnu # skip-pr skip-master mode: - dev - release include: - target: x86_64-pc-windows-msvc run_tests: YES - target: x86_64-pc-windows-gnu # skip-pr mingw: https://ci-mirrors.rust-lang.org/rustc/x86_64-6.3.0-release-posix-seh-rt_v5-rev2.7z # skip-pr mingwdir: mingw64 # skip-pr - target: i686-pc-windows-gnu # skip-pr skip-master mingwdir: mingw32 # skip-pr skip-master mingw: https://ci-mirrors.rust-lang.org/rustc/i686-6.3.0-release-posix-dwarf-rt_v5-rev2.7z # skip-pr skip-master steps: - uses: actions/checkout@v3 # v2 defaults to a shallow checkout, but we need at least to the previous tag with: fetch-depth: 0 - name: Acquire tags for the repo run: | git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* - name: Display the current git status run: | git status git describe - name: Prep cargo dirs run: | New-Item "${env:USERPROFILE}\.cargo\registry" -ItemType Directory -Force New-Item "${env:USERPROFILE}\.cargo\git" -ItemType Directory -Force shell: powershell - name: Install mingw run: | # We retrieve mingw from the Rust CI buckets # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest ${{ matrix.mingw }} -OutFile mingw.7z 7z x -y mingw.7z -oC:\msys64 | Out-Null del mingw.7z echo "C:\msys64\usr\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "C:\msys64\${{ matrix.mingwdir }}\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 shell: powershell if: matrix.mingw != '' - name: Set PATH run: | echo "%USERPROFILE%\.cargo\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 echo "TARGET=${{ matrix.target }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8 - name: Skip tests if: matrix.run_tests == '' || matrix.mode == 'release' run: | echo "SKIP_TESTS=yes" >> $GITHUB_ENV shell: bash - name: Cache cargo registry and git trees uses: actions/cache@v3 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Get rustc commit hash id: cargo-target-cache run: | echo "{rust_hash}={$(rustc -Vv | grep commit-hash | awk '{print $2}')}" >> $GITHUB_OUTPUT shell: bash - name: Cache cargo build uses: actions/cache@v3 with: path: target key: ${{ github.base_ref }}-${{ github.head_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ github.base_ref }}-${{ matrix.target }}-${{ matrix.mode }}-cargo-target-dir-${{ steps.cargo-target-cache.outputs.rust_hash }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Rustup using win.rustup.rs run: | # Disable the download progress bar which can cause perf issues $ProgressPreference = "SilentlyContinue" Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal del rustup-init.exe shell: powershell - name: Ensure stable toolchain is up to date run: rustup update stable shell: bash - name: Install the target run: | rustup target install ${{ matrix.target }} - name: Run a full build env: TARGET: ${{ matrix.target }} BUILD_PROFILE: ${{ matrix.mode }} run: bash ci/run.bash - name: Run cargo check and clippy if: matrix.mode != 'release' env: TARGET: ${{ matrix.target }} # os-specific code leads to lints escaping if we only run this in one target run: | cargo check --all --all-targets git ls-files -- '*.rs' | xargs touch cargo clippy --workspace --all-targets - name: Upload the built artifact if: matrix.mode == 'release' uses: actions/upload-artifact@v3 with: name: rustup-init-${{ matrix.target }} path: | target/${{ matrix.target }}/release/rustup-init.exe retention-days: 7 - name: Acquire the AWS tooling if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | choco upgrade awscli - name: Prepare the dist if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | .\ci\prepare-deploy.ps1 shell: powershell - name: Deploy build to dev-static dist tree for release team if: github.event_name == 'push' && github.ref == 'refs/heads/stable' && matrix.mode == 'release' run: | aws --debug s3 cp --recursive dist s3://dev-static-rust-lang-org/rustup/dist env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-1 - name: Clear the cargo caches run: | cargo install cargo-cache --no-default-features --features ci-autoclean cargo-cache rustup-1.26.0/ci/cirrus-templates/000077500000000000000000000000001441327105200170375ustar00rootroot00000000000000rustup-1.26.0/ci/cirrus-templates/freebsd.yaml000066400000000000000000000007501441327105200213370ustar00rootroot00000000000000# This is ci/actions-templates/linux-builds-template.yaml # Do not edit this file in .cirrus.yml task: name: FreeBSD freebsd_instance: image: freebsd-13-0-release-amd64 setup_script: | pkg install -y git gmake bash echo "=========" echo "create non-root user and log into it" pw group add -n tester pw user add -n tester -g tester -s `which bash` pw user mod tester -d `pwd` chown -R tester . sudo -u tester bash ci/cirrus-templates/script.bash rustup-1.26.0/ci/cirrus-templates/gen-workflows.sh000066400000000000000000000001441441327105200221760ustar00rootroot00000000000000#!/bin/sh INPATH=$(dirname "$0") OUTPATH="${INPATH}/../.." cp freebsd.yaml "$OUTPATH/.cirrus.yml" rustup-1.26.0/ci/cirrus-templates/script.bash000066400000000000000000000020331441327105200212000ustar00rootroot00000000000000#!/bin/bash set -ex # First, we check that this script is not run as root because it would fail tests. if [ "root" == "$(whoami)" ]; then exit 1; fi echo "=========" echo "Acquire tags for the repo" git fetch --no-tags --prune --depth=1 origin +refs/tags/*:refs/tags/* echo "=========" echo "Display the current git status" git status git describe echo "=========" echo "Prep cargo dirs" mkdir -p ~/.cargo/{registry,git} echo "=========" echo "Install Rustup using ./rustup-init.sh" sh rustup-init.sh --default-toolchain=stable --profile=minimal -y # It's the equivalent of `source` # shellcheck source=src/cli/self_update/env.sh source "$HOME"/.cargo/env echo "=========" echo "Ensure we have the components we need" rustup component add rustfmt rustup component add clippy echo "=========" echo "Run the freebsd check" unset SKIP_TESTS export LIBZ_SYS_STATIC=1 export CARGO_BUILD_JOBS=1 export TARGET="x86_64-unknown-freebsd" # TODO: This should be split into two as the other jobs are. export BUILD_PROFILE="release" bash ci/run.bash rustup-1.26.0/ci/cloudfront-invalidation.txt000066400000000000000000000061651441327105200211430ustar00rootroot00000000000000rustup/* rustup/www/* rustup/stable-release.toml rustup/dist/aarch64-linux-android/rustup-init rustup/dist/aarch64-linux-android/rustup-init.sha256 rustup/dist/aarch64-unknown-linux-gnu/rustup-init rustup/dist/aarch64-unknown-linux-gnu/rustup-init.sha256 rustup/dist/aarch64-unknown-linux-musl/rustup-init rustup/dist/aarch64-unknown-linux-musl/rustup-init.sha256 rustup/dist/arm-linux-androideabi/rustup-init rustup/dist/arm-linux-androideabi/rustup-init.sha256 rustup/dist/arm-unknown-linux-gnueabi/rustup-init rustup/dist/arm-unknown-linux-gnueabi/rustup-init.sha256 rustup/dist/arm-unknown-linux-gnueabihf/rustup-init rustup/dist/arm-unknown-linux-gnueabihf/rustup-init.sha256 rustup/dist/armv7-linux-androideabi/rustup-init rustup/dist/armv7-linux-androideabi/rustup-init.sha256 rustup/dist/armv7-unknown-linux-gnueabihf/rustup-init rustup/dist/armv7-unknown-linux-gnueabihf/rustup-init.sha256 rustup/dist/i686-apple-darwin/rustup-init rustup/dist/i686-apple-darwin/rustup-init.sha256 rustup/dist/i686-linux-android/rustup-init rustup/dist/i686-linux-android/rustup-init.sha256 rustup/dist/i686-pc-windows-gnu/rustup-init.exe rustup/dist/i686-pc-windows-gnu/rustup-init.exe.sha256 rustup/dist/i686-pc-windows-msvc/rustup-init.exe rustup/dist/i686-pc-windows-msvc/rustup-init.exe.sha256 rustup/dist/i686-unknown-linux-gnu/rustup-init rustup/dist/i686-unknown-linux-gnu/rustup-init.sha256 rustup/dist/mips-unknown-linux-gnu/rustup-init rustup/dist/mips-unknown-linux-gnu/rustup-init.sha256 rustup/dist/mips64-unknown-linux-gnuabi64/rustup-init rustup/dist/mips64-unknown-linux-gnuabi64/rustup-init.sha256 rustup/dist/mips64el-unknown-linux-gnuabi64/rustup-init rustup/dist/mips64el-unknown-linux-gnuabi64/rustup-init.sha256 rustup/dist/mipsel-unknown-linux-gnu/rustup-init rustup/dist/mipsel-unknown-linux-gnu/rustup-init.sha256 rustup/dist/powerpc-unknown-linux-gnu/rustup-init rustup/dist/powerpc-unknown-linux-gnu/rustup-init.sha256 rustup/dist/powerpc64-unknown-linux-gnu/rustup-init rustup/dist/powerpc64-unknown-linux-gnu/rustup-init.sha256 rustup/dist/powerpc64le-unknown-linux-gnu/rustup-init rustup/dist/powerpc64le-unknown-linux-gnu/rustup-init.sha256 rustup/dist/s390x-unknown-linux-gnu/rustup-init rustup/dist/s390x-unknown-linux-gnu/rustup-init.sha256 rustup/dist/x86_64-apple-darwin/rustup-init rustup/dist/x86_64-apple-darwin/rustup-init.sha256 rustup/dist/x86_64-linux-android/rustup-init rustup/dist/x86_64-linux-android/rustup-init.sha256 rustup/dist/x86_64-pc-windows-gnu/rustup-init.exe rustup/dist/x86_64-pc-windows-gnu/rustup-init.exe.sha256 rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe.sha256 rustup/dist/x86_64-unknown-freebsd/rustup-init rustup/dist/x86_64-unknown-freebsd/rustup-init.sha256 rustup/dist/x86_64-unknown-illumos/rustup-init rustup/dist/x86_64-unknown-illumos/rustup-init.sha256 rustup/dist/x86_64-unknown-linux-gnu/rustup-init rustup/dist/x86_64-unknown-linux-gnu/rustup-init.sha256 rustup/dist/x86_64-unknown-linux-musl/rustup-init rustup/dist/x86_64-unknown-linux-musl/rustup-init.sha256 rustup/dist/x86_64-unknown-netbsd/rustup-init rustup/dist/x86_64-unknown-netbsd/rustup-init.sha256 rustup-1.26.0/ci/deploy.bash000077500000000000000000000030651441327105200156760ustar00rootroot00000000000000#!/bin/bash # This script is used to do rustup releases. It requires an AWS access token # allowed to update our S3 buckets and to invalidate CloudFront distributions. # # Usage: # # 1. Deploy the release on the dev environment: # ./deploy.bash dev VERSION_NUMBER # # 2. Test everything works correctly: # RUSTUP_UPDATE_ROOT=https://dev-static.rust-lang.org/rustup rustup self update # # 3. Deploy the release to the prod environment: # ./deploy.bash prod VERSION_NUMBER set -euo pipefail IFS=$'\n\t' CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" LOCAL_RUSTUP_DIR="local-rustup" usage() { echo "usage: $0 {dev,prod} " exit 1 } run() { python3 "${CI_DIR}/sync-dist.py" "$@" --live-run } if [[ $# -ne 2 ]]; then usage fi mode="$1" version="$2" case "${mode}" in dev) # Ask for confirmation before clearing the local directory if [[ -e "${LOCAL_RUSTUP_DIR}" ]]; then read -rp "The directory ${LOCAL_RUSTUP_DIR} will be removed. Continue (y/n)?" choice case "${choice}" in y|Y) rm -rf "${LOCAL_RUSTUP_DIR}" ;; *) echo "Exiting..." exit 0 esac fi run dev-to-local run local-to-dev-archives "${version}" run update-dev-release "${version}" ;; prod) run local-to-prod-archives "${version}" run local-to-prod run update-prod-release "${version}" ;; *) usage ;; esac rustup-1.26.0/ci/docker/000077500000000000000000000000001441327105200150035ustar00rootroot00000000000000rustup-1.26.0/ci/docker/aarch64-unknown-linux-gnu/000077500000000000000000000000001441327105200216545ustar00rootroot00000000000000rustup-1.26.0/ci/docker/aarch64-unknown-linux-gnu/Dockerfile000066400000000000000000000002661441327105200236520ustar00rootroot00000000000000FROM rust-aarch64-unknown-linux-gnu ENV CC_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-gcc \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/aarch64-unknown-linux-musl/000077500000000000000000000000001441327105200220435ustar00rootroot00000000000000rustup-1.26.0/ci/docker/aarch64-unknown-linux-musl/Dockerfile000066400000000000000000000003551441327105200240400ustar00rootroot00000000000000FROM rust-aarch64-unknown-linux-musl ENV CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc \ RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-lgcc" rustup-1.26.0/ci/docker/android/000077500000000000000000000000001441327105200164235ustar00rootroot00000000000000rustup-1.26.0/ci/docker/android/Dockerfile000066400000000000000000000027721441327105200204250ustar00rootroot00000000000000FROM rust-android ENV PATH=$PATH:/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin \ AR_arm_linux_androideabi=llvm-ar \ AR_armv7_linux_androideabi=llvm-ar \ AR_aarch64_linux_android=llvm-ar \ AR_i686_linux_android=llvm-ar \ AR_x86_64_linux_android=llvm-ar \ RANLIB_arm_linux_androideabi=llvm-ranlib \ RANLIB_armv7_linux_androideabi=llvm-ranlib \ RANLIB_aarch64_linux_android=llvm-ranlib \ RANLIB_i686_linux_android=llvm-ranlib \ RANLIB_x86_64_linux_android=llvm-ranlib \ CC_arm_linux_androideabi=armv7a-linux-androideabi21-clang \ CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang \ CC_aarch64_linux_android=aarch64-linux-android21-clang \ CC_i686_linux_android=i686-linux-android21-clang \ CC_x86_64_linux_android=x86_64-linux-android21-clang \ CXX_arm_linux_androideabi=armv7a-linux-androideabi21-clang++ \ CXX_armv7_linux_androideabi=armv7a-linux-androideabi21-clang++ \ CXX_aarch64_linux_android=aarch64-linux-android21-clang++ \ CXX_i686_linux_android=i686-linux-android21-clang++ \ CXX_x86_64_linux_android=x86_64-linux-android21-clang++ \ CARGO_TARGET_ARM_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang \ CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang \ CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang \ CARGO_TARGET_I686_LINUX_ANDROID_LINKER=i686-linux-android21-clang \ CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=x86_64-linux-android21-clang rustup-1.26.0/ci/docker/arm-unknown-linux-gnueabi/000077500000000000000000000000001441327105200220245ustar00rootroot00000000000000rustup-1.26.0/ci/docker/arm-unknown-linux-gnueabi/Dockerfile000066400000000000000000000002661441327105200240220ustar00rootroot00000000000000FROM rust-arm-unknown-linux-gnueabi ENV CC_arm_unknown_linux_gnueabi=arm-unknown-linux-gnueabi-gcc \ CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER=arm-unknown-linux-gnueabi-gcc rustup-1.26.0/ci/docker/arm-unknown-linux-gnueabihf/000077500000000000000000000000001441327105200223425ustar00rootroot00000000000000rustup-1.26.0/ci/docker/arm-unknown-linux-gnueabihf/Dockerfile000066400000000000000000000003001441327105200243250ustar00rootroot00000000000000FROM rust-arm-unknown-linux-gnueabihf ENV CC_arm_unknown_linux_gnueabihf=arm-unknown-linux-gnueabihf-gcc \ CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-unknown-linux-gnueabihf-gcc rustup-1.26.0/ci/docker/armv7-unknown-linux-gnueabihf/000077500000000000000000000000001441327105200226175ustar00rootroot00000000000000rustup-1.26.0/ci/docker/armv7-unknown-linux-gnueabihf/Dockerfile000066400000000000000000000003121441327105200246050ustar00rootroot00000000000000FROM rust-armv7-unknown-linux-gnueabihf ENV CC_armv7_unknown_linux_gnueabihf=armv7-unknown-linux-gnueabihf-gcc \ CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=armv7-unknown-linux-gnueabihf-gcc rustup-1.26.0/ci/docker/i686-unknown-linux-gnu/000077500000000000000000000000001441327105200211205ustar00rootroot00000000000000rustup-1.26.0/ci/docker/i686-unknown-linux-gnu/Dockerfile000066400000000000000000000000411441327105200231050ustar00rootroot00000000000000FROM rust-i686-unknown-linux-gnu rustup-1.26.0/ci/docker/mips-unknown-linux-gnu/000077500000000000000000000000001441327105200213745ustar00rootroot00000000000000rustup-1.26.0/ci/docker/mips-unknown-linux-gnu/Dockerfile000066400000000000000000000002471441327105200233710ustar00rootroot00000000000000FROM rust-mips-unknown-linux-gnu ENV CC_mips_unknown_linux_gnu=mips-unknown-linux-gnu-gcc \ CARGO_TARGET_MIPS_UNKNOWN_LINUX_GNU_LINKER=mips-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/mips64-unknown-linux-gnuabi64/000077500000000000000000000000001441327105200223745ustar00rootroot00000000000000rustup-1.26.0/ci/docker/mips64-unknown-linux-gnuabi64/Dockerfile000066400000000000000000000003001441327105200243570ustar00rootroot00000000000000FROM rust-mips64-unknown-linux-gnuabi64 ENV CC_mips64_unknown_linux_gnuabi64=mips64-unknown-linux-gnu-gcc \ CARGO_TARGET_MIPS64_UNKNOWN_LINUX_GNUABI64_LINKER=mips64-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/mips64el-unknown-linux-gnuabi64/000077500000000000000000000000001441327105200227155ustar00rootroot00000000000000rustup-1.26.0/ci/docker/mips64el-unknown-linux-gnuabi64/Dockerfile000066400000000000000000000003121441327105200247030ustar00rootroot00000000000000FROM rust-mips64el-unknown-linux-gnuabi64 ENV CC_mips64el_unknown_linux_gnuabi64=mips64el-unknown-linux-gnu-gcc \ CARGO_TARGET_MIPS64EL_UNKNOWN_LINUX_GNUABI64_LINKER=mips64el-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/mipsel-unknown-linux-gnu/000077500000000000000000000000001441327105200217155ustar00rootroot00000000000000rustup-1.26.0/ci/docker/mipsel-unknown-linux-gnu/Dockerfile000066400000000000000000000002611441327105200237060ustar00rootroot00000000000000FROM rust-mipsel-unknown-linux-gnu ENV CC_mipsel_unknown_linux_gnu=mipsel-unknown-linux-gnu-gcc \ CARGO_TARGET_MIPSEL_UNKNOWN_LINUX_GNU_LINKER=mipsel-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/powerpc-unknown-linux-gnu/000077500000000000000000000000001441327105200221035ustar00rootroot00000000000000rustup-1.26.0/ci/docker/powerpc-unknown-linux-gnu/Dockerfile000066400000000000000000000002661441327105200241010ustar00rootroot00000000000000FROM rust-powerpc-unknown-linux-gnu ENV CC_powerpc_unknown_linux_gnu=powerpc-unknown-linux-gnu-gcc \ CARGO_TARGET_POWERPC_UNKNOWN_LINUX_GNU_LINKER=powerpc-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/powerpc64-unknown-linux-gnu/000077500000000000000000000000001441327105200222555ustar00rootroot00000000000000rustup-1.26.0/ci/docker/powerpc64-unknown-linux-gnu/Dockerfile000066400000000000000000000003001441327105200242400ustar00rootroot00000000000000FROM rust-powerpc64-unknown-linux-gnu ENV CC_powerpc64_unknown_linux_gnu=powerpc64-unknown-linux-gnu-gcc \ CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_LINKER=powerpc64-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/powerpc64le-unknown-linux-gnu/000077500000000000000000000000001441327105200225765ustar00rootroot00000000000000rustup-1.26.0/ci/docker/powerpc64le-unknown-linux-gnu/Dockerfile000066400000000000000000000002721441327105200245710ustar00rootroot00000000000000FROM rust-powerpc64le-unknown-linux-gnu ENV CC_powerpc64le_unknown_linux_gnu=powerpc64le-linux-gnu-gcc \ CARGO_TARGET_POWERPC64LE_UNKNOWN_LINUX_GNU_LINKER=powerpc64le-linux-gnu-gcc rustup-1.26.0/ci/docker/riscv64gc-unknown-linux-gnu/000077500000000000000000000000001441327105200222365ustar00rootroot00000000000000rustup-1.26.0/ci/docker/riscv64gc-unknown-linux-gnu/Dockerfile000066400000000000000000000002741441327105200242330ustar00rootroot00000000000000FROM rust-riscv64gc-unknown-linux-gnu ENV CC_riscv64gc_unknown_linux_gnu=riscv64-unknown-linux-gnu-gcc \ CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-unknown-linux-gnu-gcc rustup-1.26.0/ci/docker/s390x-unknown-linux-gnu/000077500000000000000000000000001441327105200213125ustar00rootroot00000000000000rustup-1.26.0/ci/docker/s390x-unknown-linux-gnu/Dockerfile000066400000000000000000000002441441327105200233040ustar00rootroot00000000000000FROM rust-s390x-unknown-linux-gnu ENV CC_s390x_unknown_linux_gnu=s390x-ibm-linux-gnu-gcc \ CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER=s390x-ibm-linux-gnu-gcc rustup-1.26.0/ci/docker/scripts/000077500000000000000000000000001441327105200164725ustar00rootroot00000000000000rustup-1.26.0/ci/docker/scripts/sccache.bash000066400000000000000000000011151441327105200207200ustar00rootroot00000000000000#!/bin/bash set -xe VERSION=0.2.12 TARGET=x86_64-unknown-linux-musl # from https://github.com/mozilla/sccache/releases SHA="26fd04c1273952cc2a0f359a71c8a1857137f0ee3634058b3f4a63b69fc8eb7f" DL_URL="https://github.com/mozilla/sccache/releases/download" BIN_DIR=/usr/local/bin TEMP_DIR=$(mktemp -d) TAR_NAME="sccache-${VERSION}-${TARGET}.tar.gz" cd "${TEMP_DIR}" mkdir -p "${BIN_DIR}" curl -sSL -O "${DL_URL}/${VERSION}/${TAR_NAME}" echo "${SHA} ${TAR_NAME}" | sha256sum --check - tar -xzf "${TAR_NAME}" --strip-components 1 cp sccache "${BIN_DIR}/sccache" chmod +x "${BIN_DIR}/sccache" rustup-1.26.0/ci/docker/x86_64-unknown-freebsd/000077500000000000000000000000001441327105200210465ustar00rootroot00000000000000rustup-1.26.0/ci/docker/x86_64-unknown-freebsd/Dockerfile000066400000000000000000000002571441327105200230440ustar00rootroot00000000000000FROM rust-x86_64-unknown-freebsd ENV CC_x86_64_unknown_freebsd=x86_64-unknown-freebsd12-clang \ CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_LINKER=x86_64-unknown-freebsd12-clang rustup-1.26.0/ci/docker/x86_64-unknown-illumos/000077500000000000000000000000001441327105200211205ustar00rootroot00000000000000rustup-1.26.0/ci/docker/x86_64-unknown-illumos/Dockerfile000066400000000000000000000004671441327105200231210ustar00rootroot00000000000000FROM rust-x86_64-unknown-illumos ENV \ AR_x86_64_unknown_illumos="x86_64-illumos-ar" \ RANLIB_x86_64_unknown_illumos="x86_64-illumos-ranlib" \ CC_x86_64_unknown_illumos=x86_64-illumos-gcc \ CXX_x86_64_unknown_illumos=x86_64-illumos-g++ \ CARGO_TARGET_X86_64_UNKNOWN_ILLUMOS_LINKER=x86_64-illumos-gcc rustup-1.26.0/ci/docker/x86_64-unknown-linux-gnu/000077500000000000000000000000001441327105200213625ustar00rootroot00000000000000rustup-1.26.0/ci/docker/x86_64-unknown-linux-gnu/Dockerfile000066400000000000000000000000431441327105200233510ustar00rootroot00000000000000FROM rust-x86_64-unknown-linux-gnu rustup-1.26.0/ci/docker/x86_64-unknown-linux-musl/000077500000000000000000000000001441327105200215515ustar00rootroot00000000000000rustup-1.26.0/ci/docker/x86_64-unknown-linux-musl/Dockerfile000066400000000000000000000003671441327105200235510ustar00rootroot00000000000000FROM ubuntu:22.04 COPY ci/docker/scripts/sccache.bash /scripts/ RUN \ apt-get update && \ apt-get install -qy \ musl-dev \ musl-tools \ curl \ ca-certificates \ perl \ make \ gcc && \ bash /scripts/sccache.bash rustup-1.26.0/ci/docker/x86_64-unknown-netbsd/000077500000000000000000000000001441327105200207135ustar00rootroot00000000000000rustup-1.26.0/ci/docker/x86_64-unknown-netbsd/Dockerfile000066400000000000000000000001521441327105200227030ustar00rootroot00000000000000FROM rust-x86_64-unknown-netbsd ENV CARGO_TARGET_X86_64_UNKNOWN_NETBSD_LINKER=x86_64--netbsd-gcc-sysroot rustup-1.26.0/ci/fetch-rust-docker.bash000066400000000000000000000043571441327105200177350ustar00rootroot00000000000000#!/bin/bash script_dir=$(cd "$(dirname "$0")" && pwd) # shellcheck source=ci/shared.bash . "$script_dir/shared.bash" set -e # Disable cause it makes shared script not to work properly #set -x TARGET="$1" RUST_REPO="https://github.com/rust-lang/rust" ARTIFACTS_BASE_URL="https://ci-artifacts.rust-lang.org/rustc-builds" LOCAL_DOCKER_TAG="rust-$TARGET" # Use images from rustc master case "$TARGET" in aarch64-unknown-linux-gnu) image=dist-aarch64-linux ;; aarch64-unknown-linux-musl) image=dist-arm-linux ;; arm-unknown-linux-gnueabi) image=dist-arm-linux ;; arm-unknown-linux-gnueabihf) image=dist-armhf-linux ;; armv7-unknown-linux-gnueabihf) image=dist-armv7-linux ;; i686-unknown-linux-gnu) image=dist-i686-linux ;; *-linux-android*) image=dist-android; LOCAL_DOCKER_TAG=rust-android ;; mips-unknown-linux-gnu) image=dist-mips-linux ;; mips64-unknown-linux-gnuabi64) image=dist-mips64-linux ;; mips64el-unknown-linux-gnuabi64) image=dist-mips64el-linux ;; mipsel-unknown-linux-gnu) image=dist-mipsel-linux ;; powerpc-unknown-linux-gnu) image=dist-powerpc-linux ;; powerpc64-unknown-linux-gnu) image=dist-powerpc64-linux ;; powerpc64le-unknown-linux-gnu) image=dist-powerpc64le-linux ;; s390x-unknown-linux-gnu) image=dist-s390x-linux ;; x86_64-unknown-freebsd) image=dist-x86_64-freebsd ;; 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 ;; *) exit ;; esac master=$(git ls-remote "$RUST_REPO" refs/heads/master | cut -f1) image_url="$ARTIFACTS_BASE_URL/$master/image-$image.txt" info="/tmp/image-$image.txt" rm -f "$info" curl -o "$info" "$image_url" digest=$(grep -m1 ^sha "$info") if [ -z "$(docker images -q "${LOCAL_DOCKER_TAG}")" ]; then url=$(grep -m1 ^https "$info") cache=/tmp/rustci_docker_cache echo "Attempting to download $url" rm -f "$cache" set +e command_retry curl -y 30 -Y 10 --connect-timeout 30 -f -L -C - -o "$cache" "$url" set -e docker load --quiet -i "$cache" docker tag "$digest" "${LOCAL_DOCKER_TAG}" fi rustup-1.26.0/ci/prepare-deploy.bash000066400000000000000000000017251441327105200173300ustar00rootroot00000000000000#!/bin/bash set -u -e # Copy rustup-init to rustup-setup for backwards compatibility cp target/"$TARGET"/release/rustup-init target/"$TARGET"/release/rustup-setup # Generate hashes if [ "$(uname -s)" = "Darwin" ]; then find target/"$TARGET"/release/ -maxdepth 1 -type f -exec sh -c 'fn="$1"; shasum -a 256 -b "$fn" > "$fn".sha256' sh {} \; else find target/"$TARGET"/release/ -maxdepth 1 -type f -exec sh -c 'fn="$1"; sha256sum -b "$fn" > "$fn".sha256' sh {} \; fi # The directory for deployment artifacts dest="deploy" # Prepare bins for upload bindest="$dest/dist/$TARGET" mkdir -p "$bindest/" cp target/"$TARGET"/release/rustup-init "$bindest/" cp target/"$TARGET"/release/rustup-init.sha256 "$bindest/" cp target/"$TARGET"/release/rustup-setup "$bindest/" cp target/"$TARGET"/release/rustup-setup.sha256 "$bindest/" if [ "$TARGET" != "x86_64-unknown-linux-gnu" ]; then exit 0 fi cp rustup-init.sh "$dest/" # Prepare website for upload cp -R www "$dest/www" rustup-1.26.0/ci/prepare-deploy.ps1000066400000000000000000000012011441327105200171030ustar00rootroot00000000000000 # Copy rustup-init to rustup-setup for backwards compatibility cp target\${env:TARGET}\release\rustup-init.exe target\${env:TARGET}\release\rustup-setup.exe # Generate hashes Get-FileHash .\target\${env:TARGET}\release\* | ForEach-Object {[io.file]::WriteAllText($_.Path + ".sha256", $_.Hash.ToLower() + "`n")} # Prepare bins for upload $dest = "dist\$env:TARGET" md -Force "$dest" cp target\${env:TARGET}\release\rustup-init.exe "$dest/" cp target\${env:TARGET}\release\rustup-init.exe.sha256 "$dest/" cp target\${env:TARGET}\release\rustup-setup.exe "$dest/" cp target\${env:TARGET}\release\rustup-setup.exe.sha256 "$dest/" ls "$dest" rustup-1.26.0/ci/raw_init.sh000066400000000000000000000002231441327105200157010ustar00rootroot00000000000000#!/bin/sh set -ex sh ./rustup-init.sh --default-toolchain none -y # shellcheck source=src/cli/self_update/env.sh . "$HOME"/.cargo/env rustup -Vv rustup-1.26.0/ci/run.bash000066400000000000000000000040221441327105200151750ustar00rootroot00000000000000#!/bin/bash set -ex export RUST_BACKTRACE=1 rustc -vV cargo -vV FEATURES=('--no-default-features' '--features' 'curl-backend,reqwest-backend,reqwest-default-tls') case "$(uname -s)" in *NT* ) ;; # Windows NT * ) FEATURES+=('--features' 'vendored-openssl') ;; esac case "$TARGET" in # these platforms aren't supported by ring: powerpc* ) ;; mips* ) ;; riscv* ) ;; s390x* ) ;; aarch64-pc-windows-msvc ) ;; # default case, build with rustls enabled * ) FEATURES+=('--features' 'reqwest-rustls-tls') ;; esac # rustc only supports armv7: https://doc.rust-lang.org/nightly/rustc/platform-support.html if [ "$TARGET" = arm-linux-androideabi ]; then export CFLAGS='-march=armv7' fi target_cargo() { cmd="$1" shift cargo "${cmd}" --locked --profile "$BUILD_PROFILE" --target "$TARGET" "${FEATURES[@]}" "$@" } target_cargo build download_pkg_test() { features=('--no-default-features' '--features' 'curl-backend,reqwest-backend,reqwest-default-tls') case "$TARGET" in # these platforms aren't supported by ring: powerpc* ) ;; mips* ) ;; riscv* ) ;; s390x* ) ;; aarch64-pc-windows-msvc ) ;; # default case, build with rustls enabled * ) features+=('--features' 'reqwest-rustls-tls') ;; esac cargo "$1" --locked --profile "$BUILD_PROFILE" --target "$TARGET" "${features[@]}" -p download } # Machines have 7GB of RAM, and our target/ contents is large enough that # thrashing will occur if we build-run-build-run rather than # build-build-build-run-run-run. build_test() { cmd="$1" shift download_pkg_test "${cmd}" if [ "build" = "${cmd}" ]; then target_cargo "${cmd}" --workspace --all-targets else # free runners have 2 or 3(mac) cores target_cargo "${cmd}" --workspace --tests -- --test-threads 2 fi if [ "build" != "${cmd}" ]; then target_cargo "${cmd}" --doc --workspace fi } if [ -z "$SKIP_TESTS" ]; then cargo run --locked --profile "$BUILD_PROFILE" --target "$TARGET" "${FEATURES[@]}" -- --dump-testament build_test build build_test test fi rustup-1.26.0/ci/shared.bash000066400000000000000000000020231441327105200156360ustar00rootroot00000000000000#!/bin/bash # # This file is intended to be sourced, so it is not being marked as # an executable file in git. # This is modified from # https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/bash/travis_setup_env.bash export ANSI_RED='\033[31;1m' export ANSI_GREEN='\033[32;1m' export ANSI_YELLOW='\033[33;1m' export ANSI_RESET='\033[0m' export ANSI_CLEAR='\033[0K' # This is modified loop version of # https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/bash/travis_retry.bash command_retry() { local result=0 local count=1 local max=5 while [ "${count}" -le "${max}" ]; do [ "${result}" -ne 0 ] && { printf "${ANSI_RED}"'The command "%s" failed. Retrying, %s of %s.'"${ANSI_RESET}"'\n' "${*}" "${count}" "${max}" >&2 } "${@}" && { result=0 && break; } || result="${?}" : $((count=count+1)) sleep 1 done [ "${count}" -gt "${max}" ] && { printf "${ANSI_RED}"'The command "%s" failed %s times.'"${ANSI_RESET}"'\n' "${*}" "${max}" >&2 } return "${result}" } rustup-1.26.0/ci/sync-dist.py000066400000000000000000000135241441327105200160300ustar00rootroot00000000000000# This script is used for syncing parts of the rustup dist server # between the dev environment (dev-static.rlo), the local machine, and # the prod environment (static.rlo). It's used during the deployment process. # # It is used after a successful CI build on the 'stable' branch. # # It does only a few things (this is the release process!): # # * Sync dev bins to local host: # python sync-dist.py dev-to-local # # * Sync local bins to dev archives # python sync-dist.py local-to-dev-archives 0.2.0 # # * Update dev release number # python sync-dist.py update-dev-release 0.2.0 # # Test here with `RUSTUP_UPDATE_ROOT=https://dev-static.rust-lang.org/rustup rustup self update` # # * Sync local bins to prod archives # python sync-dist.py local-to-prod-archives 0.2.0 # # * Sync local bins to prod # python sync-dist.py local-to-prod # # * Update prod release number # python sync-dist.py update-prod-release 0.2.0 # # Run the invalidation in cloudfront-invalidation.txt, # then tag the release. from __future__ import print_function import sys import os import subprocess import shutil def usage(): print("usage: sync-dist dev-to-local [--live-run]\n" " sync-dist local-to-dev-archives $version [--live-run]\n" " sync-dist update-dev-release $version [--live-run]\n" " sync-dist local-to-prod-archives $version [--live-run]\n" " sync-dist local-to-prod [--live-run]\n" " sync-dist update-prod-release $version [--live-run]\n") sys.exit(1) command = None archive_version = None live_run = False if len(sys.argv) < 2: usage() command = sys.argv[1] if not command in ["dev-to-local", "local-to-dev-archives", "update-dev-release", "local-to-prod-archives", "local-to-prod", "update-prod-release"]: usage() if "--live-run" in sys.argv: live_run = True sys.argv.remove("--live-run") if "archives" in command or "release" in command: if len(sys.argv) != 3: usage() archive_version = sys.argv[2] elif len(sys.argv) != 2: usage() dev_s3_bucket = "dev-static-rust-lang-org" prod_s3_bucket = "static-rust-lang-org" s3_bucket = dev_s3_bucket if "prod" in command: s3_bucket = prod_s3_bucket print("s3 bucket: " + s3_bucket) print("command: " + command) print("archive version: " + str(archive_version)) # First, deal with the binaries s3cmd = None if command == "dev-to-local": if os.path.exists("local-rustup/dist"): shutil.rmtree("local-rustup/dist") os.makedirs("local-rustup/dist") s3cmd = "aws s3 cp --recursive s3://{}/rustup/dist/ ./local-rustup/dist/".format(s3_bucket) elif command == "local-to-dev-archives" \ or command == "local-to-prod-archives": s3cmd = "aws s3 cp --recursive ./local-rustup/dist/ s3://{}/rustup/archive/{}/".format(s3_bucket, archive_version) elif command == "local-to-prod": s3cmd = "aws s3 cp --recursive local-rustup/dist/ s3://{}/rustup/dist/".format(s3_bucket) elif command == "update-dev-release" \ or command == "update-prod-release": s3cmd = "aws s3 cp ./local-rustup/release-stable.toml s3://{}/rustup/release-stable.toml".format(s3_bucket) else: sys.exit(1) print("s3 command: {}".format(s3cmd)) print() # Create the release information if command == "update-dev-release" \ or command == "update-prod-release": with open("./local-rustup/release-stable.toml", "w") as f: f.write("schema-version = '1'\n") f.write("version = '{}'\n".format(archive_version)) def run_s3cmd(command): s3cmd = command.split(" ") if not live_run: s3cmd += ["--dryrun"] # These are old installer names for compatibility. They don't need to # be touched ever again. if "cloudfront" not in command: s3cmd += ["--exclude=*rustup-setup*"] print('executing: ', s3cmd) subprocess.check_call(s3cmd) run_s3cmd(s3cmd) # Next deal with the rustup-init.sh script and website if command == "dev-to-local": if os.path.exists("local-rustup/rustup-init.sh"): os.remove("local-rustup/rustup-init.sh") run_s3cmd("aws s3 cp s3://{}/rustup/rustup-init.sh ./local-rustup/rustup-init.sh" .format(s3_bucket)) if os.path.exists("local-rustup/www"): shutil.rmtree("local-rustup/www") os.makedirs("local-rustup/www") run_s3cmd("aws s3 cp --recursive s3://{}/rustup/www/ ./local-rustup/www/" .format(s3_bucket)) if command == "local-to-prod": run_s3cmd("aws s3 cp ./local-rustup/rustup-init.sh s3://{}/rustup/rustup-init.sh" .format(s3_bucket)) run_s3cmd("aws s3 cp ./local-rustup/rustup-init.sh s3://{}/rustup.sh" .format(s3_bucket)) run_s3cmd("aws s3 cp --recursive ./local-rustup/www/ s3://{}/rustup/www/" .format(s3_bucket)) if live_run: # Invalidate sh.rustup.rs run_s3cmd("aws cloudfront create-invalidation --distribution-id " + "E70E9RGZ6Q27W --paths /*".format(s3_bucket)) # Invalidate win.rustup.rs run_s3cmd("aws cloudfront create-invalidation --distribution-id " + "E2XBMULPACBLNE --paths /*".format(s3_bucket)) # Invalidate rustup.rs run_s3cmd("aws cloudfront create-invalidation --distribution-id " + "EVJCMYBQ0EX26 --paths /*".format(s3_bucket)) if command == "update-dev-release" and live_run: run_s3cmd("aws cloudfront create-invalidation --distribution-id " + "E30AO2GXMDY230 --paths /rustup/*".format(s3_bucket)) # Invalidate dev.rustup.rs run_s3cmd("aws cloudfront create-invalidation --distribution-id " + "E3OQOQ34607Z0A --paths /*") if command == "update-prod-release" and live_run: run_s3cmd("aws cloudfront create-invalidation --distribution-id " + "E3NZU1LCBHH4A4 --paths /rustup/*".format(s3_bucket)) rustup-1.26.0/doc/000077500000000000000000000000001441327105200137065ustar00rootroot00000000000000rustup-1.26.0/doc/.gitignore000066400000000000000000000000321441327105200156710ustar00rootroot00000000000000# Ignore built book book/ rustup-1.26.0/doc/README.md000066400000000000000000000013111441327105200151610ustar00rootroot00000000000000# rustup documentation This directory contains rustup's documentation. ## Building the book Building the book requires [mdBook](https://github.com/rust-lang/mdBook). To get it: ```console $ cargo install mdbook ``` To build the book: ```console $ mdbook build ``` `mdbook` provides a variety of different commands and options to help you work on the book: * `mdbook build --open`: Build the book and open it in a web browser. * `mdbook serve`: Launches a web server on localhost. It also automatically rebuilds the book whenever any file changes and automatically reloads your web browser. The book contents are driven by the [`SUMMARY.md`](src/SUMMARY.md) file, and every file must be linked there. rustup-1.26.0/doc/book.toml000066400000000000000000000004471441327105200155420ustar00rootroot00000000000000[book] title = "The rustup book" authors = ["The Rust Project Developers"] [output.html] git-repository-url = "https://github.com/rust-lang/rustup/tree/master/doc" site-url = "https://rust-lang.github.io/rustup/" edit-url-template = "https://github.com/rust-lang/rustup/edit/master/doc/{path}" rustup-1.26.0/doc/src/000077500000000000000000000000001441327105200144755ustar00rootroot00000000000000rustup-1.26.0/doc/src/SUMMARY.md000066400000000000000000000014661441327105200161630ustar00rootroot00000000000000# Summary [Introduction](index.md) - [Installation](installation/index.md) - [Windows](installation/windows.md) - [MSVC Prerequistes](installation/windows-msvc.md) - [Other installation methods](installation/other.md) - [Package managers](installation/package-managers.md) - [Concepts](concepts/index.md) - [Channels](concepts/channels.md) - [Toolchains](concepts/toolchains.md) - [Components](concepts/components.md) - [Profiles](concepts/profiles.md) - [Proxies](concepts/proxies.md) - [Basic usage](basics.md) - [Overrides](overrides.md) - [Cross-compilation](cross-compilation.md) - [Environment variables](environment-variables.md) - [Configuration](configuration.md) - [Network proxies](network-proxies.md) - [Examples](examples.md) - [Security](security.md) - [FAQ](faq.md) rustup-1.26.0/doc/src/basics.md000066400000000000000000000051461441327105200162710ustar00rootroot00000000000000# Basic usage ## Keeping Rust up to date Rust is distributed on three different [release channels]: stable, beta, and nightly. `rustup` uses the stable channel by default, which represents the latest release of Rust. Stable publishes new releases every six weeks. [release channels]: concepts/channels.md When a new version of Rust is released, simply type `rustup update` to update: ```console $ rustup update info: syncing channel updates for 'stable' info: downloading component 'rustc' info: downloading component 'rust-std' info: downloading component 'rust-docs' info: downloading component 'cargo' info: installing component 'rustc' info: installing component 'rust-std' info: installing component 'rust-docs' info: installing component 'cargo' info: checking for self-update info: downloading self-update stable updated: rustc 1.7.0 (a5d1e7a59 2016-02-29) ``` ## Keeping `rustup` up to date If your `rustup` was built with the [no-self-update feature](https://github.com/rust-lang/rustup/blob/master/Cargo.toml#L25), it can not update itself. This is not the default, and only versions of `rustup` built with `--no-default-features`, or obtained from a third-party distributor who has disabled it (such as Nixos). Otherwise Rustup can update itself. It is possible to control Rustup's automatic self update mechanism with the `auto-self-update` configuration variable. This setting supports three values: `enable` and `disable` and `check-only`. * `disable` will ensure that no automatic self updating actions are taken. * `enable` will mean that `rustup update` and similar commands will also check for, and install, any update to Rustup. * `check-only` will cause any automatic self update to check and report on any updates, but not to automatically install them. Whether `auto-self-update` is `enable` or not, you can request that Rustup update itself to the latest version of `rustup` by running `rustup self update`. This will not download new toolchains: ```console $ rustup self update info: checking for self-update info: downloading self-update ``` ### Disabling self updates on a per-invocation basis > Self updates can also be suppressed on individual invocations of `rustup` by > passing the argurment `--no-self-update` when running `rustup update` or > `rustup toolchain install`. ## Help system The `rustup` command-line has a built-in help system that provides more information about each command. Run `rustup help` for an overview. Detailed help for each subcommand is also available. For example, run `rustup toolchain install --help` for specifics on installing [toolchains]. [toolchains]: concepts/toolchains.md rustup-1.26.0/doc/src/concepts/000077500000000000000000000000001441327105200163135ustar00rootroot00000000000000rustup-1.26.0/doc/src/concepts/channels.md000066400000000000000000000110051441327105200204250ustar00rootroot00000000000000# Channels Rust is released to three different "channels": stable, beta, and nightly. The stable releases are made every 6 weeks (with occasional point releases). Beta releases are the version that will appear in the next stable release. Nightly releases are made every night. See [The Rust Book][channels] for more details on Rust's train release model. The release schedule is posted to the [Rust Forge]. `rustup` assists with installing different channels, keeping them up-to-date, and easily switching between them. After a release channel has been installed, `rustup` can be used to update the installed version to the latest release on that channel. See the [Keeping rust up to date] section for more information. `rustup` can also install specific versions of Rust, such as `1.45.2` or `nightly-2020-07-27`. See the [Toolchains] chapter for more information on installing different channels and releases. See the [Overrides] chapter for details on switching between toolchains and pinning your project to a specific toolchain. [channels]: https://doc.rust-lang.org/book/appendix-07-nightly-rust.html [Keeping rust up to date]: ../basics.md#keeping-rust-up-to-date [rust forge]: https://forge.rust-lang.org/ [toolchains]: toolchains.md ## Working with nightly Rust `rustup` gives you easy access to the nightly compiler and its [experimental features]. To add it just run `rustup toolchain install nightly`: [experimental features]: https://doc.rust-lang.org/unstable-book/ ```console $ rustup toolchain install nightly info: syncing channel updates for 'nightly' info: downloading toolchain manifest info: downloading component 'rustc' info: downloading component 'rust-std' info: downloading component 'rust-docs' info: downloading component 'cargo' info: installing component 'rustc' info: installing component 'rust-std' info: installing component 'rust-docs' info: installing component 'cargo' nightly installed: rustc 1.9.0-nightly (02310fd31 2016-03-19) ``` Now Rust nightly is installed, but not activated. To test it out you can run a command from the nightly toolchain like ```console $ rustup run nightly rustc --version rustc 1.9.0-nightly (02310fd31 2016-03-19) ``` But more likely you want to use it for a while. To switch to nightly globally, change [the default] with `rustup default nightly`: ```console $ rustup default nightly info: using existing install for 'nightly' info: default toolchain set to 'nightly' nightly unchanged: rustc 1.9.0-nightly (02310fd31 2016-03-19) ``` Now any time you run `cargo` or `rustc` you will be running the nightly compiler. With nightly installed any time you run `rustup update`, the nightly channel will be updated in addition to stable: ```console $ rustup update info: syncing channel updates for 'stable' info: syncing channel updates for 'nightly' info: checking for self-update info: downloading self-update stable unchanged: rustc 1.7.0 (a5d1e7a59 2016-02-29) nightly unchanged: rustc 1.9.0-nightly (02310fd31 2016-03-19) ``` [the default]: ../overrides.md#default-toolchain ## Nightly availability Nightly toolchains may fail to build, so for any given date and target platform there may not be a toolchain available. Furthermore, nightly builds may be published with missing non-default [components] (such as [`clippy`]). As such, it can be difficult to find fully-working nightlies. Use the [rustup-components-history][rch] project to find the build status of recent nightly toolchains and components. When you attempt to install or update the `nightly` channel, `rustup` will check if a required or previously installed component is missing. If it is missing, `rustup` will automatically search for an older release that contains the required components. There are several ways to change this behavior: * Use the `--force` flag to `rustup toolchain install` to force it to install the most recent version even if there is a missing component. * Use the `--profile` flag to `rustup toolchain install` to use a different profile that does not contain the missing component. For example, `--profile=minimal` should always work, as the minimal set is required to exist. See the [Profiles] chapter for more detail. * Install a specific date that contains the components you need. For example, `rustup toolchain install nightly-2020-07-27`. You can then use [overrides] to pin to that specific release. [`clippy`]: https://github.com/rust-lang/rust-clippy [rch]: https://rust-lang.github.io/rustup-components-history/ [components]: components.md [profiles]: profiles.md [overrides]: ../overrides.md rustup-1.26.0/doc/src/concepts/components.md000066400000000000000000000100431441327105200210200ustar00rootroot00000000000000# Components Each [toolchain] has several "components", some of which are required (like `rustc`) and some that are optional (like [`clippy`][clippy]). The `rustup component` command is used to manage the installed components. For example, run `rustup component list` to see a list of available and installed components. Components can be added when installing a toolchain with the `--component` flag. For example: ```console rustup toolchain install nightly --component rust-docs ``` Components can be added to an already-installed toolchain with the `rustup component` command: ```console rustup component add rust-docs ``` To make it easier to choose which components are installed, `rustup` has the concept of "profiles" which provide named groupings of different components. See the [Profiles] chapter for more detail. Most components have a target-triple suffix, such as `rustc-x86_64-apple-darwin`, to signify the platform the component is for. The set of available components may vary with different releases and toolchains. The following is an overview of the different components: * `rustc` — The Rust compiler and [Rustdoc]. * `cargo` — [Cargo] is a package manager and build tool. * `rustfmt` — [Rustfmt] is a tool for automatically formatting code. * `rust-std` — This is the Rust [standard library]. There is a separate `rust-std` component for each target that `rustc` supports, such as `rust-std-x86_64-pc-windows-msvc`. See the [Cross-compilation] chapter for more detail. * `rust-docs` — This is a local copy of the [Rust documentation]. Use the `rustup doc` command to open the documentation in a web browser. Run `rustup doc --help` for more options. * `rust-analyzer` — [rust-analyzer] is a language server that provides support for editors and IDEs. * `rls` — [RLS] is a language server that is deprecated and has been replaced by rust-analyzer. * `clippy` — [Clippy] is a lint tool that provides extra checks for common mistakes and stylistic choices. * `miri` — [Miri] is an experimental Rust interpreter, which can be used for checking for undefined-behavior. * `rust-src` — This is a local copy of the source code of the Rust standard library. This can be used by some tools, such as [RLS], to provide auto-completion for functions within the standard library; [Miri] which is a Rust interpreter; and Cargo's experimental [build-std] feature, which allows you to rebuild the standard library locally. * `rust-analysis` — Metadata about the standard library, used by tools like [RLS]. * `rust-mingw` — This contains a linker and platform libraries for building on the `x86_64-pc-windows-gnu` platform. * `llvm-tools-preview` — This is an experimental component which contains a collection of [LLVM] tools. * `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]. ## Component availability Not all components are available for all toolchains. Especially on the nightly channel, some components may not be included if they are in a broken state. The current status of all the components may be found on the [rustup components history] page. See the [Nightly availability] section for more details. [toolchain]: toolchains.md [standard library]: https://doc.rust-lang.org/std/ [rust documentation]: https://doc.rust-lang.org/ [cross-compilation]: ../cross-compilation.md [build-std]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std [miri]: https://github.com/rust-lang/miri/ [RLS]: https://github.com/rust-lang/rls [rust-analyzer]: https://rust-analyzer.github.io/ [rustdoc]: https://doc.rust-lang.org/rustdoc/ [cargo]: https://doc.rust-lang.org/cargo/ [clippy]: https://github.com/rust-lang/rust-clippy [LLVM]: https://llvm.org/ [rustfmt]: https://github.com/rust-lang/rustfmt [rustup components history]: https://rust-lang.github.io/rustup-components-history/ [profiles]: profiles.md [nightly availability]: channels.md#nightly-availability rustup-1.26.0/doc/src/concepts/index.md000066400000000000000000000051561441327105200177530ustar00rootroot00000000000000# Concepts ## How rustup works `rustup` is a *toolchain multiplexer*. It installs and manages many Rust toolchains and presents them all through a single set of tools installed to `~/.cargo/bin`. The [`rustc`] and [`cargo`] executables installed in `~/.cargo/bin` are *[proxies]* that delegate to the real toolchain. `rustup` then provides mechanisms to easily change the active toolchain by reconfiguring the behavior of the proxies. So when `rustup` is first installed, running `rustc` will run the proxy in `$HOME/.cargo/bin/rustc`, which in turn will run the stable compiler. If you later *change the default toolchain* to nightly with `rustup default nightly`, then that same proxy will run the `nightly` compiler instead. This is similar to Ruby's [rbenv], Python's [pyenv], or Node's [nvm]. [rbenv]: https://github.com/rbenv/rbenv [pyenv]: https://github.com/yyuu/pyenv [nvm]: https://github.com/creationix/nvm [`rustc`]: https://doc.rust-lang.org/rustc/ [`cargo`]: https://doc.rust-lang.org/cargo/ [proxies]: proxies.md ## Terminology * **channel** — Rust is released to three different "channels": stable, beta, and nightly. See the [Channels] chapter for more details. * **toolchain** — A "toolchain" is a complete installation of the Rust compiler (`rustc`) and related tools (like `cargo`). A [toolchain specification] includes the release channel or version, and the host platform that the toolchain runs on. * **target** — `rustc` is capable of generating code for many platforms. The "target" specifies the platform that the code will be generated for. By default, `cargo` and `rustc` use the host toolchain's platform as the target. To build for a different target, usually the target's standard library needs to be installed first via the `rustup target` command. See the [Cross-compilation] chapter for more details. * **component** — Each release of Rust includes several "components", some of which are required (like `rustc`) and some that are optional (like [`clippy`]). See the [Components] chapter for more detail. * **profile** — In order to make it easier to work with components, a "profile" defines a grouping of components. See the [Profiles] chapter for more details. * **proxy** —  A wrapper for a common Rust component (like `rustc`), built to forward CLI invocations to the active Rust toolchain. See the [Proxies] chapter for more details. [`clippy`]: https://github.com/rust-lang/rust-clippy [components]: components.md [cross-compilation]: ../cross-compilation.md [profiles]: profiles.md [toolchain specification]: toolchains.md [channels]: channels.md [proxies]: proxies.md rustup-1.26.0/doc/src/concepts/profiles.md000066400000000000000000000034741441327105200204700ustar00rootroot00000000000000# Profiles `rustup` has the concept of "profiles". They are groups of [components] you can choose to download while installing a new Rust toolchain. The profiles available at this time are `minimal`, `default`, and `complete`: * The **minimal** profile includes as few components as possible to get a working compiler (`rustc`, `rust-std`, and `cargo`). It's recommended to use this component on Windows systems if you don't use local documentation (the large number of files can cause issues with some Antivirus systems), and in CI. * The **default** profile includes all of components in the **minimal** profile, and adds `rust-docs`, `rustfmt`, and `clippy`. This profile will be used by `rustup` by default, and it's the one recommended for general use. * The **complete** profile includes all the components available through `rustup`. This should never be used, as it includes *every* component ever included in the metadata and thus will almost always fail. If you are looking for a way to install devtools such as `miri` or IDE integration tools (`rust-analyzer`), you should use the `default` profile and install the needed additional components manually, either by using `rustup component add` or by using `-c` when installing the toolchain. To change the `rustup` profile you can use the `rustup set profile` command. For example, to select the minimal profile you can use: ```console rustup set profile minimal ``` It's also possible to choose the profile when installing `rustup` for the first time, either interactively by choosing the "Customize installation" option or programmatically by passing the `--profile=` flag. Profiles will only affect newly installed toolchains: as usual it will be possible to install individual components later with: `rustup component add`. [components]: components.md rustup-1.26.0/doc/src/concepts/proxies.md000066400000000000000000000033641441327105200203340ustar00rootroot00000000000000# Proxies `rustup` provides a number of wrappers for common Rust tools. These are called _proxies_ and represent commands which are provided by the various [components]. The list of proxies is currently static in `rustup` and is as follows: [components]: components.md - `rustc` is the compiler for the Rust programming language, provided by the project itself and comes from the `rustc` component. - `rustdoc` is a tool distributed in the `rustc` component which helps you to generate documentation for Rust projects. - `cargo` is the Rust package manager which downloads your Rust package’s dependencies, compiles your packages, makes distributable packages, and uploads them to crates.io (the Rust community’s package registry). It comes from the `cargo` component. - `rust-lldb`, `rust-gdb`, and `rust-gdbgui` are simple wrappers around the `lldb`, `gdb`, and `gdbgui` debuggers respectively. The wrappers enable some pretty-printing of Rust values and add some convenience features to the debuggers by means of their scripting interfaces. - `rust-analyzer` is part of the Rust IDE integration tooling. It implements the language-server protocol to permit IDEs and editors such as Visual Studio Code, ViM, or Emacs, access to the semantics of the Rust code you are editing. It comes from the `rust-analyzer` component. - `cargo-clippy` and `clippy-driver` are related to the `clippy` linting tool which provides extra checks for common mistakes and stylistic choices and it comes from the `clippy` component. - `cargo-miri` is an experimental interpreter for Rust's mid-level intermediate representation (MIR) and it comes from the `miri` component. - `rls` is a deprecated IDE tool that has been replaced by `rust-analyzer`. It comes from the `rls` component. rustup-1.26.0/doc/src/concepts/toolchains.md000066400000000000000000000050041441327105200207770ustar00rootroot00000000000000# Toolchains Many `rustup` commands deal with *toolchains*, a single installation of the Rust compiler. `rustup` supports multiple types of toolchains. The most basic track the official release [channels]: *stable*, *beta* and *nightly*; but `rustup` can also install toolchains from the official archives, for alternate host platforms, and from local builds. [channels]: channels.md ## Toolchain specification Standard release channel toolchain names have the following form: ``` [-][-] = stable|beta|nightly|| = YYYY-MM-DD = ``` 'channel' is a named release channel, a major and minor version number such as `1.42`, or a fully specified version number, such as `1.42.0`. Channel names can be optionally appended with an archive date, as in `nightly-2014-12-18`, in which case the toolchain is downloaded from the archive for that date. Finally, the host may be specified as a target triple. This is most useful for installing a 32-bit compiler on a 64-bit platform, or for installing the [MSVC-based toolchain][msvc-toolchain] on Windows. For example: ```console $ rustup toolchain install stable-x86_64-pc-windows-msvc ``` For convenience, elements of the target triple that are omitted will be inferred, so the above could be written: ```console $ rustup toolchain install stable-msvc ``` Toolchain names that don't name a channel instead can be used to name [custom toolchains]. [msvc-toolchain]: https://www.rust-lang.org/tools/install?platform_override=win [custom toolchains]: #custom-toolchains ## Custom toolchains For convenience of developers working on Rust itself, `rustup` can manage local builds of the Rust toolchain. To teach `rustup` about your build, run: ```console $ rustup toolchain link my-toolchain path/to/my/toolchain/sysroot ``` For example, on Ubuntu you might clone `rust-lang/rust` into `~/rust`, build it, and then run: ```console $ rustup toolchain link my-toolchain ~/rust/build/x86_64-unknown-linux-gnu/stage2/ $ rustup default my-toolchain ``` Now you can name `my-toolchain` as any other `rustup` toolchain. Create a `rustup` toolchain for each of your `rust-lang/rust` workspaces and test them easily with `rustup run my-toolchain rustc`. Because the `rust-lang/rust` tree does not include Cargo, *when `cargo` is invoked for a custom toolchain and it is not available, `rustup` will attempt to use `cargo` from one of the release channels*, preferring 'nightly', then 'beta' or 'stable'. rustup-1.26.0/doc/src/configuration.md000066400000000000000000000007731441327105200176750ustar00rootroot00000000000000# Configuration Rustup has a [TOML](https://github.com/toml-lang/toml) settings file at `${RUSTUP_HOME}/settings.toml` (which defaults to `~/.rustup` or `%USERPROFILE%/.rustup`). The schema for this file is not part of the public interface for rustup - the rustup CLI should be used to query and set settings. On Unix operating systems a fallback settings file is consulted for some settings. This fallback file is located at `/etc/rustup/settings.toml` and currently can define only `default_toolchain`. rustup-1.26.0/doc/src/cross-compilation.md000066400000000000000000000036321441327105200204700ustar00rootroot00000000000000# Cross-compilation Rust [supports a great number of platforms][p]. For many of these platforms The Rust Project publishes binary releases of the standard library, and for some the full compiler. `rustup` gives easy access to all of them. [p]: https://doc.rust-lang.org/nightly/rustc/platform-support.html When you first install a toolchain, `rustup` installs only the standard library for your *host* platform - that is, the architecture and operating system you are presently running. To compile to other platforms you must install other *target* platforms. This is done with the `rustup target add` command. For example, to add the Android target: ```console $ rustup target add arm-linux-androideabi info: downloading component 'rust-std' for 'arm-linux-androideabi' info: installing component 'rust-std' for 'arm-linux-androideabi' ``` With the `arm-linux-androideabi` target installed you can then build for Android with Cargo by passing the `--target` flag, as in `cargo build --target=arm-linux-androideabi`. Note that `rustup target add` only installs the Rust standard library for a given target. There are typically other tools necessary to cross-compile, particularly a linker. For example, to cross compile to Android the [Android NDK] must be installed. In the future, `rustup` will provide assistance installing the NDK components as well. See the [target section] of the `cargo` configuration for how to setup a linker to use for a certain target. [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html [target section]: https://doc.rust-lang.org/cargo/reference/config.html#target To install a target for a toolchain that isn't the default toolchain use the `--toolchain` argument of `rustup target add`, like so: ```console $ rustup target add --toolchain ... ``` To see a list of available targets, `rustup target list`. To remove a previously-added target, `rustup target remove`. rustup-1.26.0/doc/src/environment-variables.md000066400000000000000000000041511441327105200213320ustar00rootroot00000000000000# Environment variables - `RUSTUP_HOME` (default: `~/.rustup` or `%USERPROFILE%/.rustup`) Sets the root `rustup` folder, used for storing installed toolchains and configuration options. - `RUSTUP_TOOLCHAIN` (default: none) If set, will [override] the toolchain used for all rust tool invocations. A toolchain with this name should be installed, or invocations will fail. - `RUSTUP_DIST_SERVER` (default: `https://static.rust-lang.org`) Sets the root URL for downloading static resources related to Rust. You can change this to instead use a local mirror, or to test the binaries from the staging directory. - `RUSTUP_DIST_ROOT` (default: `https://static.rust-lang.org/dist`) Deprecated. Use `RUSTUP_DIST_SERVER` instead. - `RUSTUP_UPDATE_ROOT` (default `https://static.rust-lang.org/rustup`) Sets the root URL for downloading self-update. - `RUSTUP_IO_THREADS` *unstable* (defaults to reported cpu count). Sets the number of threads to perform close IO in. Set to `1` to force single-threaded IO for troubleshooting, or an arbitrary number to override automatic detection. - `RUSTUP_TRACE_DIR` *unstable* (default: no tracing) Enables tracing and determines the directory that traces will be written too. Traces are of the form PID.trace. Traces can be read by the Catapult project [tracing viewer]. - `RUSTUP_UNPACK_RAM` *unstable* (default free memory or 500MiB if unable to tell, min 210MiB) Caps the amount of RAM `rustup` will use for IO tasks while unpacking. - `RUSTUP_NO_BACKTRACE` Disables backtraces on non-panic errors even when `RUST_BACKTRACE` is set. - `RUSTUP_PERMIT_COPY_RENAME` *unstable* When set, allows rustup to fall-back to copying files if attempts to `rename` result in cross-device link errors. These errors occur on OverlayFS, which is used by [Docker][dc]. This feature sacrifices some transactions protections and may be removed at any point. Linux only. [dc]: https://docs.docker.com/storage/storagedriver/overlayfs-driver/#modifying-files-or-directories [override]: overrides.md [tracing viewer]: https://github.com/catapult-project/catapult/blob/master/tracing/README.md rustup-1.26.0/doc/src/examples.md000066400000000000000000000037521441327105200166440ustar00rootroot00000000000000# Examples Command | Description ----------------------------------------------------------- | ------------------------------------------------------------ `rustup default nightly` | Set the [default toolchain] to the latest nightly `rustup set profile minimal` | Set the default [profile] `rustup target list` | List all available [targets] for the active toolchain `rustup target add arm-linux-androideabi` | Install the Android target `rustup target remove arm-linux-androideabi` | Remove the Android target `rustup run nightly rustc foo.rs` | Run the nightly regardless of the active toolchain `rustc +nightly foo.rs` | [Shorthand] way to run a nightly compiler `rustup run nightly bash` | Run a shell configured for the nightly compiler `rustup default stable-msvc` | On Windows, use the MSVC toolchain instead of GNU `rustup override set nightly-2015-04-01` | For the current directory, use a nightly from a specific date `rustup toolchain link my-toolchain "C:\RustInstallation"` | Install a custom toolchain by symlinking an existing installation `rustup show` | Show which toolchain will be used in the current directory `rustup toolchain uninstall nightly` | Uninstall a given toolchain `rustup toolchain help` | Show the `help` page for a subcommand (like `toolchain`) `rustup man cargo` | \(*Unix only*\) View the man page for a given command (like `cargo`) [default toolchain]: overrides.md#default-toolchain [profile]: concepts/profiles.md [shorthand]: overrides.md#toolchain-override-shorthand [targets]: cross-compilation.md rustup-1.26.0/doc/src/faq.md000066400000000000000000000031641441327105200155720ustar00rootroot00000000000000# FAQ ### Is this an official Rust project? Yes. rustup is an official Rust project. It is the recommended way to install Rust at https://www.rust-lang.org. ### How is this related to multirust? rustup is the successor to [multirust]. rustup began as multirust-rs, a rewrite of multirust from shell script to Rust, by [Diggory Blake], and is now maintained by The Rust Project. [multirust]: https://github.com/brson/multirust [Diggory Blake]: https://github.com/Diggsey ### Can rustup download the Rust source code? The source for Rust's standard library can be obtained by running `rustup component add rust-src`. It will be downloaded to the `/lib/rustlib/src/rust` directory of the current toolchain. The source for the compiler and tools must be obtained from the [Rust repository] or the standalone [source tarballs]. [rust repository]: https://github.com/rust-lang/rust/ [source tarballs]: https://forge.rust-lang.org/infra/other-installation-methods.html#source-code ### rustup fails with Windows error 32 If `rustup` fails with Windows error 32, it may be due to antivirus scanning in the background. Disable antivirus scanner and try again. ### I get "error: could not remove 'rustup-bin' file: 'C:\Users\USER\\.cargo\bin\rustup.exe'" If `rustup` fails to self-update in this way it's usually because RLS is running (your editor is open and running RLS). The solution is to stop RLS (by closing your editor) and try again. ### rustup exited successfully but I can't run `rustc --version` Restart your shell. This will reload your `PATH` environment variable to include Cargo's bin directory (`$CARGO_HOME/bin`). rustup-1.26.0/doc/src/index.md000066400000000000000000000017271441327105200161350ustar00rootroot00000000000000# Introduction *rustup* installs [The Rust Programming Language][rustlang] from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling simpler with binary builds of the standard library for common platforms. And it runs on all platforms Rust supports. Check out the [Concepts] chapter for an overview of how rustup works and some of the terminology it uses. The [Installation] chapter covers installing rustup and getting started. The source code of rustup and this manual may be found at . If you find a problem, check out the [issue tracker]. Release notes for rustup may be found in the [CHANGELOG]. [CHANGELOG]: https://github.com/rust-lang/rustup/blob/master/CHANGELOG.md [concepts]: concepts/index.md [installation]: installation/index.md [issue tracker]: https://github.com/rust-lang/rustup/issues [rustlang]: https://www.rust-lang.org rustup-1.26.0/doc/src/installation/000077500000000000000000000000001441327105200171765ustar00rootroot00000000000000rustup-1.26.0/doc/src/installation/images/000077500000000000000000000000001441327105200204435ustar00rootroot00000000000000rustup-1.26.0/doc/src/installation/images/component-msvc.png000066400000000000000000000772111441327105200241310ustar00rootroot00000000000000PNG  IHDR-dPLTEBBBhhhڿ@@@___󘘘fff3ؐʷvvvլ!!!ҵỻnnne߈ЃɌ]]]LJbbbQQQ&ԜrrrNNNWWWTTTϫkkk|||Q&}Թ\KKKCCCxxxFFF222ZZZ===&&&~~~***H7灱)憵s=eݺi888R M,ggg~||00hccL##(FFތm g|IDATxjZAcrB[Imڢ!RbhT$A:p](R7. r:H8 f3Osf6%m           TFӱ$jFl؞$E$<#a|I3'Q!2|mY+i.@ޟ]KrZ&(.UL 2Lq G~IݨVusn Xyh諶k_}Jv@ە$oוc?h꟒y/cc= GؿN 7% Hf8;[֓ զA5#LDؑ._/bz k0F۞Fn#0000000+&@f@f@f@f@f@f@f@f@f@f@f@f@f@;v&aqdx`2$8ZA01+vJ76ˣ{-j)"Z #çGs?HCeM $L#V`PDx (7md-qp7nD fWƍZJPKx#+"[@_ \l#9`@- {~N"ri4}Z6 Z~P9 /"/I2HMQ^}4{% `*[kZ2d0KXD}^0쇧N0٩f@|c펖wЖt@ h*β˟=LM8g3ȧ-!ge?bɕ{Tm l +hY/G<6W3\yŵ)QT9#jDmrnPK9w $~I|q.xxpx% qwdL`9MD`g[g;k "7?DD# ("j)"Z ("oͦm Cy]uq]p K-9A.:&c)]لCJ>=H0> 7[Q." q8?p/w}Z^vOa| `a/`(C7xkȽ;@%ECx܄:c=zC/@;ZLg P+陽,L1} EB$N1 :{m/@')/nD,%@n-h*?z^w1y?}xqJ9%/2JUhZ :kXrNtq e V-q,j'Rew~e`_ cҨԀ`D %@St`sD щ CZ#[M)C,LQkw=lGAM >-@{*[.FbD݋ 2A |P&HZa` i 1s60MIb%JEAC,X@ %@,b KX@ ֏{GkT8^fzꋽ3̑bI|:1e|m6qc]? 9쟫}/z {1<~vDB@/@3/.KKW`q)@b-Yf]~ߊ+(0sgۈGe+9ywa ݇zB611n .̃}F)ZɩiA.*gݷQaDe%=+Bc$$2 i,唓b|eSSZkӕ0󹆥z#RzN}?5cpL5{&ĕ;R~Ux+/.9 X#ŕ1q C;i/>Kp43=CJLf,2dZ<{̈́AP|5f>_/ U%MYx:$wJs%VW6nl$3k0fs-ǥj\bE O/*.qi$> :r*23Vx@V\<8;6 1S.Y"iq`Xcxғ_ S>Ps#tQfS@z&ye _9OBҧEIPY?li@(X`0UDԨ=~t>0PSn/<‹&xR<Ux-0 '3-p<s{.r[ZyKR CbT<Q @:1ARb@󙥊'|1~H2| `41370fWp9t!1D KoWT\PǷK&驯Dސ~ /u4f6-dZ֗ppg=X K&Xr^ q ,@"l2 L4z&kf {;M#| za1b+ҧkcسca 9j#`T&Pېگ?Mi&& s%OxRgXPam!6 W,c+@H6@ȫlҝB"@HD[eMR{?hj] 1y("I&"`@("I&"`@("Εkk`CO2Wห [xeP Lgm`52l2# CɗCA !XkPX$ Êȝ )6p1@'G,}G80`vb_`74B:Dw |`c7>g|Ir.msNסarA@r]]._vu.h, ``*9CV<-gULsԇk.'Rh/AήTTv.w`ZLnx.Yl h3\g `H]@] ٴBzUl @q fIMf?EVUaVkCE wKy$f젥K'i9r8Uz5 :]afEDjjά*(3:,*v9@ˤ@{2 n~ 2f*(2u dͱu!ân^`hƲg`% r i ?%w:̈N, EbW- t`EHYsOMnSѨ-y`L˥(~LA7goӢnup 'h-$빩-iICi6m119\'__\#$Txiuڂ45nr?l_tRfSqy5=L3?vrG-ZW(NZ.P+ pWvy{Hwg>,r+<x=M7V.PKH"pA|)9|sCE͛itUKe^'mS^Ԙvukm 7CZ]?`՘N\ZZ(-sP3"T7R$О kŐSMa%MyOv"OrX+Chモ9qc!ڕ6ʝhWc:V%?pu* @pq`D{MG3VD^xlfѯ \I T(o)8as `*VU-i*\3v0ozX&WhAEuQW>LvԓaKBV&- " z 0>ŷݹj^o@Һ$y;qMܥTGrn0/ # mS`CGuvv>"7-wC2SH j!Y @~@>/mPZd*I`-^UR}4 7 x!Rwij^Qw ƃ]5n=AB=sى#vgc>c\'o71gOOSѾM405nW<̯(q;Vc0p4|lcv} Dy W<ȿTt *- '`u7x|N1x'(uVF*ʶ3D;FCLHqN<{2ĠQK}1~>VKPq>Q*P>'tYe#*Uh kc#} 2vڒvRM֥~C<1w=rg cNK"2\P\Wْca>a<U^?BuЊvrSM(}>ۑc$_bZ8=d}x5\$IWOmz7)r_щ Pm**[~QY@PQb!ѰiަnӢEyOC$ V39غ{`I &+JJQ$`*{bB^dФ^]uS>@FU4sHCS=HUdcL;؟ M]P܀fIn@SLeɆY3OJӏ$p@I*Q(I%J%D ~$(O% T@?PJ'JR@I*Q(dv(0<"E,]L6V}AJE*pnz-/P 98Iڤ󩻳;YO38k&a&%g ZW&@0V aT`*0J@☷DƂr.Qk;v]` '@.3:εeU82׊Jrbg)m6 KE<2H0 [h5`*Wi{R-'FKD}\YNꈼ:ǵ[N"(m5YMt0  L" Y&(N`j.M8 a5i\V{Zy:LC sYݖw0&k^9a)r['F!M8 F)C;)@#505$+@?ދ>DoOms360СȽy4_bj p֧|2CBo.Й14F xII# )Kc h<{4ݧ&d Bekf4X:p>X*r/@t-o^Iu-!caO^.8| 0Mxop8ue(`%pE}0^h^DV>ٷb"((|H{>(/-gÁl3%c,&@c}U;҄fWɅ$My*ޱC7tmDZ"kg5(JUp]+@IWJ<`Ūzh1`sXtw;D%Ȯ~=1fϟj8mNqt%?tՆ3v4i}B0r#Uƺ27S4SM*p!YܻnwJcXbEՆj3 :H~ -|̝g-H6zμ[Hnj; [ggC4mi[-_xuh3(zؕ&f{CW-Hr8$슩=`G(@Ӆ h =o3 rͨ>^beO$y1̀Ήxohl/ djK ?Ϩv[ B6>]p{vpѴ@ui{8`G&Zw#o^ ,I|]ϧ&ZT@|!#ζ|af9LX5s_c_Lxq 3I+z#W-W4o&H(@$mxD1 p({%/`p:Y$ŀ_x* 0̭%smvJ1tNKܗi -%~&@è20l ]&ȩR>Y!]($@CM|8Oł lfxXV?D> 3F 1!:VLkW~Y)K8컫ԡ8))ݧ2ܟb9dja4*S_&@è2&K c}0^h &@X**U bUL" ʘ ʘCVAKV87`Sܻh STM \ CVBVn0`zG 򎚋c kXuLX.[G߁a:V{8\!g \c-on= u;w"1OPs4@΂fԞm)3Ϋ]V8,?K>0KAsx"'hlp5bI(;5A`'rjĴf+-_& p '@JᄃsJ}GvJTyIr݃܂ P;bx\@kH pmA#+VȵXU-+/%z+ W^, }d jEEer#y3L­jcIFYh#uB uPVADZܘMv( } ;tu PZL< "Q35|u"+X bf9vKJp:W%g5)Cٷ@6#  6r 9I 1f XܓD)W PB-j _|aLhNWor1z} ֗,sTG]vn wS޼\n$@u\ wF }'EEYLK=s_2`v%_2|S&Qn aYwnscXc-ϵv9Xj!cYL# PfP&we*3UV c .^ ;r 5jӹ%pn&ʲy. 0 02F}vAGvRjF3Iv(iQhoL*Wdx ao4&@è2&a&a&%J}02a/E2*c3Q0"R@:xtRR  $ E2g "I`z[K7@@CêΘ x(LiN7Az& r&On|]`@(I< nC < p8 @!S @)`D 0"` 0FLA#$@@HC @Sj`]&k~+ݢ\&'Os;Cأ*7X*0 G; {0TS ;j@zA} 5ܮpAV_^UǭF-:K%i{>pfډ}4N ػgF3A-"͝ xΝ fҜvW[}.eҚ6w;MyTd 4=Ф^Lgvsxr XjW8ZO  P)S?;KM5/h.TS ?zgew"Ž[X)O1 pI λu,7+0d` 0FLA# @S @) 'A )A)`D XhW&Y+$>)VW- sLBevk {0=ow{ 2iM}H*z_.ع&(xzgje-l[F!`>$}D >ܼ̐ܝ9se`_v]G'q9ߞmgWeY:&߼isJ#Ev>n@ҁ'YJwLzZ[/E HnhRRRj$<o=k}`Sd}m]Е/CG-߲5s+{};cg%Q@GC\~'})_ij'a m2? 8rdH%ty|fj8dG e`^ ;!ϮC3?I90R(תFS@yTrCj4&I) PuN :7\.VZhd#|?ry$!z],m>N_"Fuz1c~H D۠tXӬۚd GmsHWr. @$IITѯL%cƣ8kɻK[!z<*/!(?y*Sj"%Ieȑ<{w &n>ǟ>aoX).8üߦU̎{m>bAz̜b imr/.Ͼ kYB2D~8NOJ{ fgtu3eՒ=^K )vmm"xRpI8NbOh4.ޱT&[Trjr[~x ͳp;ܛwwu8{c:S km`MSRolwTs̳a$=}syF&?0f_Ol.j |9^ljt8z;9fhP ,6L+*`QK3|F*[/~o"%=!ܯ_Ay^6 jLon6m2*oF 0gPpVY1t ȭkmUvh qiO EiCNlyAZfDc0%ܺ9Tk<^„i}9RIJm=#8"/0gl#%~HeV~ٹvmD3+~ Kq//tt+K<[;;: zQ%(wTar'?MpovR<`(qN8h0Zn]i%?XmE1 ѡ@‡h~%Yɟ?ek>* u*r~Am~]oם~w3;qlY|lZQ$2g2c&- 7jSrVԇi>haMjl5#&[ &d,bЖ"6VrR ƅ^oɺBgi*VO,HShlu/8+>*|wnVUrfjF!Gmcf jci24[hE\+b@tF_C/Z(aa(aY@ -j<NFf5;WmϿC8[P{GeTsS~yhAח$>*?]j'SH6#fdӋ̙"p/0f$wF`U$.T,C(%M:3!+rS^S8lS\.<^,\(b&hB)1B9KgR0p1RˆE4҉%%n >aoZe Ex978b C@UX9\B*RKP}{G78╵7tZio؞iNY_X5ןKbSC!3}!syiO!!xѐ"fB)EvL1$"/=sa< @QQ2;L0dR$ ]UXuÝ3@"hJEz'X&h:5jёr󹚅$OѴ7N8fɭs9>' Ŧ|TjnsJ%K9έHG/1fǖ( JѶöiTHбI"hF+XH02"pA=#d={T޻yE^ѽf{g8p*XHHNbWPj6ƨm\r~sKBooPjvkfN/L36Nv>}=ZaZK1Y10 Mj1 qiA $B 2YӐ<2K]) gLH+M!LAn ]}"7ZFOLMɟ!8w5Ȏu#a)'2uv̵*>}3[| 4̸|6Nv6JJf𖃺:1[>U|[<*5 au]E=Ũꢥ jzzmAU5 )Ah5-4lJWԨ:pЕ`0 gb0IQ+M$ߙ VߤkJCVtZ@t`пQd떆 XYxd)X}|S3x'S; |K0;s~AX_؟dEh<'FL ԞDkK\gYXO/n/dP`tL\TTwh2jQt&C (F$nE!ߒ'@f7y|'S; @_8ɕO?ŁnM0In6:l:6#M:ZGT)%`+( \d/L?&&A:4ѣ 1S^ (A)K.::M@ou cG21Si9q٧{M, 踻xV{on6ߥ_ή' Û m]4Z!݊GK5+4T($D4zzab%>g 4Ƅ}Ι9_3ܼ؝blP3I5)sb/Vմ,%u|y~OusES=x0/J#~[$j$I}bL~&sJV^91GW(֗ 7H/k@e8Ш02)zƲGY/cdw%c|rA_&`E;l'ώ^m`_nQZcoA]IG@$B_jπfT.r@/(/s+d QF0/e-Vv/`gM϶K] R \Vg+D Q FBݔ:Ͽ_OTg 3\C}YND#@LŸ@E٧T*~\ewV QztPKhu:R:@,T22 +M3ԲLj\iQAG֔LL+ \lCxuH%LVEkW*/[KVjvpBmZ_OmtmGݳl.8kPj{Je^!ZЉy xo:L2Jx)>`tp[FG!0 OЈF9 t$RKk9jH:? OK &sE뵲$l& veo3D]1^P9\OG/m%wu rnNtyCí[3O4sޱ<]"-޹&Da|Ŋ-DZ(RWlA4>o> qGfegwvl9{9YJ)$Gσ+?p0208.2+cqX*2ɀyŭ!V80#ȍ=eAERZ-Ly\@1 {aM幢|S5V'c qcF9݀)jE d/6oDomLlrԡn~)ϸoHm6cw*UDoBT`_2JK)k8aJ>ZdˎžA̮d=4dpJr+m@Ԕ:;}] xG.,ltȉpj9Vt@x0Pidr7`^U.}  {JpSm*,r\4J5fP[;c hk{9}ZhCm08{>+[nh2G!p)*@@y"|uk)џMwhμa!{oA\8uxJT3=THfІGR鎞4 P ntrϭy$Ճ"`.EA-(9vnئ`t ӗN!['ЂR>m!=U xyg! SV\3.$580 9<N! %߭?xBㆉ9V.t#g.yWaߑlk7i?4f(=jzgCGR~  0<؇xgSxYW 4X2`"D}<@}pyLu b[>'nY>Ho.#ѕZ!\R &DB2A3̥Il4ȃR\@ӽJCٝ;[xf%W% )6,8}Ҍe^|Pw h'b0ҡ__QD|Ml#ʃzpҾxYJstP #to1tH ̫#'g-ܟMvjˍHܾ6,\_$s#تm篘= $r*Ҵ4MQHL]fff=׷knq¹r"+Ę_8.\?A"ZfPě6C6@VBaQT`=A*!J5~*ss ->b +A5.UU&vɫaR!l<&ytނ=Od8`e(bFT3#l %={~>Tj`{r' Ue+3>j9Uۥ}Yiԡd*rsm* ,tKǜ"zy^`qp¬pyvA Gg(ʶ=]\P؃*>TP=o. $Mn\R~ ͊~2<91/>2`"Yᾲw~iDA_*Dh7jbՔ($X-JOЧ7&=:[2n=71gf yu.X>mBz&eES,o> վOR/w6DP6;qxtc.!Q1% q U@ hL |{z \P#4a.Jѣ:h$A,\%T )h=s05]@yz!>vH{.:;vFq Ui@Ӵ^<^ylP} z߸x4:Q!IpST!@nH]OBJ{QPV`L%`P-y)z!T!@4Dfʚ'7oPi8Vbp?sPPƖfX_d)0 h۰ >j~HvQpEziY. _ZṲHjW3=RZn H& $"QtIP5Y"@0a8a1>xXo`<&@T>rP*ϟS`N6݆zg'_tLX)Y"TjOcąty/  KYXOj,;|wuMPCl[uUt[X G۔TKX,JM I@pO@H{IӔ?9'ױKoۍJe}J>k\/`΅VЕO?|ۭo?W/[߳YF֩Zq[,bS(f"JrWWG"3KwlmT<[r_/oP7,O ^ĕ:͵'o`v|߻sp>mKG4Mmf醶jZ-6SJF4m'h9T)LLIy(MYDN*!>Z(Zaigɂl[ҸfqpfJ怎"h+  c0IH%<)0ēKXp lt &(&7l Q#z?58YSdEܻ]j w+IZeiZjv˹^[xWFK2ytFk{_v SBHp #\乪;1BqqX#su=3%3qj@@άq7@(V-#D#dq0(LT4DXQp#9Q X7<(ӄ Cifv" D7s'H.PF(!L7Cn yz1&چ~"Ë́r~b ]:yu[WKۿ4\/ l,ݸ[+Va j-m81ט[`wzc6%ݙ&.ϙ1B#<h>dJD&!G3epPK ,[c(08XT=ATHT1FHT&Pb%(Bᙏ644pq Cǟw}0Zxeld!C)PCF/7;G ef+. -̆%)\0Fg$.8@;s YcSn/GAUJs J5k\/ 상]7u Ww+mQm(j RY׶T۪:Ʃ1"]a@WR&;;s\6&h?\0`:k>u[]Z{qlM婁 Ԋ;`.7}۲ԭAaŵGNL 'Vؗз<ۻ~r2ĪJq8Manġ~ٌpx+ DO.qo AP[&I>݈Dp CoFP"*D.84Ri T]A\!D )]svjzЕȃKT/|?Z(,̌vՇUݝ ںc{B @-d1q#/{g8 Eѡ ojBg!ؠE&P:FDZn̹i7F%%!$]1ٶ\ٺ4`_F_zQE OhIIF(@Â[O‘ eKD d TJ [%_*x8X5ŸiOf_7?n/w~lgOUߧ{V7/ۻ\1Fc1Ղxl՚[1mXfQ-Gl8(lk +s z,: a`C!#7iE~X"# bMq+WM6c@QdxRp " U9U> ˘xMN[n-/w~lg W| ٰY#CVn7M91R7x<b1Φ<%ӛ>aL&LWKJYFp $$eh,tfJ렔+ <kSNg4Aࢠ!6&bX-Caac9\軷'g=0@ 4g x)_~~|\;#T ocD --Z8Qth6 0&6tm|< LH_K):ݳc`uW?ṛŜ8k| MiLB wfN>BD gb! q3|TڡD% PB,(8SԐhDO&u1ܐI}=)K~1VO< j@)_i~gzɹmXsXòDzS?MtTzGaLC~!W/c*},DŽ;MI3q6EǠ4BZ :Bmτt514yAzgzdM"GPkײ/6Xcs1;w7?>r5O_n*ػѻfc{eYvYB rZúPpಁՆegǰڲek-"ha_+X&W'daoԁv- S"/HJ'Rc*@Xxv`AċEAݺJrPJ$b\@(Xrqqxtc`vĨ )}1@~\ Sǀg zr.Fc1f8`; h C9`Qc@%\X4i1ȣCN qP ZE6fsCHOrM.r!akv4E캐`HC `բNVTt7Zߐ76}]+7? x5x= SRf9爠2E&0l͑(% [;!&A(a\ +*PDaC ",m @Y6Քrc"yhWB0$qP5lа E $D]ѹq'7z+jm Nԃ[{XGƚ*V5E\tեs{p|M1Vj"ISP$qf&IA'{(B+D#7 9:Ր3xE3JRSwBu1%5sF 'Ջ:PjrRj_ da;X~P[je /;g9fNzoftW?h`HgϞ ?B;bs1 dpdn٬ B.}S2HRڹ>š}יCp$LS2(PCaEKa8gIh9hL;rT׹j9=ݿ;;'`?>,g.j.58d_L2E=!.]!2b*QNd6ۧ  :U%cmBŰ ;%mj]énIVK5Gbi <ֵxpp7^O/땾 "> $CZFxʝKXBȄl+pCn˕,yT |[ɠdfm@VJ*P䘑R ;d*Z*ʛĒH]~͑ie晳&?ͷZ݁8|'{9>jBʒߣNynPJ9T$K9y l+hO#\ F9BN {KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,ةcA[{a>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|;uL0ko3 B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  Bn@ /;Mp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB<q@rٷԈaZha+y3EWG@[%@[%@[%@[%@[%@[%@[%@[p\p 8m=QQ#Vf^"^ELE@Xc?/b=ƌyY`]\9c px|{c p7x_x+3#$J3䋽sYrh/zAw8}J&8qtO0<)03Be !$\ rY(@Be !3(@B`jZ YtH&)d`VTGrPyY2,Q5ϣK ^:i?ǫ%|,˲&s*i_}Hg U]~NV<.oI$$Wd4a40!ΰ (3gQT$NKjr uwΓ[fI-cy|ol",w*,ˇBh& V2{bz(%=`j:VB]ˋN*6+k)`Pd)~Bh hil{PĦ^~:X fxC|;n[Ev e B]: !]WDwCrsHrj~ Ƴ!m;ؽ5~qF\"I_^ ^<O^y-!T#P).@sBI*)6@0v+sB>0YOCVkoXJCP<jG"dPP<47Z#p(1P6=" }FO? 0[V&u/l)GU yUBȩXϵ0Sܭ%6CjJ/ھNNE10R`" U-%uECE$_>ch"R QZO'< 7q#,. 8)@B@6yOI N=62qV>sy2? T߅$oOO$CB. H, !P򕽳yq"x3A%Q6 I V=Qr"ut'?"twuUu?2j VEEEU`QQ`UXTTt*,**ڻ@ULu|s7# ƽ菪2*^؄%WfTew+Y"Tݳ߽w޻I8l oޙd"9zj~궆0Dϋfu`v]}$Gw31ouu݅ ۝Ť]Thvd1q?*{O6[KU&AJwID]/K:-ܪwOQ. ̱{ u^%vv0vN;H4NZ]$XM["Xv m#P{4n39Q8c)aƯ 5#N= N{`*QPWMD$)URu5ҡe:Y|ТمaEԶ:8jiLF;gU[xZ|F~o0hioX-o)qu_FTq78lvyo=w0 "nuP s04 `’Vd^~r3ȉ:#3b FsJc&pEF`h&pюy,rUd"mLœHh'F"3:+59R8Q) @MV+K_=1JGrև+r0Yk}3[i綻b,?-Dʼvד2u_W0p>j 34HHJ{ta-idAkZ#DL^(D6c9V(yaa3'J| k s1K!]s2h2 xIzMZsQ?Z]:W` @4\|WQiBmG"=֋bZ[z|CbqX/"9gD ~g"@2y/1j`*#RS/`Ў2FuhQJL@\{/3&{>.\Mwb͆w0e3?&Xើt^;&~ݧv/aԒmŖ%0-:1c5-1 %_py0z7]̜*pMWWZƶ.Dc /4G3^" gc&0AЁ_P m{uuѹQ3ug̰IM[D W%O[e84‹X(uj@?e5wW., 2yrS([_`a%qz1(X~bc @_clO: >* xf @0C~  %06m@r$Z :TpH;S2C[]\0 Lx( .iFaPvv djl>u`k# _珚we(0 Ip3R |=wTCdˆl<;7w``1lA*O]K4g&EİNW=x=Ւzh` |!%h7K-lIEb1 `Z @3kn l8pRޚ3 ^TK,)mut}0: ¾]/,uGy{N An Wf`t~`*cV;[H,_AjZ*Rw>, T!tLǿDȘ?\_Hg7(od#[⫂N۬#Xڲ#3_k7x*FN $҅(=`: &dff 5C~p͜/HhAqЮ+^$HıWeT#m-̱3axä-J.@ BQ[F @G@#<8X Ê<$3׵/~SJAz $,ZC0C.|#.+ܯͳzZ(5ZRx眩oR{ʱu*BQQ`UXTTt6Dq|03@#Ш4^TxxRŃHi= _';MlΗfwV2x'(&37yI-2"f:M!Yw.׷֦͟9\/^vNJˍ :Ө'"QUCck.I`&%CH 7G۴#Pŧsw 1nWJgc@gT8S5M ޮtN`qMpch]IL:F\8As"NӼ}XڵtvYD9Lp{~zfxat\OQ<mfR͟ψ?3q@5Q l³l;%}\ 7{ɯ65ۯ :z:Dsc.w?"졃c^Gw9i-PA9 06>#3/>D/H,UgÀUFᵩ;C`C -} tl0RolK扢-Ph tlC3kv݀J+myMeC j2m-XSr{`TcP1ODY+@PZ[ S{ZiSwpw;0BX.f8 l:둲` +FOq+V{{uox* /̹o=;-0@Gw>- !ʇ&L6wѰFdL ^p2>Z\ȃ9ç'XN$hr&7pJ`gznW6A=0x [b6ԸK) "M%D+GP"ncm_tiC4`a Lr&"Tvʠ5U[J@ nX^F"XaPZi{kfH<">b hb9/0*hNG'ܗ Wa*Oz "J%pP liN'P@ .[%!L&*} o9~=0́ |7Y'DD: \ە+oU!|A5M0v4[pP>Яu`50kӐ9Z%0xо+s?*Zoxt J2 ꁳEXWO}u$ pfżx2aCj ;4Sa\j <(~{hn+_R@ ࣻpciJ@}Rp5L VrI/!RޗJID\-J`5lh%0^nUn%pKJPιI<$;: '.3xx% HсL ~@nHZ62xLNgj* hL 9gf$ޗ7ATC-F @0 X_.`|"K-} oP0e:+@,4 LE> H}@a <@ ĪR`MS^(6Aj$;l ] 1t` -D0VhjqԥhcW% i231Uk3Yù~5{${vjZ;;3ц^}7{ϳ%.?}% ݍ*%2іַ3tؖ}"3rZz6F,;3%s ]߇?*f]D^Wea h#7/߹C u|Bk III III]qz'E/:VOo,CNćX,h7) KoW9͕yZU}VG;%lv e|+HX8K7T)` s*s|V!`D)[esY0/)\)po Ś t8_e*^hR$̸gXpk!CJ{ݒDf`RR!l ([-a^2ޡPN`l7AsNkᄼ{/T$%`U[s R K`]=*/40+Cc'IJJ(r;@ -&$3ya @NsG5ֿC3 DtwO.c7ğ!-8sXDr 1))i>47 Qh.~aw{q~~X@v-LT'%bq [i-x=GgvˆĔ=Zo,N4|y`[C羦׸|S:?zlwY;^_gQN@O@P_yxU%@Y%@Y%@Y%@Y%@Y%@Y%@Yi~{\b~z8nb[>2˾o}Hd?CckGoͥuַcs -RWO53!$k}þw[[W <Ŋ1HO;rp`DȬ;8#xl̞n)b>||*}@x]Ocm_ L=Nz#k*VVQkdGVs~1Ȟkyo;6@P,@(K@P,@(K@P,@(K@P,@(K@P,@(K@w;uL0kCxB@Y d P%@B{\IIENDB`rustup-1.26.0/doc/src/installation/images/component-sdk.png000066400000000000000000000631171441327105200237420ustar00rootroot00000000000000PNG  IHDR-dPLTEAAAhhhڿ___fffeQ₂Ʋ采!!!bbbTTTጌuuuNNNʐWWWooo&}3rrr\QQQlllxxx[[[FFF|||䧧KKKvvvӳ񅅅7)&&&ꆵ@حIer***iߏ888RLĒ ,Z6||dd%%unnnM=22)t11\% dEIDATxJQ+ J! -AQl*ZA!] yGhn/BV?sν7uJa a a a a >GТ~HKbtu*R=@x'Rcg!@ QWM#Z\ =6"Olg\Rif|*v@9 z7)W%XG0݁pGudw_ `\BP['Q\B:X5 ȷ;@VJڹZ(g0<PDZ`W}J#EkJڴT~,Rg t܂-^\W-~v(2.CAآCqZ^] `~f `*Κ%Vr}-YC؛؆L!9m^!jxS[5ꃚ? `pԎ!NEh=gnNΈ"D+Ӟ~ lPW{X Pd^cg6 `O7rmj_J&SSYk{#6(בV%j&QEn6zAGqIb4&l̪}յxe1 .JWݫª7?.m,6;c^y!"QEd@-PDfKREd@-Pf69.C] rY c 4 |q!ؕMnP|xYnj킔Og`2%e`A>Ӵ%1Ym3g@E=e4T@< 0|r $ )/}Z,j5Qh~v`5VXPJKMۜ&b.7sz^a#E ]6;YsRQfo*M_a^]9J@/l@@g@Bzr^ J$盾rʣPHmշXO&3ZN~Qٝ`j~_A 95FU+ZAӪQ& ̰UK;2~X`Y:ۀMK@WT| D혻ޏ޶H,4KEK8F7W %󘊣44)[) r*]Ќ+I*xN|~`_w g5ykX[zCh`eoAƎ@ Mq<@慃 "- T}$pla<pXaSN.p'9TXee1 HUR jQH H/ul0 @0>xR "MSDBLE0 $PDBLE0 $PDO3S<ɘ~]EYLC'&0 ^m 㸇 +)m^D!u!SFeEGvTJbJ\bؖ,ۏ()r}|؃@h `L چ#!zi7;S]t9Ѻ/3 DH5&etjvE// :Z@[`:P,'D0'6B1 Ymsyc0~( ;@ؼiPjKk/p9^%altJsN)U~6J>z=µvq$F8|o=fV1 ׏'6:u*>oњZcc;@@tt[` fs@ȦWZx6;pgkVDupnU |u$|T\~YQ9Vo%+HSSVuNx/._= ȝUnw45TY"ʃy<p,$)*(7Y_rZ =)6SDLʌ)i9Itu tAs;/u5+Ω{s;`rlaDsG4i1|<qR6o]ػ֦0Gu2MB):$Cl5d.G(w}/yyXoQzeRak/ݪ}ISQEs/m`;elGMdl*Rqi;蛖7W<5׭ɍ S_ǤVD$B!YT#2@ q%hVT U4Q7_?U࿣h@pq\ \1HWQ`*'GJE2cv*N8(l@stTԟ)~ەs\]3d x(B&JXUnX&H\ tejE6;\g*(@l.syTzRv`%͋n8ZxRF2JR.=P(a])nԻbPF@K DFըlP=LxoZ#V$Z{p8(p|b5$k{0ͦ=Y)G8.ODdQ!0} {2>7"{vP8F=z+SzҀ_QQ;~ Y `1k vw0rm}[8D,SB;]c>tڕ _g iD;SN|T0bb WЄR/OC^r Om~ QŐ| '2 n*;&k &CKnxqG&?D 8xGjQËV{<B{g .pJ8KкٓOSnfG\?m҂ t%K+O(Nsҷi`TTThR0^H,vΠ'a “AwWCH 7 #^1+fꁋf_i;;mPPSfŗb q{ #[ًc0z}/׷SqE}xAY185G\cO]CD7-KXֻ0x4_Z [`u5TN q%N귎Y[w[X$6|W*R(>ɂh,bO5BjG5YM.%98K 2)N6uS*7B[P{?C 8&qDD5sx!wAMl1sT5ʛ@B'B])j%(TYCtz%cz*VfEZSDMTHx+A~DH,1w bg3n2Hm$|}.5 klHUIkЀ 'P Nij`dpuЊ`,ꥲy#sTD2  &`K"xMofHCKu&(RuQq(A,5eD&sβ gRyXMUx@E5N@`wJ b Agܤ#Vsc^?jൿ 7gz=L*7ؘG8:)2#X-)+ω<8` 'Π$H{sHջ;!mt648Gkʞq"IIͫ%pg? /{f#I 3kaX:) {EF);ՍJ;E4@N=`[Mkd Y [2ox瀧wvOi\a_#q4XF"PQ@t#2E!&7^;EozӏpVL<=o&/}uc"]D !r*'Z 4[-3½J$flˉLiϔ('pRkv8TS}THUP@okY" #b/fp$Z!BBH [bP6 'M @M9 -nY&iZ/KTZUg~79S!ORy9^TU=W*~Ȍ癊r8rVUHT@%T"w<(F%u(^K.+Tz)=d $k-*{@bFyǽu4 "n}7D?(B6n[-4@L5v|PiWʠ=x] ?Ơm0d ^FbF1 D >0p#̓*JoנR!COQHT.XQuU@Meb`Nxh*pIn}Ҵ\geX92?h1q%iլ9˚Y%˲666P@JjVkژ欹9 tcKZ>: H_\.zx%"bQI놞F7  4fFl&[ijm\ez|&KYW}]qda'F^FȚz2֋z~$Wdx?Dbã0ٱX!+"\.\G#1t;HFmG0FIrq? Fj>킽2LER,,h_{+8ڬG2Xl(, mnz.(hvXk|#i\Bp"?$F eB\rk):QY'{p; Q}xԅx)yƗR_c&nq'#`3]` A@οh##kxP#n,c#aelȍ5N+e; >&wmۆaa8u,놣8h7oyi4--ͯϟc9qk?y0apP4 ,Bp#Q!3l2XnT ;"%^&N_ZEw։OuRRqm\BX3Βp.*27jm P`eRvj cm[ <+Rڠє q)N 4S'O~r}'rݕrM<=L|,M,&ugt]"h4}+L#XюlܟL_n`^RO2.;Qc /K)}+Xh&mBs>pmF`[u h1SfQB}m[q@<[ [פ5R ̞?]x-f \ZA;ezqF`y>A,)4W?f GؘeE$GLhB':Mѻ]c1)ɛfP3.oC)M? [tGlomNU_{v4I #S&bV,k(2h0d~H-|tn7;L'g^i[nϛ]Zn9G@yckt4&>.FnьcCg@BS6fRvz8}&k%v%H/s`ͬo Ru qMH)@hzhL+c\̭-\1@53{+mBv\AݻꗏEyB_ `pif?lзM0bIK<fs3C?{'1@ #M @ U0o-o+L_?\X {vV}bPhBH/w L/wܛA4Ф Ӡ77,6~\]uoBlAq"o@A:eݘ4P  1`f(S1j ӿ#&&~g-]Q7׻{zyzw V*(tJ󤉿:@EI/uu\=Z>Wܷ[FTNB~ Γ&~U;84^䚿w/{S<mI$â.j:`4Wldx!\gb5]^sÆ:x)*VB Ip#խ{;b$r @s5K=F&'ѲXЁCf@ m @tՔ(h2=̞'%~IDM` zMXd ?rOxncRkKL 5|4e E'ŸG*)%? w tB}Iz\g$Y萤 |:.6 2#X*I9o]/ n"H?{;SdF{/n^  >2`oDWpà% b]ƿ| jG{חd[Ks1DޏpxRIK`JUrɬ.X{5,S,Q sr!Q'BcE` 4B1@p֫#t !G\L|xSЇ=TY[LC51 "ӔOw}`Xœ*0) I>Rđ(orPpP3 @N;AZ!05{5UnY`9#typ.p@v$UfY䱠F ߸#OӞ'1"d9ˑ7!ȥ4v<@\!oPLLF8j\ ґpZPJ['H63[g1UW Fb'HBPȯ싼$hƘgab`小Hȅ,-*$e.w1@"rfaP Hw_3H#z}.7@|fwɸޯ쇏d9S\0pq̦8I(@ "UoW<.P>1u`'o$iώ:O@TNP: 7R%A49< Tn%76,s .-wȇzsfx6bcUkH ]4e"F|qe&ǿ:UYuL%zꋁ6GL(~ {u@c gp yLM @.HuMyZZv#ǻ%EKe0Bc4YoT`BJMhZuM,Kp4 Hm>/]үb;pqX4+}ʆZ Bh!I{D5eRr?sI#p1VU D.RT5m!4EFI›T{Ջ>ߧ*i;~af>p- #A@ <[I!`s}ut} ?=a| $Hnb0rA Y8U*1__ӗp3<knTV & |Lnj턛U{0'J>a ~X/De5`Ow1 |63D,e{7 J*%{ w'kh"\na~z1D 1T^qmpv%HxBU ERadl`D7 txy~¸\2}O``O`=A+TF,FnkB|Jm0юK RA.,\h[r< 3AϘ I,Txt]~rr+z'0 yȞsT0S{Z kN6dj5؏RϫK5Nt9R6A|שּׂU B1WZ8{u]ͭ `7OE?ŧFۢkttK0 F4SpTq1F*in!Q,G; mv7[;?ʌ:m9:ZY{H0 0 'õa.֡KAfD!P - Z:l@.S ȄcX9ۡ|5sTՆqZm_(vnq(pck"Hw)).n馫!\IꢞY{oHE(HX[lq~~4 u:^ߘ[Be!h)%{3$0؞ ϸQٙy@ܛ-bf`6š]HTFDS=3@0\|7 'gBn1`g^y GNh!Fmm lRikP-\a [C5 [6{5~r`DÇ7?r0aJCjOFWHTU$ R^ks{]{@O&H. ӽp}E&y+JDr=QV_ ^s3~ _^:o ZzMcF<㘎)5~J'# oc12A&44Ҧ0oFSLfԑgUB|"`3HOH}Y87HTCm]Q<[d?Ȼ)&_? }8Kx:c bJ8iLX5s`6@8+W|Lu31ԊFuL 2SaتuhrH^ \SAwM~'i0\ľU nyGDQ6b4P{*S=EPbPiPEGI6IEA8Jұī|ZnJxIJ:(5YF璨+|+OQ7{&w7/ۡgB</,?s)%I2- ܒ4vɲ%shY`2,Jm`Xba926 @ lLV+i..𒉿'BE^0,%.-:bE|=1?s>}7mrWHG' x<\ K;C3ؙ8$DQ$`*&F0$JdTi `ጔF(6nTJ0 19qf*!(GrGƄyۑ(Οt@v?= v&d+$o׭+Bmy:V0,gO/}љ7+=Qu,rE;+ <B 9K:&~yQuVE2 J,M2(qwФKJFV}y-s\,y80f>8pv^> Qĵ<=$Nj~~pwor7oO9e'6'i9-EB8S] ӕ #$d.~5zоt~r7o l#ƾwZjٱmdr ( ~DsX2R clhaêW A3Po 0r+g}Dh2I'l3B;qIqN4?.}!TiB !AxZ-3 ^+!P!`T%Fsxgz37>sމmGm̀v8y8[4{Ž}aeQ 9/nvNK*9|ݽ/_|_x~ <Ϊjj[d5%qC1ڒ8;Mc*Jq`@! b5&.l'A[Ye'֊ ]/> /k|kkECbqaV/jvɮBìn;g@ Ad!Ke/\#dY#TΛMeߕyĺ86!/V%BJ 1*ZEsZA@Ж颻?՛Xh,-gO\RtlCbFSW~||F> ((98Q"' P D(@P(r pEBNA(a omA DO"'@  B!|>B!DO"'@ p1 AA GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" :&@Կ!  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'S6 "'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!d1 AKpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,KpD%@8" ` GX#,;uL0ko3 B@!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|>B!DO"'@  B!|:_2+ w".\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @2#@Aj0 @Q-řR被I#AF`BhKhKhKhKhKhKhKh+T3N?\@'835**s̫VثqkVǘ52"8>ڛ+3Ob~\Sx=cW`f oef75#?^ p&޹ Q>ꃪb5Y1-Y돹`h2N:őQA3Ce !$\ rY(@Be !3(@B`jZ YtH&)d`VTGrPyY2,Q5ϣK ^:i?ǫ%|,˲&s*i_}Hg U]~NV<.oI$$Wd4a40!ΰ (3gQT$NKjr uwΓ[fI-cy|oຬ",w&,ˇB gt/XBPYs顔CCا Z>v-/:جt@qkIKmi+#@zX^`O(u)5(M>g/|B'`v3c_hv]g`aH"ai`I#φ``BFvljCp~$}y]xGק;FV?zN$P).@sBI*)6@0v+sB>0YOCVkoXJCP<jG"dPP<47Z#p(1P6=" }FO? 0[Z&m/)GM yUBI\ 3Z^j3DB$\#1 &ˆbQRRWD8T[D%Hڮ;&"K\TzpG;rp c$Kd$Tc/!gs1 !3MU]M+o $@B91 !PBB. H,W絉 sx5PALUӴ -A)%TXIP*ěNFݝ7*~U'))ikJLJJZ%&%%mV III[U`RR'perr b=0))eh2x^tG )fB,sUl7ܓOn2n{rLg&H컕!L掞UO0.J箼&+}oչ;swa볘4-u vܯʏp ֝.P\UyahvE X-03q(f4@ifGl$\2XUs~"Y<#HN7ci߰)jgRҾ }9Z ٹGC̢~DXNaz%P/L?ds:#3b Fs]}&pEF" QLxXU0͇d"mHOaaR3e ljDBR]I/dijeEC ` @4\|W:pHP#}plz -T$T40KIήd ?3m |+aIK m=$  % $5zmn=F9l6]gLlx\8@pe~~)Ř4Qe@tâ _;}z][X>duETc3\, 2Yr/.&YxZF4^ka}V~SKօh 0gOD8si `Nyv<`>@m3ޛׅ#ШGn 0bM_nQ%"`rQv0 {( ;(/bKeFp U#^{%pȓkZ1= @Yݚ C 樶Xs +`9Q*˳9mc}YOH鱫24 3ǀP]c6G"kЪڅCڙ򺎧a̴ 1YX[v%jN Lkȵn uх}C0nt6QQ .M |=I%eO g,&w]0)4GLe w3Ht;+b نdb6ў gu l4Ǧ>e |!%c% 4de"qZpW5cZT(+oͬ_|{^TK,)ml~q0: s~\V,5|*ݽ) . >{~^0YV;8|T.!mVYw`  C * |V՛Vn&nZ/AwM4m5^vgg;djF(V1Jȍpo$0\n53cl[Bo-7XExŖj9طN']Կ}&%_ O4!&# @->tF6H{WZ 4:]t](ʰ݈/fIH 0m1_ڃNR;AKERANPmk}i8/h!(SV9ϥ톞jvY9<>d;[v};Y-'T/ա19hoٽB8" zLᏩZ'}?a4H'7=Yq{wP&O0hqzYX/3ফ;+S5oLR \,m&މ1FVU^fa뙙Fkл C@v9K`  |+]$DzӏO¥ @x23џ釹0O9NαPV{ B-j7t'C2gڡ}Yhj RDY`A=p֊@]i<ִ&!g '*WK`<ұ3Fr }`྇AnՎXGJ_U^7\T\o֐ ib%W.D&0KTz/*xNJZ(!朕Y,PJ%03 gG@+|:T"odzCmX8^.f6pt On@b  Z<1dP8V&0mdT5[~/eS ͬ{& "\: p"6A8~\&Ox[ta%nfms/u:V@YW7A|4@|sA&r98'*]My g=+NLs Bѱ}6꘸ot=nU"_cE\57!UkX诳̀~5W?nn}x-tx!D{\)|gM"/__|O߾Fa&GZ!;lgE&X_OnZܚ,Znn*1%esڮ%U5X-e+G_ళw1;(L~"ɿM7@sr_ՠ;wPZ;!/^,ᴿiAET!P&%zn(lM2[*G8[ hbanbWBh  'txNáQ>vIS5%%QTI.`@V^MѨQdXwt_oՍ]] @;e8s%d@bTTthj~ اO6׎Oo%,4u͔%pTԔ;{w6 Pt0:Ah s,6P4.2qZi?ޟ{K9\_޶Oy=Olϱ},51l\ɘI}Uo߮c^%+f"=co΁HWu6շ{-Ǝxl̞1GQ>62z^  <8z#zGTNq=6ql}ZSZ#Gv!P %e BY(P %e BY(P %e BY(P %e BY(P %e BY(P کcA[s(@!K, 5AaòIENDB`rustup-1.26.0/doc/src/installation/images/step1.png000066400000000000000000000151421441327105200222100ustar00rootroot00000000000000PNG  IHDRtgAMA asRGBPLTEؘu!!!pppdddݸLؘLuuؘLuLؘLukkǫ9kkڕ9fuuuuLuڕ}u===99L9p99kk>Lؓ00099>9hYYYѤpBBB$$$h9xыrrrkkM󹹹egggTTTLLL((()9]~+++>}+77778xxx+h```V2x+eeMJ>]n7uunnnujjjwm=}}}GGBɜV\\\*?3dܸ|LLݬk9k9ta#mLLp8iܶw]nu@~BlBd9ki>9ki99uuL+eюki`٘mm&MXؘu]nn???nnLɜظuLܽܠ]9k9:Ŗ9M2oڕ9Vkkmɕܠ2ڕkkڼ!kk9ku?]]ظعk)ژuؘظF*YIDATxk[UcG#5E!X7xJi@e22"3 Y,:BIgU(-4S.ZzϹ>Yc9=@byoV{-LI0 &!$`LBIA)~?ϊ6GǗ>>I3 m[,W&\0&!$`LB 0 &!$`LG^?kVwacYUkq3,cYígKIga|,M,/ x:cY2%ƥt!k=K5|lSpvz egdwlwNηtzdՉ]usl=4# U^f3aE3l.L 0Y e`5Ww+ǽtcAx\r;V-wPrS*g iaV wP[wÜ;wžúp!eSnv I`+8-kfKD vJ1Wjd]N78T%)gi Uv3r8vr̀4p6ۙ^φEwOx/f `Ķ$`}1,M;(z6oc;ǰKξpX{:ؖ&zY4m7e r6]ͱIn0X?[=l] x#i-?`ty T d)$bAOoZ݂=>dLup`%?`Fe̖р3}0Z: p5?-}Y) □TNש- eV:X2; z+ ܖ>- K2Ӏ==c$4wט wO se:+1gj݇xWh:64ؖY<|#Bple,G4Yeށh>N,meՀAMq`p5xz|pD/ 2G<|m@m5s 6߅!9trWl۸_xK[]l8-D}O 갎/w;.?TK4amU_n At׭TJ/ʬ[^3lz:s&?IZlcspZ^&YZ,(}tWmi>lPF>%k!p5]b]{Z=^%Rܰ:Ilŗ%Pdip\jN*Ti#:6SMg#&!$`LB 0 &!$`LI0 &!$`LI0 &ygGw])Iܛݪ4}4'?otSFz:BlLO'Sx]Hu't;vS? ;,!n0X/IST?FJjE'XkFz{_{)x­ ąsU׿R Fؙsd oBR4;$veycIYENS9#JEu_ K-Fp 3`I~KW^%xu+A?>>,\ThY!4.!&G)^5}^lc'3krqTUomy2z,Ոz̓Oa PMՉV #T%O^Ǐǽ }-$`1"˛2Ӿڬ+A ,Y\`1l4qWk~^ Y =/>SU㹙 5e3/&Zdp&~1^kNY a1`<'2e0&0U0!8NN(>c0fdPKwa`R`TU{JEcѳ^N ' ڊ!pBUՆF$H&B|v;L'iL92X= Иs4yMyh V>2C>ҕͨJr05XB:x|"7ѳ`<5ثd3SyZ|$d*-jsh\=x]V`o+F7Qy&=lKJ-`VPUUxq_ϙ=y%7Cp=F꣔e>ql,`X ܵM"Dee3ِ0Wx5N`O;%DU88_geʨ^3d`^/}[]g0zuNhd\-6L!0F/cX){P] %\{A[zU9zwJh'n:ogGGnLﯷ0)l 0+OU:}MM"!$`wp4^=d|6LED'TR%|MΧy"oerJQw1,4o s{ԫvQL3'hAv)R^ϛ ۷s:&}9RTPD x%dIрWBҫ&z`y%-gJY|doW"mJ=џ$`hipj\z)LJQ&ZQ] \o4DROD{Lix Lf>ͨGhk=c`drTRH.f>Mȇ1|}=|i4jK & TlHOBqL'ÀuMII@փ>\fč(:d7r왘<6Gg|zɻh8I~M4 &!$`LBI0 &!$`LBI0 &!$䃟˯>7~U{NT_:xGO?ت?Ϗ?}e=AA@ґdb٣6[`\cc0?#&"hyY҇ ;#%]\HеۺKa\|i$%_NGdrAsO~u dy#xx=,vbK+ Wx|w> 9gYBvnr|~Z7p@ӰL&swC%'rˣAj筽| NnUa"AP#!MPGa"lT&ĈӍZڨk Q ӦD0R_Ñ.+;6%5P)(LwtKCU0i97ό$x~fC^aa#d?Ep8OMS9)sXKFpw\N8J8bm,/ DQuz$l\ 8&5DQ ˰^R=kOcg@ Ҁr&!9d\2bH0)>0ʱ8|wʕ/'C[C &˟2jQw I,C1B[ι Mn#ppM%R o?ިռY\uKߕ1sw 2'Giߛ\^k^ xEZ4UOIK|ȳh.]0]) ]ihM jǷ+-7׭&٨FpX4V6E7_)co(\ lNbY,zh9H蹱Zd]de~Df,PߑLPE3W _|DNd`yӭ C9 "Z %sd;4EbD\&uBZd~ v-k/jjJwabZ^f*7Jy҆~MeUu'-B 0C C ` WBV%de{֖v=}ݬ8[[K2W->^ L:1V9)~hQSb J)͌yeԘݾSL>cn4ol0#ݸ?igPɃé( @XPhEբW%"YVNrV$Mt0u.,y^(l/J9~SbRUkyhY=}{L y*`]2eyrtQUEk[ke5ɱ5d-/y nx.f [1>Svru$¢pĽw6o,Z=|EwK-zL;/i5E)W[/J``^caffME4Yeie!I2X8!^RSњ]Kt;D]s}N\ Xoe,4b&e"OK(cesjm,-Ak)G `fֻdӏqyR ܧlR %k?Ƚ.gɪÛSQx&$/Bx0 0C 0 0C 0 0C 0 0C 0 0C 00C 00C 00C 0C C 0C C UAX C 0C C 0-W `!x~m&xl5}v<`Oo,s. `v.yؿՏgg#A~KGO6|pvvzjŽ7^ʼQ=}k3/d!/ 2\[_;ycU?:`r4M_ZZ&띚^ء,Ǻ\\d= ~րS%00C 0>`EC"`!`!`!0_PMPn"IENDB`rustup-1.26.0/doc/src/installation/images/step2.png000066400000000000000000000103771441327105200222160ustar00rootroot00000000000000PNG  IHDRtgAMA asRGBPLTEddd!!!33fv Lv uuuؙLؙM===M uu uM YYY$$$ꋋ'''k EEEgggv L///vuL+++9666M }}}xxxoooLUUU999l9ѣ222@@@```u;lKKKXX@@֭l9岲PPPږl uʓM L†ܤjjj\\\8k==k77{{OOk2(ږ:> lk.ҁۀѼݼcc+ ii jŀ7II~|̑WnӏQ NJ 5vNાگ¡MLFv>ruTq_#`&Z{|;l [?̃ ?Xj0iJգhҟݢvؘv(Ku Dph!|[sg{ A*\AR\vuxS,~کő v MuMؙv:+q :>L+sפ]|˖]vul4wUөv uҔD-9k\D(ۤR IDATxytTW$C:f=,f&L,0&HHҬV ==TJK]Vkkm]]Z7AO3$sݗ~wg @HA0F!`F!`F!`F(B(B(BQQQ0 0 0 0F!`FWaz;[&ufKᾞDVj%6;jL/tvԂ,/p퐣J%q|) 7)vkvp#[U/p#иvmIM4u6|e{Ba4S,xh脀5>` EWɪU9Vf3Khm S4Q&= 22jWҨQҐpJ;鉢 EG 7UFҮCj y9`qPopQvm]CSPwi)Vt(d }<^:$ǐINÂj-m{%Cq+%cl *t|{/鰷Z {&ho7Vkc(OjwFZqC-l`7viM*ɀk[gLAhmo5vo^גZg\Ӆd|tZVN x+e-pЭxV>x[/v:p CF|hf [ݠm'˜ ")|UV Jt6Xр=}8ڌ5p٬:*2wXߘIt%z6o2`uC$0n>;(d;t vm6ToЕ10Yrl5ɜ96efݦvzc!Zl=DU0vE8cX(5D䨅 L`l\f5?fGluN5訔eNuj,~uEp#tSCڠ,R6LvEE6(`|R{iTv[#Yl[. #̈$` &yCR_ |8N0O|aAW0*QiCS6Nwn tRClQF m(O4qA\._@4;h+#J샽!Gw8vKM&¼kNSGj#Ctp<Tl`P8+IWzcs~2CIDڷ7Xox<˚Ba-J޼f6T?; *5KK[Ţ[0*6=ȣ- lտ(N_[#FC  Cf3:9ײ//XBܗK5?flδD+ h9#["\8Ź J!O[haWs?K9Uzn[H8M0fU|$`y b\&Lb┪!]h ^9dn ܗhngXdkYWq0 Q#-&O#KHi\'tO%E7iyXMC *yŋuirh!k|J.IS 5XPnJ2/’{>سk0Yݽ^=MM,+d]&+pieBC{&?[s2>CeܐyI7&""}%gpa6Mf,*ҒO%`&5k|mMJB=3G,+o1-xd58 _Hd 5Xe>g h݂0jUb ffM`7mZ?(gdzn^{<.ZM2^}E ԪAvޚى.bi&_1kp1_ `rhz[0Vaɛg@aj&xwӦMDw0fx/6ww7]Fl =_XR5z) `F!`F!`F(B(B(BQQQ05Ϝ㜼nCK QS0!w=?8M^ݾ[3߸xdKGv?v?6~[̯ތk?~oY|_]e]3{|ό|iT*#3n*K5o V\Xt3>J9BW8M8nfk_A'GGG#A"U%ji %vj됥K)NN8w"ONŢx>c!?|I^Z^j }|3[^|uyN,!J[Yt~LA_*=ܞf 0`Vxw?>9dϿHYn6\l˺F(6F.>J!gϿmLO??;{MCONcoGֽz }~=β#ͭNu6|x}|q_)/:tOc l>88}pp`}e7cl```,,, XXXX ````,,,XXX 0```߀8XXX ````,,,XXX 0``VZPTIENDB`rustup-1.26.0/doc/src/installation/images/step3.png000066400000000000000000001463231441327105200222200ustar00rootroot00000000000000PNG  IHDR-dPLTEBBBhhh簾ߨQ΢@@@___!!!Rᙙ緷ѐ٘ɕԜzwwwOOOfffkK䲲oooZB&&&GGG˭TTTbbbQQQ&}Աvvv&ԵUe}}}\\\WWWKKK3sssO\ރszYYY888&qOSWzzzmmmkkkEEEddd===yc߷MCوl왃搻KMN{YdtOx222ϔ좎=i߬h啾ۂ즰v~X~ kVr\[捴N|cc%%c[z\{{22=xJue~~lCi11xIDATxJ[AA.wWic m(HP.ܴi]\>v-$po5O3 *RB@ ,@ ,@ ,@ ,@ ,@ԋ N @n2<%@x\'حSkTϯ @}_ `k7DRm@Oh)Xv@g9LsfAue=m{ @볛\z\MV\tp9ˍTuUkolf @`NOorRaueJ-0m=yw7LFlQlPl_M%?Aܨ7RGݟLNU_ 07{_D+nnR@{;ʭwZj%@X%@X%@Xp $@ ,@ ,@ ,@ ,@ ,@ ,@ˎ&Da^Mk X Hd9HS  v@]gU" {nsk,PDKREd_aہ3wBxkDn` 911 =MA[x0WE΁WQpQSEД|CVX{0n;p ti$vKl;O\n `ځ3&F%(5yH@@i /';<"s("A}Hȩd4iC~R /{ '={} 5-dgC^$Ua{]l˥@˥ 73U]g?/p^DoMls̈U@K(Tw+.)1~E%O 9*uvr6~R>i&NA"E}hީJelM~!nb˦zlPM-ab wSGt89 fRwx9@AȬ-8x(r^rՏ] ֭ ҩъ \'U7^:\k}r!y=oM8gUk3j2ؑ-\!"QEd@,PDKREd@,P~omAn_\E  1|H/W?G޼7Y&7d>ьVc&&^.V2 ˷!m=ͮY~n.Jlsqd{ y5#k:oA߹@(M83R _K^-Vv4Uu\P^ЂڧDBJשFa=P7YAȇ8HQF"0l`_ 0}K!aYC-cv-Pw7f90gS L-yI%W h`mqMX~F-x慨X|UꀂѨl9z'k^-dǶ)6Ʉv8hY<w`&+H>MnIS3ɡֆAfމGx+nbq%Q-O kj%%K$%YL[ .@=.)n+̣W$@5F y ! LR<$ [:zfN=cWIErcը+I !*XKdt:)/WR,6<E4O<3I*$z@lJҙTr =#pvQc3W9:d&@uM)0VhK <,h^t%*qe3H=̮ pr8 p83RAů7OU&#D8;Ⱦp8^yLe|lO8ٹCa^!0̛gJ*T%كX!%@,b KX@ %@8֙/֨qn̞{4 Dl?UwCDGR.FlLt~>5>(RF>򹋿o%fo8Mhp #=Xlnm&VIA/OO4ɭw/P`F[^`$t2Xw>Ţ5W0Ͻ3󶵨cּwN,5q g,,U~DiژL ){٨,z,[ :p^,4RI%w@L3l7lpE>ak^Ѡp?ӷL6r716ϽГ_:5 Lv1á֓h $~"^ߤX[˯X k1Ҥșt(t7Vly oPiTx&9pfA nn];ximd1֍f+< đ3~%KXA(r@.pƇ>1.줙?F(<9Q^$nfWp :A,#R%HZp>wlq igEZ+=r`!{/0:l9hL)W(=.TP O9*=0x@6>\/% - % Wء9PRT=> /ԨYa-Tx^ BL/_Ĉ?c@PLLi u v\U<-;i<V+̶wWCV*Ma r zL)ZfCD~ L#`$0~ qW{yVbٯYE5n+ (>Dx<[8Ax%@Yq/bY{R7fE (+H溷ޤ^w:ջc<JMcoEm<\TGD[p-Q$BǺΛniђcNA-P8aiGe0i`>Fy@yx&i6㇃lY|a^+>1@O'f*h;ȳ|l fx ~_e0TWw=eaz!r_/ps?/mQ˃4EJB[0vGB=كxJxYCJǜ?yykfLa$@c:t 4%v=ڲ\с >]uM2hDy FqR6/`ꁟdcSp ^@\caB%?n$͎Q+uU `מf&&"GU08z>ql&g~ uoa/ Zka lAHy?@VDi_8rdNLҎS3XH3@FKp/~qlCDԫاIv3[ʣZ&X'acF 9w!.o' _Td_ `4j\Y0b wjͤ,|Js Nd`!\HO# vY )f`\= л~.B@͌UDVwhQܤl7h}4Ush2Bf@Q[f ^vLM]z%Cşm3)K[*Jv'%0y |K`/.^x ,io&QZ%9 %)JHQonxꙙ o-дKZ8@v8n0@Wjbܑ%^0!'4'|4q^:'V #4͕d03cb8 @O|}Y5p!hEMAXVx72qzE6߈%H K^WƹvDߜcڋթ^Sj:%p0eܤt]m (e&n!DcS,8kM5jh_簜(gEth4 #Q5ЉѼ(N@%ENAP8DDBD!@X" ,`0K>` CX!,aK0sMQ'yk!YRQlwےĦ G& !! Sࡊ=ISD=7/kjJ4v3;ffۗ|.ќ"hj4 9H,}ќ#@ E+!@;'QD75z:ʉZ̝Z [jFsz8Y# F 5C)LLhj4#B& Y!$$f!`B""E·ThMbqmR""qvQP0[1'Yk+U4gTȤڥ8=YrPx P%t`bY<@㸯ќB7 %BuǩTeq , ceWݧXV#d>FqwT29r%N*4tƩ49,qCŲ޴U[V;i)2ꟌH%kj4Lx_[J\Aexk˸Das8A REgL+B1#4$w1, xQJ bܤyZoL\V.Ai%B"3m vl/_4G ;=`-@9ʢӾvPk ^dv}L`lhUϭnB[蜋}VZƹ8 [\ 8QQLnRzHAxC0R @䷽]}&@=[΍WVz .a!=[U (k+k 9h' DDpd:iʲ", DR>ߒ^L<09C Mu }3GlGwJnf:%Y, {xoy8T8L1p3Cfцi,^ ^e_DᢺuL(10ҶK٫w X`q"&-IEY3Y q)9h'*-@/-R0:6 gIE zɲ®'#j1lL0ous`f)LX=$`^8/؎"0aK~]߾A& cpܭ57V}?6q+aKt# 1u 8TŊX JA2& 2Y܈T*|:%"jĽj*-7AXRB\ &#A^b?o~`̭)y9_Zڝ И7'/@DsLjkqX@,[HJ|ICU9 zENQ@*i%Y)me eiFڜsS <-U\be*(kt\6x*7{N@Ce! QSl5v Z +ac眠&u4 UP߮1 Onhj!@o -] ݩ7bhj^/0>4 S ;FTkhF P zgt2L֛)oG2z<]O.h:^mjF pwA /0t rZ pW&"RLdC/JQ^3wbap@cPo7\'k,e.f,[:ڳ1h2o_!ٹԇRp戚GPcj;-zvk]I30r8пB&h'"1 C `V|pu? ;ɨ9Q 3PB7hQɌZCW1pITX{ڟg[4>DQYDuFZ@(z+rkX1qsp>£܉L>IjFl>yOBH?;{aK {sLwt;X6(u\b0A0*HJc4}k#UWDtTUW0v =٨MJ飾*#)2B)3[f֠mt_,Q&Jm\iD3xe?JA x x|8~k+ki 4%B⪓R**>V1'\:` t4|nb:}̿U(+nu,p\\m[T絽*O`<]^ KUw\_ BDH ~d@w-tn@ܫ~|L(/hZ!Lӡ164a~@q$ǫ)s*hGHT Lp5ϳ ↓069%E *BNk)콸` )XPYb]zH=s•@SUS` !#zCے 7)9cā +VX Y,"n\~ϳ#a!fwgǓ"cG"0K61yMy6jt'Ŵ+87M ?tٹD>73gy,nqUr"gO##]QO@qt|@3Rk;(i-׹b/Љ]pK;CBLQ=kq24\4&dH mr\C }Wcr\4|5櫤19TH5vHA~ ._AR.X(3d}71E 0D=n'@_5c"GC1c!,boH-#4U-ڣ[^Q@Y>@>c;>`K1kJuM B#8?=zK LM29Pq 'N[uu3̀`74!s8ad$C>HZeCšEb3 2qc*1g(p-&uKv{ &q% (\%j#9\.2E# .2vƐO${R ,MX?Xf2Rn=XAN pq7(ey)j:$0rKa[[)@ M(@#2?ߓVH"ҧe"1anԒDjy언lsPWp  }j. ( .; 0B(dDTRlR -YX H(s}v$Hb:d`:T;%fHM^3 P=l0q>(@B!nK'64U vII4 p,$ ޢ; qZвd=8tRoGtn.Y1XGV1, P p(3}&I(bIM^6WyD+رlð wA |+tl ?(&Czl ?m lPMB$(@PMB$(@PMB$(@PMB:nKC姕ހ$` p\.r!H`R C)蟗U ^u0!;JRMAX xN*d!%M F ^cVmǾS``?Z]0Q9{ L"p,a"+PB@>+.%(4 JJ:@]a f1(zw+??$Lsiw[8M?3U~Z{޺ M?LoG1j ce&ea? @'a70ƀ p&Eb)&@L1`\$&1ƀ p&E1F.@#xv+ۣ&ubz= 0\ݚc= WM׌Xn\_/EKأ{bˈ[M.lb\|0dhr9%fD8( yE=t`X„nȑ>I&WtTdqӵ^oV+ۣ&gIl*VK\Eqvǁ){ԇ)D[Sum->ٻ̦sQ5W΢ 4LW;a*Idk[ŌߣQ S4 0Wʌ9θ"Cȉyѧ88}/3潼bˈۣqnN Ͽ S9)3|t!+`E ȓ6t$X#c;Ȁ09>kW?MAjOݳ~N?,0y#4 FQ80+S]g"Vøك>5!L27o/9| <#@F;, 1zQ*Aˉ{u øs/JƉˌwyl 2pW䞯:> m LR|@) < xJe8 ;a\\(ORh % * {Go5 kֿLfꨮ&B  &@(OK̵͵NC|CCSbPܔ>m!贎o6.טkΖXmP\,{u#J(V4&@''HơC 0:;Ń쵸uT;w Oʲ %CG?c6a %dVxg9G-!'>-}ip۩9 j?F.MF[MsKz)Y\j%H4`l<&L)~h ~]"IGRPMs_Z'\KJ :l3+ ;<}V5Ms[Z'\P 7MnnL I>@dK`,,d% :e9nwim!Y6]ipEo(g9HuVWS;ʀ†8ݦRsmh%V\NL1pss93(@BެB`EtĊ(618i:CPʾIܚWHKQ#*a`8HoG qܗ 0")Sz&$&5Ɛq&@:c`O@>!pi0NǨcPG&m7q1Ch&oҿ"@u)C!W:$!p87!pso^'wvPܟ,Y9{^r ư|CMt9$Jǰ,N1ٗҋH >p }7/D1LUw~WU;Wės/*Grm6GakDe y@Mc0y {)SA,BhRiNa-K4޹-h0 PFiGf X |0=A܏ <C!8K\p 2+ yRURDdWL?%}nɈO)Hz)\s^'He' ~`J8)YDL)ÉI ] yrl>̄5h inJ/;#:+AI[Mo/ n'ˠGVoF} bݾ5S2g5$B٤X٬^mGjV(mlq#DžHhkԧ D-o"]u;53蒭kafњ''Qn$ɥG GZ#'[+ gIMGhEK!7K&t#I.=P*.}kԏ "[<|"J2dxcRf9Y#&4I nV"_:ҹ[%IfGaXF^5OO7Ei9q=%N,,R Պ,U&> 0Ftq- Ԩ=AY޴^^SȦr;ߓ =5u(KNuv7FW*)HzD@Vw ЦX4Y2(E8X܈DU p'`LXH{.cۤ]:MogI.ؙ Qc;H9` QEl}Ү Qy KFdɬDB gS}$FCLh=cvօWTTYG"W¦`E>U*@+hXB1wO% ǡ01]6t1daoСڄ&‘ 0FBXD;}`XɱI6y n]AW18v=#>Xf@[<"h`P pkk3Hv"[ZsPB%`Lk K` +@d epV{$@s&{?NLlOvb F G$ `gvḼl[)*/cKXu,ac1z8 .UA\-\_ƍņmND%|@L&\} `ߟZze#%MnD7`j4x,ۗ6ǣYIDw_I _H~_Gk8Bp#.sM۔UD,Q D9E|=S#OSք8[cj3ٍ̗Eл Dһ] v{ӏsNu۴q$9I&dvβNz ÛC9 YKQlH@op>Qmgb];f3r7t}ePb ⌵74\o{Q`L*Y\)8ؼ7 Wֱg\-\G&ދ߹Hm+LIFZ*rXsy}~QeԼ^#GܔS`LZשP LLkBw+bzVjHQ5yHYrWYѠsʫS۷FN̗ű=zh'!.<5PUk}+>sgW-d0*L>=7:1:gw *7h Rd\EJ!Eɷ%4rj/bXf:^b< ,hy,F5y5"4 @@RF5A!fs_.B[͸}qF1Iw N:M]-ai(ٶ41N3sWI Snzς+ݩy3qA&>^`k1C2iw8sQ)F̩&) JR._/4y 4@/oob \0s xx0O`21~&ikE$9ɩIQ jhs~)3 AEAnM_Xx 5Z,AB;; ;l}Ӡ1N"v%Ί|@MC3# [9>^`? ڸc ތ\C!^ج.B_V5vCmZ7I 9S0= =ii3@a (XTS!r~CDz9H~2̒gSo?vkT25A0"o/Ϗ w?>HmoX @Dݐ 0w^yOSWO,opN^i0?w̲TDJגy*lV묅n,՟ $ʐrpठnTb= 1|]aXKy3HW㸊#Ma`^,/CEm_g/8, P˴wm|Q >ؼ%Pp1*^O\ Bńm{pu_P| *^}TK-I@|.0،f+ (Y߳>H|b[{U"ilFE")z&ծgn4<  zLh&ZJj0ݱeޚv3<r$t17UxIhXcm29·I[4^8CXI95kd[X&( ㍕D hFbP0_U{KԃcPlM_DvR$Ѳn.\NƽE( e뤭C}x#hyAĄubɕ4E;؊`8b#Jf,4ed)1֕4G,@_Φ64?_F`us)8VJ6T3 0 :z0 ( ,Ne@shqѡHWF%Pf1 b$eކÈR%wV2ńע4ط0dmU藭rĹLP$V1큖Q6d5 Kp $ l.9o&I8J C=EvH*-V_ y<1-!Ě[jn :RQ(t "0Ч{GKr#wogWU^%ҫΛh 8a.QuW2TyLS`8Qw0g0[;4u58L2Z(szs Lsn"jU`[W좧tS`S`>5'+ɨ'FE)kȿ,Gͅ+~Qql] ;@aLhDUn\IUU@xq xoWw̴gDj]x;%(7]m_Μ ۋ4̅0Kߨ,v,(UU`O>S ]5Ylg6ٲS_.iTɉ #PW5MX\Dz P+2.3fƤp[U 4r MRc>Lk ?|dF5i$w@,H +0QCGק0 XڍUVxopXskG(oQFEnHf` ;&Jv7\ / 6%kQ 0A(lLY$LgݓptgyyiyXhιc M/ fL-#Z{22/uvdm4ee%t? >}1 P*E&Thb֓^c3 1[5NoyZ jR;>'rn:'/BZP-8ySī8՛XQ(LeZwO1e= `ՃKC  1m> "<+ld5/6(V[$z:I!ӽa74_VӹbY劐 d]*SU!Т߬K7 /w ǭ(0F..W0 *1X 4Hk ٖ>~b|o LGlr~mEMk (YJ%vӹ 1r'@'#[ @L̑U8Z9&p5`֍Qi.1P k+C:?6' {'^ \nRy 1*%!{>>70O0C#8]v%Lua '6Qc\C!4=5!8׸4wS#0xZU6r%Ia'fn: ҇!{̈&'Gfkl= nVP_nH m~UY0 p>LVR_k Y6s(Kkn0;??&ڐcZ`N`ÍfþUMH.Hee97 \ 8ۚnǹ2 N rp.Hm:qvt!`0m!PC͉= N^UCS}>_}Kp0M0XvV7Ѩ@0~ā.`>nO@w4C #h_ —7܏{}Z~qt8&רc| *V~k^'uLY 5}Y\40-58d YiE-(}cXc-J\I3~r]\ #*NNMdV(eø6T,_a;:1uwڝ3RtOpcP>7 .^Oy=jl,$r0H72 p$ r╥$Ut $ٟ_H%Η_QL2 {Ho%>/,7 $f/*ڶp'/xq6y -O6|p>)*̾ΝZ ayQ 39zFk3*mZتZлņeqEBron'q࠲@ &S+,b^!,DlS8fs+`,-g0QYM6k1`/ A{T[qTBbh@*kƯ~IS#ȿ`Ok@LFΧKUc$Q$]~#DgR0EّݡlcI/#TI5<,QQQisЉ"!X^o'F k@&3!.jEӐwt3b`4,po:F24L^'\K(-S~s1U0`a @(Qp|{{<7SU,GhBVj?/>5$ذ::Z&۴zTTU䴵-HG*[HADv6iȣ;CJ8"mv=e.\0,iU|L|hov[x[ykxѮ0zF دmf,va$4w%J^ڒ?Ƨ}~>uNT2=QӅU jLcB#,*1$FlhO&.8L/EÙFr[I0ZeRjYÅ[Xu'5KhTʭ4*i4+Wo. 7"]9pc?_̚dO`[{[O|M{ 0zf|9\u;:mthU9GHnbo˿1RgMRpGDzRd'Z[ܐRT@# ř4N#-.#sk3$MM3@>ຍs+M'W Pa9.QGtYǮFPũScҸ=5iSnތGޓ ƫ>a|z|<>޹ҭ[SV~Q~Zbt`4tq2VΆ-ae.j  Jiʄ8.2qp,-QX&;UeH yVߠL"n,E"R]KjHC D,IE9Zb(;$TƔWa7Hl"_K!a.P8Z2;ףW|H ;|f{Z["6F԰a)F7<.1 !2ö4/aD]d,2IK6qwKAXgkj֤WpN a ;aWYx,)$wL%M`ŏΠ6 sk|+ɂ#D ~5ó*ΰUKzu G CY+fCzT~9w=7l} ݽ{ =G=eg\?>;Zޞ\Kx ߜg tC;KbFm,eU!ضY@/Mh0AY;c@39;>F!Ā2cLr7f1xЄo1k#,`-XE.[*1IIH2EoDXpJhP+qGtH ]ts\0(UUl˒n2p!nxv+.2wo#XyeMc<|cs]c 5 [ĦsuDYMQl0avO=n,.[ p>bvJrq8 j, ڙ!"A!PMQ .*WW 4 g cqn.|cc B Z7P }ʷT̤,Gvc:yю@7_|w=Mw5 .(,}߽_?O>gQt8y0p83ݡစYmB9Hb}+>ؘçDpt073vS? KK)Y:@2 @0 zBooKNY8+}Tm!‰:܄= hN\`g:L=$8h.]MihmU}B_ޯm$V{HVSsw ORX]Ry:t#>06ZQRš`[jY~],@Ū~Y3ՎhlXus$[jR fZQ1VQBBK4(edS(!ɝ2rGh 1s-?=DI;Z;ᦉб"5% +R,ʏs Ez]Pr.r'#6 ͣۛ0oY*-b}4O}>_W?k㍎ϟScQOg\lNnvGw'Fv7G6QF4ӑ١G>;v'4R9p$ 1Sܥg舡'Sbe($gƟpccDR fj59[r;Y6uYZ Mp%) s ,ŋUf#XQp/DzYHzG iu 8DQDih#ZkC@MP$Y.Dα*Et#Smoܙ3g.޼MzOH[{;*_p( 5qO=E]uwSUIU^yR%MI9ORs4%]WRW&,PeR뺂B]QW9:kK.7P3`0 bVz0*{)Le͡;sT$LQD^Uj`q˒%jD Y1/`2Lx'LyyG}{ww2Rw<}HhsOCe7I&rhT}]^m՜ +8@a ST~8nhi/y{>=zvzƟ^ozрA569GHt,@V2oPc.Q 9'̐" pjD2A)*(^xD)M$_` m"ḧup8 aa`?^9BIV [ ݒk#>7ҫ܄̓;>'=A?άG :6TӨxbӡߵNf#93@+Q8=^/r'|_uQy8g9G4d.&$~" 1LG3Npzb̈́\J<S3Ь5ËGDy)f(_)6%X̲;e&]`apࢋrn&AF E рCumG{a(Z Sޖ;XŤs*j^G'||!/ HmRO0ʓ;3>G^:f ~̆Pb@Xi>㯋>|{7npM׋.tG>V[.K;h44 CN/H;dMGadDpKtL;iH F@;̀` q@:$xtjh}Xy) +g%š+oF@ dDB00h@[9bA i4&ջ=> Ns<NO;'=A\EB( D@_@qT@?P]sF)P|{/9-vl6kYMƻ.k8+i qYaf,蚢(".}'Kֵ賢-ڸyW 0kĘ5`޵4pf@~n!. 5=!ɐ,D1 CTT z&NtiEsBDLjS0mHLB}&aX%Ƌ|l3̌HƋ.ڎ亮͞3A.Lfs ; W_߼/Bx&9޷׽:.p C ؼ8͇$m0uYpˣw?}t>w6IDQ  L Tȴcg XP H01d [Wvw3GsmI:ˢ{9/m}4ϓmH1.Ϟ ip$<s:4;pF>[eIʿvvC4ElH|Gn?63C_YFk'D?Ah(ii.C #fߣ|ٶ6Yˢݞ4^ah(iL+B=; zq0vu~1ࠬe+zY@Ļ$P`QKkV&f\F9xY6N_pR♀4_BD9q2W?&u͜^mn^vܼ:-uav5 Ƨ!3(<39t ZֻB۞OO nLsOvs^r>+DXր%O>`eRL~4ǓNZ)` Vz{>}.0:cQH8!!~65̬]`"ܡҬ%TNS`lͺYQp xEI Oy! ^L3}VM9\i dN^a_0fQѮxo)[B0KDCYK"` <`j5ˍqɏ]I`0{ d_ +rmJdOuܲ@4O 3O׿ $"׍HQDz-_\ʜ )03HF.'H&)~o`p75 @^#@H*WYy(l, g-m | 2,-f ZǏBPA@ DX+Td.OR.AnmN^7n1)*x3#LCYND@ qz3ץ Yt>B f r}D`V z%TLUx 9VD-{)D(Yeت@HPr 3+70H{O6^*IC8Itƻh^D@g9;!Zg$-~ЦzD5 YBTLaPny~2,73k'Nm<(g8}+<H|@zFExQn)pHPUlYጴ6$t94M1F/:{/?׋+六*۲c[B^)BQRh?;K a &ȃDc|51iDW}7coЮ&Jwޝ!D5: C*]BwBSu8sG>ȩnZTVtޫg0#DŢ"2`zfu24ΪzsNg`]U!5,@љ~n_>xwAeL}3,@IK@! Iң^10dLD"MR,p'' zӚ/7% ~iQ7%e@_xpZm1[tUBu)CzSɜ+$c5A:##1h . &S3G*Eed:U4pV9(t z]`nUc"?(dBa b,\op& x( !`F1eRw  EALT NAM|.XՈ)ʠ* %·gRlJmX*˛AY mU{i cZ|(I%k%„\F;׸-YL =r@ ,}'s P8P7gU  u6+#<p`BFH"ݱ *<჌x`(X >3.>m-E,@ $J"[#Njq= 섂D-ʾ]P4w>E*(ˍ .R }l0(D0ˍ;;1\`) Y I ᭒QwG@^0v  FoûB ʈSe.0s3+iPGu5G7REZ{28\cȨhtoZnJk5 _i`~zǿ!x= ZAI0l 㡲7&Lqq͋ !.E}8M\|+28T VV8`54K!N:q^uG !9IDJ sOQmhL _l@<G Y$f?Qjtj: d{W0VDu X'(<r44 1i֡j|:=5?dU>$`{s L:5_=FX]*h~:qk$YNuW\$ih` ehN%.jd_v"jE< -Mu]Ѹ@a$Xz@9fuJBNd2w,{q$]ࡖjg = -SoDN]}VMxX2~Ÿr4e?%L}RY]W'R%wqj,D=f鰔@rtxx|ɖ< w\TT_4wD?}lâ[XhyO̮GYa|+~a G#5ey1x ?l࿹;z `%:*h}B !9:0~3ta3w /lv "b@.> ݢ)G>@^3/U%ֳ9 ޘQ’A^΋aǓ!# @ ry4֮?>_زXA=F'XhL`nlNl(yt#N[< @+uxK l}Äux]Ad ! 2Xp\e (;c[n`hA F鰘 ߱hy"12LqBhd Dkl$ zmO2d!>wYFRp8\Xl1 B/Rt>XX ËW.E%pqyEExT :be]峏]g#%G Om%0E>y6cv&0N:iqe7v6l,ń$MmHiETB%WHRğr #<|NLMcOn~:3f,`LWYA f3m ac*׼9g@d4hp#U f=~yEu[s+=piE,JPKж| p,@zXgXnG`,0 o^UtnFx{؆6q|~Uw(q1^8;l2]fۻG`9c bSՌ0B.9|*8Fwa"g~V% F,Ju#;# rd[Whheu[L%b}`'"o7.,R0YFMC9,] _M -lx1wN+fSoM# -eXi f?`*'Fd'⩣J4 ػ=d, 4f ݮ'B?^@V 9̽jh JKȅ L *C>i A/ʾwxNt1}*@vqɦ 9UU" zo0z@2[&_w 3|h&7"N /cq-%- ce F3 ]yHZpֈ_%jL<ڋUھc7`?8l95`)0'C,An*pP4,-nSn+h8tV*ND$xlF-/+)\agIGlMyN`x Tپ[Xxau'<TE:'J .SU$4b5 =4#׸_^&?SlI{&6bJ3$I;/&S5^.؛t:8tMT%!X)efB$Z=g w8XfV1]pt{~ˊ(J`Ii?I0e[ʝO|έֵOrp$2V6scCN 0 @=¸un$ @эl`w:L+_3pĚ @X; .` {?}2NyX0(y>] \%9@@a lXHeJ`eq%ţ_cXt` w/~|3E҇Gkd.eMmKԕ/a_ и8 Rj+?v%0v{H~ u oT38axOTu{۲< ,t}}}G^_rՊ,Id7E#-a 9.; ()嚄CpA-9LSY${D{Y7/@q7/HJ}&&.!S,yX/akUv?6}f 7V5R5,+ܹ =EXn U|la|w & …?1s< r. 7BF QW=ijeq A6 p󣧋h-@9pM!O_DUO:,!䁳2n.'?4 3+G Kaͫp`o|Ny cM?\xu!$X3 2 mrQ1ПQF߲}j0Ai*M h# ΂VsIp bnM;K sp'T2BHOdYzc '\]䰺&a QozT%T˘46HA5nJbF -b;9(?ŲPfĊUj0yٱ`!(ͪ Ձ,9 WlQC8[2e 8gݟ66?{ҵ8{iDsr},gtrMI3@C<d|1?QkOq[(qtTJ_I˕6NJƶ%oO@ۘQJv9Y?,țK ? cjO!(e)QGWN=k E/J։R(0҃zQ @[sItWw~gn$XEaM1$9 _w+pq|`Ks|N슈*:M ,ee$yBYU`jDz&,,Dp@qy]G儩#@O_UszO[@b4/49{w&ȣ}QNU/ez`ns&)Gq?ַoTxкZLa]H k<[b·¹AŢ-6:"%$%=3{IHF'-Tߍ`<ޜ^k8  ʲa"Zf0:(5O ulVcݤ*)(+^(E;2&*pmO,pä͒ts6^O*--Cb 7EhJJAN`Dȁ`}3 ?;| 9,y&GE; Vo "3..QL.fMQ'b+W "S%RT=Qu R`~]5W7OWVJ,EzNI+4C<Fk1bc%'Ȩ=ABm )0p IgՓ+BI DRNfͯT&V2Y29'1 үW Pdk'rXEGc{tAn=A#f=@X'H%>,ZL⩹ɒLh1FWA[pm}TߝG$LOkF6{8$Y0%?fI \cW8DL18zSV^FTOk)m Al,O@Z/8Jcԁ'HʍAUֵ'Hm9"f! Q8 i6E`2z}Qr$p'D ZE,<-@`5 J C73/{'kfI <$G$H/E{L%kU4y΁) 'A FS`ppГ%`*+$H{0i j8}'w)R xB#0,8q$hKW[ym0$H>'AYInGh}oPbK$HrXuc`^U`p :`*Jfףu~5"qH'@?Oa\0aYU#ˬíRR%QVun}z_2G F3Z2iɊ4x15eDPS i *"&9 ViKfP0HV`ʱ$>`.u͒cj>L$T n D݅yB 䕿|`Q8(p>*peZ}){f~8N-6.ֈ"EZ၊S66!}ij|wZKiZ%o]?og1.jGL,bWK,Õgk9SѲgL8 _P$Hܧ/jĝQ‰b BGC;<++\9@ssxo y#u 臲d0iq؅5tR7nQ & a)Z%aDe\z["C >RNաxwAE%Pzcg=C9ABj2gE1nkפ&vC(h ,Q`EcDHs"Hl5-2UQ`腲`̲" R\8 tg>P $:X2#BbOW]uW(q8 ZNAPFDX 0Tvčcr!؛'"6HlzFte$z(.phǏu &]7|\ ";A@T,t s[r +D h5L@,"9.N 3)>@uDFBPG0 u'Qxbm@kV%P8 2 'qS YQ ''[skRePzMsb!*h0"k fk j.R< )B>>[Hz)}sE67~F{::[+j)}Q$ D}vB-@V/{;_zp{!# Dݸh%vSRz;xŧ @Aspsݸ#o(q9Z ?/TRzy!yL|]r @oim|6  BNfWx|{=_BbEؽv;\ζKŊ^F4$~6ίQ8ʊTN}8agpH \9@OT(Gb v?8&E\.| ,`J>Ur{{QQU `+ooTV4O٢YhS@.}>#&U HvFhޜ uv[-s~i1"X.|R+Փg۟J*cbf0 ,md~Wv<n6( Of @k MYFjMLJekE[UMۣM,?A@@ X2W~D0 q@͓ TYr[)10 *DRcUƪjd@d@"\1sGil9Kf @&Ș6h] 3P? ;ʨ[F[(!J \ O攁iBҢo`>}CA9(+G $=цsK:jƗ %f[ -I Q3N%uiKPdFh'k6zĕ<70Fs_bX.(3WDFH)ġt1=b<"d{2J[ĖR:@za$P-AKM?.tcsPTOYj MwCN$֊W-P@l KФH@caJHDr=sܸZplת/峘I+ޗV$& (bFaTHĨ H|Lp-p(U.F+@~32wiY; l4ypt.qa9Iy%@MQ)4jI mtOK#Q"rS sDYJʀ]sN4#9'@чt}ohljߞ% 0u!aLXS(rkO_XH|%"ݗ>-pt_6N4`W yp;W ӠK i~gg.k+Jw`JVB,Hշ)aL`% a\tyx0?d 5`|%I3|ݬ~ׁK+)N$~a]^`NwV%.?KY- 5uG,恇}>ywtc$Mm{sz~<dKg&l6WN>mۅJfj$74$ dd$Β$dYRp-?&O^Hr`e隍P;{z.?8> d $֒$dW)-RGyxA ]55zFV8~臞oxN`Gi-/t<x=߱,'{AZ>N<,8"lXANxƱ'a} ad!cPcq{> ] F!Bx0ĕ>WH`3 #x9#-H,8\[0hP| *Zd "'6pU;j6ˣv+pKfx*;+8Kgy/qe𠿙Ϲ ^)%N lu^DѣҦՈVXXGb`(CGpU.<:Q=1BiGbvSI>>OK00:P50 !ahAte0)DGqV_]Q)4Vvޮj| p3#IYߒv+/\?]?.<ݬ)ǫvo{u,m tP'mEu _Q/^R*ҵҠliQ*C @ e ZGFوBP`X0E0:5z9@ uX蔫&`4b'5#u( AX$=9qNN.N t۫\n*[KIgNs )ɫqdKd M,.VkM&Zv 9͒M[s$ˏM"N+&tGtfbprr+xYmP( [u5GSaTY8p#A+BFS,$Q&D d$H8h,;ަ J@5q Rڦ&SDqZR){) PHBwzbhܪ:wwfyg7UVwJ3˧mN 0їSJ'Q%LVoޜ(ojihZ]Ik]p?Z' 04+J~X,ǸiYcWkҴhkQi^hhZikT;M:S@jD!{zk[HinԈ^k z'426jq4zHNPvQ85t)݇1M6x)B1fԴ%8C7zA pL$詌 - %j ş_O-Uq øؚ@Y+k5ANjh[J0 ?)H_XtXh 8,M>x07ZXw~)X8U|J}nblZ̕f6` F#)&}[[GX*Ml^I(¦,꘭BH)bMȌJACҐ艷B;wDPsҶhF2!AF P L%7@j*mJ\PEON@!VM!$X1;{8 j=8~4o9%翧N {_MuU4$c('P7A* ֳcd<739)Ք:gVbOyP*|b%~{6ǨE ٥=(V#hcl7FAuvhe  D@i`HvDZ-P}[ͻDXPf|A,촥DieJ-XO _lc! @$VPGM]CcP1Ee_Q!.0^J tud*8 PD kBsN}_l]}fzrYa A8~ pe{?8xs HIRi 9 ӟosJ~3aًz:?979hz^0v p]#qWoamʌ2wav׷:q|zӷw0t_-^i_֍7UPx~)ȩŜ˴eXX(CgQu*fV;Ceӯ*#ٵ8 DaXdmmlb]7F:jDbtZե.jνʿ9CQP (|wdd68r ~?*z=8^3H J\ ~9APlk@ݰ=>0ȬLn?nAl&={#7q{bK^^uojcp .ڠGCwL#D 4#.X7ЌfaSk'ÞuѥZG#3IH J *`鏿~-~ p{oe~ K #-~cT,_[\gG;?WgAiqbYn{!Pچpth<ǰ iP{epjOSҲ'= ^rsmdmYepN&1/Ż}us 3:]V`fվ G^M[q{ҰUf6Ȁ7[j`'*S oX'CP^iT`4.D9*wPő]"ynCТ@S C2IB#dR<1v <e%j&pFi{%+Z>^IkHUN!\ 3I^͐O}/c| ?ljgmsa&&;A3 2sP }W 9zp)2xeJhg>Š R D V BG_=a,$>]"#>_LO& {78E[`8[o|?)Pi& oqT^enKvAÒSnݧZ_: e+` N"LH ?&LR@T t)%Wݹ{%UIڰ|E(1Wab8!!u?Ñ &G:h:幛BsebVl\֟~:W 9.#PpoƵ[Nc0#L A ҿ/`~!gWzP݇@v=<'k We];ʫMJP5M]PuYᆶRۏ_D -ekQS 㓒$ K¡؈ @!/Mk:($2=Th,.&Pz\-&,?V 0z2T4Q=b>۽|4t~LVj+{;Sݧ53Gy@H5wpbk"=qT(Xuk>7] 6YA,s)M:eOLq/zE0ǭ0.Zl6cB +t280<I#D=ȱcꚔ>ױ~`+M) ~DiVOjR;,8*ihW41@Ϸ P(APW[7LcKͮQ\h2]q;s]R.d+)v/>- AQ{0N82zՐ 0Zp-윱n0៬0"50LU&&G} L}<~/wDT-I ',a'%.WH%UJ YZ(_6x]qP}^<`#Ծ xuf>cSqUۋq*Ge.z T\s'+8ް qBjYpm@9!rX)^Fh '@ P}FN#;GhKAdda3l @3:zސCMÇA{q}jWq@#Ofr޵VdZzVp>R髭pN+UsF88ikҡv}KDb)? @Wмy- }wmAƅ==T0%9TQhC/^9 |/d9@Ѡٝ~HD Ns$@:+M ;P.W{ `kpR6,sp[psl)$Ϟpk xI>cAV0@w Y*W.3JeS0ac{銾 oGK2˜W4pk4RQN'sg,Y8r :êc{Eګ3/ZΕ\CT# mIrYV8vPc FJ@*ҮEX(R{،fB *i0,QNm>:+"(2A_ @yB2Umv)~88|ె4`;@R,\e+sR"ҚTb)fu%ǣ8O\ -pNffU @+ku!۫ ks \[t 0m[`M?`V vte H{[Z,zwel!s7$/Z )VS~6c\?@?l*aMټf[Ux6 #HM?Үb5 -K1a݂>}dx@(K6w?lnk?zZ,v=-$Ǐ [ bV. + A0hd BD{h-\FJϏH(byB~^׷8Kb_Y}ߧ2DAN`ƾo(uIsvbBAaobTnq>^5&etN%1B}GhO? ܱ 0ab؊":Ksqgh !- +@p~.u|i R8F7OÀ@@+$ M t-h@*9F2L`;T`^MoQ' apbR,,0RY2,P'${~gÛkk[N8pyƓ쀛*7@4 V*PB|)j"c]^zc3;3kjB%?NE:_8Ry P8(Ju?r"s$8r!$ĒM(#8rP_@!g.Re=Y7;P$/ s5bAiaMdlb@sV(~|U#x rb %0Y^dfǟ]D XəZ8r̸fZQ(͠k0{*t׀p'ogNCzxJe$Ɨ"e yL0TIHҔne< i![˃A8.I;g:3٬%/ⶳ|*ĉ"7'{;}V(Ylg3I_}"Ӗ'.G Mm&l( vG *qp/퟉p]^o|m=vVѮG{Bo6'- iN% xXsDXwYDCO$z)f\FngmN=rfz0G$`Uq\'H[wqҚIR>1AKmLNZ- \TnR]KuaG$'=:O Ddq@YhH4&Q!<&TCW=Ao&ˍL,3wI"`ӵHqf S*$04$l7ɼ_/`h"M¥y\B*Uip5qum k*NV1G-ms Ykb-]>ì3∉, 2hdp^"CNlJX.OV'r(VO{xf捍3deA# CۦA$tXA#w6$cg7'#d 㟾!{Č۾țdvnӴ>1+#{ qp/s/d' (  m4!oN0ޜQS$$:#$]D+TtCJEſ&ܐ8iij8-ܧcÓֳY~ ,,9sM[7an gb8nsW#T c Ҧeҹ'{6ƙ^860>C' z<[V"UPy4wh~ǬP* eQ8BPsW9>~_8ӛޜkg&|w.N@ITP)%,! ”)l}O{rJBĈ" !.(,HB" !.˧#$@!`s t ;LT+geX#}X w{ʳWx,rBS 0| VwXw!dUY/9à v hXV~c~,& i>䏋SoIiO IقJ9 VV']EN$^7 V.Ex5GZ /NbYG7j}oȄnlǼYvnټ1sG1 9)A2k7Sv;3w~=N(r7,f=ÄɉabB驘lLJpRϟ]`Iuu`p81oSq !c6k Wi[ָI(e y85K"FB(>pP^nnBDLA,RB(7 #jlٯ=@t:,o401? 0X3zk`z3Q` mpcS3 p\y"1 db<ltʴ;{m`T>`/vxDa?p;y,-p/ٗN΢fk;OMV[m6Xf;7D?!fN$@eR NܶC)mOV\dQ^! ^lQk Аo>Ed[[d4)0 m̡9wQl\Q7_h#CDS p3q1XZAh(O؜i'kU<vS2t2 !q$ #l:A2kWMBwP28ȩ'VG|w~l!$J P\aDs{qVn&M(ĞI{NIBqb$@!eEB\ PqY$@{a0dC_mZZhiT*Ť JC}Z(OCI)J}@1?&i3_Μ9[[[[[[[R45o>`6W(:R֎EEE/A9: ZBq36@5LVUK+5~ &k%5.|<<ץ5L(^;v! das{VF~+A<}-^X8=-tQ F&gU:PoL U FRW?^ ܩ_#sI؀܉Gvc8V9y;@Y|q >0_WVu@&P>!h2מxX8%9voLzL!UEQZ0O[9&  ŋ[5E<ӂ̸2 Ew%Eܞ(e:e@-[1n#,&c}G MceA]`_uʣZ&6]FZ'T5#3:h?pl*? XE LZԈZz =f}䘔eP/S @N8 E2hcNYp*sfѾk'T;@[A=>Ὕ#';nFh#gEr A žfC!ڊ1zdp% i!>ėcaC$ %buYsx,)OaĂx9K4?M]ˣ3?爕 GI >{eXؖq҆wX,ӆ}')TS'7Xob#6}oj ИeAOQ 70=i_ϔi Z 4L~r#L=YߙQD?99suуp@{bf "e? u*;KDdkB-cu, ~Գ ߬`xZQDfGdzFd\cut5ֺ}r‚°G \G g"q_ &z`ЛkVgwpnm 4 r6Rוs^PIbFZƢ}S3=쨋@n78` x0 @ QZ sF=R {J2v A6' @>h eo5XB3Gl@zs g6k֩d|u0ڧGƸ<Ũ>U^~s8U ڗoN{l 72vD]uO)c baZ(FO NZ)+qg1*E|đ=,[K`P} |K{rR>ٶp <Z(WpL@8Z<9|JtV0Sjut;(,ž^@/`UVGJ఼ƔN:H#nr]V M`/>z~:@,p0-{J `ܧ.vI_3~z5>S@l  z|oi`kPX\:2  3HOO@l׮ٳD9٣aőM VeIgOTm_VJ);6Y|RyAk%Fu?^77u`s6C1M"YwB՗Vkݙ`㗁Z@ؿؒ]&x7+Ro^<afn/p3"ǒk ?B{Z|#̢y@ߟ6ƹwχwϯƯw4LϪ9-ssICni5ƁOui.֑cs9܌jǿ/- l]|X PjZJ1Rz#G =5S(5>=VdBLze *\yV:*G5ظW4&(ssD/ps^>vsƇ 5 :q0LX *:d !m*/Qd ZcӯJkq `EY z m3#L 1`n/9j}k51:@}g ext]$2'ɟ~='.a HƤ2Xx`*j? 3pڛ 0v}m.AxfUp L<*5Ub(Q9":/jT=2kDq *XK`ή0w3{0;{J`FJU8 fRl3b z*I|EY uJ $fK`V"|n1*TXwv%Yq6 Ay*H ʞIУUvz2aͱf'GD鯁(P蔻Ak؜`\z 9&#lV%@8 O\r}vIg5u@duḱYd4f 7A<:8 x#賜-dbOd6ЪAdn|dPL RQA< b!=2M8H|I 7I5);On_G M%9ك/3︫0W8^|olY6| # ){Sm40SKd=1NɱKVKO Tثh<ӕQhb\xb[͎MscyO`QQte@tx\Y*,***,,**zN8[ŘNAtqJu"G'i+ƪca{Fsc샭]' 9OW#Z6K+1rCsi=P:u@5ђ8o@z"As!_eĤ /N|kv: ȇ@6k5aS[kN8Wnԙ[2HE:sxɧwM @ۚ͂2Gh~.98 ՖJ1m%h$=!@,O;*?(**'0¶5 @ [)^J1*h\L0P_D6Z X>У]:qକ@* 07 @h qnUA@|0L qc/zi!E/A^_D=л&a24Rq+}`ە_Uwq@F>T'ʐIadX¼kMgWrLuZzG5kQn@C!-@(K(K(K(K(K(K(K LX?Gܾfkg}\ cφh3z1nڵzkqc=Gc|x9fn [_?@x3R32O;yf C&!{ҳz9%qg#B+= ?8_2kבW38ӖbC+>(Owe}7 \op#?hG﯑ kvdCZ;ב5[p1ξdh!>ܟmD Xhcxss.[Oυ("<^w8H&YN|g+("`ہ7pq}{mPgx7yj050tبPE~&?ہ)h("W3nP{S0<PDf݁k0 PD ^}8 Qf-Yelʇ>PAf62n8G^53h_ +`*κKώGLJ6<Yj~8ZxCYf4-g)4&d6 I9+׉CQ@g @ؠRʀt,M ha@с[? `^P6$iGmPAaw("OCCGuIǃVָ0ǹ,> ` ~cc.GA#Tg Yܼȡ ` .vgoX DREd@Y,PDKREd@˾ٵ&axw1HeKIM ma!voU߱3/(%K㙜x0|Bl"h"g>%J2[潓gr:lY!Uq{mP^#pg,n_/'|Yۑ*r9QBZ7;KYI s`.c=6lM|?2ڋrv) 3 7+bz(EKpw7B/{v.Mё3\Ԁߵ%X# HՕ"HtBd/9*%-0Kmֶ+zR.YTB*eZf XԨwlu㶡9?γ3d}/ /M˥e8`YW`$H к XHϥaMn^wZ2b%ȥ]dTåϳ6*QRwԕͭwPrUv8h+ Pڣǹ2{S_r0 uÁ܀Ԇ cIY`7Itbk.%z(8L KK_@2 A _@3iͥW,mt ᠋eIl:5Lu73%;L-}b q=Z=- CK wCi{LtA2埮$9'2֤y/ZG,n9,- PxDߚfF=m &-yv 1tҢ[?dV!QeGCBK" x b_Ta#1htPhڦV5OP`{fXsV%t^fmst`qYkfk' c~J-KG;,./48 Gܓ4$dM%.*iH&pȤƒܙ J `\:&ǚ@a.&KK%3߆jM" e.Sqm5I>Fvg B qqfCU,H\|Z>w@tS8UI5 qpIqs1C!L^o,g@ p}t, zIb{tp <`@Pb[D!$ɴy,{Z\z.PM xoiMX`BsGFc@BM5OGoy/L2x*<fxӕG_K`K`0?Yax+&H5{e&I&ʖrnl|}ȽMm`|Rl#l]XQt) םI%*<2O82<W dɰ Ҹ 0aD K/tt\C`Rny"|?fm p3YݺeC5o[ H@.=—N2,DfK{wBöW4c< w4k0DKOeرA(ao' jo1 `lض0[.`Mf~J\!&f{m pK%_^г9a LD#`$0~ݬ4 Dai҅E .*U";7Q7\//H&,Lv/ !0FBcH@ !0FBcH@ !0O;8*7G` 0FBcH@ !0FM,s&mj~kɍzM R:7 8 -6_/]k@;T/fqCGC@/X|uVlU?e2dJYu嵶-dNp.luj|m6ץ|nm5v㩱O_]| ")R n59: _lixW/0Ƙր\V,gGx;= Ԕ"|%{]x\xvy@nr c̜X U_9'7~sKڪs` &@3',K@ˢL*ǫp p9kJKe~9{Bҫ4_R˻a41sbz}_lVnlj^|,Vo(@gޤT>^-9V^ג%EOWˁl 7_CRS7Ds]AaM>$9 {v W|~X,7zM|A 재!([|zJqK7ؼ)3/68z@]礣9|b?JI&Hۻ{w]/ JD)-p\@I ȵG cZ! ̶*~PrTz܇DTr!1ҕ[ʱR>֊p,=n58S3*չ)"a )@'^Irqdō*#""Yq/SԱZAibNJuuQ 䃖A\xWrUM,#Y9ug=X ZE!.O֛a {`B^UW1'ՃQ=œ|{2i^d2j4=u.VSqm#z'qQ*5@`e PVWj : Cl5N/& m\=kR&ɾ`:^> 0̦*QG]Vh#ӯ}bUVF8N, W C3&~&O5HD,w-p-0_!EhcWD/@Ni"4"$ T^%mc;(F*nm`謏;[`ʝͰ0o&1g:SǷ=݉dR7]қ J)8蓫}UxG]""q;rr&6%RVtPP{(n'O?&!?)V=& qj 03`Sdg(u;d'aLCLq@ 01ab48h p 0k@G!GAQN@B_I^{i|YӤwCM&J%V%i;;?ԯ`izj(IuaYKh_`v(eT@IsG:Oӹ&86{%0Pj[?|_%έ" F+Y."\㒢. a 4^+ZQcRn聆d&9BW1 o%έ"=iƙ'f m֡{bE%1( )ZQ Ӑ|cF#R$;! Dbz?eS,n6:g9QDlΩ TiH=lGvC{uQ>'qs--P` =,OkNx 9ͬE@sCO O o0L'Ĥegy  aU }1U1ogЏ]A=~S(Ig1B y? o\}>5eB3U;ctLw|mf#:t3~daHnAH,JF~zOq.uH6=Ч,`DCg u݌Nq-2h n⤙CfwD#$-@gn;\7 p'$xqt5!rf^5H)J1]:"QpqI{,>&_(%漜#9DQ~.hXR|_ `5H!ԾVp}J1fe]zP/#pMACV"pV/ X@cBg6<ïVyȷ~7$IuOR)x S^pd25/ sQOX SgC?g2L_HË-Ȇ3Qҗ7JHt8/仸*,c,Lp&lFJңɂ>9/Hq6"7›#ɪ$˲),Kj2JhLX%X$SBG`<|r H108W>$,{GYD<͑lqB;cuqtXѵm]% N12m)岌09tonO9>dۨYaO#`4|`ioC}<cC35bt8c4MnLc6cdx`Xa<bjdxa DЈt(6eA7.(KW0+ń 첫`?L<,=®MvOv삌@fU/ d_6{? a h5jQZVA 3,Q זQm1h3Ht4Ds`.rc27A=[E.:nZ6ɍ~qmT0h@04 f*aHAbc`WFj1f~țLRԔZMQ\2*щL-Z:(n)VFV!lep R'ϓ\c6~EζGaœpU r IX%iFa4/qO ʻۊ̳yB7ŭ%P:!\~~? 'z_Y@SD3A)F0屏HȚjwa@ؙ0 DxY]V !%6G1o]a7glcVuKJP=1JOR^ I*%Y a M!P޸``jH=+H2!({&?8Ii`,uPjn[,"k i.Db{;8AlWA4_RGon-4-EE, 2>EA!ڊoC2u(0 9 QGL#@UsF70=z+PMb#,m#{U@.pJT('˃\cRP8p\l,13/ۅeo ~8;}v;Zn:ZJmÙ[<ޡ ̏If{S:̰@qЫأy'@)Jn d\i֛g^`߈vT\"ml+07o6,y̛@ ,_Uu߾iOo֢-#ee.'V8 8,} TQo:T1QssD4e QCZ&K֓e l pDZ"D 4V5KoE˫Aꬹ7pf'<;^]Y֋W *UcHU:}8.*yjhmQԸj059N3MtCj /<ȜCׅK% p3uU#P&!a!=A8*`>2IeDhYCTd|#%µ@ś,\j. k4z#r)2=ieb" L Z PƏT5$\WqI}u]*BZYZ6sT ™2jT\Z)r֌9~: 0H8 \ʝhe-OzUZGQ#ױIDHXDOI0>!@@*_M m4Ex=q^u}8 Y'UTw]<'kq͑K;а > !pDr,L2FR:n* .6Vk(=@qP 'zl~'sY_et+2HkF@"$,'U /cF0 ed(ڥ7CEj2sxk@?U|=1w ' =MǏ_^B>ݪP ObcS pN EE='uVnkj0==No{ P ACY HĺN,+ 7W K0%]²8j#F,w|]/_Th1צ[DXX0h$ !Ni]Sk=%eX8ZlY -@Ƕ"Aθ;^ʧl ؀.SInlmGh H~"m: Z7=^*Y`aw,@8KV4 M'A@;(_m03J|Wv`V]0HI",EkkEA'M5V{a$>cy( LPqc2hOGᠻ9Nng]줟d޼Dh  cQ?&eocp,c5ƟUw7D_́'0GP0ږW[`[vGGɫAop>8 HдE6vtlTm}sܡDX  eO$M1'w{o-"tp=T"'04MueAOeﲛO֗':nx8cR $@?) be wr M@W継# H?;0.մ/ؖRZ**`bD@e1&MnP/&~$M:vfޞyNEqHt>W'; }idc[E$-uwI`y\{=лrGDU9 LI"@QT":7޳NX3aY]1Y 0IZ8丹fi< PxK9 d%We#Ҭ9!C6dd&$i CͲ @(p\(|~pcyA,k>dY0$-ThMxsՃuN _kR(|P4eR4BXI|FV`L^F`a1g:zz~|&wK2>us(/ Eġ 0IZr(9+ꂽ9T/|x45fd&$i `!ւC xQ\|a IbYs±G$5$"F# !PwmIr&_?_S}6]r;MEDrFHT2bC>[xadީ+Gcptt͛G7 -ōo7]u nح>|rpIozδ< =ni+^cd&$ a6xc@]zϐ8vvwzJo75X߮5Ѿ~߿Ѹ];~r|'۵zr9_O'F{g ">҈F7AY 0I(C* ag=W[rW;Jj?\UV+JrJխk٪lFϼP:[D7'CUfd&$PhsM[,$Xtaii4tuV?۩@iz}ZW/h~y߾}~mZ]5iҳ7K;p 0BA LIR#K*Jb*!d)oL۶%wB߯h{|\kN&999Lht0cBǚSzoi =Or 9ha)\90L$=[sEE,#Mu7 @?LlW*KΖmu֥шǗhM80;i`5EXEAQe[02` PšPLb.`8< \ڶ|/ CCM }1B{%*eCKWF s"6dd&$)(vd> և6. lˍ]! 8 8Ҏ!:%zho.7CUC6 و# a#c0&Iʩ}<08R Hv]Fāˡ9ki*ziX ԉr?cI-mn#\=oҧ}EfU>GN9/Bk x嚭n{-2ƛ,ۦ-ziۼFr.?{:ռ$%0>ZCk `HXJys6OV/q|~cámΫbh|h>/9%& h]3'"i--\nZGR]=)vjIJNټ(|y &Ex*IYP*i?0Zt&&Z׬|i/ZFs䱛 | iu>LޙS {Ndn6:{B?.pLi=,ɬPmNF O=%ӟ4  >ֹ"8m;W|6x9 #B+#54 X#@C` !54 X#@C`.I^y pevV˼ ,~:; v8Asvn{c^ J Y *t qĐ)1@UMgG7 @" g^g @ mp@5&|mȳ8V X#@C` !54 X#@C` !54@K+#50F10@Mb:Z,l @@|3 iP?;SP]ͤF[W ELmn|S)qء5TTF˺s(¸]#<˯_# z;N ,t^8V*#6La p=X@@@@@@@@pu EVF3B}lu>\tL-B,f:O I85䮩 5 l#zjP3~65`@~@Mr~lv6vj!u{=bޠns{  | :M@[p"rX Tu֩p%t>? 4BKjSjC+"_=)= `F `HC:]jX bI%xNSf<CrSoJ<[z|<Xj '|9[Sׄݎ9y]Ǖ1OXTu>J^`&Cz77Ajv<B!6B+lV X%L1 2X@`#}w1tMKx  8{Iq1lv5@;\̓"ckd>qL7?  dp ?OFg@`#pF6Bg@Nq(dpF V_VEl ;P^L$QVZQJ{(@/-RH)B"TxeqpO= @$9T>#U@~,;#] kp_1 !;&"3aK0D%@" ` CX!,aK0D%@" ` CX!,aK0D%@" ` CX!,aK0D%@" ` CX!,aK0D%@" ` CX!,aK0D%@" ` CX!,uL1p¿eW@BD4\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @%"K!DB.\!Bp @2#@o0E"y3EWG%@[%@[%@[%@[%@[%@[%@[^' 9ceU+bUTD8p̈5R+VcQWuj͕'{1igy.ʈ!Gx Ǟ1+gz323bڛAr8?Asq0(G3* HN6}.$Kb4. , @.˗g@+'@c\rJ.7T(MDp$4HgZPҐz4vaX)d 0s?%sy3Mrnbq},Of#C; k.FpȉH< 5Ne8 E3(i[Bౢ\父R\tjx{8&_Ww,՟%I^*.@#ީW%ЙXZ7ݭ%37CXiH/h>'(H6JV;tN̗FNfkarB3aVDy\k+$J4,mO^,oxGv$@p.BOI/1[cz;X8wDbOfƷ @. , @+{f"1 Q&9!&O0Wz?ԉcY"T(ʡˎyIie2'O#90Nx`P"R0PDgb2#J 3(r3"zyV9{ ;@$̏Lu+jN^LRV '.fP[7[|Yz[2h`.]VhLs:bUHZ1JRop1 nk} i>sgq)m9&!u/\fʌ@2,yax`*J9f̣0QjvhMk7JU=݉Ggmp =^ & ;JYwWpm Ӟ~F}s޻>ZkâЯgbCv%Vp?bz_W06 q_]Y%4p:$Ivv>I_wv~=; ac^fYǾj]> |8Ԁ\lQ O#5ܬe)& EacMۀ ,Jʋ*Hc"lYAKjtY~?yZo[r-pmhaDMkOxHj߄7&QigØr2#8h}cje#Ew6g Ieu .!κ:RN1NN%@jx0\Y,LgM2F)Ь,!ފm:3W PF 0y}Άh k`"qPIA'8߾?-V4T #0+K.'q:w@76=v}orߟl̰a+;٣îZ~&d׆LvoَI*dnv2Bk^BF:}޸qS &P0HLRo)F̊LTPjF@FqTe$0~#˷S޾"=:u9U)3&wsA @%&@6by=sѨǍM }17@ !JT\4Rվ" v`]$o JGz*TVdF,)52I@Vf+ߥѤ M[hbI21f-sUelkv@I ms-pHTZU'܃? <7"RެݥcgEGpwz}pcFQxXú7(D^E-2^퍡Q4Dñ cohw=2Yfwq7Gqw ᇓ,3U貟p6 |Q)}* 1˵;mP4m=c('x;7/RhϘ&C| ,F(`ȔI0I_g K:&zE4iDTkTKڗ" t-)#%n:u&xAxq n ߥ81xzIoiEҙGZUҬ:}0)H>e?`r H I[sOuc6;-zC KW[ƺj0V5,Pk0g Uj"$ fJZY"p4Hl `ѺKd\8*j# JbO%T? R '[ };v'鍃a& ~PhT Ֆa;JPM\fN㠾!U? ~N83LJR}PJgw8~Q/Šn>"ċcUVJԈXZXPo/މ_V$%0z_*uM rv uJ+R X.m Jn[,i*ĹWMZ`R. ZLJ` M`s}^ ZG2BZ KU+?Sm/gN\9h CD%<贜Ӯj[+ڵr.cğ hNmHP 2Lp5(՚vi:&7dsC֝S2`zEO`<ikdi!kKԦP41-X0x䁜ky`NUyj٪ YL5,1;jF5 @^ѱ!(Tj:+zy]&d Y}5I5rcXI芻+A .pE@5@#3s|H? c"L5ssվY~a*ê&$, @2 0$-Q@UԾToïk9y?v%N vu N䭕J%rɶ풍i.Ul TqbSO4eF((#ϟ46P>Ѯ#4%10U#3)ٻ(V%@ZΟ5ɒtHR Ata߽E>ȏ ;~y7]Ff Ss*]Me> IDYL0c<6s3 / ?]`.0S,ƒ굚,˗NjfG 1Q0vb#!tOw? ]ǃlʃ(GtۂOP-{-vhj[|ڐ%@h9F[au/ա)aw!_S8촒(IL,r[{ H!I8y40g> fU<^\QB'aWE:SV` jmseXs%PBz#Jki Xfo_ @0kI~w=mA?膽J@rq(ـcA)qL qBiqP4)jhҨ FGR>ϰkR'#};;姙ۻqXI$`/ӣ=Dbr_0)v&T{Kj\ 0R?vr_vUZ^~aqO^&l[ zwqsPʮֱ4"\8m0WrʁǻN*.CӫcݸGrcW3ыWhɹM⢚gWjlPjZh@oGH.-ݦ*lQ鼓otc#d T7q=lNtpk'&筀GZ1 q٢qt )^N~YZC`W3"7>Ph>@M>bM+L(aKpЯݧm=?cuNk@xBOVMBcq9VYK(㧸L:|[ofYOOudejs ݽ|Ppg6Ovw8Hy쭳X*+58"^79;WPcT4JʄNN)dZF**e[M3'9O?tw34 _\$n,\C"9>S`O)*DklPdŭ2[!,k tsev8G4Q'&@yˆ%$U p`24MԡA^4hh3 {\gWӰp);Y<1u;Z.7-\,Z_0յc=L]z|Yj\x(BTQ$DjAj`R#[ KO/~:!:rVs&SIP+H'f9Jnh3yUNE#9RhF`'Ugfh*z@KXqPA#{(SPj8aS`դt; v^t:}"IQ u~aKH6"@A9JGHFx@Oo jS`(| JT*a1TY u8O'tn=BLlo ce&aM9!@|Eñ4s" |N  H"5`v[l \"B!ϖ r⇱}زfKVC(nȩfV+D|/Qׁ<} :;/*GS#e^}6a%MEn4b6 ƛt SZΆIg`3xj_̙$;V&YY?~0צFC`м'F3=54!x+ oH lsn`V:)0D @A-a p\`BV@亻)0|?sd(3sfS`1Wb , e[ K/9?xo}=9TaP4AdQz`I&^b#.Y$Hm@[2>d/lH}ڠY!\Ǔ92̙0klAʄo6{J'LBY'@3~MP(U&7  s [M`AK0%^Q&d:1oXAeN. " A(?MX`4 ,Vj#@rdhlHxMg=5@fa)©Q294Ei@{ESk''&Pkx,p+`> RSGQ8¸+;!@vD~/^+[fH`7aැonp9 3ffʳ-Pvo;Ҳ.?)mLbږܾ}m)QQ\Y9u؅sµhcѳ'Oo=? Օ-QI% Vܤm"=d5`REmɯMX_a+ p-~JJ|dAtzQf?\4'O[[H4w rXb;XI;}PT=^:F5juЗ$@(z[}ck_z>Wu8XzDb]Wm @pp f\ AŖ(~/SDY|D3q++bR~*]l  bJ_>\2Eydj*`XvV8Rg{fgQÁ+&8x. >;Zez:[ plkj|5>[[[hmmoeGH";i VXе2P`hCWv/1Ҡ8kGg`.瑘ݗ__on ǖO(QKJs6GS3ȇMU}0_X/,A" $Xf@<4 J =%Np /puq5dF3^xtRo}WOt+ S_?t94-06H>;\ƍ( j0]1cz VUlPX IFU#{iTӻ 0jp І6-2NO'fXgj-|c:偊X^[E1I:Jֵ$]Ob8NF$ }>Ks`i c)0.Tk*h KhĊCU+5(%A߫,)DiڻIV7S^B?Olot`im _im!s-zXe`&MJ=yH%A$Ɗ31O\\9} = B֓gN̳Q܏M`ֺ=2!eyq +Vc7m0_LT׺ҙ`~ ;״(Gj+MDM1ުM4*iS(4( 0*`c'iι7˽sϭ4@Y 5jpwV_ 5* T)=NEB$7 ݬ-U5<# 9, *x(sᲅJ 6ႨCSY8sʃ=O[}*|! $~gxF3^<N yM)YiC`F.`b7OPP PWB1Fb %5:x4h_ 2 8^("&}0ٌA`躪`IU HqgnM'X׼0' 0 r@reHuDP{A Y-IK`@a4=(h3aq|H?s:Ќ;`2 9aO}3Zz[ tK* Oq嶺d XF>ISeSҬ_AoNMb k:F!w 9 a@1Br5xz OK/пh#p]@ 0LbбpJ\hLP16[_oK`aQ%0wXq)N`2 $e _p>5 uk'3.0L. p9`M0hTyuaYquqA\!.yqct|*XƝUO 8(C]a;=R?9ŝik z(4U56k OҀ @Q7}ch۶t:Rwt˞ L~, $G`NJd2un>(^4!&TPn5{Xkág{hڰ0$>O@x 0L@QGPpU*- n@z72yqqteKǁuZ $ρ'VawWvY9eIB 0̿yhV}E_-^B/,WOhFC`Ootyiփ U /CW~nyx>[Fz/,Dv OC=2 C}ey4/')()&R2pgd/ tIw#'@w#?Ҁ^6_mCa)η'}?UR(s@c^م #W"_?y7Q ?{~?BfA<d7 IGs#|cq䆁 F92I & > *q0~fMchMWF`AڊkcCc~յN0Jf:pezI'ș4qCkO6}>R@ qn P]kOip&bSR;f]T>8n'+V~} lO2hb(]κrkRi-?#^:=JpRyFwlbЁƪyd*C/sk> lBEz8BBf1R$tH9vdl'ɡPt(n-@~ ,jlV qh T-,zjTZUm`Y=j"7>qJab`IRL֛P8wBf͠N9$&HѪueKF驍O tp1Q{ uFDOhEK5VP="T9 㱓mX6P_܆k hV9Tƒ5%}`1?QuqCPM*,N\N!s20UV]SYt(wydw:έSftR|C"$2NyKyx@3clllI؞fʮQ???"zѣ;;;ꏺ傂t^C{{{+++YۦyCCCggg4ԣwUUU|ߠtي888nsQQQslpppr666뎎ɪv|òFFFxxxjLf߳dddpϠˮhnOaaaeҽ[A<Řy\xNNNĸjR˿ݽiތ尞пY@m###Aز IDATxMK#i6OY C%Qvŭ]hdX"AH0ҽx9@ toֆaڰKl<^XѼT*UK*꧂τ~+ @ @yuwq7&Q{&mj3Zz@m&_[o)/ ;V;\n'@#^r\Z?mUVEIoճv68i{@Y@yͷ0ptRښV;#@aݑ->Fn@^@ b<ӿ9Mn@g_z ru9ۻV>>Ȳ.@V۹UoZ,=Hou"wc-?ݺ:G{)*Gw. @ @  @ @ @ @f2ms/b~¸%!(g<ʏt=ȅ{~p3k㦜hn?k?lA϶X=bax7kB4u o f.̽` 7iQ/&[Od^]sb1#~[.'T8uPJg@Hp!@O*mm!cQ&XJI Tc j^#!l=я1kbJzwԚB\&YL0|0Ѱ?6e+wWOG*<;}7sܴv[x@#D?H2h~AY^@7]L f~5=7ll̶@O3X#cD`ԅCCVc|0%rOk&gPgp,KNO˲fy|sWa'8z_̾ˑhֱ:{G\+ iUcaߧ[VWHxI@`mF]{AO yޖY8P+UfFl1lOֱ2?%Cw*zZgtMKܗcݞ5{],P/Z5ߏVdc;۝ `)DUׂeWNYX_j/SX }j"6f.ZoD70p)jpSNZ2j8tM `?Hq/VG^'(ğȿn{U/ id0^;Gz.FɾLP'6qK4 ;\kp穷Pw|xዞk=R1R;h(~@!eɣ!Ƒv&>$Nbk ޲y 28/ќ;Zǒ>7-wM j?:=jG[v)-h `b<gEY ঳ <R\DF9v1 nߵ6PmZ. `j;w`|b4@ӊ(@`Xr7֖ՙ~zFWmaon35elD칛K `l-_j2GikG {w?o"rp=+xyu\&({M `@n}"670\h#jA9x \ؐ' $%Kk4|?t!Ncx:E-0Q%ybp.N_G6ÍŬ]2@n4Gz8[䎙]þd=? iD|X+uptgeQx@PE/.#d;Wg^n)Wڙ,vOF x Ƒa߂2K b pp#X+mdɶﭽ 0(QYֱ0~DS3?.rl5b>%oʨ;WwgF]\@]rSɋ! #ChOCIS\+Z gt'LC-23uMݰ'm 0<l"b+ Ȱ}71w ߫:[} /a2:g i pu-v~fZ?0Ik3=}z.W74W݊"Zݖ2sꚪ={wZQ ꢤM[ARJ3y.Y ͮ? n @ @ @ @ @ @ @~VJq3˚R ed 0s,CK+f19Y(gOC<fqei~@C4",ND?*7k˛NL?eoA;Pl 2JW DPSWn G&;*vF@oڊazo6uUb?~e}}`?'vA?gn^ۺ8; EiFWr,K4Ee!b^Jt,CljH)dQI!i"v3@0yrc;V"}4HsMKLۃ0J!g{kk״I Jw,qq,Vw\tW `սB@&ښfULe=oړ{& 0>`o"!T@mD-FM#rmNIXH wpX ˷IT5 A2OD'Zۗtw/t9$!i:)6ɸL&F\[8ᙍ"hc rfBBBBB<wބ^!Z`B.oG !H$H$  @  !@B !H!d&@$ @@B@$ @@B$H$ G%1U1 z_CÞ?#e "eu7Yc <=~w"k1[뮨z D(7Vucz׌1}YmL 9_uG espqvگf񚁻MYpK&3w7bqK3rA(ɦIf57/ uu-žP̈́,V"/>u~P-~7pYrTDBF];*7(x'Y]q1i~ zՒ;wriU#g򆵅Ps `_ 3w@lJ; zc*Hm}ߞ2ZB!! kndFeNϕB.q։)):n[0F \XhZ; t_7Lc8Z7=T=UVoH҈M{ FfқHkn )kS ltS-p|r*k[i+˓5}`~:Byy@ժQʌd"8U&jCl<<5}%?(}3X%bm˨2A2ksrj܏~X(}9t ׳ YxT ". \P&X2$w[;\Ou%u&x"uNՔ#N9VuCݶ!j0qͨ*3S#U`}ѵFX %EJ[(nد rnJbEh#n# %`VۆJ403Bw̥]@$ @@B !H$H$B.H @ @B !H$H$ɻ!TK ]%23@\'*i pӧS\~%/K|}ӕ\x ! ?@$dQ[y* !?8$  !HKUUZ^ 75?2-{ӭ3zBՊv++œ@BEnD$\Dv˷Vlךio*ʪԈew΃ʶmnwKJeR*=U*VQ[]9h/-U vdx/o5 (Uߊ8gU#L=gٻ;)w'Ҟw-q{b+tkkXU.Bj_O/`@J1!KaXJ=/)F*K'=dzQYtZڴ"9xr$+mV{- )G=C+ݰ^[]9.c0?m*) 3#\dObYFe׷@q1%H\uѮq_۳_=zO~ > -"i  Ik}pf+@kE>(`E< nYoRd'9_x]ʚk.? 2ݿf > !h+k$o*<֖ ۧ9b}ƌtF+u/={mmAq /^t- '[@su=dS>L% r8ѕL}.޽} ZQmk#n߿7.PG^d~w` =sG Sx"g_{1`u#='pWKN;D=1~/ t2t{TDS@_AKqHn 8e(~Ύ]Z~Vݑ7㝥چs 2=@|;K/>5KE:+\hP*Z9ۡNtq0u%vq#~vtٕ;-md@s-lwrj·͐W.?B}>?+?`<0,E:ZN;KFE)Ym yܱ{1`X(ͨT6}k4PЁ[`O)}fՌ8T؎'glvKN:sln. X e&@ussSSo&NrN؝0өvfv2ob2%9j-wOWc0̗AB-.5vl=VMY';oPv't;Tao|#WMKLg!޻~ e!]'\6qfq;k9 \7;v)i2NW!ljo;zվ4K͜I}j֘1:;sW+ 305ohNTJ;ȑ1s=(0@ %6  ٪.3r!ٵp4.9Zu~g&Oh;}rCLld WiiYpX! h9uM;_gK7AٷoմQi|@GEev.~1-sQoGb皉o[— {0~+a!,u XJѯ@V'|5%ˬP(.~ﯾ|3i0S-2MVBN=# jdK V#32qɯI!3R,hZN #iH- , R\V8YC4XxvmP+l/?D@?>B:-YDHЭE0*swEGBUrmbeC)!N\RΜZ, ۂ>$5|Z&-VQ6W@Q1b8?Cw !8C2mc%K_M.X5\N-*qJb0752jB7ZO8K+v+:6s6\@Gwp&VAtvr ]1{^ EzKEqZcJqdAR۽aIOÇK,)h`;)@&8wNƝҿ`xy a =3ڎcf+cfE.)V ABW'}0el3FDa˺S*HE #CZ-$zrBi.} }S+g͗ r@4tM^zhzѷxQ/Dڹ,['gJIG(j֜vcLK9jHO8Wâm=twMNήtDJ!ַ a$Hø$6D+r7⠓'-vzn>t|a' [Oe.ڙX##@mw%d% ZrXJ1QNV@6j5uY͢.Hruh8s9:wvg4=yU&AʅR;-[`(ǡ4#+>U{ڔf@fcI'K<5s9D'Jz!;j'Aq>r@6R;$AC@kXjwpPzrm@f{?7A.%8x?0,rܮxקM4 l'>iYGqzc7{o6vF [9b|=n`q/{^B wm?B  A B  A8jZt5C8#򓯺Fx V {HiK-@g@'# >8=s0#.8Ҝ9 0_h6nlv9S+%\pC#L4*j7| 1+ꚧTmi"9M@ w e*!UqYNA*CU4E4v =}unۥȿ]Om T $3$45C‚TXHjb*d/ X܃SQPIxVY''vY˙fJ`ܛg"keo@Rm20»8'H풁pH`Iw$Z0 ӲZ`ǎ_B$d 'a`Jq$#[<sI3+B^5Yu{&`dx@}vCn73kp+]o#/;w춲o?  !Rn|v H}m6Mr/ZDaMOߋ \*e]^@e_?&~{@B@L@pgC`IЏ<2  Ƚb|3V= r5~/7 !| ɿ- !S   !H  kK !H@$ B !@B@$$H@B>粛WWvivg0ۤ|{90UlTƅKy8t禌^_f.smᖯޭ.l}-oِ Tы*4sK5Oﲴ]*MliuOWin::}EBw7z: [F=UpMJ g(lt*L jK 0X@Bl?i;UQӴ6W.fKK]T6ƍI^ل=M*{+ԌDWL1k5U7X9uc+ùd|;걿8)ȱH.Uf_|< ,ŶX ]˻q_["z*[mCg, P&.3e)p2%_cɣ,;|`WAJ2$TcIUX]yI7DC*^SYӤPl*Fi2tvn*R z@-3_f߭; p uƭb33Wz@?̼]d| X7[.ъlv=}Ig[5ͼtީ[b㯋6ܔ!7ϭ,4(8 [/ _#$<PsViu i$|P /bmggl]k֪z^ElŏT:ODrZ`OM: B3yvZJۺ\-/ u 2_H&amLC0?0!]C$4a9NklKs KV!M0$^\? .hp|R{8n?o,mB?']kAth mlshIl ) S5?}t _#Q~ 7 !dBVOWoe !@$ @@B0w>ץ(HlW ]m&|n7 !q Ponl=hM鿩p)+; r+-p Ao8JgeI.Šw@$f4<5v?@Bn.O-5yUG -bc !H$H$/{Bp24=0x !81Os>y$ʰL`{[}>phQ4;ej*<'|ңf @k@-!C!:["+&aO7LԊNfWԩ4ʸקg[ zP^ @[{Wh~_\]gobj]] ,{:GN{F" GLXcYv dDD;Dߎe/J5Vpvxh=ރ^ӥ>۳Ml=5`h}Vh3uZJE׷~?@f:׵nC)lhs6-g7msAbe?}WYӆS;'>쩇s VϨYi B PT˲a!s^nGW 7 5'KZQ63'#c颶ȍ֡;6"HT+УYNcm$Io/w)+ /Ϊ' zԣy ;Y(9'FW)XzFQV}rEÞ;؎(-7]-+<n@'A /@@PPPPPP@@xpf1ߒ◞2F,|>?@;}p1_DW}-cr54g@l0L[)r.Q(p=xc盂-NNNc&WrxM|-7#7K@~tq{ Ӻ㴼]4n=;>x\wz,w:|c}d599y@v _ ׯ?-<%t::2qw g>çM]ҰX\nyN˔`q"O?}:<|ӧO7iwL4o9/[O-M2S,A= `q)k`/A^<to b;F]5 =? AA@@DA@@@@@P@@@P@@@PQxt'yp~:|X-.F7P0Wg6IgÜeFj ~G,M_0e):4lcALaIҨvdӎY4*.궆 xymfzy].M%hT_3Es LK@`FvRN"Ii:צpX]-+󜱜mf%|AjjW)38_U۸5 ƷɮKg*?en`hŻ5p M|lL,c߼{xlpDy}JA5A*R&zMǃffEc*"b{^NW'D2%݇ {rϻEٹYO>?|a*JRT2KUsZ Q+UO%L\p'.|Y~2ʼnRҐF^1kpW(uNi$'BiicA`驭lmMo1L('wyd4ZzY,nRB7zc/5;ֻ;OC Dd Pr:.BKL/]w} /^.T5qp6zJR$/8CJ=V):uZ!cǘO87ߋo<(Z\<ؾXfD-,mZݥM\FI2kR]qX*oU-zLXe3 AXg}$ PRݷ'G8ҚIrX$e.n#,V?!,,#!M#@x?z?ǧO6Y+@w @O.@}sEOugC;$^gA)pf{MկmN8,m>Bbz)Ҷ9϶K m(w` '} 0("@`]>oIo}')p/*}9jXe(Na3/ذlW7һV - @@ "@@ "@@ "@@QR|IDAT "@@ ̻l[Y7Z(6sm[+bctY0?Zǂ Nj/\׵V.[~J"֯v/kw>to_) 4o~,A%Cak"Awr:$u, cXX"I˷=` +DCrѐzj|;c;̵yI5(ҥu ֩c[--Vj. If that doesn't work for you there are [other installation methods]. `rustup` installs `rustc`, `cargo`, `rustup` and other standard tools to Cargo's `bin` directory. On Unix it is located at `$HOME/.cargo/bin` and on Windows at `%USERPROFILE%\.cargo\bin`. This is the same directory that `cargo install` will install Rust programs and Cargo plugins. This directory will be in your `$PATH` environment variable, which means you can run them from the shell without further configuration. Open a *new* shell and type the following: ```console rustc --version ``` If you see something like `rustc 1.19.0 (0ade33941 2017-07-17)` then you are ready to Rust. If you decide Rust isn't your thing, you can completely remove it from your system by running `rustup self uninstall`. [other installation methods]: other.md ## Choosing where to install `rustup` allows you to customise your installation by setting the environment variables `CARGO_HOME` and `RUSTUP_HOME` before running the `rustup-init` executable. As mentioned in the [Environment Variables] section, `RUSTUP_HOME` sets the root `rustup` folder, which is used for storing installed toolchains and configuration options. `CARGO_HOME` contains cache files used by [cargo]. Note that you will need to ensure these environment variables are always set and that `CARGO_HOME/bin` is in the `$PATH` environment variable when using the toolchain. [Environment Variables]: ../environment-variables.md [cargo]: https://doc.rust-lang.org/cargo/ ## Installing nightly If you specify the [nightly channel] when installing `rustup`, the `rustup-init` script will do a "forced" installation by default. A "forced" installation means it will install the nightly channel regardless of whether it might be missing [components] that you want. If you want to install rustup with the nightly channel, and ensure it has the components that you want, you will need to do this in two phases. For example, if you want to make a fresh installation of `rustup` and then install `nightly` along with `clippy` or `miri`, first install `rustup` without a toolchain: ```console curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y ``` Next you can install `nightly` allowing `rustup` to downgrade until it finds the components you need: ```console rustup toolchain install nightly --allow-downgrade --profile minimal --component clippy ``` This can be used to great effect in CI, to get you a toolchain rapidly which meets your criteria. [nightly channel]: ../concepts/channels.md [components]: ../concepts/components.md ## Enable tab completion for Bash, Fish, Zsh, or PowerShell `rustup` now supports generating completion scripts for Bash, Fish, Zsh, and PowerShell. See `rustup help completions` for full details, but the gist is as simple as using one of the following: ```console # Bash $ rustup completions bash > ~/.local/share/bash-completion/completions/rustup # Bash (macOS/Homebrew) $ rustup completions bash > $(brew --prefix)/etc/bash_completion.d/rustup.bash-completion # Fish $ mkdir -p ~/.config/fish/completions $ rustup completions fish > ~/.config/fish/completions/rustup.fish # Zsh $ rustup completions zsh > ~/.zfunc/_rustup # PowerShell v5.0+ $ rustup completions powershell >> $PROFILE.CurrentUserCurrentHost # or $ rustup completions powershell | Out-String | Invoke-Expression ``` **Note**: you may need to restart your shell in order for the changes to take effect. For `zsh`, you must then add the following line in your `~/.zshrc` before `compinit`: ```zsh fpath+=~/.zfunc ``` rustup-1.26.0/doc/src/installation/other.md000066400000000000000000000213361441327105200206460ustar00rootroot00000000000000# Other installation methods The primary installation method, as described at , differs by platform: * On Windows, download and run the [`rustup-init.exe` built for the `x86_64-pc-windows-msvc` target][setup]. In general, this is the build of `rustup` one should install on Windows. This will require the Visual C++ Build Tools 2019 or equivalent (Visual Studio 2019, etc.) to already be installed. If you would prefer to install GNU toolchains or the i686 toolchains by default this can be modified at install time, either interactively, with the `--default-host` flag, or after installation via `rustup set default-host`. * On Unix, run `curl https://sh.rustup.rs -sSf | sh` in your shell. This downloads and runs [`rustup-init.sh`], which in turn downloads and runs the correct version of the `rustup-init` executable for your platform. [setup]: https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe [`rustup-init.sh`]: https://static.rust-lang.org/rustup/rustup-init.sh `rustup-init` accepts arguments, which can be passed through the shell script. Some examples: ```console $ curl https://sh.rustup.rs -sSf | sh -s -- --help $ curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path $ curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly $ curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain none $ curl https://sh.rustup.rs -sSf | sh -s -- --profile minimal --default-toolchain nightly ``` If you prefer you can directly download `rustup-init` for the platform of your choice: - [aarch64-apple-darwin](https://static.rust-lang.org/rustup/dist/aarch64-apple-darwin/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/aarch64-apple-darwin/rustup-init.sha256) - [aarch64-linux-android](https://static.rust-lang.org/rustup/dist/aarch64-linux-android/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/aarch64-linux-android/rustup-init.sha256) - [aarch64-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-gnu/rustup-init.sha256) - [aarch64-unknown-linux-musl](https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-musl/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-musl/rustup-init.sha256) - [arm-linux-androideabi](https://static.rust-lang.org/rustup/dist/arm-linux-androideabi/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/arm-linux-androideabi/rustup-init.sha256) - [arm-unknown-linux-gnueabi](https://static.rust-lang.org/rustup/dist/arm-unknown-linux-gnueabi/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/arm-unknown-linux-gnueabi/rustup-init.sha256) - [arm-unknown-linux-gnueabihf](https://static.rust-lang.org/rustup/dist/arm-unknown-linux-gnueabihf/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/arm-unknown-linux-gnueabihf/rustup-init.sha256) - [armv7-linux-androideabi](https://static.rust-lang.org/rustup/dist/armv7-linux-androideabi/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/armv7-linux-androideabi/rustup-init.sha256) - [armv7-unknown-linux-gnueabihf](https://static.rust-lang.org/rustup/dist/armv7-unknown-linux-gnueabihf/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/armv7-unknown-linux-gnueabihf/rustup-init.sha256) - [i686-apple-darwin](https://static.rust-lang.org/rustup/dist/i686-apple-darwin/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/i686-apple-darwin/rustup-init.sha256) - [i686-linux-android](https://static.rust-lang.org/rustup/dist/i686-linux-android/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/i686-linux-android/rustup-init.sha256) - [i686-pc-windows-gnu](https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe) - [sha256 file](https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe.sha256) - [i686-pc-windows-msvc](https://static.rust-lang.org/rustup/dist/i686-pc-windows-msvc/rustup-init.exe)[^msvc] - [sha256 file](https://static.rust-lang.org/rustup/dist/i686-pc-windows-msvc/rustup-init.exe.sha256) - [i686-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/i686-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/i686-unknown-linux-gnu/rustup-init.sha256) - [mips-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/mips-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/mips-unknown-linux-gnu/rustup-init.sha256) - [mips64-unknown-linux-gnuabi64](https://static.rust-lang.org/rustup/dist/mips64-unknown-linux-gnuabi64/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/mips64-unknown-linux-gnuabi64/rustup-init.sha256) - [mips64el-unknown-linux-gnuabi64](https://static.rust-lang.org/rustup/dist/mips64el-unknown-linux-gnuabi64/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/mips64el-unknown-linux-gnuabi64/rustup-init.sha256) - [mipsel-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/mipsel-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/mipsel-unknown-linux-gnu/rustup-init.sha256) - [powerpc-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/powerpc-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/powerpc-unknown-linux-gnu/rustup-init.sha256) - [powerpc64-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/powerpc64-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/powerpc64-unknown-linux-gnu/rustup-init.sha256) - [powerpc64le-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/powerpc64le-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/powerpc64le-unknown-linux-gnu/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) - [x86_64-apple-darwin](https://static.rust-lang.org/rustup/dist/x86_64-apple-darwin/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-apple-darwin/rustup-init.sha256) - [x86_64-linux-android](https://static.rust-lang.org/rustup/dist/x86_64-linux-android/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-linux-android/rustup-init.sha256) - [x86_64-pc-windows-gnu](https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-gnu/rustup-init.exe) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-gnu/rustup-init.exe.sha256) - [x86_64-pc-windows-msvc](https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe)[^msvc] - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe.sha256) - [x86_64-unknown-freebsd](https://static.rust-lang.org/rustup/dist/x86_64-unknown-freebsd/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-unknown-freebsd/rustup-init.sha256) - [x86_64-unknown-illumos](https://static.rust-lang.org/rustup/dist/x86_64-unknown-illumos/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-unknown-illumos/rustup-init.sha256) - [x86_64-unknown-linux-gnu](https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init.sha256) - [x86_64-unknown-linux-musl](https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-musl/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-musl/rustup-init.sha256) - [x86_64-unknown-netbsd](https://static.rust-lang.org/rustup/dist/x86_64-unknown-netbsd/rustup-init) - [sha256 file](https://static.rust-lang.org/rustup/dist/x86_64-unknown-netbsd/rustup-init.sha256) [^msvc]: MSVC builds of `rustup` additionally require an [installation of Visual Studio 2019 or the Visual C++ Build Tools 2019][vs]. For Visual Studio, make sure to check the "C++ tools" and "Windows 10 SDK" option. No additional software installation is necessary for basic use of the GNU build. [vs]: https://visualstudio.microsoft.com/downloads/ You can fetch an older version from `https://static.rust-lang.org/rustup/archive/{rustup-version}/{target-triple}/rustup-init[.exe]` To install `rustup` from source, check out the git repository from and run `cargo run --release`. Note that after installation the `rustup` toolchains will supersede any pre-existing toolchains by prepending `~/.cargo/bin` to the `PATH` environment variable. rustup-1.26.0/doc/src/installation/package-managers.md000066400000000000000000000025011441327105200227040ustar00rootroot00000000000000# Package managers Several Linux distributions package Rust, and you may wish to use the packaged toolchain, such as for distribution package development. You may also wish to use a `rustup`-managed toolchain such as nightly or beta. Normally, `rustup` will complain that you already have Rust installed in `/usr` and refuse to install. However, you can install Rust via `rustup` and have it coexist with your distribution's packaged Rust. When you initially install Rust with `rustup`, pass the `-y` option to make it ignore the packaged Rust toolchain and install a `rustup`-managed toolchain into `~/.cargo/bin`. Add that directory to your `$PATH` (or let `rustup` do it for you by not passing `--no-modify-path`). Then, to tell `rustup` about your system toolchain, run: ```console rustup toolchain link system /usr ``` You can then use "system" as a `rustup` toolchain, just like "nightly". For example, using the [toolchain override shorthand], you can run `cargo +system build` to build with the system toolchain, or `cargo +nightly build` to build with nightly. If you do distribution Rust development, you should likely make "system" your [default toolchain]: ```console rustup default system ``` [toolchain override shorthand]: ../overrides.md#toolchain-override-shorthand [default toolchain]: ../overrides.md#default-toolchain rustup-1.26.0/doc/src/installation/windows-msvc.md000066400000000000000000000066511441327105200221700ustar00rootroot00000000000000# MSVC prerequistes To compile programs into an exe file, Rust requires a linker, libraries and Windows API import libraries. For `msvc` targets these can be acquired through Visual Studio. ## Automatic install If you don't have Visual Studio already installed then [rustup-init] will offer to automatically install the prerequisites. Doing so means you can skip the rest of this page. However, it installs Visual Studio Community edition which may not be appropriate for all users. It is free for individuals, academic and open source use but other uses, such as in proprietary enterprise software, should ask their organisation which edition is right for them. See [licensing terms][vs licences] for more details. ## Manual install [Download Visual Studio][vs downloads]. Rust supports Visual Studio 2013 and later but it is recommended that you use the latest version (currently 2022) for new projects. You can opt to download only the Build Tools for Visual Studio, which does not include the IDE. However this requires you already have a license to the Community, Professional or Enterprise edition. Once you've downloaded and started the installer, the easiest way to get everything installed is to select "Desktop Development with C++". This will include the necessary components. On the "Language Packs" tab, make sure the English language pack is installed in addition to your preferred language. If you want more details on the installation process or want to further customize the install then follow the walkthrough below. Otherwise complete the Visual Studio install and continue with installing Rust. ## Walkthrough: Installing Visual Studio 2022 This walkthrough uses the Community edition of Visual Studio but the Professional, Enterprise and the Build Tools all work the same way. The installer will start by linking to the [license][vs licences] and for your edition of Visual Studio and then preparing the installer. ![Accept the license](images/step1.png) ![Installing the installer](images/step2.png) Once this finishes, you can then select the components to be installed. Here we use the "Workload" tab to select the "Desktop Development with C++" workload. This will includes all needed components for Rust: ![Select the C++ Workload](images/step3.png) ### Installing only the required components (optional) If you'd like a more minimal install (and won't doing C++ development) then you can use the "Individual Components" tab to select just the essentials, which are: * MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest) * Windows 11 SDK (10.0.22621.0) Note that the specific version of the Windows SDK doesn't matter for pure Rust code but if using C++ as well you'll likely want either the latest or whichever version is required by the C++ project (or both). ![Select the latest MSVC component](images/component-msvc.png) ![Select the Windows 11 SDK component](images/component-sdk.png) ### Completing the install After choosing the components, switch to the "Language Packs" tab and add the English language pack in addition to your preferred language. ![Add the English language](images/step4.png) Finally click the install button and wait for everything to be installed. ![Wait for the install to complete](images/step5.png) Once finished, you can continue on to installing Rust. [rustup-init]: https://rustup.rs [vs downloads]: https://visualstudio.microsoft.com/downloads/ [vs licences]: https://visualstudio.microsoft.com/license-terms/ rustup-1.26.0/doc/src/installation/windows.md000066400000000000000000000052151441327105200212150ustar00rootroot00000000000000# Windows `rustup` works the same on Windows as it does on Unix, but there are some special considerations for Rust developers on Windows. As [mentioned on the Rust download page][msvc-toolchain], there are two [ABIs] in use on Windows: the native (MSVC) ABI used by [Visual Studio], and the GNU ABI used by the [GCC toolchain]. Which version of Rust you need depends largely on what C/C++ libraries you want to interoperate with: for interop with software produced by Visual Studio use the MSVC build of Rust; for interop with GNU software built using the [MinGW/MSYS2 toolchain] use the GNU build. When targeting the MSVC ABI, Rust additionally requires an [installation of Visual Studio][msvc install] so `rustc` can use its linker and libraries. No additional software installation is necessary for basic use of the GNU build. By default `rustup` on Windows configures Rust to target the MSVC ABI, that is a target triple of either `i686-pc-windows-msvc`, `x86_64-pc-windows-msvc`, or `aarch64-pc-windows-msvc` depending on the CPU architecture of the host Windows OS. The toolchains that `rustup` chooses to install, unless told otherwise through the [toolchain specification], will be compiled to run on that target triple host and will target that triple 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: ```console $ rustup set default-host x86_64-pc-windows-gnu ``` Since the MSVC ABI provides the best interoperation with other Windows software it is recommended for most purposes. The GNU toolchain is always available, even if you don't use it by default. Just install it with `rustup toolchain install`: ```console $ rustup toolchain install stable-gnu ``` You don't need to switch toolchains to support all windows targets though; a single toolchain supports all four x86 windows targets: ```console $ rustup target add x86_64-pc-windows-msvc $ rustup target add x86_64-pc-windows-gnu $ rustup target add i686-pc-windows-msvc $ rustup target add i686-pc-windows-gnu ``` See the [Cross-compilation] chapter for more details on specifying different targets with the same compiler. [ABIs]: https://en.wikipedia.org/wiki/Application_binary_interface [cross-compilation]: ../cross-compilation.md [GCC toolchain]: https://gcc.gnu.org/ [MinGW/MSYS2 toolchain]: https://msys2.github.io/ [msvc-toolchain]: https://www.rust-lang.org/tools/install?platform_override=win [toolchain specification]: ../concepts/toolchains.md#toolchain-specification [msvc install]: windows-msvc.html rustup-1.26.0/doc/src/network-proxies.md000066400000000000000000000033471441327105200202060ustar00rootroot00000000000000# Network proxies Enterprise networks often don't have direct outside HTTP access, but enforce the use of proxies. If you're on such a network, you can request that `rustup` uses a proxy by setting its URL in the environment. In most cases, setting `https_proxy` should be sufficient. Commands may differ between different systems and shells: - On a Unix-like system with a shell like __bash__ or __zsh__: ```bash export https_proxy=socks5://proxy.example.com:1080 ``` - On Windows [__Command Prompt (cmd)__][cmd]: ```cmd set https_proxy=socks5://proxy.example.com:1080 ``` - On Windows [__PowerShell__][ps] (or __PowerShell Core__): ```cmd $env:https_proxy="socks5://proxy.example.com:1080" ``` - Replace `socks5://proxy.example.com:1080` with `http://proxy.example.com:8080` when an HTTP proxy is used instead. If you need a more complex setup, `rustup` supports the convention used by the __curl__ program, documented in the ENVIRONMENT section of [its manual page][curlman]. The use of `curl` is presently **deprecated**, however it can still be used by providing the `RUSTUP_USE_CURL` environment variable, for example: ```bash RUSTUP_USE_CURL=1 rustup update ``` Note that some versions of `libcurl` apparently require you to drop the `http://` or `https://` prefix in environment variables. For example, `export http_proxy=proxy.example.com:1080` (and likewise for HTTPS). If you are getting an SSL `unknown protocol` error from `rustup` via `libcurl` but the command-line `curl` command works fine, this may be the problem. [curlman]: https://curl.se/docs/manpage.html#:~:text=Environment,-The%20environment%20variables [cmd]: https://en.wikipedia.org/wiki/Cmd.exe [ps]: https://en.wikipedia.org/wiki/PowerShell rustup-1.26.0/doc/src/overrides.md000066400000000000000000000153601441327105200170260ustar00rootroot00000000000000# Overrides `rustup` automatically determines which [toolchain] to use when one of the installed commands like `rustc` is executed. There are several ways to control and override which toolchain is used: 1. A [toolchain override shorthand] used on the command-line, such as `cargo +beta`. 2. The `RUSTUP_TOOLCHAIN` environment variable. 3. A [directory override], set with the `rustup override` command. 4. The [`rust-toolchain.toml`] file. 5. The [default toolchain]. The toolchain is chosen in the order listed above, using the first one that is specified. There is one exception though: directory overrides and the `rust-toolchain.toml` file are also preferred by their proximity to the current directory. That is, these two override methods are discovered by walking up the directory tree toward the filesystem root, and a `rust-toolchain.toml` file that is closer to the current directory will be preferred over a directory override that is further away. To verify which toolchain is active, you can use `rustup show`, which will also try to install the corresponding toolchain if the current one has not been installed according to the above rules. (Please note that this behavior is subject to change, as detailed in issue [#1397].) [toolchain]: concepts/toolchains.md [toolchain override shorthand]: #toolchain-override-shorthand [directory override]: #directory-overrides [`rust-toolchain.toml`]: #the-toolchain-file [default toolchain]: #default-toolchain ## Toolchain override shorthand The `rustup` toolchain proxies can be instructed directly to use a specific toolchain, a convenience for developers who often test different toolchains. If the first argument to `cargo`, `rustc` or other tools in the toolchain begins with `+`, it will be interpreted as a `rustup` toolchain name, and that toolchain will be preferred, as in ```console cargo +beta test ``` ## Directory overrides Directories can be assigned their own Rust toolchain with `rustup override`. When a directory has an override then any time `rustc` or `cargo` is run inside that directory, or one of its child directories, the override toolchain will be invoked. To use to a specific nightly for a directory: ```console rustup override set nightly-2014-12-18 ``` Or a specific stable release: ```console rustup override set 1.0.0 ``` To see the active toolchain use `rustup show`. To remove the override and use the default toolchain again, `rustup override unset`. The per-directory overrides are stored in [a configuration file] in `rustup`'s home directory. [a configuration file]: configuration.md ## The toolchain file Some projects find themselves 'pinned' to a specific release of Rust and want this information reflected in their source repository. This is most often the case for nightly-only software that pins to a revision from the release archives. In these cases the toolchain can be named in the project's directory in a file called `rust-toolchain.toml` or `rust-toolchain`. If both files are present in a directory, the latter is used for backwards compatibility. The files use the [TOML] format and have the following layout: [TOML]: https://toml.io/ ``` toml [toolchain] channel = "nightly-2020-07-10" components = [ "rustfmt", "rustc-dev" ] targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ] profile = "minimal" ``` The `[toolchain]` section is mandatory, and at least one property must be specified. `channel` and `path` are mutually exclusive. For backwards compatibility, `rust-toolchain` files also support a legacy format that only contains a toolchain name without any TOML encoding, e.g. just `nightly-2021-01-21`. The file has to be encoded in US-ASCII in this case (if you are on Windows, check the encoding and that it does not start with a BOM). The legacy format is not available in `rust-toolchain.toml` files. If you see the following error (when running `rustc`, `cargo` or other command) ``` error: invalid channel name '[toolchain]' in '/PATH/TO/DIRECTORY/rust-toolchain' ``` it means you're running `rustup` pre-1.23.0 and trying to interact with a project that uses the new TOML encoding in the `rust-toolchain` file. You need to upgrade `rustup` to 1.23.0+. The `rust-toolchain.toml`/`rust-toolchain` files are suitable to check in to source control. If that's done, `Cargo.lock` should probably be tracked too if the toolchain is pinned to a specific release, to avoid potential compatibility issues with dependencies. ### Toolchain file settings #### channel The `channel` setting specifies which [toolchain] to use. The value is a string in the following form: ``` [-] = stable|beta|nightly| = YYYY-MM-DD ``` Note that this is a more restricted form than `rustup` toolchains generally, and cannot be used to specify custom toolchains or host-specific toolchains. [toolchain]: concepts/toolchains.md #### path The `path` setting allows a custom toolchain to be used. The value is a path string. A relative path is resolved relative to the location of the `rust-toolchain.toml` file. Since a `path` directive directly names a local toolchain, other options like `components`, `targets`, and `profile` have no effect. `channel` and `path` are mutually exclusive, since a `path` already points to a specific toolchain. #### profile The `profile` setting names a group of components to be installed. The value is a string. The valid options are: `minimal`, `default`, and `complete`. See [profiles] for details of each. Note that if not specified, the `default` profile is not necessarily used, as a different default profile might have been set with `rustup set profile`. [profiles]: concepts/profiles.md #### components The `components` setting contains a list of additional components to install. The value is a list of strings. See [components] for a list of components. Note that different toolchains may have different components available. The components listed here are additive with the current profile. [components]: concepts/components.md #### targets The `targets` setting contains a list of platforms to install for [cross-compilation]. The value is a list of strings. The host platform is automatically included; the targets listed here are additive. [cross-compilation]: https://rust-lang.github.io/rustup/cross-compilation.html ## Default toolchain If no other overrides are set, the global default toolchain will be used. This default can be chosen when `rustup` is [installed]. The `rustup default` command can be used to set and query the current default. Run `rustup default` without any arguments to print the current default. Specify a toolchain as an argument to change the default: ```console rustup default nightly-2020-07-27 ``` [installed]: installation/index.md [#1397]: https://github.com/rust-lang/rustup/issues/1397 rustup-1.26.0/doc/src/security.md000066400000000000000000000010111441327105200166570ustar00rootroot00000000000000# Security `rustup` is secure enough for most people, but it [still needs work][s]. `rustup` performs all downloads over HTTPS, but does not yet validate signatures of downloads. [s]: https://github.com/rust-lang/rustup/issues?q=is%3Aopen+is%3Aissue+label%3Asecurity File modes on installation honor umask as of 1.18.4, use umask if very tight controls are desired. If you wish to report a security issue, please follow the [Rust security policy]. [Rust security policy]: https://www.rust-lang.org/policies/security rustup-1.26.0/download/000077500000000000000000000000001441327105200147505ustar00rootroot00000000000000rustup-1.26.0/download/Cargo.toml000066400000000000000000000017061441327105200167040ustar00rootroot00000000000000[package] authors = ["Brian Anderson "] edition = "2021" license = "MIT OR Apache-2.0" name = "download" version = "1.26.0" [features] default = ["reqwest-backend", "reqwest-rustls-tls", "reqwest-default-tls"] curl-backend = ["curl"] reqwest-backend = ["reqwest", "env_proxy", "lazy_static"] reqwest-default-tls = ["reqwest/default-tls"] reqwest-rustls-tls = ["reqwest/rustls-tls-native-roots"] [dependencies] anyhow.workspace = true curl = {version = "0.4.44", optional = true} env_proxy = {version = "0.4.1", optional = true} lazy_static = {workspace=true, optional = true} reqwest = {version = "0.11", default-features = false, features = ["blocking", "gzip", "socks"], optional = true} thiserror.workspace = true url.workspace = true [dev-dependencies] hyper = {version = "0.14", default-features = false, features = ["tcp", "server"]} tempfile.workspace = true tokio = {version = "1", default-features = false, features = ["sync"]} rustup-1.26.0/download/src/000077500000000000000000000000001441327105200155375ustar00rootroot00000000000000rustup-1.26.0/download/src/errors.rs000066400000000000000000000011441441327105200174210ustar00rootroot00000000000000use thiserror::Error; #[derive(Debug, Error)] pub enum DownloadError { #[error("http request returned an unsuccessful status code: {0}")] HttpStatus(u32), #[error("file not found")] FileNotFound, #[error("download backend '{0}' unavailable")] BackendUnavailable(&'static str), #[error("{0}")] Message(String), #[error(transparent)] IoError(#[from] std::io::Error), #[cfg(feature = "reqwest-backend")] #[error(transparent)] Reqwest(#[from] ::reqwest::Error), #[cfg(feature = "curl-backend")] #[error(transparent)] CurlError(#[from] curl::Error), } rustup-1.26.0/download/src/lib.rs000066400000000000000000000346451441327105200166670ustar00rootroot00000000000000//! Easy file downloading #![deny(rust_2018_idioms)] use std::path::Path; use anyhow::Context; pub use anyhow::Result; use url::Url; mod errors; pub use crate::errors::*; /// User agent header value for HTTP request. /// See: https://github.com/rust-lang/rustup/issues/2860. const USER_AGENT: &str = concat!("rustup/", env!("CARGO_PKG_VERSION")); #[derive(Debug, Copy, Clone)] pub enum Backend { Curl, Reqwest(TlsBackend), } #[derive(Debug, Copy, Clone)] pub enum TlsBackend { Rustls, Default, } #[derive(Debug, Copy, Clone)] pub enum Event<'a> { ResumingPartialDownload, /// Received the Content-Length of the to-be downloaded data. DownloadContentLengthReceived(u64), /// Received some data. DownloadDataReceived(&'a [u8]), } fn download_with_backend( backend: Backend, url: &Url, resume_from: u64, callback: &dyn Fn(Event<'_>) -> Result<()>, ) -> Result<()> { match backend { Backend::Curl => curl::download(url, resume_from, callback), Backend::Reqwest(tls) => reqwest_be::download(url, resume_from, callback, tls), } } type DownloadCallback<'a> = &'a dyn Fn(Event<'_>) -> Result<()>; pub fn download_to_path_with_backend( backend: Backend, url: &Url, path: &Path, resume_from_partial: bool, callback: Option>, ) -> Result<()> { use std::cell::RefCell; use std::fs::remove_file; use std::fs::OpenOptions; use std::io::{Read, Seek, SeekFrom, Write}; || -> Result<()> { let (file, resume_from) = if resume_from_partial { let possible_partial = OpenOptions::new().read(true).open(path); let downloaded_so_far = if let Ok(mut partial) = possible_partial { if let Some(cb) = callback { cb(Event::ResumingPartialDownload)?; let mut buf = vec![0; 32768]; let mut downloaded_so_far = 0; loop { let n = partial.read(&mut buf)?; downloaded_so_far += n as u64; if n == 0 { break; } cb(Event::DownloadDataReceived(&buf[..n]))?; } downloaded_so_far } else { let file_info = partial.metadata()?; file_info.len() } } else { 0 }; let mut possible_partial = OpenOptions::new() .write(true) .create(true) .open(path) .context("error opening file for download")?; possible_partial.seek(SeekFrom::End(0))?; (possible_partial, downloaded_so_far) } else { ( OpenOptions::new() .write(true) .create(true) .open(path) .context("error creating file for download")?, 0, ) }; let file = RefCell::new(file); download_with_backend(backend, url, resume_from, &|event| { if let Event::DownloadDataReceived(data) = event { file.borrow_mut() .write_all(data) .context("unable to write download to disk")?; } match callback { Some(cb) => cb(event), None => Ok(()), } })?; file.borrow_mut() .sync_data() .context("unable to sync download to disk")?; Ok(()) }() .map_err(|e| { // TODO: We currently clear up the cached download on any error, should we restrict it to a subset? if let Err(file_err) = remove_file(path).context("cleaning up cached downloads") { file_err.context(e) } else { e } }) } /// Download via libcurl; encrypt with the native (or OpenSSl) TLS /// stack via libcurl #[cfg(feature = "curl-backend")] pub mod curl { use std::cell::RefCell; use std::str; use std::time::Duration; use anyhow::{Context, Result}; use curl::easy::Easy; use url::Url; use super::Event; use crate::errors::*; pub fn download( url: &Url, resume_from: u64, callback: &dyn Fn(Event<'_>) -> Result<()>, ) -> Result<()> { // Fetch either a cached libcurl handle (which will preserve open // connections) or create a new one if it isn't listed. // // Once we've acquired it, reset the lifetime from 'static to our local // scope. thread_local!(static EASY: RefCell = RefCell::new(Easy::new())); EASY.with(|handle| { let mut handle = handle.borrow_mut(); handle.url(url.as_ref())?; handle.follow_location(true)?; handle.useragent(super::USER_AGENT)?; if resume_from > 0 { handle.resume_from(resume_from)?; } else { // an error here indicates that the range header isn't supported by underlying curl, // so there's nothing to "clear" - safe to ignore this error. let _ = handle.resume_from(0); } // Take at most 30s to connect handle.connect_timeout(Duration::new(30, 0))?; { let cberr = RefCell::new(None); let mut transfer = handle.transfer(); // Data callback for libcurl which is called with data that's // downloaded. We just feed it into our hasher and also write it out // to disk. transfer.write_function(|data| { match callback(Event::DownloadDataReceived(data)) { Ok(()) => Ok(data.len()), Err(e) => { *cberr.borrow_mut() = Some(e); Ok(0) } } })?; // Listen for headers and parse out a `Content-Length` (case-insensitive) if it // comes so we know how much we're downloading. transfer.header_function(|header| { if let Ok(data) = str::from_utf8(header) { let prefix = "content-length: "; if data.to_ascii_lowercase().starts_with(prefix) { if let Ok(s) = data[prefix.len()..].trim().parse::() { let msg = Event::DownloadContentLengthReceived(s + resume_from); match callback(msg) { Ok(()) => (), Err(e) => { *cberr.borrow_mut() = Some(e); return false; } } } } } true })?; // If an error happens check to see if we had a filesystem error up // in `cberr`, but we always want to punt it up. transfer.perform().or_else(|e| { // If the original error was generated by one of our // callbacks, return it. match cberr.borrow_mut().take() { Some(cberr) => Err(cberr), None => { // Otherwise, return the error from curl if e.is_file_couldnt_read_file() { Err(e).context(DownloadError::FileNotFound) } else { Err(e).context("error during download")? } } } })?; } // If we didn't get a 20x or 0 ("OK" for files) then return an error let code = handle.response_code()?; match code { 0 | 200..=299 => {} _ => { return Err(DownloadError::HttpStatus(code).into()); } }; Ok(()) }) } } #[cfg(feature = "reqwest-backend")] pub mod reqwest_be { use std::io; use std::time::Duration; use anyhow::{anyhow, Context, Result}; use lazy_static::lazy_static; use reqwest::blocking::{Client, ClientBuilder, Response}; use reqwest::{header, Proxy}; use url::Url; use super::Event; use super::TlsBackend; use crate::errors::*; pub fn download( url: &Url, resume_from: u64, callback: &dyn Fn(Event<'_>) -> Result<()>, tls: TlsBackend, ) -> Result<()> { // Short-circuit reqwest for the "file:" URL scheme if download_from_file_url(url, resume_from, callback)? { return Ok(()); } let mut res = request(url, resume_from, tls).context("failed to make network request")?; if !res.status().is_success() { let code: u16 = res.status().into(); return Err(anyhow!(DownloadError::HttpStatus(u32::from(code)))); } let buffer_size = 0x10000; let mut buffer = vec![0u8; buffer_size]; if let Some(len) = res.headers().get(header::CONTENT_LENGTH) { // TODO possible issues during unwrap? let len = len.to_str().unwrap().parse::().unwrap() + resume_from; callback(Event::DownloadContentLengthReceived(len))?; } loop { let bytes_read = io::Read::read(&mut res, &mut buffer)?; if bytes_read != 0 { callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?; } else { return Ok(()); } } } fn client_generic() -> ClientBuilder { Client::builder() .gzip(false) .user_agent(super::USER_AGENT) .proxy(Proxy::custom(env_proxy)) .timeout(Duration::from_secs(30)) } #[cfg(feature = "reqwest-rustls-tls")] lazy_static! { static ref CLIENT_RUSTLS_TLS: Client = { let catcher = || { client_generic().use_rustls_tls() .build() }; // woah, an unwrap?! // It's OK. This is the same as what is happening in curl. // // The curl::Easy::new() internally assert!s that the initialized // Easy is not null. Inside reqwest, the errors here would be from // the TLS library returning a null pointer as well. catcher().unwrap() }; } #[cfg(feature = "reqwest-default-tls")] lazy_static! { static ref CLIENT_DEFAULT_TLS: Client = { let catcher = || { client_generic() .build() }; // woah, an unwrap?! // It's OK. This is the same as what is happening in curl. // // The curl::Easy::new() internally assert!s that the initialized // Easy is not null. Inside reqwest, the errors here would be from // the TLS library returning a null pointer as well. catcher().unwrap() }; } fn env_proxy(url: &Url) -> Option { env_proxy::for_url(url).to_url() } fn request( url: &Url, resume_from: u64, backend: TlsBackend, ) -> Result { let client: &Client = match backend { #[cfg(feature = "reqwest-rustls-tls")] TlsBackend::Rustls => &CLIENT_RUSTLS_TLS, #[cfg(not(feature = "reqwest-rustls-tls"))] TlsBackend::Rustls => { return Err(DownloadError::BackendUnavailable("reqwest rustls")); } #[cfg(feature = "reqwest-default-tls")] TlsBackend::Default => &CLIENT_DEFAULT_TLS, #[cfg(not(feature = "reqwest-default-tls"))] TlsBackend::Default => { return Err(DownloadError::BackendUnavailable("reqwest default TLS")); } }; let mut req = client.get(url.as_str()); if resume_from != 0 { req = req.header(header::RANGE, format!("bytes={resume_from}-")); } Ok(req.send()?) } fn download_from_file_url( url: &Url, resume_from: u64, callback: &dyn Fn(Event<'_>) -> Result<()>, ) -> Result { use std::fs; // The file scheme is mostly for use by tests to mock the dist server if url.scheme() == "file" { let src = url .to_file_path() .map_err(|_| DownloadError::Message(format!("bogus file url: '{url}'")))?; if !src.is_file() { // Because some of rustup's logic depends on checking // the error when a downloaded file doesn't exist, make // the file case return the same error value as the // network case. return Err(anyhow!(DownloadError::FileNotFound)); } let mut f = fs::File::open(src).context("unable to open downloaded file")?; io::Seek::seek(&mut f, io::SeekFrom::Start(resume_from))?; let mut buffer = vec![0u8; 0x10000]; loop { let bytes_read = io::Read::read(&mut f, &mut buffer)?; if bytes_read == 0 { break; } callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?; } Ok(true) } else { Ok(false) } } } #[cfg(not(feature = "curl-backend"))] pub mod curl { use anyhow::{anyhow, Result}; use super::Event; use crate::errors::*; use url::Url; pub fn download( _url: &Url, _resume_from: u64, _callback: &dyn Fn(Event<'_>) -> Result<()>, ) -> Result<()> { Err(anyhow!(DownloadError::BackendUnavailable("curl"))) } } #[cfg(not(feature = "reqwest-backend"))] pub mod reqwest_be { use anyhow::{anyhow, Result}; use super::Event; use super::TlsBackend; use crate::errors::*; use url::Url; pub fn download( _url: &Url, _resume_from: u64, _callback: &dyn Fn(Event<'_>) -> Result<()>, _tls: TlsBackend, ) -> Result<()> { Err(anyhow!(DownloadError::BackendUnavailable("reqwest"))) } } rustup-1.26.0/download/tests/000077500000000000000000000000001441327105200161125ustar00rootroot00000000000000rustup-1.26.0/download/tests/download-curl-resume.rs000066400000000000000000000045751441327105200225430ustar00rootroot00000000000000#![cfg(feature = "curl-backend")] use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use url::Url; use download::*; mod support; use crate::support::{serve_file, tmp_dir, write_file}; #[test] fn partially_downloaded_file_gets_resumed_from_byte_offset() { let tmpdir = tmp_dir(); let from_path = tmpdir.path().join("download-source"); write_file(&from_path, "xxx45"); let target_path = tmpdir.path().join("downloaded"); write_file(&target_path, "123"); let from_url = Url::from_file_path(&from_path).unwrap(); download_to_path_with_backend(Backend::Curl, &from_url, &target_path, true, None) .expect("Test download failed"); assert_eq!(std::fs::read_to_string(&target_path).unwrap(), "12345"); } #[test] fn callback_gets_all_data_as_if_the_download_happened_all_at_once() { let tmpdir = tmp_dir(); let target_path = tmpdir.path().join("downloaded"); write_file(&target_path, "123"); let addr = serve_file(b"xxx45".to_vec()); let from_url = format!("http://{addr}").parse().unwrap(); let callback_partial = AtomicBool::new(false); let callback_len = Mutex::new(None); let received_in_callback = Mutex::new(Vec::new()); download_to_path_with_backend( Backend::Curl, &from_url, &target_path, true, Some(&|msg| { match msg { Event::ResumingPartialDownload => { assert!(!callback_partial.load(Ordering::SeqCst)); callback_partial.store(true, Ordering::SeqCst); } Event::DownloadContentLengthReceived(len) => { let mut flag = callback_len.lock().unwrap(); assert!(flag.is_none()); *flag = Some(len); } Event::DownloadDataReceived(data) => { for b in data.iter() { received_in_callback.lock().unwrap().push(*b); } } } Ok(()) }), ) .expect("Test download failed"); assert!(callback_partial.into_inner()); assert_eq!(*callback_len.lock().unwrap(), Some(5)); let observed_bytes = received_in_callback.into_inner().unwrap(); assert_eq!(observed_bytes, vec![b'1', b'2', b'3', b'4', b'5']); assert_eq!(std::fs::read_to_string(&target_path).unwrap(), "12345"); } rustup-1.26.0/download/tests/download-reqwest-resume.rs000066400000000000000000000047001441327105200232560ustar00rootroot00000000000000#![cfg(feature = "reqwest-backend")] use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use url::Url; use download::*; mod support; use crate::support::{serve_file, tmp_dir, write_file}; #[test] fn resume_partial_from_file_url() { let tmpdir = tmp_dir(); let from_path = tmpdir.path().join("download-source"); write_file(&from_path, "xxx45"); let target_path = tmpdir.path().join("downloaded"); write_file(&target_path, "123"); let from_url = Url::from_file_path(&from_path).unwrap(); download_to_path_with_backend( Backend::Reqwest(TlsBackend::Default), &from_url, &target_path, true, None, ) .expect("Test download failed"); assert_eq!(std::fs::read_to_string(&target_path).unwrap(), "12345"); } #[test] fn callback_gets_all_data_as_if_the_download_happened_all_at_once() { let tmpdir = tmp_dir(); let target_path = tmpdir.path().join("downloaded"); write_file(&target_path, "123"); let addr = serve_file(b"xxx45".to_vec()); let from_url = format!("http://{addr}").parse().unwrap(); let callback_partial = AtomicBool::new(false); let callback_len = Mutex::new(None); let received_in_callback = Mutex::new(Vec::new()); download_to_path_with_backend( Backend::Reqwest(TlsBackend::Default), &from_url, &target_path, true, Some(&|msg| { match msg { Event::ResumingPartialDownload => { assert!(!callback_partial.load(Ordering::SeqCst)); callback_partial.store(true, Ordering::SeqCst); } Event::DownloadContentLengthReceived(len) => { let mut flag = callback_len.lock().unwrap(); assert!(flag.is_none()); *flag = Some(len); } Event::DownloadDataReceived(data) => { for b in data.iter() { received_in_callback.lock().unwrap().push(*b); } } } Ok(()) }), ) .expect("Test download failed"); assert!(callback_partial.into_inner()); assert_eq!(*callback_len.lock().unwrap(), Some(5)); let observed_bytes = received_in_callback.into_inner().unwrap(); assert_eq!(observed_bytes, vec![b'1', b'2', b'3', b'4', b'5']); assert_eq!(std::fs::read_to_string(&target_path).unwrap(), "12345"); } rustup-1.26.0/download/tests/read-proxy-env.rs000066400000000000000000000044161441327105200213450ustar00rootroot00000000000000#![cfg(feature = "reqwest-backend")] use std::env::{remove_var, set_var}; use std::error::Error; use std::net::TcpListener; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; use std::thread; use std::time::Duration; use env_proxy::for_url; use lazy_static::lazy_static; use reqwest::{blocking::Client, Proxy}; use url::Url; lazy_static! { static ref SERIALISE_TESTS: Mutex<()> = Mutex::new(()); } fn scrub_env() { remove_var("http_proxy"); remove_var("https_proxy"); remove_var("HTTPS_PROXY"); remove_var("ftp_proxy"); remove_var("FTP_PROXY"); remove_var("all_proxy"); remove_var("ALL_PROXY"); remove_var("no_proxy"); remove_var("NO_PROXY"); } // Tests for correctly retrieving the proxy (host, port) tuple from $https_proxy #[test] fn read_basic_proxy_params() { let _guard = SERIALISE_TESTS .lock() .expect("Unable to lock the test guard"); scrub_env(); set_var("https_proxy", "http://proxy.example.com:8080"); let u = Url::parse("https://www.example.org").ok().unwrap(); assert_eq!( for_url(&u).host_port(), Some(("proxy.example.com".to_string(), 8080)) ); } // Tests to verify if socks feature is available and being used #[test] fn socks_proxy_request() { static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); let _guard = SERIALISE_TESTS .lock() .expect("Unable to lock the test guard"); scrub_env(); set_var("all_proxy", "socks5://127.0.0.1:1080"); thread::spawn(move || { let listener = TcpListener::bind("127.0.0.1:1080").unwrap(); let incoming = listener.incoming(); for _ in incoming { CALL_COUNT.fetch_add(1, Ordering::SeqCst); } }); let env_proxy = |url: &Url| for_url(url).to_url(); let url = Url::parse("http://192.168.0.1/").unwrap(); let client = Client::builder() .proxy(Proxy::custom(env_proxy)) .timeout(Duration::from_secs(1)) .build() .unwrap(); let res = client.get(url.as_str()).send(); if let Err(e) = res { let s = e.source().unwrap(); assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); assert!(s.to_string().contains("socks connect error")); } else { panic!("Socks proxy was ignored") } } rustup-1.26.0/download/tests/support/000077500000000000000000000000001441327105200176265ustar00rootroot00000000000000rustup-1.26.0/download/tests/support/mod.rs000066400000000000000000000062441441327105200207610ustar00rootroot00000000000000use std::convert::Infallible; use std::fs; use std::io; use std::net::SocketAddr; use std::path::Path; use std::sync::mpsc::{channel, Sender}; use std::thread; use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request}; use tempfile::TempDir; pub fn tmp_dir() -> TempDir { tempfile::Builder::new() .prefix("rustup-download-test-") .tempdir() .expect("creating tempdir for test") } pub fn write_file(path: &Path, contents: &str) { let mut file = fs::OpenOptions::new() .write(true) .truncate(true) .create(true) .open(path) .expect("writing test data"); io::Write::write_all(&mut file, contents.as_bytes()).expect("writing test data"); file.sync_data().expect("writing test data"); } async fn run_server(addr_tx: Sender, addr: SocketAddr, contents: Vec) { let make_svc = make_service_fn(|_: &AddrStream| { let contents = contents.clone(); async move { Ok::<_, Infallible>(service_fn(move |req: Request| { let contents = contents.clone(); async move { let res = serve_contents(req, contents); Ok::<_, Infallible>(res) } })) } }); let server = hyper::server::Server::bind(&addr).serve(make_svc); let addr = server.local_addr(); addr_tx.send(addr).unwrap(); if let Err(e) = server.await { eprintln!("server error: {e}"); } } pub fn serve_file(contents: Vec) -> SocketAddr { let addr = ([127, 0, 0, 1], 0).into(); let (addr_tx, addr_rx) = channel(); thread::spawn(move || { let server = run_server(addr_tx, addr, contents); let rt = tokio::runtime::Runtime::new().expect("could not creating Runtime"); rt.block_on(server); }); let addr = addr_rx.recv(); addr.unwrap() } fn serve_contents( req: hyper::Request, contents: Vec, ) -> hyper::Response { let mut range_header = None; let (status, body) = if let Some(range) = req.headers().get(hyper::header::RANGE) { // extract range "bytes={start}-" let range = range.to_str().expect("unexpected Range header"); assert!(range.starts_with("bytes=")); let range = range.trim_start_matches("bytes="); assert!(range.ends_with('-')); let range = range.trim_end_matches('-'); assert_eq!(range.split('-').count(), 1); let start: u64 = range.parse().expect("unexpected Range header"); range_header = Some(format!("bytes {}-{len}/{len}", start, len = contents.len())); ( hyper::StatusCode::PARTIAL_CONTENT, contents[start as usize..].to_vec(), ) } else { (hyper::StatusCode::OK, contents) }; let mut res = hyper::Response::builder() .status(status) .header(hyper::header::CONTENT_LENGTH, body.len()) .body(hyper::Body::from(body)) .unwrap(); if let Some(range) = range_header { res.headers_mut() .insert(hyper::header::CONTENT_RANGE, range.parse().unwrap()); } res } rustup-1.26.0/flake.nix000066400000000000000000000011621441327105200147430ustar00rootroot00000000000000# This is a cheap nix flake for direnv use for developing # Rustup if you are running on NixOS. # # We deliberately don't commit a flake.lock because we only # provide this for developers, not as a way to have rustup # built for NixOS. { inputs = { flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; in { devShell = pkgs.mkShell { buildInputs = with pkgs; [ stdenv openssl pkg-config ]; }; }); } rustup-1.26.0/rustup-init.sh000077500000000000000000000551671441327105200160210ustar00rootroot00000000000000#!/bin/sh # shellcheck shell=dash # This is just a little script that can be downloaded from the internet to # install rustup. It just does platform detection, downloads the installer # and runs it. # It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local` # extension. Note: Most shells limit `local` to 1 var per line, contra bash. if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then # The version of ksh93 that ships with many illumos systems does not # support the "local" extension. Print a message rather than fail in # subtle ways later on: echo 'rustup does not work with this ksh93 version; please try bash!' >&2 exit 1 fi set -u # If RUSTUP_UPDATE_ROOT is unset or empty, default it. RUSTUP_UPDATE_ROOT="${RUSTUP_UPDATE_ROOT:-https://static.rust-lang.org/rustup}" # NOTICE: If you change anything here, please make the same changes in setup_mode.rs usage() { cat < Choose a default host triple --default-toolchain Choose a default toolchain to install. Use 'none' to not install any toolchains at all --profile [default: default] [possible values: minimal, default, complete] -c, --component ... Component name to also install -t, --target ... Target name to also install --no-update-default-toolchain Don't update any existing default toolchain after install --no-modify-path Don't configure the PATH environment variable -h, --help Print help information -V, --version Print version information EOF } main() { downloader --check need_cmd uname need_cmd mktemp need_cmd chmod need_cmd mkdir need_cmd rm need_cmd rmdir get_architecture || return 1 local _arch="$RETVAL" assert_nz "$_arch" "arch" local _ext="" case "$_arch" in *windows*) _ext=".exe" ;; esac local _url="${RUSTUP_UPDATE_ROOT}/dist/${_arch}/rustup-init${_ext}" local _dir if ! _dir="$(ensure mktemp -d)"; then # Because the previous command ran in a subshell, we must manually # propagate exit status. exit 1 fi local _file="${_dir}/rustup-init${_ext}" local _ansi_escapes_are_valid=false if [ -t 2 ]; then if [ "${TERM+set}" = 'set' ]; then case "$TERM" in xterm*|rxvt*|urxvt*|linux*|vt*) _ansi_escapes_are_valid=true ;; esac fi fi # check if we have to use /dev/tty to prompt the user local need_tty=yes for arg in "$@"; do case "$arg" in --help) usage exit 0 ;; *) OPTIND=1 if [ "${arg%%--*}" = "" ]; then # Long option (other than --help); # don't attempt to interpret it. continue fi while getopts :hy sub_arg "$arg"; do case "$sub_arg" in h) usage exit 0 ;; y) # user wants to skip the prompt -- # we don't need /dev/tty need_tty=no ;; *) ;; esac done ;; esac done if $_ansi_escapes_are_valid; then printf "\33[1minfo:\33[0m downloading installer\n" 1>&2 else printf '%s\n' 'info: downloading installer' 1>&2 fi ensure mkdir -p "$_dir" ensure downloader "$_url" "$_file" "$_arch" ensure chmod u+x "$_file" if [ ! -x "$_file" ]; then printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2 printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./rustup-init${_ext}." 1>&2 exit 1 fi if [ "$need_tty" = "yes" ] && [ ! -t 0 ]; then # The installer is going to want to ask for confirmation by # reading stdin. This script was piped into `sh` though and # doesn't have stdin to pass to its children. Instead we're going # to explicitly connect /dev/tty to the installer's stdin. if [ ! -t 1 ]; then err "Unable to run interactively. Run with -y to accept defaults, --help for additional options" fi ignore "$_file" "$@" < /dev/tty else ignore "$_file" "$@" fi local _retval=$? ignore rm "$_file" ignore rmdir "$_dir" return "$_retval" } check_proc() { # Check for /proc by looking for the /proc/self/exe link # This is only run on Linux if ! test -L /proc/self/exe ; then err "fatal: Unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc." fi } get_bitness() { need_cmd head # Architecture detection without dependencies beyond coreutils. # ELF files start out "\x7fELF", and the following byte is # 0x01 for 32-bit and # 0x02 for 64-bit. # The printf builtin on some shells like dash only supports octal # escape sequences, so we use those. local _current_exe_head _current_exe_head=$(head -c 5 /proc/self/exe ) if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then echo 32 elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then echo 64 else err "unknown platform bitness" fi } is_host_amd64_elf() { need_cmd head need_cmd tail # ELF e_machine detection without dependencies beyond coreutils. # Two-byte field at offset 0x12 indicates the CPU, # but we're interested in it being 0x3E to indicate amd64, or not that. local _current_exe_machine _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1) [ "$_current_exe_machine" = "$(printf '\076')" ] } get_endianness() { local cputype=$1 local suffix_eb=$2 local suffix_el=$3 # detect endianness without od/hexdump, like get_bitness() does. need_cmd head need_cmd tail local _current_exe_endianness _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)" if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then echo "${cputype}${suffix_el}" elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then echo "${cputype}${suffix_eb}" else err "unknown platform endianness" fi } get_architecture() { local _ostype _cputype _bitness _arch _clibtype _ostype="$(uname -s)" _cputype="$(uname -m)" _clibtype="gnu" if [ "$_ostype" = Linux ]; then if [ "$(uname -o)" = Android ]; then _ostype=Android fi if ldd --version 2>&1 | grep -q 'musl'; then _clibtype="musl" fi fi if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then # Darwin `uname -m` lies if sysctl hw.optional.x86_64 | grep -q ': 1'; then _cputype=x86_64 fi fi if [ "$_ostype" = SunOS ]; then # Both Solaris and illumos presently announce as "SunOS" in "uname -s" # so use "uname -o" to disambiguate. We use the full path to the # system uname in case the user has coreutils uname first in PATH, # which has historically sometimes printed the wrong value here. if [ "$(/usr/bin/uname -o)" = illumos ]; then _ostype=illumos fi # illumos systems have multi-arch userlands, and "uname -m" reports the # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 # systems. Check for the native (widest) instruction set on the # running kernel: if [ "$_cputype" = i86pc ]; then _cputype="$(isainfo -n)" fi fi case "$_ostype" in Android) _ostype=linux-android ;; Linux) check_proc _ostype=unknown-linux-$_clibtype _bitness=$(get_bitness) ;; FreeBSD) _ostype=unknown-freebsd ;; NetBSD) _ostype=unknown-netbsd ;; DragonFly) _ostype=unknown-dragonfly ;; Darwin) _ostype=apple-darwin ;; illumos) _ostype=unknown-illumos ;; MINGW* | MSYS* | CYGWIN* | Windows_NT) _ostype=pc-windows-gnu ;; *) err "unrecognized OS type: $_ostype" ;; esac case "$_cputype" in i386 | i486 | i686 | i786 | x86) _cputype=i686 ;; xscale | arm) _cputype=arm if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi fi ;; armv6l) _cputype=arm if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi else _ostype="${_ostype}eabihf" fi ;; armv7l | armv8l) _cputype=armv7 if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi else _ostype="${_ostype}eabihf" fi ;; aarch64 | arm64) _cputype=aarch64 ;; x86_64 | x86-64 | x64 | amd64) _cputype=x86_64 ;; mips) _cputype=$(get_endianness mips '' el) ;; mips64) if [ "$_bitness" -eq 64 ]; then # only n64 ABI is supported for now _ostype="${_ostype}abi64" _cputype=$(get_endianness mips64 '' el) fi ;; ppc) _cputype=powerpc ;; ppc64) _cputype=powerpc64 ;; ppc64le) _cputype=powerpc64le ;; s390x) _cputype=s390x ;; riscv64) _cputype=riscv64gc ;; loongarch64) _cputype=loongarch64 ;; *) err "unknown CPU type: $_cputype" esac # Detect 64-bit linux with 32-bit userland if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then case $_cputype in x86_64) if [ -n "${RUSTUP_CPUTYPE:-}" ]; then _cputype="$RUSTUP_CPUTYPE" else { # 32-bit executable for amd64 = x32 if is_host_amd64_elf; then { echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2 echo "and there isn't a native toolchain -- you will have to install" 1>&2 echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2 echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2 echo "set to i686 or x86_64, respectively." 1>&2 echo 1>&2 echo "You will be able to add an x32 target after installation by running" 1>&2 echo " rustup target add x86_64-unknown-linux-gnux32" 1>&2 exit 1 }; else _cputype=i686 fi }; fi ;; mips64) _cputype=$(get_endianness mips '' el) ;; powerpc64) _cputype=powerpc ;; aarch64) _cputype=armv7 if [ "$_ostype" = "linux-android" ]; then _ostype=linux-androideabi else _ostype="${_ostype}eabihf" fi ;; riscv64gc) err "riscv64 with 32-bit userland unsupported" ;; esac fi # Detect armv7 but without the CPU features Rust needs in that build, # and fall back to arm. # See https://github.com/rust-lang/rustup.rs/issues/587. if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then # At least one processor does not have NEON. _cputype=arm fi fi _arch="${_cputype}-${_ostype}" RETVAL="$_arch" } say() { printf 'rustup: %s\n' "$1" } err() { say "$1" >&2 exit 1 } need_cmd() { if ! check_cmd "$1"; then err "need '$1' (command not found)" fi } check_cmd() { command -v "$1" > /dev/null 2>&1 } assert_nz() { if [ -z "$1" ]; then err "assert_nz $2"; fi } # Run a command that should never fail. If the command fails execution # will immediately terminate with an error showing the failing # command. ensure() { if ! "$@"; then err "command failed: $*"; fi } # This is just for indicating that commands' results are being # intentionally ignored. Usually, because it's being executed # as part of error handling. ignore() { "$@" } # This wraps curl or wget. Try curl first, if not installed, # use wget instead. downloader() { local _dld local _ciphersuites local _err local _status local _retry if check_cmd curl; then _dld=curl elif check_cmd wget; then _dld=wget else _dld='curl or wget' # to be used in error message of need_cmd fi if [ "$1" = --check ]; then need_cmd "$_dld" elif [ "$_dld" = curl ]; then check_curl_for_retry_support _retry="$RETVAL" get_ciphersuites_for_curl _ciphersuites="$RETVAL" if [ -n "$_ciphersuites" ]; then _err=$(curl $_retry --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1) _status=$? else echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" if ! check_help_for "$3" curl --proto --tlsv1.2; then echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" _err=$(curl $_retry --silent --show-error --fail --location "$1" --output "$2" 2>&1) _status=$? else _err=$(curl $_retry --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1) _status=$? fi fi if [ -n "$_err" ]; then echo "$_err" >&2 if echo "$_err" | grep -q 404$; then err "installer for platform '$3' not found, this may be unsupported" fi fi return $_status elif [ "$_dld" = wget ]; then if [ "$(wget -V 2>&1|head -2|tail -1|cut -f1 -d" ")" = "BusyBox" ]; then echo "Warning: using the BusyBox version of wget. Not enforcing strong cipher suites for TLS or TLS v1.2, this is potentially less secure" _err=$(wget "$1" -O "$2" 2>&1) _status=$? else get_ciphersuites_for_wget _ciphersuites="$RETVAL" if [ -n "$_ciphersuites" ]; then _err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1) _status=$? else echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" if ! check_help_for "$3" wget --https-only --secure-protocol; then echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" _err=$(wget "$1" -O "$2" 2>&1) _status=$? else _err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1) _status=$? fi fi fi if [ -n "$_err" ]; then echo "$_err" >&2 if echo "$_err" | grep -q ' 404 Not Found$'; then err "installer for platform '$3' not found, this may be unsupported" fi fi return $_status else err "Unknown downloader" # should not reach here fi } check_help_for() { local _arch local _cmd local _arg _arch="$1" shift _cmd="$1" shift local _category if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then _category="all" else _category="" fi case "$_arch" in *darwin*) if check_cmd sw_vers; then case $(sw_vers -productVersion) in 10.*) # If we're running on macOS, older than 10.13, then we always # fail to find these options to force fallback if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then # Older than 10.13 echo "Warning: Detected macOS platform older than 10.13" return 1 fi ;; 11.*) # We assume Big Sur will be OK for now ;; *) # Unknown product version, warn and continue echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)" echo "Warning TLS capabilities detection may fail" ;; esac fi ;; esac for _arg in "$@"; do if ! "$_cmd" --help "$_category" | grep -q -- "$_arg"; then return 1 fi done true # not strictly needed } # Check if curl supports the --retry flag, then pass it to the curl invocation. check_curl_for_retry_support() { local _retry_supported="" # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. if check_help_for "notspecified" "curl" "--retry"; then _retry_supported="--retry 3" if check_help_for "notspecified" "curl" "--continue-at"; then # "-C -" tells curl to automatically find where to resume the download when retrying. _retry_supported="--retry 3 -C -" fi fi RETVAL="$_retry_supported" } # Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites # if support by local tools is detected. Detection currently supports these curl backends: # GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. get_ciphersuites_for_curl() { if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then # user specified custom cipher suites, assume they know what they're doing RETVAL="$RUSTUP_TLS_CIPHERSUITES" return fi local _openssl_syntax="no" local _gnutls_syntax="no" local _backend_supported="yes" if curl -V | grep -q ' OpenSSL/'; then _openssl_syntax="yes" elif curl -V | grep -iq ' LibreSSL/'; then _openssl_syntax="yes" elif curl -V | grep -iq ' BoringSSL/'; then _openssl_syntax="yes" elif curl -V | grep -iq ' GnuTLS/'; then _gnutls_syntax="yes" else _backend_supported="no" fi local _args_supported="no" if [ "$_backend_supported" = "yes" ]; then # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then _args_supported="yes" fi fi local _cs="" if [ "$_args_supported" = "yes" ]; then if [ "$_openssl_syntax" = "yes" ]; then _cs=$(get_strong_ciphersuites_for "openssl") elif [ "$_gnutls_syntax" = "yes" ]; then _cs=$(get_strong_ciphersuites_for "gnutls") fi fi RETVAL="$_cs" } # Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites # if support by local tools is detected. Detection currently supports these wget backends: # GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. get_ciphersuites_for_wget() { if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then # user specified custom cipher suites, assume they know what they're doing RETVAL="$RUSTUP_TLS_CIPHERSUITES" return fi local _cs="" if wget -V | grep -q '\-DHAVE_LIBSSL'; then # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then _cs=$(get_strong_ciphersuites_for "openssl") fi elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then _cs=$(get_strong_ciphersuites_for "gnutls") fi fi RETVAL="$_cs" } # Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 # excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad # DH params often found on servers (see RFC 7919). Sequence matches or is # similar to Firefox 68 ESR with weak cipher suites disabled via about:config. # $1 must be openssl or gnutls. get_strong_ciphersuites_for() { if [ "$1" = "openssl" ]; then # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet. echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" elif [ "$1" = "gnutls" ]; then # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't. # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order. echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM" fi } main "$@" || exit 1 rustup-1.26.0/src/000077500000000000000000000000001441327105200137305ustar00rootroot00000000000000rustup-1.26.0/src/bin/000077500000000000000000000000001441327105200145005ustar00rootroot00000000000000rustup-1.26.0/src/bin/rustup-init.rs000066400000000000000000000065351441327105200173620ustar00rootroot00000000000000//! The main Rustup command-line interface //! //! The rustup binary is a chimera, changing its behavior based on the //! name of the binary. This is used most prominently to enable //! Rustup's tool 'proxies' - that is, rustup itself and the rustup //! proxies are the same binary: when the binary is called 'rustup' or //! 'rustup.exe' it offers the Rustup command-line interface, and //! when it is called 'rustc' it behaves as a proxy to 'rustc'. //! //! This scheme is further used to distinguish the Rustup installer, //! called 'rustup-init', which is again just the rustup binary under a //! different name. #![recursion_limit = "1024"] use anyhow::{anyhow, Result}; use cfg_if::cfg_if; use rs_tracing::*; use rustup::cli::common; use rustup::cli::proxy_mode; use rustup::cli::rustup_mode; #[cfg(windows)] use rustup::cli::self_update; use rustup::cli::setup_mode; use rustup::currentprocess::{process, with, OSProcess}; use rustup::env_var::RUST_RECURSION_COUNT_MAX; use rustup::is_proxyable_tools; use rustup::utils::utils; fn main() { let process = OSProcess::default(); with(Box::new(process), || match run_rustup() { Err(e) => { common::report_error(&e); std::process::exit(1); } Ok(utils::ExitCode(c)) => std::process::exit(c), }); } fn run_rustup() -> Result { if let Ok(dir) = process().var("RUSTUP_TRACE_DIR") { open_trace_file!(dir)?; } let result = run_rustup_inner(); if process().var("RUSTUP_TRACE_DIR").is_ok() { close_trace_file!(); } result } fn run_rustup_inner() -> Result { // Guard against infinite proxy recursion. This mostly happens due to // bugs in rustup. do_recursion_guard()?; // Before we do anything else, ensure we know where we are and who we // are because otherwise we cannot proceed usefully. utils::current_dir()?; utils::current_exe()?; match process().name().as_deref() { Some("rustup") => rustup_mode::main(), Some(n) if n.starts_with("rustup-setup") || n.starts_with("rustup-init") => { // NB: The above check is only for the prefix of the file // name. Browsers rename duplicates to // e.g. rustup-setup(2), and this allows all variations // to work. setup_mode::main() } 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() } else { unreachable!("Attempted to use Windows-specific code on a non-Windows platform. Aborting.") } } } Some(n) => { is_proxyable_tools(n)?; proxy_mode::main(n) } None => { // Weird case. No arg0, or it's unparsable. Err(rustup::cli::errors::CLIError::NoExeName.into()) } } } fn do_recursion_guard() -> Result<()> { let recursion_count = process() .var("RUST_RECURSION_COUNT") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(0); if recursion_count > RUST_RECURSION_COUNT_MAX { return Err(anyhow!("infinite recursion detected")); } Ok(()) } rustup-1.26.0/src/cli.rs000066400000000000000000000004421441327105200150450ustar00rootroot00000000000000/// The CLI specific code lives in the cli module and sub-modules. #[macro_use] pub mod log; pub mod common; mod download_tracker; pub mod errors; mod help; mod job; mod markdown; pub mod proxy_mode; pub mod rustup_mode; pub mod self_update; pub mod setup_mode; mod term2; mod topical_doc; rustup-1.26.0/src/cli/000077500000000000000000000000001441327105200144775ustar00rootroot00000000000000rustup-1.26.0/src/cli/common.rs000066400000000000000000000443121441327105200163410ustar00rootroot00000000000000//! Just a dumping ground for cli stuff use std::fs; use std::io::{BufRead, ErrorKind, Write}; use std::path::Path; use std::sync::Arc; use std::{cmp, env}; use anyhow::{anyhow, Context, Result}; use git_testament::{git_testament, render_testament}; use lazy_static::lazy_static; use term2::Terminal; use super::self_update; use super::term2; use crate::dist::notifications as dist_notifications; use crate::process; use crate::toolchain::DistributableToolchain; use crate::utils::notifications as util_notifications; use crate::utils::notify::NotificationLevel; use crate::utils::utils; use crate::{Cfg, Notification, Toolchain, UpdateStatus}; 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) fn confirm(question: &str, default: bool) -> Result { write!(process().stdout(), "{question} ")?; let _ = std::io::stdout().flush(); let input = read_line()?; let r = match &*input.to_lowercase() { "y" | "yes" => true, "n" | "no" => false, "" => default, _ => false, }; writeln!(process().stdout())?; Ok(r) } pub(crate) enum Confirm { Yes, No, Advanced, } pub(crate) fn confirm_advanced() -> Result { writeln!(process().stdout())?; writeln!(process().stdout(), "1) Proceed with installation (default)")?; writeln!(process().stdout(), "2) Customize installation")?; writeln!(process().stdout(), "3) Cancel installation")?; write!(process().stdout(), ">")?; let _ = std::io::stdout().flush(); let input = read_line()?; let r = match &*input { "1" | "" => Confirm::Yes, "2" => Confirm::Advanced, _ => Confirm::No, }; writeln!(process().stdout())?; Ok(r) } pub(crate) fn question_str(question: &str, default: &str) -> Result { writeln!(process().stdout(), "{question} [{default}]")?; let _ = std::io::stdout().flush(); let input = read_line()?; writeln!(process().stdout())?; if input.is_empty() { Ok(default.to_string()) } else { Ok(input) } } pub(crate) fn question_bool(question: &str, default: bool) -> Result { let default_text = if default { "(Y/n)" } else { "(y/N)" }; writeln!(process().stdout(), "{question} {default_text}")?; let _ = std::io::stdout().flush(); let input = read_line()?; writeln!(process().stdout())?; if input.is_empty() { Ok(default) } else { match &*input.to_lowercase() { "y" | "yes" => Ok(true), "n" | "no" => Ok(false), _ => Ok(default), } } } pub(crate) fn read_line() -> Result { let stdin = process().stdin(); let stdin = stdin.lock(); let mut lines = stdin.lines(); let lines = lines.next().transpose()?; match lines { None => Err(anyhow!("no lines found from stdin")), Some(v) => Ok(v), } .context("unable to read from stdin for confirmation") } #[derive(Default)] struct NotifyOnConsole { ram_notice_shown: bool, verbose: bool, } impl NotifyOnConsole { fn handle(&mut self, n: Notification<'_>) { if let Notification::Install(dist_notifications::Notification::Utils( util_notifications::Notification::SetDefaultBufferSize(_), )) = &n { if self.ram_notice_shown { return; } else { self.ram_notice_shown = true; } }; let level = n.level(); for n in format!("{n}").lines() { match level { NotificationLevel::Verbose => { if self.verbose { verbose!("{}", n); } } NotificationLevel::Info => { info!("{}", n); } NotificationLevel::Warn => { warn!("{}", n); } NotificationLevel::Error => { err!("{}", n); } NotificationLevel::Debug => { debug!("{}", n); } } } } } pub(crate) fn set_globals(verbose: bool, quiet: bool) -> Result { use std::cell::RefCell; use super::download_tracker::DownloadTracker; let download_tracker = RefCell::new(DownloadTracker::new().with_display_progress(!quiet)); let console_notifier = RefCell::new(NotifyOnConsole { verbose, ..Default::default() }); Cfg::from_env(Arc::new(move |n: Notification<'_>| { if download_tracker.borrow_mut().handle_notification(&n) { return; } console_notifier.borrow_mut().handle(n); })) } pub(crate) fn show_channel_update( cfg: &Cfg, name: &str, updated: Result, ) -> Result<()> { show_channel_updates(cfg, vec![(name.to_string(), updated)]) } fn show_channel_updates(cfg: &Cfg, toolchains: Vec<(String, Result)>) -> Result<()> { let data = toolchains.into_iter().map(|(name, result)| { let toolchain = cfg.get_toolchain(&name, false)?; let mut version: String = toolchain.rustc_version(); let banner; let color; let mut previous_version: Option = None; match result { Ok(UpdateStatus::Installed) => { banner = "installed"; color = Some(term2::color::GREEN); } Ok(UpdateStatus::Updated(v)) => { if name == "rustup" { previous_version = Some(env!("CARGO_PKG_VERSION").into()); version = v; } else { previous_version = Some(v); } banner = "updated"; color = Some(term2::color::GREEN); } Ok(UpdateStatus::Unchanged) => { if name == "rustup" { version = env!("CARGO_PKG_VERSION").into(); } banner = "unchanged"; color = None; } Err(_) => { banner = "update failed"; color = Some(term2::color::RED); } } let width = name.len() + 1 + banner.len(); Ok((name, banner, width, color, version, previous_version)) }); let mut t = term2::stdout(); let data: Vec<_> = data.collect::>()?; let max_width = data .iter() .fold(0, |a, &(_, _, width, _, _, _)| cmp::max(a, width)); for (name, banner, width, color, version, previous_version) in data { let padding = max_width - width; let padding: String = " ".repeat(padding); let _ = write!(t, " {padding}"); let _ = t.attr(term2::Attr::Bold); if let Some(color) = color { let _ = t.fg(color); } let _ = write!(t, "{name} "); let _ = write!(t, "{banner}"); let _ = t.reset(); let _ = write!(t, " - {version}"); if let Some(previous_version) = previous_version { let _ = write!(t, " (from {previous_version})"); } let _ = writeln!(t); } let _ = writeln!(t); Ok(()) } pub(crate) fn update_all_channels( cfg: &Cfg, do_self_update: bool, force_update: bool, ) -> Result { let toolchains = cfg.update_all_channels(force_update)?; if toolchains.is_empty() { info!("no updatable toolchains installed"); } let show_channel_updates = || { if !toolchains.is_empty() { writeln!(process().stdout())?; show_channel_updates(cfg, toolchains)?; } Ok(utils::ExitCode(0)) }; if do_self_update { self_update(show_channel_updates) } else { show_channel_updates() } } #[derive(Clone, Copy, Debug)] pub(crate) enum SelfUpdatePermission { HardFail, Skip, Permit, } pub(crate) fn self_update_permitted(explicit: bool) -> Result { if cfg!(windows) { Ok(SelfUpdatePermission::Permit) } else { // Detect if rustup is not meant to self-update let current_exe = env::current_exe()?; let current_exe_dir = current_exe.parent().expect("Rustup isn't in a directory‽"); if let Err(e) = tempfile::Builder::new() .prefix("updtest") .tempdir_in(current_exe_dir) { match e.kind() { ErrorKind::PermissionDenied => { debug!("Skipping self-update because we cannot write to the rustup dir"); if explicit { return Ok(SelfUpdatePermission::HardFail); } else { return Ok(SelfUpdatePermission::Skip); } } _ => return Err(e.into()), } } Ok(SelfUpdatePermission::Permit) } } pub(crate) fn self_update(before_restart: F) -> Result where F: FnOnce() -> Result, { match self_update_permitted(false)? { SelfUpdatePermission::HardFail => { err!("Unable to self-update. STOP"); return Ok(utils::ExitCode(1)); } SelfUpdatePermission::Skip => return Ok(utils::ExitCode(0)), SelfUpdatePermission::Permit => {} } let setup_path = self_update::prepare_update()?; before_restart()?; if let Some(ref setup_path) = setup_path { return self_update::run_update(setup_path); } else { // Try again in case we emitted "tool `{}` is already installed" last time. self_update::install_proxies()?; } Ok(utils::ExitCode(0)) } pub(crate) fn list_targets(toolchain: &Toolchain<'_>) -> Result { let mut t = term2::stdout(); let distributable = DistributableToolchain::new_for_components(toolchain)?; let components = distributable.list_components()?; for component in components { if component.component.short_name_in_manifest() == "rust-std" { let target = component .component .target .as_ref() .expect("rust-std should have a target"); if component.installed { let _ = t.attr(term2::Attr::Bold); let _ = writeln!(t, "{target} (installed)"); let _ = t.reset(); } else if component.available { let _ = writeln!(t, "{target}"); } } } Ok(utils::ExitCode(0)) } pub(crate) fn list_installed_targets(toolchain: &Toolchain<'_>) -> Result { let mut t = term2::stdout(); let distributable = DistributableToolchain::new_for_components(toolchain)?; let components = distributable.list_components()?; for component in components { if component.component.short_name_in_manifest() == "rust-std" { let target = component .component .target .as_ref() .expect("rust-std should have a target"); if component.installed { writeln!(t, "{target}")?; } } } Ok(utils::ExitCode(0)) } pub(crate) fn list_components(toolchain: &Toolchain<'_>) -> Result { let mut t = term2::stdout(); let distributable = DistributableToolchain::new_for_components(toolchain)?; let components = distributable.list_components()?; for component in components { let name = component.name; if component.installed { t.attr(term2::Attr::Bold)?; writeln!(t, "{name} (installed)")?; t.reset()?; } else if component.available { writeln!(t, "{name}")?; } } Ok(utils::ExitCode(0)) } pub(crate) fn list_installed_components(toolchain: &Toolchain<'_>) -> Result { let mut t = term2::stdout(); let distributable = DistributableToolchain::new_for_components(toolchain)?; let components = distributable.list_components()?; for component in components { if component.installed { writeln!(t, "{}", component.name)?; } } Ok(utils::ExitCode(0)) } fn print_toolchain_path( cfg: &Cfg, toolchain: &str, if_default: &str, if_override: &str, verbose: bool, ) -> Result<()> { let toolchain_path = { let mut t_path = cfg.toolchains_dir.clone(); t_path.push(toolchain); t_path }; let toolchain_meta = fs::symlink_metadata(&toolchain_path)?; let toolchain_path = if verbose { if toolchain_meta.is_dir() { format!("\t{}", toolchain_path.display()) } else { format!("\t{}", fs::read_link(toolchain_path)?.display()) } } else { String::new() }; writeln!( process().stdout(), "{}{}{}{}", &toolchain, if_default, if_override, toolchain_path )?; Ok(()) } pub(crate) fn list_toolchains(cfg: &Cfg, verbose: bool) -> Result { let toolchains = cfg.list_toolchains()?; if toolchains.is_empty() { writeln!(process().stdout(), "no installed toolchains")?; } else { let def_toolchain_name = if let Ok(Some(def_toolchain)) = cfg.find_default() { def_toolchain.name().to_string() } else { String::new() }; let cwd = utils::current_dir()?; let ovr_toolchain_name = if let Ok(Some((toolchain, _reason))) = cfg.find_override(&cwd) { toolchain.name().to_string() } else { String::new() }; for toolchain in toolchains { let if_default = if def_toolchain_name == *toolchain { " (default)" } else { "" }; let if_override = if ovr_toolchain_name == *toolchain { " (override)" } else { "" }; print_toolchain_path(cfg, &toolchain, if_default, if_override, verbose) .context("Failed to list toolchains' directories")?; } } Ok(utils::ExitCode(0)) } pub(crate) fn list_overrides(cfg: &Cfg) -> Result { let overrides = cfg.settings_file.with(|s| Ok(s.overrides.clone()))?; if overrides.is_empty() { writeln!(process().stdout(), "no overrides")?; } else { let mut any_not_exist = false; for (k, v) in overrides { let dir_exists = Path::new(&k).is_dir(); if !dir_exists { any_not_exist = true; } writeln!( process().stdout(), "{:<40}\t{:<20}", utils::format_path_for_display(&k) + if dir_exists { "" } else { " (not a directory)" }, v )? } if any_not_exist { writeln!(process().stdout())?; info!( "you may remove overrides for non-existent directories with `rustup override unset --nonexistent`" ); } } Ok(utils::ExitCode(0)) } git_testament!(TESTAMENT); pub(crate) fn version() -> &'static str { lazy_static! { // Because we trust our `stable` branch given the careful release // process, we mark it trusted here so that our version numbers look // right when built from CI before the tag is pushed static ref RENDERED: String = render_testament!(TESTAMENT, "stable"); } &RENDERED } pub(crate) fn dump_testament() -> Result { use git_testament::GitModification::*; writeln!( process().stdout(), "Rustup version renders as: {}", version() )?; writeln!( process().stdout(), "Current crate version: {}", env!("CARGO_PKG_VERSION") )?; if TESTAMENT.branch_name.is_some() { writeln!( process().stdout(), "Built from branch: {}", TESTAMENT.branch_name.unwrap() )?; } else { writeln!(process().stdout(), "Branch information missing")?; } writeln!(process().stdout(), "Commit info: {}", TESTAMENT.commit)?; if TESTAMENT.modifications.is_empty() { writeln!(process().stdout(), "Working tree is clean")?; } else { for fmod in TESTAMENT.modifications { match fmod { Added(f) => writeln!(process().stdout(), "Added: {}", String::from_utf8_lossy(f))?, Removed(f) => writeln!( process().stdout(), "Removed: {}", String::from_utf8_lossy(f) )?, Modified(f) => writeln!( process().stdout(), "Modified: {}", String::from_utf8_lossy(f) )?, Untracked(f) => writeln!( process().stdout(), "Untracked: {}", String::from_utf8_lossy(f) )?, } } } Ok(utils::ExitCode(0)) } fn show_backtrace() -> bool { if let Ok(true) = process().var("RUSTUP_NO_BACKTRACE").map(|s| s == "1") { return false; } if let Ok(true) = process().var("RUST_BACKTRACE").map(|s| s == "1") { return true; } for arg in process().args() { if arg == "-v" || arg == "--verbose" { return true; } } false } pub fn report_error(e: &anyhow::Error) { // NB: This shows one error: even for multiple causes and backtraces etc, // rather than one per cause, and one for the backtrace. This seems like a // reasonable tradeoff, but if we want to do differently, this is the code // hunk to revisit, that and a similar build.rs auto-detect glue as anyhow // has to detect when backtrace is available. if show_backtrace() { err!("{:?}", e); } else { err!("{:#}", e); } } pub(crate) fn ignorable_error(error: &'static str, no_prompt: bool) -> Result<()> { let error = anyhow!(error); report_error(&error); if no_prompt { warn!("continuing (because the -y flag is set and the error is ignorable)"); Ok(()) } else if confirm("\nContinue? (y/N)", false).unwrap_or(false) { Ok(()) } else { Err(error) } } rustup-1.26.0/src/cli/download_tracker.rs000066400000000000000000000245571441327105200204040ustar00rootroot00000000000000use std::collections::VecDeque; use std::fmt; use std::io::Write; use std::time::{Duration, Instant}; use term::Terminal; use super::term2; use crate::dist::Notification as In; use crate::utils::tty; use crate::utils::units::{Size, Unit, UnitMode}; use crate::utils::Notification as Un; use crate::Notification; /// Keep track of this many past download amounts const DOWNLOAD_TRACK_COUNT: usize = 5; /// Tracks download progress and displays information about it to a terminal. pub(crate) struct DownloadTracker { /// Content-Length of the to-be downloaded object. content_len: Option, /// Total data downloaded in bytes. total_downloaded: usize, /// Data downloaded this second. downloaded_this_sec: usize, /// Keeps track of amount of data downloaded every last few secs. /// Used for averaging the download speed. NB: This does not necessarily /// represent adjacent seconds; thus it may not show the average at all. downloaded_last_few_secs: VecDeque, /// Time stamp of the last second last_sec: Option, /// Time stamp of the start of the download start_sec: Option, /// The terminal we write the information to. /// XXX: Could be a term trait, but with #1818 on the horizon that /// is a pointless change to make - better to let that transition /// happen and take stock after that. term: term2::StdoutTerminal, /// Whether we displayed progress for the download or not. /// /// If the download is quick enough, we don't have time to /// display the progress info. /// In that case, we do not want to do some cleanup stuff we normally do. /// /// If we have displayed progress, this is the number of characters we /// rendered, so we can erase it cleanly. displayed_charcount: Option, /// What units to show progress in units: Vec, /// Whether we display progress display_progress: bool, } impl DownloadTracker { /// Creates a new DownloadTracker. pub(crate) fn new() -> Self { Self { content_len: None, total_downloaded: 0, downloaded_this_sec: 0, downloaded_last_few_secs: VecDeque::with_capacity(DOWNLOAD_TRACK_COUNT), start_sec: None, last_sec: None, term: term2::stdout(), displayed_charcount: None, units: vec![Unit::B], display_progress: true, } } pub(crate) fn with_display_progress(mut self, display_progress: bool) -> Self { self.display_progress = display_progress; self } pub(crate) fn handle_notification(&mut self, n: &Notification<'_>) -> bool { match *n { Notification::Install(In::Utils(Un::DownloadContentLengthReceived(content_len))) => { self.content_length_received(content_len); true } Notification::Install(In::Utils(Un::DownloadDataReceived(data))) => { if tty::stdout_isatty() { self.data_received(data.len()); } true } Notification::Install(In::Utils(Un::DownloadFinished)) => { self.download_finished(); true } Notification::Install(In::Utils(Un::DownloadPushUnit(unit))) => { self.push_unit(unit); true } Notification::Install(In::Utils(Un::DownloadPopUnit)) => { self.pop_unit(); true } _ => false, } } /// Notifies self that Content-Length information has been received. pub(crate) fn content_length_received(&mut self, content_len: u64) { self.content_len = Some(content_len as usize); } /// Notifies self that data of size `len` has been received. pub(crate) fn data_received(&mut self, len: usize) { self.total_downloaded += len; self.downloaded_this_sec += len; let current_time = Instant::now(); match self.last_sec { None => self.last_sec = Some(current_time), Some(prev) => { let elapsed = current_time.saturating_duration_since(prev); if elapsed >= Duration::from_secs(1) { if self.display_progress { self.display(); } self.last_sec = Some(current_time); if self.downloaded_last_few_secs.len() == DOWNLOAD_TRACK_COUNT { self.downloaded_last_few_secs.pop_back(); } self.downloaded_last_few_secs .push_front(self.downloaded_this_sec); self.downloaded_this_sec = 0; } } } } /// Notifies self that the download has finished. pub(crate) fn download_finished(&mut self) { if self.displayed_charcount.is_some() { // Display the finished state self.display(); let _ = writeln!(self.term); } self.prepare_for_new_download(); } /// Resets the state to be ready for a new download. fn prepare_for_new_download(&mut self) { self.content_len = None; self.total_downloaded = 0; self.downloaded_this_sec = 0; self.downloaded_last_few_secs.clear(); self.start_sec = Some(Instant::now()); self.last_sec = None; self.displayed_charcount = None; } /// Display the tracked download information to the terminal. fn display(&mut self) { match self.start_sec { // Maybe forgot to call `prepare_for_new_download` first None => {} Some(start_sec) => { // Panic if someone pops the default bytes unit... let unit = *self.units.last().unwrap(); let total_h = Size::new(self.total_downloaded, unit, UnitMode::Norm); let sum: usize = self.downloaded_last_few_secs.iter().sum(); let len = self.downloaded_last_few_secs.len(); let speed = if len > 0 { sum / len } else { 0 }; let speed_h = Size::new(speed, unit, UnitMode::Rate); let elapsed_h = Instant::now().saturating_duration_since(start_sec); // First, move to the start of the current line and clear it. let _ = self.term.carriage_return(); // We'd prefer to use delete_line() but on Windows it seems to // sometimes do unusual things // let _ = self.term.as_mut().unwrap().delete_line(); // So instead we do: if let Some(n) = self.displayed_charcount { // This is not ideal as very narrow terminals might mess up, // but it is more likely to succeed until term's windows console // fixes whatever's up with delete_line(). let _ = write!(self.term, "{}", " ".repeat(n)); let _ = self.term.flush(); let _ = self.term.carriage_return(); } let output = match self.content_len { Some(content_len) => { let content_len_h = Size::new(content_len, unit, UnitMode::Norm); let percent = (self.total_downloaded as f64 / content_len as f64) * 100.; let remaining = content_len - self.total_downloaded; let eta_h = Duration::from_secs(if speed == 0 { std::u64::MAX } else { (remaining / speed) as u64 }); format!( "{} / {} ({:3.0} %) {} in {} ETA: {}", total_h, content_len_h, percent, speed_h, elapsed_h.display(), eta_h.display(), ) } None => format!( "Total: {} Speed: {} Elapsed: {}", total_h, speed_h, elapsed_h.display() ), }; let _ = write!(self.term, "{output}"); // Since stdout is typically line-buffered and we don't print a newline, we manually flush. let _ = self.term.flush(); self.displayed_charcount = Some(output.chars().count()); } } } pub(crate) fn push_unit(&mut self, new_unit: Unit) { self.units.push(new_unit); } pub(crate) fn pop_unit(&mut self) { self.units.pop(); } } trait DurationDisplay { fn display(self) -> Display; } impl DurationDisplay for Duration { fn display(self) -> Display { Display(self) } } /// Human readable representation of a `Duration`. struct Display(Duration); impl fmt::Display for Display { #[allow(clippy::many_single_char_names)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { const SECS_PER_YEAR: u64 = 60 * 60 * 24 * 365; let secs = self.0.as_secs(); if secs > SECS_PER_YEAR { return f.write_str("Unknown"); } match format_dhms(secs) { (0, 0, 0, s) => write!(f, "{s:2.0}s"), (0, 0, m, s) => write!(f, "{m:2.0}m {s:2.0}s"), (0, h, m, s) => write!(f, "{h:2.0}h {m:2.0}m {s:2.0}s"), (d, h, m, s) => write!(f, "{d:3.0}d {h:2.0}h {m:2.0}m {s:2.0}s"), } } } // we're doing modular arithmetic, treat as integer fn format_dhms(sec: u64) -> (u64, u8, u8, u8) { let (mins, sec) = (sec / 60, (sec % 60) as u8); let (hours, mins) = (mins / 60, (mins % 60) as u8); let (days, hours) = (hours / 24, (hours % 24) as u8); (days, hours, mins, sec) } #[cfg(test)] mod tests { use super::format_dhms; #[test] fn download_tracker_format_dhms_test() { assert_eq!(format_dhms(2), (0, 0, 0, 2)); assert_eq!(format_dhms(60), (0, 0, 1, 0)); assert_eq!(format_dhms(3_600), (0, 1, 0, 0)); assert_eq!(format_dhms(3_600 * 24), (1, 0, 0, 0)); assert_eq!(format_dhms(52_292), (0, 14, 31, 32)); assert_eq!(format_dhms(222_292), (2, 13, 44, 52)); } } rustup-1.26.0/src/cli/errors.rs000066400000000000000000000031701441327105200163620ustar00rootroot00000000000000#![allow(clippy::large_enum_variant)] #![allow(dead_code)] use std::io; use std::path::PathBuf; use lazy_static::lazy_static; use regex::Regex; use strsim::damerau_levenshtein; use thiserror::Error as ThisError; #[derive(ThisError, Debug)] pub enum CLIError { #[error("couldn't determine self executable name")] NoExeName, #[error("rustup is not installed at '{}'", .p.display())] NotSelfInstalled { p: PathBuf }, #[error("failure reading directory {}", .p.display())] ReadDirError { p: PathBuf, source: io::Error }, #[error("failure during windows uninstall")] WindowsUninstallMadness, } fn maybe_suggest_toolchain(bad_name: &str) -> String { let bad_name = &bad_name.to_ascii_lowercase(); static VALID_CHANNELS: &[&str] = &["stable", "beta", "nightly"]; lazy_static! { static ref NUMBERED: Regex = Regex::new(r"^\d+\.\d+$").unwrap(); } if NUMBERED.is_match(bad_name) { return format!(". Toolchain numbers tend to have three parts, e.g. {bad_name}.0"); } // Suggest only for very small differences // High number can result in inaccurate suggestions for short queries e.g. `rls` const MAX_DISTANCE: usize = 3; let mut scored: Vec<_> = VALID_CHANNELS .iter() .filter_map(|s| { let distance = damerau_levenshtein(bad_name, s); if distance <= MAX_DISTANCE { Some((distance, s)) } else { None } }) .collect(); scored.sort(); if scored.is_empty() { String::new() } else { format!(". Did you mean '{}'?", scored[0].1) } } rustup-1.26.0/src/cli/help.rs000066400000000000000000000261151441327105200160020ustar00rootroot00000000000000pub(crate) static RUSTUP_HELP: &str = r"DISCUSSION: Rustup installs The Rust Programming Language from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling simpler with binary builds of the standard library for common platforms. If you are new to Rust consider running `rustup doc --book` to learn Rust."; pub(crate) static SHOW_HELP: &str = r"DISCUSSION: Shows the name of the active toolchain and the version of `rustc`. If the active toolchain has installed support for additional compilation targets, then they are listed as well. If there are multiple toolchains installed then all installed toolchains are listed as well."; pub(crate) static SHOW_ACTIVE_TOOLCHAIN_HELP: &str = r"DISCUSSION: Shows the name of the active toolchain. This is useful for figuring out the active tool chain from scripts. You should use `rustc --print sysroot` to get the sysroot, or `rustc --version` to get the toolchain version."; pub(crate) static UPDATE_HELP: &str = r"DISCUSSION: With no toolchain specified, the `update` command updates each of the installed toolchains from the official release channels, then updates rustup itself. If given a toolchain argument then `update` updates that toolchain, the same as `rustup toolchain install`."; pub(crate) static INSTALL_HELP: &str = r"DISCUSSION: Installs a specific rust toolchain. The 'install' command is an alias for 'rustup update '."; pub(crate) static DEFAULT_HELP: &str = r"DISCUSSION: Sets the default toolchain to the one specified. If the toolchain is not already installed then it is installed first."; pub(crate) static TOOLCHAIN_HELP: &str = r"DISCUSSION: Many `rustup` commands deal with *toolchains*, a single installation of the Rust compiler. `rustup` supports multiple types of toolchains. The most basic track the official release channels: 'stable', 'beta' and 'nightly'; but `rustup` can also install toolchains from the official archives, for alternate host platforms, and from local builds. Standard release channel toolchain names have the following form: [-][-] = stable|beta|nightly|| = YYYY-MM-DD = 'channel' is a named release channel, a major and minor version number such as `1.42`, or a fully specified version number, such as `1.42.0`. Channel names can be optionally appended with an archive date, as in `nightly-2014-12-18`, in which case the toolchain is downloaded from the archive for that date. The host may be specified as a target triple. This is most useful for installing a 32-bit compiler on a 64-bit platform, or for installing the [MSVC-based toolchain] on Windows. For example: $ rustup toolchain install stable-x86_64-pc-windows-msvc For convenience, omitted elements of the target triple will be inferred, so the above could be written: $ rustup toolchain install stable-msvc The `rustup default` command may be used to both install and set the desired toolchain as default in a single command: $ rustup default stable-msvc rustup can also manage symlinked local toolchain builds, which are often used for developing Rust itself. For more information see `rustup toolchain help link`."; pub(crate) static TOOLCHAIN_LINK_HELP: &str = r"DISCUSSION: 'toolchain' is the custom name to be assigned to the new toolchain. Any name is permitted as long as it does not fully match an initial substring of a standard release channel. For example, you can use the names 'latest' or '2017-04-01' but you cannot use 'stable' or 'beta-i686' or 'nightly-x86_64-unknown-linux-gnu'. 'path' specifies the directory where the binaries and libraries for the custom toolchain can be found. For example, when used for development of Rust itself, toolchains can be linked directly out of the build directory. After building, you can test out different compiler versions as follows: $ rustup toolchain link latest-stage1 build/x86_64-unknown-linux-gnu/stage1 $ rustup override set latest-stage1 If you now compile a crate in the current directory, the custom toolchain 'latest-stage1' will be used."; pub(crate) static OVERRIDE_HELP: &str = r"DISCUSSION: Overrides configure Rustup to use a specific toolchain when running in a specific directory. Directories can be assigned their own Rust toolchain with `rustup override`. When a directory has an override then any time `rustc` or `cargo` is run inside that directory, or one of its child directories, the override toolchain will be invoked. To pin to a specific nightly: $ rustup override set nightly-2014-12-18 Or a specific stable release: $ rustup override set 1.0.0 To see the active toolchain use `rustup show`. To remove the override and use the default toolchain again, `rustup override unset`."; pub(crate) static OVERRIDE_UNSET_HELP: &str = r"DISCUSSION: If `--path` argument is present, removes the override toolchain for the specified directory. If `--nonexistent` argument is present, removes the override toolchain for all nonexistent directories. Otherwise, removes the override toolchain for the current directory."; pub(crate) static RUN_HELP: &str = r"DISCUSSION: Configures an environment to use the given toolchain and then runs the specified program. The command may be any program, not just rustc or cargo. This can be used for testing arbitrary toolchains without setting an override. Commands explicitly proxied by `rustup` (such as `rustc` and `cargo`) also have a shorthand for this available. The toolchain can be set by using `+toolchain` as the first argument. These are equivalent: $ cargo +nightly build $ rustup run nightly cargo build"; pub(crate) static DOC_HELP: &str = r"DISCUSSION: Opens the documentation for the currently active toolchain with the default browser. By default, it opens the documentation index. Use the various flags to open specific pieces of documentation."; pub(crate) static COMPLETIONS_HELP: &str = r"DISCUSSION: Enable tab completion for Bash, Fish, Zsh, or PowerShell The script is output on `stdout`, allowing one to re-direct the output to the file of their choosing. Where you place the file will depend on which shell, and which operating system you are using. Your particular configuration may also determine where these scripts need to be placed. Here are some common set ups for the three supported shells under Unix and similar operating systems (such as GNU/Linux). BASH: Completion files are commonly stored in `/etc/bash_completion.d/` for system-wide commands, but can be stored in `~/.local/share/bash-completion/completions` for user-specific commands. Run the command: $ mkdir -p ~/.local/share/bash-completion/completions $ rustup completions bash >> ~/.local/share/bash-completion/completions/rustup This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect. BASH (macOS/Homebrew): Homebrew stores bash completion files within the Homebrew directory. With the `bash-completion` brew formula installed, run the command: $ mkdir -p $(brew --prefix)/etc/bash_completion.d $ rustup completions bash > $(brew --prefix)/etc/bash_completion.d/rustup.bash-completion FISH: Fish completion files are commonly stored in `$HOME/.config/fish/completions`. Run the command: $ mkdir -p ~/.config/fish/completions $ rustup completions fish > ~/.config/fish/completions/rustup.fish This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect. ZSH: ZSH completions are commonly stored in any directory listed in your `$fpath` variable. To use these completions, you must either add the generated script to one of those directories, or add your own to this list. Adding a custom directory is often the safest bet if you are unsure of which directory to use. First create the directory; for this example we'll create a hidden directory inside our `$HOME` directory: $ mkdir ~/.zfunc Then add the following lines to your `.zshrc` just before `compinit`: fpath+=~/.zfunc Now you can install the completions script using the following command: $ rustup completions zsh > ~/.zfunc/_rustup You must then either log out and log back in, or simply run $ exec zsh for the new completions to take effect. CUSTOM LOCATIONS: Alternatively, you could save these files to the place of your choosing, such as a custom directory inside your $HOME. Doing so will require you to add the proper directives, such as `source`ing inside your login script. Consult your shells documentation for how to add such directives. POWERSHELL: The powershell completion scripts require PowerShell v5.0+ (which comes with Windows 10, but can be downloaded separately for windows 7 or 8.1). First, check if a profile has already been set PS C:\> Test-Path $profile If the above command returns `False` run the following PS C:\> New-Item -path $profile -type file -force Now open the file provided by `$profile` (if you used the `New-Item` command it will be `${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1` Next, we either save the completions file into our profile, or into a separate file and source it inside our profile. To save the completions into our profile simply use PS C:\> rustup completions powershell >> ${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 CARGO: Rustup can also generate a completion script for `cargo`. The script output by `rustup` will source the completion script distributed with your default toolchain. Not all shells are currently supported. Here are examples for the currently supported shells. BASH: $ rustup completions bash cargo >> ~/.local/share/bash-completion/completions/cargo ZSH: $ rustup completions zsh cargo > ~/.zfunc/_cargo"; pub(crate) static TOOLCHAIN_ARG_HELP: &str = "Toolchain name, such as 'stable', 'nightly', \ or '1.8.0'. For more information see `rustup \ help toolchain`"; pub(crate) static TOPIC_ARG_HELP: &str = "Topic such as 'core', 'fn', 'usize', 'eprintln!', \ 'core::arch', 'alloc::format!', 'std::fs', \ 'std::fs::read_dir', 'std::io::Bytes', \ 'std::iter::Sum', 'std::io::error::Result' etc..."; rustup-1.26.0/src/cli/job.rs000066400000000000000000000111741441327105200156230ustar00rootroot00000000000000// FIXME: stolen from cargo. Should be extracted into a common crate. //! Job management (mostly for Windows) //! //! Most of the time when you're running cargo you expect Ctrl-C to actually //! terminate the entire tree of processes in play, not just the one at the top //! (cargo). This currently works "by default" on Unix platforms because Ctrl-C //! actually sends a signal to the *process group* rather than the parent //! process, so everything will get torn down. On Windows, however, this does //! not happen and Ctrl-C just kills cargo. //! //! To achieve the same semantics on Windows we use Job Objects to ensure that //! all processes die at the same time. Job objects have a mode of operation //! where when all handles to the object are closed it causes all child //! processes associated with the object to be terminated immediately. //! Conveniently whenever a process in the job object spawns a new process the //! child will be associated with the job object as well. This means if we add //! ourselves to the job object we create then everything will get torn down! #![allow(clippy::missing_safety_doc)] pub(crate) use self::imp::Setup; pub(crate) fn setup() -> Option { unsafe { imp::setup() } } #[cfg(unix)] mod imp { pub(crate) type Setup = (); pub(crate) unsafe fn setup() -> Option<()> { Some(()) } } #[cfg(windows)] mod imp { use std::io; use std::mem; use std::ptr; use winapi::shared::minwindef::*; use winapi::um::handleapi::*; use winapi::um::jobapi2::*; use winapi::um::processthreadsapi::*; use winapi::um::winnt::HANDLE; use winapi::um::winnt::*; pub(crate) struct Setup { job: Handle, } pub(crate) struct Handle { inner: HANDLE, } fn last_err() -> io::Error { io::Error::last_os_error() } pub(crate) unsafe fn setup() -> Option { // Creates a new job object for us to use and then adds ourselves to it. // Note that all errors are basically ignored in this function, // intentionally. Job objects are "relatively new" in Windows, // particularly the ability to support nested job objects. Older // Windows installs don't support this ability. We probably don't want // to force Cargo to abort in this situation or force others to *not* // use job objects, so we instead just ignore errors and assume that // we're otherwise part of someone else's job object in this case. let job = CreateJobObjectW(ptr::null_mut(), ptr::null()); if job.is_null() { return None; } let job = Handle { inner: job }; // Indicate that when all handles to the job object are gone that all // process in the object should be killed. Note that this includes our // entire process tree by default because we've added ourselves and // our children will reside in the job once we spawn a process. let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION; info = mem::zeroed(); info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; let r = SetInformationJobObject( job.inner, JobObjectExtendedLimitInformation, &mut info as *mut _ as LPVOID, mem::size_of_val(&info) as DWORD, ); if r == 0 { return None; } // Assign our process to this job object, meaning that our children will // now live or die based on our existence. let me = GetCurrentProcess(); let r = AssignProcessToJobObject(job.inner, me); if r == 0 { return None; } Some(Setup { job }) } impl Drop for Setup { fn drop(&mut self) { // On normal exits (not ctrl-c), we don't want to kill any child // processes. The destructor here configures our job object to // *not* kill everything on close, then closes the job object. unsafe { let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION; info = mem::zeroed(); let r = SetInformationJobObject( self.job.inner, JobObjectExtendedLimitInformation, &mut info as *mut _ as LPVOID, mem::size_of_val(&info) as DWORD, ); if r == 0 { info!("failed to configure job object to defaults: {}", last_err()); } } } } impl Drop for Handle { fn drop(&mut self) { unsafe { CloseHandle(self.inner); } } } } rustup-1.26.0/src/cli/log.rs000066400000000000000000000041301441327105200156240ustar00rootroot00000000000000use std::fmt; use std::io::Write; use term2::Terminal; use super::term2; use crate::process; macro_rules! warn { ( $ ( $ arg : tt ) * ) => ( $crate::cli::log::warn_fmt ( format_args ! ( $ ( $ arg ) * ) ) ) } macro_rules! err { ( $ ( $ arg : tt ) * ) => ( $crate::cli::log::err_fmt ( format_args ! ( $ ( $ arg ) * ) ) ) } macro_rules! info { ( $ ( $ arg : tt ) * ) => ( $crate::cli::log::info_fmt ( format_args ! ( $ ( $ arg ) * ) ) ) } macro_rules! verbose { ( $ ( $ arg : tt ) * ) => ( $crate::cli::log::verbose_fmt ( format_args ! ( $ ( $ arg ) * ) ) ) } macro_rules! debug { ( $ ( $ arg : tt ) * ) => ( $crate::cli::log::debug_fmt ( format_args ! ( $ ( $ arg ) * ) ) ) } pub(crate) fn warn_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); let _ = t.fg(term2::color::YELLOW); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "warning: "); let _ = t.reset(); let _ = t.write_fmt(args); let _ = writeln!(t); } pub(crate) fn err_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); let _ = t.fg(term2::color::RED); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "error: "); let _ = t.reset(); let _ = t.write_fmt(args); let _ = writeln!(t); } pub(crate) fn info_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "info: "); let _ = t.reset(); let _ = t.write_fmt(args); let _ = writeln!(t); } pub(crate) fn verbose_fmt(args: fmt::Arguments<'_>) { let mut t = term2::stderr(); let _ = t.fg(term2::color::MAGENTA); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "verbose: "); let _ = t.reset(); let _ = t.write_fmt(args); let _ = writeln!(t); } pub(crate) fn debug_fmt(args: fmt::Arguments<'_>) { if process().var("RUSTUP_DEBUG").is_ok() { let mut t = term2::stderr(); let _ = t.fg(term2::color::BLUE); let _ = t.attr(term2::Attr::Bold); let _ = write!(t, "debug: "); let _ = t.reset(); let _ = t.write_fmt(args); let _ = writeln!(t); } } rustup-1.26.0/src/cli/markdown.rs000066400000000000000000000153131441327105200166720ustar00rootroot00000000000000// Write Markdown to the terminal use std::io; use pulldown_cmark::{Event, Tag}; use super::term2::{color, Attr, Terminal}; // Handles the wrapping of text written to the console struct LineWrapper<'a, T: Terminal> { indent: u32, margin: u32, pos: u32, w: &'a mut T, } impl<'a, T: Terminal + 'a> LineWrapper<'a, T> { // Just write a newline fn write_line(&mut self) { let _ = writeln!(self.w); // Reset column position to start of line self.pos = 0; } // Called before writing text to ensure indent is applied fn write_indent(&mut self) { if self.pos == 0 { // Write a space for each level of indent for _ in 0..self.indent { let _ = write!(self.w, " "); } self.pos = self.indent; } } // Write a non-breaking word fn write_word(&mut self, word: &str) { // Ensure correct indentation self.write_indent(); let word_len = word.len() as u32; // If this word goes past the margin if self.pos + word_len > self.margin { // And adding a newline would give us more space if self.pos > self.indent { // Then add a newline! self.write_line(); self.write_indent(); } } // Write the word let _ = write!(self.w, "{word}"); self.pos += word_len; } fn write_space(&mut self) { if self.pos > self.indent { if self.pos < self.margin { self.write_word(" "); } else { self.write_line(); } } } // Writes a span of text which wraps at the margin fn write_span(&mut self, text: &str) { // Allow words to wrap on whitespace let mut is_first = true; for word in text.split(char::is_whitespace) { if is_first { is_first = false; } else { self.write_space(); } self.write_word(word); } } // Writes code block where each line is indented fn write_code_block(&mut self, text: &str) { for line in text.lines() { self.write_word(line); // Will call write_indent() self.write_line(); } } // Constructor fn new(w: &'a mut T, indent: u32, margin: u32) -> Self { LineWrapper { indent, margin, pos: indent, w, } } } // Handles the formatting of text struct LineFormatter<'a, T: Terminal + io::Write> { is_code_block: bool, wrapper: LineWrapper<'a, T>, attrs: Vec, } impl<'a, T: Terminal + io::Write + 'a> LineFormatter<'a, T> { fn new(w: &'a mut T, indent: u32, margin: u32) -> Self { LineFormatter { is_code_block: false, wrapper: LineWrapper::new(w, indent, margin), attrs: Vec::new(), } } fn push_attr(&mut self, attr: Attr) { self.attrs.push(attr); let _ = self.wrapper.w.attr(attr); } fn pop_attr(&mut self) { self.attrs.pop(); let _ = self.wrapper.w.reset(); for attr in &self.attrs { let _ = self.wrapper.w.attr(*attr); } } fn start_tag(&mut self, tag: Tag<'a>) { match tag { Tag::Paragraph => { self.wrapper.write_line(); } Tag::Heading(_level, _identifier, _classes) => { self.push_attr(Attr::Bold); self.wrapper.write_line(); } Tag::Table(_alignments) => {} Tag::TableHead => {} Tag::TableRow => {} Tag::TableCell => {} Tag::BlockQuote => {} Tag::CodeBlock(_lang) => { self.wrapper.write_line(); self.wrapper.indent += 2; self.is_code_block = true; } Tag::List(_) => { self.wrapper.write_line(); self.wrapper.indent += 2; } Tag::Item => { self.wrapper.write_line(); } Tag::Emphasis => { self.push_attr(Attr::ForegroundColor(color::RED)); } Tag::Strong => {} Tag::Strikethrough => {} Tag::Link(_link_type, _dest, _title) => {} Tag::Image(_link_type, _dest, _title) => {} Tag::FootnoteDefinition(_name) => {} } } fn end_tag(&mut self, tag: Tag<'a>) { match tag { Tag::Paragraph => { self.wrapper.write_line(); } Tag::Heading(_level, _identifier, _classes) => { self.wrapper.write_line(); self.pop_attr(); } Tag::Table(_) => {} Tag::TableHead => {} Tag::TableRow => {} Tag::TableCell => {} Tag::BlockQuote => {} Tag::CodeBlock(_) => { self.is_code_block = false; self.wrapper.indent -= 2; } Tag::List(_) => { self.wrapper.indent -= 2; self.wrapper.write_line(); } Tag::Item => {} Tag::Emphasis => { self.pop_attr(); } Tag::Strong => {} Tag::Strikethrough => {} Tag::Link(_, _, _) => {} Tag::Image(_, _, _) => {} // shouldn't happen, handled in start Tag::FootnoteDefinition(_) => {} } } fn process_event(&mut self, event: Event<'a>) { use self::Event::*; match event { Start(tag) => self.start_tag(tag), End(tag) => self.end_tag(tag), Text(text) => { if self.is_code_block { self.wrapper.write_code_block(&text); } else { self.wrapper.write_span(&text); } } Code(code) => { self.push_attr(Attr::Bold); self.wrapper.write_word(&code); self.pop_attr(); } Html(_html) => {} SoftBreak => { self.wrapper.write_line(); } HardBreak => { self.wrapper.write_line(); } Rule => {} FootnoteReference(_name) => {} TaskListMarker(true) => {} TaskListMarker(false) => {} } } } pub(crate) fn md<'a, S: AsRef, T: Terminal + io::Write + 'a>(t: &'a mut T, content: S) { let mut f = LineFormatter::new(t, 0, 79); let parser = pulldown_cmark::Parser::new(content.as_ref()); for event in parser { f.process_event(event); } } rustup-1.26.0/src/cli/proxy_mode.rs000066400000000000000000000030041441327105200172270ustar00rootroot00000000000000use std::ffi::OsString; use std::process; use anyhow::Result; use super::common::set_globals; use super::job; use super::self_update; use crate::command::run_command_for_dir; use crate::utils::utils::{self, ExitCode}; use crate::Cfg; pub fn main(arg0: &str) -> Result { self_update::cleanup_self_updater()?; let ExitCode(c) = { let _setup = job::setup(); let mut args = crate::process().args_os().skip(1); // Check for a toolchain specifier. let arg1 = args.next(); let toolchain_arg = arg1 .as_ref() .map(|arg| arg.to_string_lossy()) .filter(|arg| arg.starts_with('+')); let toolchain = toolchain_arg.as_ref().map(|a| &a[1..]); // Build command args now while we know whether or not to skip arg 1. let cmd_args: Vec<_> = if toolchain.is_none() { crate::process().args_os().skip(1).collect() } else { crate::process().args_os().skip(2).collect() }; let cfg = set_globals(false, true)?; cfg.check_metadata_version()?; direct_proxy(&cfg, arg0, toolchain, &cmd_args)? }; process::exit(c) } fn direct_proxy( cfg: &Cfg, arg0: &str, toolchain: Option<&str>, args: &[OsString], ) -> Result { let cmd = match toolchain { None => cfg.create_command_for_dir(&utils::current_dir()?, arg0)?, Some(tc) => cfg.create_command_for_toolchain(tc, false, arg0)?, }; run_command_for_dir(cmd, arg0, args) } rustup-1.26.0/src/cli/rustup_mode.rs000066400000000000000000002015531441327105200174210ustar00rootroot00000000000000use std::fmt; use std::io::Write; use std::path::{Path, PathBuf}; use std::process; use std::str::FromStr; use anyhow::{anyhow, bail, Error, Result}; use clap::{ builder::{EnumValueParser, PossibleValuesParser}, AppSettings, Arg, ArgAction, ArgEnum, ArgGroup, ArgMatches, Command, PossibleValue, }; use clap_complete::Shell; use super::help::*; use super::self_update; use super::term2; use super::term2::Terminal; use super::topical_doc; use super::{ common, self_update::{check_rustup_update, SelfUpdateMode}, }; use crate::cli::errors::CLIError; use crate::dist::dist::{PartialTargetTriple, PartialToolchainDesc, Profile, TargetTriple}; use crate::dist::manifest::Component; use crate::errors::RustupError; use crate::process; use crate::toolchain::{CustomToolchain, DistributableToolchain}; use crate::utils::utils; use crate::Notification; use crate::{command, Cfg, ComponentStatus, Toolchain}; const TOOLCHAIN_OVERRIDE_ERROR: &str = "To override the toolchain using the 'rustup +toolchain' syntax, \ make sure to prefix the toolchain override with a '+'"; fn handle_epipe(res: Result) -> Result { match res { Err(e) => { let root = e.root_cause(); if let Some(io_err) = root.downcast_ref::() { if io_err.kind() == std::io::ErrorKind::BrokenPipe { return Ok(utils::ExitCode(0)); } } Err(e) } res => res, } } fn deprecated(instead: &str, cfg: &mut Cfg, matches: B, callee: F) -> R where F: FnOnce(&mut Cfg, B) -> R, { (cfg.notify_handler)(Notification::PlainVerboseMessage( "Use of (currently) unmaintained command line interface.", )); (cfg.notify_handler)(Notification::PlainVerboseMessage( "The exact API of this command may change without warning", )); (cfg.notify_handler)(Notification::PlainVerboseMessage( "Eventually this command will be a true alias. Until then:", )); (cfg.notify_handler)(Notification::PlainVerboseMessage(&format!( " Please use `rustup {instead}` instead" ))); callee(cfg, matches) } pub fn main() -> Result { self_update::cleanup_self_updater()?; use clap::ErrorKind::*; let matches = match cli().try_get_matches_from(process().args_os()) { Ok(matches) => Ok(matches), Err(err) if err.kind() == DisplayHelp => { write!(process().stdout().lock(), "{err}")?; return Ok(utils::ExitCode(0)); } Err(err) if err.kind() == DisplayVersion => { write!(process().stdout().lock(), "{err}")?; info!("This is the version for the rustup toolchain manager, not the rustc compiler."); fn rustc_version() -> std::result::Result> { let cfg = &mut common::set_globals(false, true)?; let cwd = std::env::current_dir()?; if let Some(t) = process().args().find(|x| x.starts_with('+')) { debug!("Fetching rustc version from toolchain `{}`", t); cfg.set_toolchain_override(&t[1..]); } let toolchain = cfg.find_or_install_override_toolchain_or_default(&cwd)?.0; Ok(toolchain.rustc_version()) } match rustc_version() { Ok(version) => info!("The currently active `rustc` version is `{}`", version), Err(err) => debug!("Wanted to tell you the current rustc version, too, but ran into this error: {}", err), } return Ok(utils::ExitCode(0)); } Err(err) => { if [ InvalidSubcommand, UnknownArgument, DisplayHelpOnMissingArgumentOrSubcommand, ] .contains(&err.kind()) { write!(process().stdout().lock(), "{err}")?; return Ok(utils::ExitCode(1)); } if err.kind() == ValueValidation && err.to_string().contains(TOOLCHAIN_OVERRIDE_ERROR) { write!(process().stderr().lock(), "{err}")?; return Ok(utils::ExitCode(1)); } Err(err) } }?; let verbose = matches.get_flag("verbose"); let quiet = matches.get_flag("quiet"); let cfg = &mut common::set_globals(verbose, quiet)?; if let Some(t) = matches.get_one::("+toolchain") { cfg.set_toolchain_override(&t[1..]); } if maybe_upgrade_data(cfg, &matches)? { return Ok(utils::ExitCode(0)); } cfg.check_metadata_version()?; Ok(match matches.subcommand() { Some(s) => match s { ("dump-testament", _) => common::dump_testament()?, ("show", c) => match c.subcommand() { Some(s) => match s { ("active-toolchain", m) => handle_epipe(show_active_toolchain(cfg, m))?, ("home", _) => handle_epipe(show_rustup_home(cfg))?, ("profile", _) => handle_epipe(show_profile(cfg))?, _ => handle_epipe(show(cfg, c))?, }, None => handle_epipe(show(cfg, c))?, }, ("install", m) => deprecated("toolchain install", cfg, m, update)?, ("update", m) => update(cfg, m)?, ("check", _) => check_updates(cfg)?, ("uninstall", m) => deprecated("toolchain uninstall", cfg, m, toolchain_remove)?, ("default", m) => default_(cfg, m)?, ("toolchain", c) => match c.subcommand() { Some(s) => match s { ("install", m) => update(cfg, m)?, ("list", m) => handle_epipe(toolchain_list(cfg, m))?, ("link", m) => toolchain_link(cfg, m)?, ("uninstall", m) => toolchain_remove(cfg, m)?, _ => unreachable!(), }, None => unreachable!(), }, ("target", c) => match c.subcommand() { Some(s) => match s { ("list", m) => handle_epipe(target_list(cfg, m))?, ("add", m) => target_add(cfg, m)?, ("remove", m) => target_remove(cfg, m)?, _ => unreachable!(), }, None => unreachable!(), }, ("component", c) => match c.subcommand() { Some(s) => match s { ("list", m) => handle_epipe(component_list(cfg, m))?, ("add", m) => component_add(cfg, m)?, ("remove", m) => component_remove(cfg, m)?, _ => unreachable!(), }, None => unreachable!(), }, ("override", c) => match c.subcommand() { Some(s) => match s { ("list", _) => handle_epipe(common::list_overrides(cfg))?, ("set", m) => override_add(cfg, m)?, ("unset", m) => override_remove(cfg, m)?, _ => unreachable!(), }, None => unreachable!(), }, ("run", m) => run(cfg, m)?, ("which", m) => which(cfg, m)?, ("doc", m) => doc(cfg, m)?, ("man", m) => man(cfg, m)?, ("self", c) => match c.subcommand() { Some(s) => match s { ("update", _) => self_update::update(cfg)?, ("uninstall", m) => self_uninstall(m)?, _ => unreachable!(), }, None => unreachable!(), }, ("set", c) => match c.subcommand() { Some(s) => match s { ("default-host", m) => set_default_host_triple(cfg, m)?, ("profile", m) => set_profile(cfg, m)?, ("auto-self-update", m) => set_auto_self_update(cfg, m)?, _ => unreachable!(), }, None => unreachable!(), }, ("completions", c) => { if let Some(&shell) = c.get_one::("shell") { output_completion_script( shell, c.get_one::("command") .copied() .unwrap_or(CompletionCommand::Rustup), )? } else { unreachable!() } } _ => unreachable!(), }, None => unreachable!(), }) } pub(crate) fn cli() -> Command<'static> { let mut app = Command::new("rustup") .version(common::version()) .about("The Rust toolchain installer") .after_help(RUSTUP_HELP) .global_setting(AppSettings::DeriveDisplayOrder) .setting(AppSettings::SubcommandRequiredElseHelp) .arg( verbose_arg("Enable verbose output"), ) .arg( Arg::new("quiet") .conflicts_with("verbose") .help("Disable progress output") .short('q') .long("quiet") .action(ArgAction::SetTrue), ) .arg( Arg::new("+toolchain") .help("release channel (e.g. +stable) or custom toolchain to set override") .value_parser(|s: &str| { if s.starts_with('+') { Ok(s.to_owned()) } else { Err(format!( "\"{s}\" is not a valid subcommand, so it was interpreted as a toolchain name, but it is also invalid. {TOOLCHAIN_OVERRIDE_ERROR}" )) } }), ) .subcommand( Command::new("dump-testament") .about("Dump information about the build") .hide(true), // Not for users, only CI ) .subcommand( Command::new("show") .about("Show the active and installed toolchains or profiles") .after_help(SHOW_HELP) .arg( verbose_arg("Enable verbose output with rustc information for all installed toolchains"), ) .subcommand( Command::new("active-toolchain") .about("Show the active toolchain") .after_help(SHOW_ACTIVE_TOOLCHAIN_HELP) .arg( verbose_arg("Enable verbose output with rustc information"), ), ) .subcommand( Command::new("home") .about("Display the computed value of RUSTUP_HOME"), ) .subcommand(Command::new("profile").about("Show the current profile")) ) .subcommand( Command::new("install") .about("Update Rust toolchains") .after_help(INSTALL_HELP) .hide(true) // synonym for 'toolchain install' .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) .takes_value(true) .multiple_values(true) ) .arg( Arg::new("profile") .long("profile") .value_parser(PossibleValuesParser::new(Profile::names())) .takes_value(true), ) .arg( Arg::new("no-self-update") .help("Don't perform self-update when running the `rustup install` command") .long("no-self-update") .action(ArgAction::SetTrue) ) .arg( Arg::new("force") .help("Force an update, even if some components are missing") .long("force") .action(ArgAction::SetTrue) ).arg( Arg::new("force-non-host") .help("Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains") .long("force-non-host") .action(ArgAction::SetTrue) ), ) .subcommand( Command::new("uninstall") .about("Uninstall Rust toolchains") .hide(true) // synonym for 'toolchain uninstall' .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) .takes_value(true) .multiple_values(true), ), ) .subcommand( Command::new("update") .about("Update Rust toolchains and rustup") .aliases(&["upgrade", "up"]) .after_help(UPDATE_HELP) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(false) .takes_value(true) .multiple_values(true), ) .arg( Arg::new("no-self-update") .help("Don't perform self update when running the `rustup update` command") .long("no-self-update") .action(ArgAction::SetTrue) ) .arg( Arg::new("force") .help("Force an update, even if some components are missing") .long("force") .action(ArgAction::SetTrue) ) .arg( Arg::new("force-non-host") .help("Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains") .long("force-non-host") .action(ArgAction::SetTrue) ), ) .subcommand(Command::new("check").about("Check for updates to Rust toolchains and rustup")) .subcommand( Command::new("default") .about("Set the default toolchain") .after_help(DEFAULT_HELP) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(false) ), ) .subcommand( Command::new("toolchain") .about("Modify or query the installed toolchains") .after_help(TOOLCHAIN_HELP) .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( Command::new("list") .about("List installed toolchains") .arg( verbose_arg("Enable verbose output with toolchain information"), ), ) .subcommand( Command::new("install") .about("Install or update a given toolchain") .aliases(&["update", "add"]) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) .takes_value(true) .multiple_values(true), ) .arg( Arg::new("profile") .long("profile") .value_parser(PossibleValuesParser::new(Profile::names())) .takes_value(true), ) .arg( Arg::new("components") .help("Add specific components on installation") .long("component") .short('c') .takes_value(true) .multiple_values(true) .use_value_delimiter(true) .action(ArgAction::Append), ) .arg( Arg::new("targets") .help("Add specific targets on installation") .long("target") .short('t') .takes_value(true) .multiple_values(true) .use_value_delimiter(true) .action(ArgAction::Append), ) .arg( Arg::new("no-self-update") .help( "Don't perform self update when running the\ `rustup toolchain install` command", ) .long("no-self-update") .takes_value(true) .action(ArgAction::SetTrue) ) .arg( Arg::new("force") .help("Force an update, even if some components are missing") .long("force") .action(ArgAction::SetTrue) ) .arg( Arg::new("allow-downgrade") .help("Allow rustup to downgrade the toolchain to satisfy your component choice") .long("allow-downgrade") .action(ArgAction::SetTrue) ) .arg( Arg::new("force-non-host") .help("Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains") .long("force-non-host") .action(ArgAction::SetTrue) ), ) .subcommand( Command::new("uninstall") .about("Uninstall a toolchain") .alias("remove") .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) .takes_value(true) .multiple_values(true), ), ) .subcommand( Command::new("link") .about("Create a custom toolchain by symlinking to a directory") .after_help(TOOLCHAIN_LINK_HELP) .arg( Arg::new("toolchain") .help("Custom toolchain name") .required(true), ) .arg( Arg::new("path") .help("Path to the directory") .required(true), ), ), ) .subcommand( Command::new("target") .about("Modify a toolchain's supported targets") .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( Command::new("list") .about("List installed and available targets") .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ) .arg( Arg::new("installed") .long("installed") .help("List only installed targets") .action(ArgAction::SetTrue) ), ) .subcommand( Command::new("add") .about("Add a target to a Rust toolchain") .alias("install") .arg( Arg::new("target") .required(true) .takes_value(true) .multiple_values(true) .help( "List of targets to install; \ \"all\" installs all available targets" ) ) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ), ) .subcommand( Command::new("remove") .about("Remove a target from a Rust toolchain") .alias("uninstall") .arg( Arg::new("target") .help("List of targets to uninstall") .required(true) .takes_value(true) .multiple_values(true) ) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ), ), ) .subcommand( Command::new("component") .about("Modify a toolchain's installed components") .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( Command::new("list") .about("List installed and available components") .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ) .arg( Arg::new("installed") .long("installed") .help("List only installed components") .action(ArgAction::SetTrue) ), ) .subcommand( Command::new("add") .about("Add a component to a Rust toolchain") .arg(Arg::new("component").required(true) .takes_value(true).multiple_values(true)) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ) .arg( Arg::new("target") .long("target") .takes_value(true) ), ) .subcommand( Command::new("remove") .about("Remove a component from a Rust toolchain") .arg(Arg::new("component").required(true) .takes_value(true).multiple_values(true)) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ) .arg( Arg::new("target") .long("target") .takes_value(true) ), ), ) .subcommand( Command::new("override") .about("Modify directory toolchain overrides") .after_help(OVERRIDE_HELP) .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( Command::new("list").about("List directory toolchain overrides"), ) .subcommand( Command::new("set") .about("Set the override toolchain for a directory") .alias("add") .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) .takes_value(true), ) .arg( Arg::new("path") .long("path") .takes_value(true) .help("Path to the directory"), ), ) .subcommand( Command::new("unset") .about("Remove the override toolchain for a directory") .after_help(OVERRIDE_UNSET_HELP) .alias("remove") .arg( Arg::new("path") .long("path") .takes_value(true) .help("Path to the directory"), ) .arg( Arg::new("nonexistent") .long("nonexistent") .help("Remove override toolchain for all nonexistent directories") .action(ArgAction::SetTrue), ), ), ) .subcommand( Command::new("run") .about("Run a command with an environment configured for a given toolchain") .after_help(RUN_HELP) .trailing_var_arg(true) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) .takes_value(true), ) .arg( Arg::new("command") .required(true) .takes_value(true) .multiple_values(true) .use_value_delimiter(false), ) .arg( Arg::new("install") .help("Install the requested toolchain if needed") .long("install") .action(ArgAction::SetTrue), ), ) .subcommand( Command::new("which") .about("Display which binary will be run for a given command") .arg(Arg::new("command").required(true)) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ), ) .subcommand( Command::new("doc") .alias("docs") .about("Open the documentation for the current toolchain") .after_help(DOC_HELP) .arg( Arg::new("path") .long("path") .help("Only print the path to the documentation") .action(ArgAction::SetTrue), ) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ) .arg(Arg::new("topic").help(TOPIC_ARG_HELP)) .group( ArgGroup::new("page").args( &DOCS_DATA .iter() .map(|(name, _, _)| *name) .collect::>(), ), ) .args( &DOCS_DATA .iter() .map(|&(name, help_msg, _)| Arg::new(name).long(name).help(help_msg).action(ArgAction::SetTrue)) .collect::>(), ), ); if cfg!(not(target_os = "windows")) { app = app.subcommand( Command::new("man") .about("View the man page for a given command") .arg(Arg::new("command").required(true)) .arg( Arg::new("toolchain") .help(TOOLCHAIN_ARG_HELP) .long("toolchain") .takes_value(true), ), ); } app = app .subcommand( Command::new("self") .about("Modify the rustup installation") .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand(Command::new("update").about("Download and install updates to rustup")) .subcommand( Command::new("uninstall") .about("Uninstall rustup.") .arg(Arg::new("no-prompt").short('y').action(ArgAction::SetTrue)), ) .subcommand( Command::new("upgrade-data").about("Upgrade the internal data format."), ), ) .subcommand( Command::new("set") .about("Alter rustup settings") .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( Command::new("default-host") .about("The triple used to identify toolchains when not specified") .arg(Arg::new("host_triple").required(true)), ) .subcommand( Command::new("profile") .about("The default components installed") .arg( Arg::new("profile-name") .required(true) .value_parser(PossibleValuesParser::new(Profile::names())) .default_value(Profile::default_name()), ), ) .subcommand( Command::new("auto-self-update") .about("The rustup auto self update mode") .arg( Arg::new("auto-self-update-mode") .required(true) .value_parser(PossibleValuesParser::new(SelfUpdateMode::modes())) .default_value(SelfUpdateMode::default_mode()), ), ), ); app.subcommand( Command::new("completions") .about("Generate tab-completion scripts for your shell") .after_help(COMPLETIONS_HELP) .arg_required_else_help(true) .arg(Arg::new("shell").value_parser(EnumValueParser::::new())) .arg( Arg::new("command") .value_parser(EnumValueParser::::new()) .default_missing_value("rustup"), ), ) } fn verbose_arg(help: &str) -> Arg<'_> { Arg::new("verbose") .help(help) .short('v') .long("verbose") .action(ArgAction::SetTrue) } fn maybe_upgrade_data(cfg: &Cfg, m: &ArgMatches) -> Result { match m.subcommand() { Some(("self", c)) => match c.subcommand() { Some(("upgrade-data", _)) => { cfg.upgrade_data()?; Ok(true) } _ => Ok(false), }, _ => Ok(false), } } fn update_bare_triple_check(cfg: &Cfg, name: &str) -> Result<()> { if let Some(triple) = PartialTargetTriple::new(name) { warn!("(partial) target triple specified instead of toolchain name"); let installed_toolchains = cfg.list_toolchains()?; let default = cfg.find_default()?; let default_name = default.map(|t| t.name().to_string()).unwrap_or_default(); let mut candidates = vec![]; for t in installed_toolchains { if t == default_name { continue; } if let Ok(desc) = PartialToolchainDesc::from_str(&t) { fn triple_comp_eq(given: &str, from_desc: Option<&String>) -> bool { from_desc.map_or(false, |s| *s == *given) } let triple_matches = triple .arch .as_ref() .map_or(true, |s| triple_comp_eq(s, desc.target.arch.as_ref())) && triple .os .as_ref() .map_or(true, |s| triple_comp_eq(s, desc.target.os.as_ref())) && triple .env .as_ref() .map_or(true, |s| triple_comp_eq(s, desc.target.env.as_ref())); if triple_matches { candidates.push(t); } } } match candidates.len() { 0 => err!("no candidate toolchains found"), 1 => writeln!( process().stdout(), "\nyou may use the following toolchain: {}\n", candidates[0] )?, _ => { writeln!( process().stdout(), "\nyou may use one of the following toolchains:" )?; for n in &candidates { writeln!(process().stdout(), "{n}")?; } writeln!(process().stdout(),)?; } } bail!(RustupError::ToolchainNotInstalled(name.to_string())); } Ok(()) } fn default_bare_triple_check(cfg: &Cfg, name: &str) -> Result<()> { if let Some(triple) = PartialTargetTriple::new(name) { warn!("(partial) target triple specified instead of toolchain name"); let default = cfg.find_default()?; let default_name = default.map(|t| t.name().to_string()).unwrap_or_default(); if let Ok(mut desc) = PartialToolchainDesc::from_str(&default_name) { desc.target = triple; let maybe_toolchain = format!("{desc}"); let toolchain = cfg.get_toolchain(maybe_toolchain.as_ref(), false)?; if toolchain.name() == default_name { warn!( "(partial) triple '{}' resolves to a toolchain that is already default", name ); } else { writeln!( process().stdout(), "\nyou may use the following toolchain: {}\n", toolchain.name() )?; } return Err(RustupError::ToolchainNotInstalled(name.to_string()).into()); } } Ok(()) } fn default_(cfg: &Cfg, m: &ArgMatches) -> Result { if let Some(toolchain) = m.get_one::("toolchain") { default_bare_triple_check(cfg, toolchain)?; let toolchain = cfg.get_toolchain(toolchain, false)?; let status = if !toolchain.is_custom() { let distributable = DistributableToolchain::new(&toolchain)?; Some(distributable.install_from_dist_if_not_installed()?) } else if !toolchain.exists() && toolchain.name() != "none" { return Err(RustupError::ToolchainNotInstalled(toolchain.name().to_string()).into()); } else { None }; toolchain.make_default()?; if let Some(status) = status { writeln!(process().stdout())?; common::show_channel_update(cfg, toolchain.name(), Ok(status))?; } let cwd = utils::current_dir()?; if let Some((toolchain, reason)) = cfg.find_override(&cwd)? { info!( "note that the toolchain '{}' is currently in use ({})", toolchain.name(), reason ); } } else { let default_toolchain: Result = cfg .get_default()? .ok_or_else(|| anyhow!("no default toolchain configured")); writeln!(process().stdout(), "{} (default)", default_toolchain?)?; } Ok(utils::ExitCode(0)) } fn check_updates(cfg: &Cfg) -> Result { let mut t = term2::stdout(); let channels = cfg.list_channels()?; for channel in channels { match channel { (ref name, Ok(ref toolchain)) => { let distributable = DistributableToolchain::new(toolchain)?; let current_version = distributable.show_version()?; let dist_version = distributable.show_dist_version()?; let _ = t.attr(term2::Attr::Bold); write!(t, "{name} - ")?; match (current_version, dist_version) { (None, None) => { let _ = t.fg(term2::color::RED); writeln!(t, "Cannot identify installed or update versions")?; } (Some(cv), None) => { let _ = t.fg(term2::color::GREEN); write!(t, "Up to date")?; let _ = t.reset(); writeln!(t, " : {cv}")?; } (Some(cv), Some(dv)) => { let _ = t.fg(term2::color::YELLOW); write!(t, "Update available")?; let _ = t.reset(); writeln!(t, " : {cv} -> {dv}")?; } (None, Some(dv)) => { let _ = t.fg(term2::color::YELLOW); write!(t, "Update available")?; let _ = t.reset(); writeln!(t, " : (Unknown version) -> {dv}")?; } } } (_, Err(err)) => return Err(err), } } check_rustup_update()?; Ok(utils::ExitCode(0)) } fn update(cfg: &mut Cfg, m: &ArgMatches) -> Result { let self_update_mode = cfg.get_self_update_mode()?; // Priority: no-self-update feature > self_update_mode > no-self-update args. // Update only if rustup does **not** have the no-self-update feature, // and auto-self-update is configured to **enable** // and has **no** no-self-update parameter. let self_update = !self_update::NEVER_SELF_UPDATE && self_update_mode == SelfUpdateMode::Enable && !m.get_flag("no-self-update"); let forced = m.get_flag("force-non-host"); if let Ok(Some(p)) = m.try_get_one::("profile") { let p = Profile::from_str(p)?; cfg.set_profile_override(p); } let cfg = &cfg; if cfg.get_profile()? == Profile::Complete { warn!("{}", common::WARN_COMPLETE_PROFILE); } if let Ok(Some(names)) = m.try_get_many::("toolchain") { for name in names { update_bare_triple_check(cfg, name)?; let toolchain_has_triple = match PartialToolchainDesc::from_str(name) { Ok(x) => x.has_triple(), _ => false, }; if toolchain_has_triple { let host_arch = TargetTriple::from_host_or_build(); if let Ok(partial_toolchain_desc) = PartialToolchainDesc::from_str(name) { let target_triple = partial_toolchain_desc.resolve(&host_arch)?.target; if !forced && !host_arch.can_run(&target_triple)? { err!("DEPRECATED: future versions of rustup will require --force-non-host to install a non-host toolchain as the default."); warn!( "toolchain '{}' may not be able to run on this system.", name ); warn!( "If you meant to build software to target that platform, perhaps try `rustup target add {}` instead?", target_triple.to_string() ); } } } let toolchain = cfg.get_toolchain(name, false)?; let status = if !toolchain.is_custom() { let components: Vec<_> = m .try_get_many::("components") .ok() .flatten() .map_or_else(Vec::new, |v| v.map(|s| &**s).collect()); let targets: Vec<_> = m .try_get_many::("targets") .ok() .flatten() .map_or_else(Vec::new, |v| v.map(|s| &**s).collect()); let distributable = DistributableToolchain::new(&toolchain)?; Some(distributable.install_from_dist( m.get_flag("force"), matches!(m.try_get_one::("allow-downgrade"), Ok(Some(true))), &components, &targets, None, )?) } else if !toolchain.exists() { bail!(RustupError::InvalidToolchainName( toolchain.name().to_string() )); } else { None }; if let Some(status) = status.clone() { writeln!(process().stdout())?; common::show_channel_update(cfg, toolchain.name(), Ok(status))?; } if cfg.get_default()?.is_none() { use crate::UpdateStatus; if let Some(UpdateStatus::Installed) = status { toolchain.make_default()?; } } } if self_update { common::self_update(|| Ok(utils::ExitCode(0)))?; } } else { common::update_all_channels(cfg, self_update, m.get_flag("force"))?; info!("cleaning up downloads & tmp directories"); utils::delete_dir_contents(&cfg.download_dir); cfg.temp_cfg.clean(); } if !self_update::NEVER_SELF_UPDATE && self_update_mode == SelfUpdateMode::CheckOnly { check_rustup_update()?; } if self_update::NEVER_SELF_UPDATE { info!("self-update is disabled for this build of rustup"); info!("any updates to rustup will need to be fetched with your system package manager") } Ok(utils::ExitCode(0)) } fn run(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = m.get_one::("toolchain").unwrap(); let args = m.get_many::("command").unwrap(); let args: Vec<_> = args.collect(); let cmd = cfg.create_command_for_toolchain(toolchain, m.get_flag("install"), args[0])?; let code = command::run_command_for_dir(cmd, args[0], &args[1..])?; Ok(code) } fn which(cfg: &Cfg, m: &ArgMatches) -> Result { let binary = m.get_one::("command").unwrap(); let binary_path = if let Some(toolchain) = m.get_one::("toolchain") { cfg.which_binary_by_toolchain(toolchain, binary)? } else { cfg.which_binary(&utils::current_dir()?, binary)? }; utils::assert_is_file(&binary_path)?; writeln!(process().stdout(), "{}", binary_path.display())?; Ok(utils::ExitCode(0)) } fn show(cfg: &Cfg, m: &ArgMatches) -> Result { let verbose = m.get_flag("verbose"); // Print host triple { let mut t = term2::stdout(); t.attr(term2::Attr::Bold)?; write!(t, "Default host: ")?; t.reset()?; writeln!(t, "{}", cfg.get_default_host_triple()?)?; } // Print rustup home directory { let mut t = term2::stdout(); t.attr(term2::Attr::Bold)?; write!(t, "rustup home: ")?; t.reset()?; writeln!(t, "{}", cfg.rustup_dir.display())?; writeln!(t)?; } let cwd = utils::current_dir()?; let installed_toolchains = cfg.list_toolchains()?; // XXX: we may want a find_without_install capability for show. let active_toolchain = cfg.find_or_install_override_toolchain_or_default(&cwd); // active_toolchain will carry the reason we don't have one in its detail. let active_targets = if let Ok(ref at) = active_toolchain { if let Ok(distributable) = DistributableToolchain::new(&at.0) { match distributable.list_components() { Ok(cs_vec) => cs_vec .into_iter() .filter(|c| c.component.short_name_in_manifest() == "rust-std") .filter(|c| c.installed) .collect(), Err(_) => vec![], } } else { // These three vec![] could perhaps be reduced with and_then on active_toolchain. vec![] } } else { vec![] }; let show_installed_toolchains = installed_toolchains.len() > 1; let show_active_targets = active_targets.len() > 1; let show_active_toolchain = true; // Only need to display headers if we have multiple sections let show_headers = [ show_installed_toolchains, show_active_targets, show_active_toolchain, ] .iter() .filter(|x| **x) .count() > 1; if show_installed_toolchains { let mut t = term2::stdout(); if show_headers { print_header::(&mut t, "installed toolchains")?; } let default_name: Result = cfg .get_default()? .ok_or_else(|| anyhow!("no default toolchain configured")); let default_name = default_name?; for it in installed_toolchains { if default_name == it { writeln!(t, "{it} (default)")?; } else { writeln!(t, "{it}")?; } if verbose { if let Ok(toolchain) = cfg.get_toolchain(&it, false) { writeln!(process().stdout(), "{}", toolchain.rustc_version())?; } // To make it easy to see what rustc that belongs to what // toolchain we separate each pair with an extra newline writeln!(process().stdout())?; } } if show_headers { writeln!(t)? }; } if show_active_targets { let mut t = term2::stdout(); if show_headers { print_header::(&mut t, "installed targets for active toolchain")?; } for at in active_targets { writeln!( t, "{}", at.component .target .as_ref() .expect("rust-std should have a target") )?; } if show_headers { writeln!(t)?; }; } if show_active_toolchain { let mut t = term2::stdout(); if show_headers { print_header::(&mut t, "active toolchain")?; } match active_toolchain { Ok(atc) => match atc { (ref toolchain, Some(ref reason)) => { writeln!(t, "{} ({})", toolchain.name(), reason)?; writeln!(t, "{}", toolchain.rustc_version())?; } (ref toolchain, None) => { writeln!(t, "{} (default)", toolchain.name())?; writeln!(t, "{}", toolchain.rustc_version())?; } }, Err(err) => { let root_cause = err.root_cause(); if let Some(RustupError::ToolchainNotSelected) = root_cause.downcast_ref::() { writeln!(t, "no active toolchain")?; } else if let Some(cause) = err.source() { writeln!(t, "(error: {err}, {cause})")?; } else { writeln!(t, "(error: {err})")?; } } } if show_headers { writeln!(t)? } } fn print_header(t: &mut term2::StdoutTerminal, s: &str) -> std::result::Result<(), E> where E: From + From, { t.attr(term2::Attr::Bold)?; writeln!(t, "{s}")?; writeln!(t, "{}", "-".repeat(s.len()))?; writeln!(t)?; t.reset()?; Ok(()) } Ok(utils::ExitCode(0)) } fn show_active_toolchain(cfg: &Cfg, m: &ArgMatches) -> Result { let verbose = m.get_flag("verbose"); let cwd = utils::current_dir()?; match cfg.find_or_install_override_toolchain_or_default(&cwd) { Err(e) => { let root_cause = e.root_cause(); if let Some(RustupError::ToolchainNotSelected) = root_cause.downcast_ref::() { } else { return Err(e); } } Ok((toolchain, reason)) => { if let Some(reason) = reason { writeln!(process().stdout(), "{} ({})", toolchain.name(), reason)?; } else { writeln!(process().stdout(), "{} (default)", toolchain.name())?; } if verbose { writeln!(process().stdout(), "{}", toolchain.rustc_version())?; } } } Ok(utils::ExitCode(0)) } fn show_rustup_home(cfg: &Cfg) -> Result { writeln!(process().stdout(), "{}", cfg.rustup_dir.display())?; Ok(utils::ExitCode(0)) } fn target_list(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; if m.get_flag("installed") { common::list_installed_targets(&toolchain) } else { common::list_targets(&toolchain) } } fn target_add(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; // XXX: long term move this error to cli ? the normal .into doesn't work // because Result here is the wrong sort and expression type ascription // isn't a feature yet. // list_components *and* add_component would both be inappropriate for // custom toolchains. let distributable = DistributableToolchain::new_for_components(&toolchain)?; let mut targets: Vec<_> = m .get_many::("target") .unwrap() .map(ToOwned::to_owned) .collect(); if targets.contains(&"all".to_string()) { if targets.len() != 1 { return Err(anyhow!( "`rustup target add {}` includes `all`", targets.join(" ") )); } targets.clear(); for component in distributable.list_components()? { if component.component.short_name_in_manifest() == "rust-std" && component.available && !component.installed { let target = component .component .target .as_ref() .expect("rust-std should have a target"); targets.push(target.to_string()); } } } for target in targets { let new_component = Component::new( "rust-std".to_string(), Some(TargetTriple::new(&target)), false, ); distributable.add_component(new_component)?; } Ok(utils::ExitCode(0)) } fn target_remove(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; for target in m.get_many::("target").unwrap() { let new_component = Component::new( "rust-std".to_string(), Some(TargetTriple::new(target)), false, ); let distributable = DistributableToolchain::new_for_components(&toolchain)?; distributable.remove_component(new_component)?; } Ok(utils::ExitCode(0)) } fn component_list(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; if m.get_flag("installed") { common::list_installed_components(&toolchain) } else { common::list_components(&toolchain)?; Ok(utils::ExitCode(0)) } } fn component_add(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; let distributable = DistributableToolchain::new(&toolchain)?; let target = m .get_one::("target") .map(|s| &**s) .map(TargetTriple::new) .or_else(|| { distributable .desc() .as_ref() .ok() .map(|desc| desc.target.clone()) }); for component in m.get_many::("component").unwrap() { let new_component = Component::new_with_target(component, false) .unwrap_or_else(|| Component::new(component.to_string(), target.clone(), true)); distributable.add_component(new_component)?; } Ok(utils::ExitCode(0)) } fn component_remove(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; let distributable = DistributableToolchain::new_for_components(&toolchain)?; let target = m .get_one::("target") .map(|s| &**s) .map(TargetTriple::new) .or_else(|| { distributable .desc() .as_ref() .ok() .map(|desc| desc.target.clone()) }); for component in m.get_many::("component").unwrap() { let new_component = Component::new_with_target(component, false) .unwrap_or_else(|| Component::new(component.to_string(), target.clone(), true)); distributable.remove_component(new_component)?; } Ok(utils::ExitCode(0)) } fn explicit_or_dir_toolchain<'a>(cfg: &'a Cfg, m: &ArgMatches) -> Result> { let toolchain = m.get_one::("toolchain"); if let Some(toolchain) = toolchain { let toolchain = cfg.get_toolchain(toolchain, false)?; return Ok(toolchain); } let cwd = utils::current_dir()?; let (toolchain, _) = cfg.toolchain_for_dir(&cwd)?; Ok(toolchain) } fn toolchain_list(cfg: &Cfg, m: &ArgMatches) -> Result { common::list_toolchains(cfg, m.get_flag("verbose")) } fn toolchain_link(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = m.get_one::("toolchain").unwrap(); let path = m.get_one::("path").unwrap(); let toolchain = cfg.get_toolchain(toolchain, true)?; if let Ok(custom) = CustomToolchain::new(&toolchain) { custom.install_from_dir(Path::new(path), true)?; Ok(utils::ExitCode(0)) } else { Err(anyhow!( "invalid custom toolchain name: '{}'", toolchain.name().to_string() )) } } fn toolchain_remove(cfg: &mut Cfg, m: &ArgMatches) -> Result { for toolchain in m.get_many::("toolchain").unwrap() { let toolchain = cfg.get_toolchain(toolchain, false)?; toolchain.remove()?; } Ok(utils::ExitCode(0)) } fn override_add(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = m.get_one::("toolchain").unwrap(); let toolchain = cfg.get_toolchain(toolchain, false)?; let status = if !toolchain.is_custom() { let distributable = DistributableToolchain::new(&toolchain)?; Some(distributable.install_from_dist_if_not_installed()?) } else if !toolchain.exists() { return Err(RustupError::ToolchainNotInstalled(toolchain.name().to_string()).into()); } else { None }; let path = if let Some(path) = m.get_one::("path") { PathBuf::from(path) } else { utils::current_dir()? }; toolchain.make_override(&path)?; if let Some(status) = status { writeln!(process().stdout(),)?; common::show_channel_update(cfg, toolchain.name(), Ok(status))?; } Ok(utils::ExitCode(0)) } fn override_remove(cfg: &Cfg, m: &ArgMatches) -> Result { let paths = if m.get_flag("nonexistent") { let list: Vec<_> = cfg.settings_file.with(|s| { Ok(s.overrides .iter() .filter_map(|(k, _)| { if Path::new(k).is_dir() { None } else { Some(k.clone()) } }) .collect()) })?; if list.is_empty() { info!("no nonexistent paths detected"); } list } else if let Some(path) = m.get_one::("path") { vec![path.to_owned()] } else { vec![utils::current_dir()?.to_str().unwrap().to_string()] }; for path in paths { if cfg .settings_file .with_mut(|s| Ok(s.remove_override(Path::new(&path), cfg.notify_handler.as_ref())))? { info!("override toolchain for '{}' removed", path); } else { info!("no override toolchain for '{}'", path); if m.get_one::("path").is_none() && !m.get_flag("nonexistent") { info!( "you may use `--path ` option to remove override toolchain \ for a specific path" ); } } } Ok(utils::ExitCode(0)) } const DOCS_DATA: &[(&str, &str, &str)] = &[ // 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"), ("core", "The Rust Core Library", "core/index.html"), ("edition-guide", "The Rust Edition Guide", "edition-guide/index.html"), ("nomicon", "The Dark Arts of Advanced and Unsafe Rust Programming", "nomicon/index.html"), ("proc_macro", "A support library for macro authors when defining new macros", "proc_macro/index.html"), ("reference", "The Rust Reference", "reference/index.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"), ("rustdoc", "Documentation generator for Rust projects", "rustdoc/index.html"), ("std", "Standard library API documentation", "std/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"), ("embedded-book", "The Embedded Rust Book", "embedded-book/index.html"), ]; fn doc(cfg: &Cfg, m: &ArgMatches) -> Result { let toolchain = explicit_or_dir_toolchain(cfg, m)?; if let Ok(distributable) = DistributableToolchain::new(&toolchain) { let components = distributable.list_components()?; if let [_] = components .into_iter() .filter(|cstatus| { cstatus.component.short_name_in_manifest() == "rust-docs" && !cstatus.installed }) .take(1) .collect::>() .as_slice() { info!( "`rust-docs` not installed in toolchain `{}`", toolchain.name() ); info!( "To install, try `rustup component add --toolchain {} rust-docs`", toolchain.name() ); return Err(anyhow!( "unable to view documentation which is not installed" )); } } let topical_path: PathBuf; let doc_url = if let Some(topic) = m.get_one::("topic") { topical_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?; topical_path.to_str().unwrap() } else if let Some((_, _, path)) = DOCS_DATA.iter().find(|(name, _, _)| m.get_flag(name)) { path } else { "index.html" }; if m.get_flag("path") { let doc_path = toolchain.doc_path(doc_url)?; writeln!(process().stdout(), "{}", doc_path.display())?; Ok(utils::ExitCode(0)) } else { toolchain.open_docs(doc_url)?; Ok(utils::ExitCode(0)) } } fn man(cfg: &Cfg, m: &ArgMatches) -> Result { let command = m.get_one::("command").unwrap(); let toolchain = explicit_or_dir_toolchain(cfg, m)?; let mut toolchain = toolchain.path().to_path_buf(); toolchain.push("share"); toolchain.push("man"); utils::assert_is_directory(&toolchain)?; let mut manpaths = std::ffi::OsString::from(toolchain); manpaths.push(":"); // prepend to the default MANPATH list if let Some(path) = process().var_os("MANPATH") { manpaths.push(path); } process::Command::new("man") .env("MANPATH", manpaths) .arg(command) .status() .expect("failed to open man page"); Ok(utils::ExitCode(0)) } fn self_uninstall(m: &ArgMatches) -> Result { let no_prompt = m.get_flag("no-prompt"); self_update::uninstall(no_prompt) } fn set_default_host_triple(cfg: &Cfg, m: &ArgMatches) -> Result { cfg.set_default_host_triple(m.get_one::("host_triple").unwrap())?; Ok(utils::ExitCode(0)) } fn set_profile(cfg: &mut Cfg, m: &ArgMatches) -> Result { cfg.set_profile(m.get_one::("profile-name").unwrap())?; Ok(utils::ExitCode(0)) } fn set_auto_self_update(cfg: &mut Cfg, m: &ArgMatches) -> Result { if self_update::NEVER_SELF_UPDATE { let mut args = crate::process().args_os(); let arg0 = args.next().map(PathBuf::from); let arg0 = arg0 .as_ref() .and_then(|a| a.to_str()) .ok_or(CLIError::NoExeName)?; warn!("{} is built with the no-self-update feature: setting auto-self-update will not have any effect.",arg0); } cfg.set_auto_self_update(m.get_one::("auto-self-update-mode").unwrap())?; Ok(utils::ExitCode(0)) } fn show_profile(cfg: &Cfg) -> Result { writeln!(process().stdout(), "{}", cfg.get_profile()?)?; Ok(utils::ExitCode(0)) } #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum CompletionCommand { Rustup, Cargo, } impl clap::ValueEnum for CompletionCommand { fn value_variants<'a>() -> &'a [Self] { &[Self::Rustup, Self::Cargo] } fn to_possible_value<'a>(&self) -> Option> { Some(match self { CompletionCommand::Rustup => PossibleValue::new("rustup"), CompletionCommand::Cargo => PossibleValue::new("cargo"), }) } } impl fmt::Display for CompletionCommand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.to_possible_value() { Some(v) => write!(f, "{}", v.get_name()), None => unreachable!(), } } } fn output_completion_script(shell: Shell, command: CompletionCommand) -> Result { match command { CompletionCommand::Rustup => { clap_complete::generate(shell, &mut cli(), "rustup", &mut term2::stdout()); } CompletionCommand::Cargo => { if let Shell::Zsh = shell { writeln!(term2::stdout(), "#compdef cargo")?; } let script = match shell { Shell::Bash => "/etc/bash_completion.d/cargo", Shell::Zsh => "/share/zsh/site-functions/_cargo", _ => { return Err(anyhow!( "{} does not currently support completions for {}", command, shell )) } }; writeln!( term2::stdout(), "if command -v rustc >/dev/null 2>&1; then\n\ \tsource \"$(rustc --print sysroot)\"{script}\n\ fi", )?; } } Ok(utils::ExitCode(0)) } rustup-1.26.0/src/cli/self_update.rs000066400000000000000000001234221441327105200173440ustar00rootroot00000000000000//! Self-installation and updating //! //! This is the installer at the heart of Rust. If it breaks //! everything breaks. It is conceptually very simple, as rustup is //! distributed as a single binary, and installation mostly requires //! copying it into place. There are some tricky bits though, mostly //! because of workarounds to self-delete an exe on Windows. //! //! During install (as `rustup-init`): //! //! * copy the self exe to $CARGO_HOME/bin //! * hardlink rustc, etc to *that* //! * update the PATH in a system-specific way //! * run the equivalent of `rustup default stable` //! //! During upgrade (`rustup self upgrade`): //! //! * download rustup-init to $CARGO_HOME/bin/rustup-init //! * run rustup-init with appropriate flags to indicate //! this is a self-upgrade //! * rustup-init copies bins and hardlinks into place. On windows //! this happens *after* the upgrade command exits successfully. //! //! During uninstall (`rustup self uninstall`): //! //! * Delete `$RUSTUP_HOME`. //! * Delete everything in `$CARGO_HOME`, including //! the rustup binary and its hardlinks //! //! Deleting the running binary during uninstall is tricky //! and racy on Windows. #[cfg(unix)] mod shell; pub(crate) mod test; #[cfg(unix)] mod unix; #[cfg(windows)] mod windows; mod os { #[cfg(unix)] pub(crate) use super::unix::*; #[cfg(windows)] pub(crate) use super::windows::*; } use std::borrow::Cow; use std::env; use std::env::consts::EXE_SUFFIX; use std::fs; use std::io::Write; use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; use std::process::Command; use std::str::FromStr; use anyhow::{anyhow, Context, Result}; use cfg_if::cfg_if; use same_file::Handle; use super::common::{self, ignorable_error, report_error, Confirm}; use super::errors::*; use super::markdown::md; use super::term2; use crate::cli::term2::Terminal; use crate::dist::dist::{self, Profile, TargetTriple}; use crate::process; use crate::toolchain::{DistributableToolchain, Toolchain}; use crate::utils::utils; use crate::utils::Notification; use crate::{Cfg, UpdateStatus}; use crate::{DUP_TOOLS, TOOLS}; use os::*; pub(crate) use os::{delete_rustup_and_cargo_home, run_update, self_replace}; #[cfg(windows)] pub use windows::complete_windows_uninstall; pub struct InstallOpts<'a> { pub default_host_triple: Option, pub default_toolchain: Option, pub profile: String, pub no_modify_path: bool, pub no_update_toolchain: bool, pub components: &'a [&'a str], pub targets: &'a [&'a str], } #[cfg(feature = "no-self-update")] pub(crate) const NEVER_SELF_UPDATE: bool = true; #[cfg(not(feature = "no-self-update"))] pub(crate) const NEVER_SELF_UPDATE: bool = false; #[derive(Clone, Debug, Eq, PartialEq)] pub enum SelfUpdateMode { Enable, Disable, CheckOnly, } impl SelfUpdateMode { pub(crate) fn modes() -> &'static [&'static str] { &["enable", "disable", "check-only"] } pub(crate) fn default_mode() -> &'static str { "enable" } } impl FromStr for SelfUpdateMode { type Err = anyhow::Error; fn from_str(mode: &str) -> Result { match mode { "enable" => Ok(Self::Enable), "disable" => Ok(Self::Disable), "check-only" => Ok(Self::CheckOnly), _ => Err(anyhow!(format!( "unknown self update mode: '{}'; valid modes are {}", mode, valid_self_update_modes(), ))), } } } impl ToString for SelfUpdateMode { fn to_string(&self) -> String { match self { SelfUpdateMode::Enable => "enable", SelfUpdateMode::Disable => "disable", SelfUpdateMode::CheckOnly => "check-only", } .into() } } // 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!( "This path will then be added to your `PATH` environment variable by modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key." ) }; } 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, run: source "{cargo_home}/env" "# }; } #[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, run: source "{cargo_home}/env" "# }; } #[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. " }; } #[cfg(windows)] static MSVC_MESSAGE: &str = r#"# Rust Visual C++ prerequisites Rust requires the Microsoft C++ build tools for Visual Studio 2013 or later, but they don't seem to be installed. "#; #[cfg(windows)] static MSVC_MANUAL_INSTALL_MESSAGE: &str = r#" You can acquire the build tools by installing Microsoft Visual Studio. https://visualstudio.microsoft.com/downloads/ Check the box for "Desktop development with C++" which will ensure that the needed components are installed. If your locale language is not English, then additionally check the box for English under Language packs. For more details see: https://rust-lang.github.io/rustup/installation/windows-msvc.html _Install the C++ build tools before proceeding_. If you will be targeting the GNU ABI or otherwise know what you are doing then it is fine to continue installation without the build tools, but otherwise, install the C++ build tools before proceeding. "#; #[cfg(windows)] static MSVC_AUTO_INSTALL_MESSAGE: &str = r#"# Rust Visual C++ prerequisites Rust requires a linker and Windows API libraries but they don't seem to be available. These components can be acquired through a Visual Studio installer. "#; static UPDATE_ROOT: &str = "https://static.rust-lang.org/rustup"; /// `CARGO_HOME` suitable for display, possibly with $HOME /// substituted for the directory prefix fn canonical_cargo_home() -> Result> { let path = utils::cargo_home()?; let default_cargo_home = utils::home_dir() .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() } } } 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) fn install( no_prompt: bool, verbose: bool, quiet: bool, mut opts: InstallOpts<'_>, ) -> Result { if !process() .var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS") .map_or(false, |s| s == "yes") { do_pre_install_sanity_checks(no_prompt)?; } do_pre_install_options_sanity_checks(&opts)?; if !process() .var_os("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS") .map_or(false, |s| s == "yes") { check_existence_of_rustc_or_cargo_in_path(no_prompt)?; } #[cfg(unix)] do_anti_sudo_check(no_prompt)?; let mut term = term2::stdout(); #[cfg(windows)] if let Some(plan) = do_msvc_check(&opts) { if no_prompt { warn!("installing msvc toolchain without its prerequisites"); } else if !quiet && plan == VsInstallPlan::Automatic { md(&mut term, MSVC_AUTO_INSTALL_MESSAGE); match windows::choose_vs_install()? { Some(VsInstallPlan::Automatic) => { match try_install_msvc(&opts) { 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); if !common::question_bool("\nContinue?", false)? { info!("aborting installation"); return Ok(utils::ExitCode(0)); } } Ok(ContinueInstall::No) => { ensure_prompt()?; return Ok(utils::ExitCode(0)); } _ => {} } } Some(VsInstallPlan::Manual) => { md(&mut term, MSVC_MANUAL_INSTALL_MESSAGE); if !common::question_bool("\nContinue?", false)? { info!("aborting installation"); return Ok(utils::ExitCode(0)); } } None => {} } } else { md(&mut term, MSVC_MESSAGE); md(&mut term, MSVC_MANUAL_INSTALL_MESSAGE); if !common::question_bool("\nContinue?", false)? { info!("aborting installation"); return Ok(utils::ExitCode(0)); } } } if !no_prompt { let msg = pre_install_msg(opts.no_modify_path)?; md(&mut term, msg); loop { md(&mut term, current_install_opts(&opts)); match common::confirm_advanced()? { Confirm::No => { info!("aborting installation"); return Ok(utils::ExitCode(0)); } Confirm::Yes => { break; } Confirm::Advanced => { opts = customize_install(opts)?; } } } } let install_res: Result = (|| { install_bins()?; #[cfg(unix)] do_write_env_files()?; if !opts.no_modify_path { do_add_to_programs()?; do_add_to_path()?; } utils::create_rustup_home()?; maybe_install_rust( opts.default_toolchain.as_deref(), &opts.profile, opts.default_host_triple.as_deref(), !opts.no_update_toolchain, opts.components, opts.targets, verbose, quiet, )?; Ok(utils::ExitCode(0)) })(); if let Err(e) = install_res { report_error(&e); // 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 { ensure_prompt()?; } return Ok(utils::ExitCode(1)); } let cargo_home = canonical_cargo_home()?; #[cfg(windows)] let cargo_home = cargo_home.replace('\\', r"\\"); #[cfg(windows)] let msg = if opts.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 msg = if opts.no_modify_path { format!( post_install_msg_unix_no_modify_path!(), cargo_home = cargo_home ) } else { format!(post_install_msg_unix!(), cargo_home = cargo_home) }; md(&mut term, msg); #[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. ensure_prompt()?; } Ok(utils::ExitCode(0)) } fn rustc_or_cargo_exists_in_path() -> Result<()> { // Ignore rustc and cargo if present in $HOME/.cargo/bin or a few other directories #[allow(clippy::ptr_arg)] fn ignore_paths(path: &PathBuf) -> bool { !path .components() .any(|c| c == Component::Normal(".cargo".as_ref())) } if let Some(paths) = process().var_os("PATH") { let paths = env::split_paths(&paths).filter(ignore_paths); for path in paths { let rustc = path.join(format!("rustc{EXE_SUFFIX}")); let cargo = path.join(format!("cargo{EXE_SUFFIX}")); if rustc.exists() || cargo.exists() { return Err(anyhow!("{}", path.to_str().unwrap().to_owned())); } } } Ok(()) } fn check_existence_of_rustc_or_cargo_in_path(no_prompt: bool) -> Result<()> { // Only the test runner should set this let skip_check = process().var_os("RUSTUP_INIT_SKIP_PATH_CHECK"); // Skip this if the environment variable is set if skip_check == Some("yes".into()) { return Ok(()); } if let Err(path) = rustc_or_cargo_exists_in_path() { warn!("it looks like you have an existing installation of Rust at:"); warn!("{}", path); warn!("It is recommended that rustup be the primary Rust installation."); warn!("Otherwise you may have confusion unless you are careful with your PATH"); warn!("If you are sure that you want both rustup and your already installed Rust"); warn!("then please reply `y' or `yes' or set RUSTUP_INIT_SKIP_PATH_CHECK to yes"); warn!("or pass `-y' to ignore all ignorable checks."); ignorable_error("cannot install while Rust is installed", no_prompt)?; } Ok(()) } fn do_pre_install_sanity_checks(no_prompt: bool) -> Result<()> { let rustc_manifest_path = PathBuf::from("/usr/local/lib/rustlib/manifest-rustc"); let uninstaller_path = PathBuf::from("/usr/local/lib/rustlib/uninstall.sh"); let rustup_sh_path = utils::home_dir().unwrap().join(".rustup"); let rustup_sh_version_path = rustup_sh_path.join("rustup-version"); let rustc_exists = rustc_manifest_path.exists() && uninstaller_path.exists(); let rustup_sh_exists = rustup_sh_version_path.exists(); if rustc_exists { warn!("it looks like you have an existing installation of Rust"); warn!("rustup cannot be installed alongside Rust. Please uninstall first"); warn!( "run `{}` as root to uninstall Rust", uninstaller_path.display() ); ignorable_error("cannot install while Rust is installed", no_prompt)?; } if rustup_sh_exists { warn!("it looks like you have existing rustup.sh metadata"); warn!("rustup cannot be installed while rustup.sh metadata exists"); warn!("delete `{}` to remove rustup.sh", rustup_sh_path.display()); warn!("or, if you already have rustup installed, you can run"); warn!("`rustup self update` and `rustup toolchain list` to upgrade"); warn!("your directory structure"); ignorable_error("cannot install while rustup.sh is installed", no_prompt)?; } Ok(()) } fn do_pre_install_options_sanity_checks(opts: &InstallOpts<'_>) -> Result<()> { // Verify that the installation options are vaguely sane (|| { let host_triple = opts .default_host_triple .as_ref() .map(|s| dist::TargetTriple::new(s)) .unwrap_or_else(TargetTriple::from_host_or_build); let toolchain_to_use = match &opts.default_toolchain { None => "stable", Some(s) if s == "none" => "stable", Some(s) => s, }; let partial_channel = dist::PartialToolchainDesc::from_str(toolchain_to_use)?; let resolved = partial_channel.resolve(&host_triple)?.to_string(); debug!( "Successfully resolved installation toolchain as: {}", resolved ); Ok(()) })() .map_err(|e: Box| { anyhow!( "Pre-checks for host and toolchain failed: {}\n\ If you are unsure of suitable values, the 'stable' toolchain is the default.\n\ Valid host triples look something like: {}", e, dist::TargetTriple::from_host_or_build() ) })?; Ok(()) } fn pre_install_msg(no_modify_path: bool) -> Result { let cargo_home = utils::cargo_home()?; let cargo_home_bin = cargo_home.join("bin"); let rustup_home = home::rustup_home()?; if !no_modify_path { // Brittle code warning: some duplication in unix::do_add_to_path #[cfg(not(windows))] { let rcfiles = shell::get_available_shells() .flat_map(|sh| sh.update_rcs().into_iter()) .map(|rc| format!(" {}", rc.display())) .collect::>(); let plural = if rcfiles.len() > 1 { "s" } else { "" }; let rcfiles = rcfiles.join("\n"); Ok(format!( pre_install_msg_unix!(), cargo_home = cargo_home.display(), cargo_home_bin = cargo_home_bin.display(), plural = plural, rcfiles = rcfiles, rustup_home = rustup_home.display(), )) } #[cfg(windows)] Ok(format!( pre_install_msg_win!(), cargo_home = cargo_home.display(), cargo_home_bin = cargo_home_bin.display(), rustup_home = rustup_home.display(), )) } else { Ok(format!( pre_install_msg_no_modify_path!(), cargo_home = cargo_home.display(), cargo_home_bin = cargo_home_bin.display(), rustup_home = rustup_home.display(), )) } } fn current_install_opts(opts: &InstallOpts<'_>) -> String { format!( r"Current installation options: - ` `default host triple: `{}` - ` `default toolchain: `{}` - ` `profile: `{}` - modify PATH variable: `{}` ", opts.default_host_triple .as_ref() .map(|s| TargetTriple::new(s)) .unwrap_or_else(TargetTriple::from_host_or_build), opts.default_toolchain .as_deref() .unwrap_or("stable (default)"), opts.profile, if !opts.no_modify_path { "yes" } else { "no" } ) } // Interactive editing of the install options fn customize_install(mut opts: InstallOpts<'_>) -> Result> { writeln!( process().stdout(), "I'm going to ask you the value of each of these installation options.\n\ You may simply press the Enter key to leave unchanged." )?; writeln!(process().stdout())?; opts.default_host_triple = Some(common::question_str( "Default host triple?", &opts .default_host_triple .unwrap_or_else(|| TargetTriple::from_host_or_build().to_string()), )?); opts.default_toolchain = Some(common::question_str( "Default toolchain? (stable/beta/nightly/none)", opts.default_toolchain.as_deref().unwrap_or("stable"), )?); opts.profile = common::question_str( &format!( "Profile (which tools and data to install)? ({})", Profile::names().join("/") ), &opts.profile, )?; opts.no_modify_path = !common::question_bool("Modify PATH variable?", !opts.no_modify_path)?; Ok(opts) } fn install_bins() -> Result<()> { let bin_path = utils::cargo_home()?.join("bin"); let this_exe_path = utils::current_exe()?; let rustup_path = bin_path.join(format!("rustup{EXE_SUFFIX}")); utils::ensure_dir_exists("bin", &bin_path, &|_: Notification<'_>| {})?; // NB: Even on Linux we can't just copy the new binary over the (running) // old binary; we must unlink it first. if rustup_path.exists() { utils::remove_file("rustup-bin", &rustup_path)?; } utils::copy_file(&this_exe_path, &rustup_path)?; utils::make_executable(&rustup_path)?; install_proxies() } pub(crate) fn install_proxies() -> Result<()> { let bin_path = utils::cargo_home()?.join("bin"); let rustup_path = bin_path.join(&format!("rustup{EXE_SUFFIX}")); let rustup = Handle::from_path(&rustup_path)?; let mut tool_handles = Vec::new(); let mut link_afterwards = Vec::new(); // Try to hardlink all the Rust exes to the rustup exe. Some systems, // like Android, does not support hardlinks, so we fallback to symlinks. // // Note that this function may not be running in the context of a fresh // self update but rather as part of a normal update to fill in missing // proxies. In that case our process may actually have the `rustup.exe` // file open, and on systems like Windows that means that you can't // even remove other hard links to the same file. Basically if we have // `rustup.exe` open and running and `cargo.exe` is a hard link to that // file, we can't remove `cargo.exe`. // // To avoid unnecessary errors from being returned here we use the // `same-file` crate and its `Handle` type to avoid clobbering hard links // that are already valid. If a hard link already points to the // `rustup.exe` file then we leave it alone and move to the next one. // // As yet one final caveat, when we're looking at handles for files we can't // actually delete files (they'll say they're deleted but they won't // actually be on Windows). As a result we manually drop all the // `tool_handles` later on. This'll allow us, afterwards, to actually // overwrite all the previous hard links with new ones. for tool in TOOLS { let tool_path = bin_path.join(&format!("{tool}{EXE_SUFFIX}")); if let Ok(handle) = Handle::from_path(&tool_path) { tool_handles.push(handle); if rustup == *tool_handles.last().unwrap() { continue; } } link_afterwards.push(tool_path); } for tool in DUP_TOOLS { let tool_path = bin_path.join(&format!("{tool}{EXE_SUFFIX}")); if let Ok(handle) = Handle::from_path(&tool_path) { // Like above, don't clobber anything that's already hardlinked to // avoid extraneous errors from being returned. if rustup == handle { continue; } // If this file exists and is *not* equivalent to all other // preexisting tools we found, then we're going to assume that it // was preinstalled and actually pointing to a totally different // binary. This is intended for cases where historically users // ran `cargo install rustfmt` and so they had custom `rustfmt` // and `cargo-fmt` executables lying around, but we as rustup have // since started managing these tools. // // If the file is managed by rustup it should be equivalent to some // previous file, and if it's not equivalent to anything then it's // pretty likely that it needs to be dealt with manually. if tool_handles.iter().all(|h| *h != handle) { warn!("tool `{}` is already installed, remove it from `{}`, then run `rustup update` \ to have rustup manage this tool.", tool, bin_path.display()); continue; } } utils::hard_or_symlink_file(&rustup_path, &tool_path)?; } drop(tool_handles); for path in link_afterwards { utils::hard_or_symlink_file(&rustup_path, &path)?; } Ok(()) } fn maybe_install_rust( toolchain: Option<&str>, profile_str: &str, default_host_triple: Option<&str>, update_existing_toolchain: bool, components: &[&str], targets: &[&str], verbose: bool, quiet: bool, ) -> Result<()> { let mut cfg = common::set_globals(verbose, quiet)?; let toolchain = _install_selection( &mut cfg, toolchain, profile_str, default_host_triple, update_existing_toolchain, components, targets, )?; if let Some(toolchain) = toolchain { if toolchain.exists() { warn!("Updating existing toolchain, profile choice will be ignored"); } let distributable = DistributableToolchain::new(&toolchain)?; let status = distributable.install_from_dist(true, false, components, targets, None)?; let toolchain_str = toolchain.name().to_owned(); toolchain.cfg().set_default(&toolchain_str)?; writeln!(process().stdout())?; common::show_channel_update(toolchain.cfg(), &toolchain_str, Ok(status))?; } Ok(()) } fn _install_selection<'a>( cfg: &'a mut Cfg, toolchain_opt: Option<&str>, profile_str: &str, default_host_triple: Option<&str>, update_existing_toolchain: bool, components: &[&str], targets: &[&str], ) -> Result>> { cfg.set_profile(profile_str)?; if let Some(default_host_triple) = default_host_triple { // Set host triple now as it will affect resolution of toolchain_str info!("setting default host triple to {}", default_host_triple); cfg.set_default_host_triple(default_host_triple)?; } else { info!("default host triple is {}", cfg.get_default_host_triple()?); } let user_specified_something = toolchain_opt.is_some() || !targets.is_empty() || !components.is_empty() || update_existing_toolchain; // If the user specified they want no toolchain, we skip this, otherwise // if they specify something directly, or we have no default, then we install // a toolchain (updating if it's already present) and then if neither of // those are true, we have a user who doesn't mind, and already has an // install, so we leave their setup alone. Ok(if toolchain_opt == Some("none") { info!("skipping toolchain installation"); if !components.is_empty() { warn!( "ignoring requested component{}: {}", if components.len() == 1 { "" } else { "s" }, components.join(", ") ); } if !targets.is_empty() { warn!( "ignoring requested target{}: {}", if targets.len() == 1 { "" } else { "s" }, targets.join(", ") ); } writeln!(process().stdout())?; None } else if user_specified_something || (update_existing_toolchain && cfg.find_default()?.is_none()) { Some(match toolchain_opt { Some(s) => cfg.get_toolchain(s, false)?, None => match cfg.find_default()? { Some(t) => t, None => cfg.get_toolchain("stable", false)?, }, }) } else { info!("updating existing rustup installation - leaving toolchains alone"); writeln!(process().stdout())?; None }) } pub(crate) fn uninstall(no_prompt: bool) -> Result { if NEVER_SELF_UPDATE { err!("self-uninstall is disabled for this build of rustup"); err!("you should probably use your system package manager to uninstall rustup"); return Ok(utils::ExitCode(1)); } let cargo_home = utils::cargo_home()?; if !cargo_home.join(&format!("bin/rustup{EXE_SUFFIX}")).exists() { return Err(CLIError::NotSelfInstalled { p: cargo_home }.into()); } if !no_prompt { writeln!(process().stdout())?; let msg = format!(pre_uninstall_msg!(), cargo_home = canonical_cargo_home()?); md(&mut term2::stdout(), msg); if !common::confirm("\nContinue? (y/N)", false)? { info!("aborting uninstallation"); return Ok(utils::ExitCode(0)); } } info!("removing rustup home"); // Delete RUSTUP_HOME let rustup_dir = home::rustup_home()?; if rustup_dir.exists() { utils::remove_dir("rustup_home", &rustup_dir, &|_: Notification<'_>| {})?; } info!("removing cargo home"); // Remove CARGO_HOME/bin from PATH do_remove_from_path()?; do_remove_from_programs()?; // Delete everything in CARGO_HOME *except* the rustup bin // First everything except the bin directory let diriter = fs::read_dir(&cargo_home).map_err(|e| CLIError::ReadDirError { p: cargo_home.clone(), source: e, })?; for dirent in diriter { let dirent = dirent.map_err(|e| CLIError::ReadDirError { p: cargo_home.clone(), source: e, })?; if dirent.file_name().to_str() != Some("bin") { if dirent.path().is_dir() { utils::remove_dir("cargo_home", &dirent.path(), &|_: Notification<'_>| {})?; } else { utils::remove_file("cargo_home", &dirent.path())?; } } } // Then everything in bin except rustup and tools. These can't be unlinked // until this process exits (on windows). let tools = TOOLS .iter() .chain(DUP_TOOLS.iter()) .map(|t| format!("{t}{EXE_SUFFIX}")); let tools: Vec<_> = tools.chain(vec![format!("rustup{EXE_SUFFIX}")]).collect(); let bin_dir = cargo_home.join("bin"); let diriter = fs::read_dir(&bin_dir).map_err(|e| CLIError::ReadDirError { p: bin_dir.clone(), source: e, })?; for dirent in diriter { let dirent = dirent.map_err(|e| CLIError::ReadDirError { p: bin_dir.clone(), source: e, })?; let name = dirent.file_name(); let file_is_tool = name.to_str().map(|n| tools.iter().any(|t| *t == n)); if file_is_tool == Some(false) { if dirent.path().is_dir() { utils::remove_dir("cargo_home", &dirent.path(), &|_: Notification<'_>| {})?; } else { utils::remove_file("cargo_home", &dirent.path())?; } } } info!("removing rustup binaries"); // Delete rustup. This is tricky because this is *probably* // the running executable and on Windows can't be unlinked until // the process exits. delete_rustup_and_cargo_home()?; info!("rustup is uninstalled"); Ok(utils::ExitCode(0)) } /// Self update downloads rustup-init to `CARGO_HOME`/bin/rustup-init /// and runs it. /// /// It does a few things to accommodate self-delete problems on windows: /// /// rustup-init is run in two stages, first with `--self-upgrade`, /// which displays update messages and asks for confirmations, etc; /// then with `--self-replace`, which replaces the rustup binary and /// hardlinks. The last step is done without waiting for confirmation /// on windows so that the running exe can be deleted. /// /// Because it's again difficult for rustup-init to delete itself /// (and on windows this process will not be running to do it), /// rustup-init is stored in `CARGO_HOME`/bin, and then deleted next /// time rustup runs. pub(crate) fn update(cfg: &Cfg) -> Result { use common::SelfUpdatePermission::*; let update_permitted = if NEVER_SELF_UPDATE { HardFail } else { common::self_update_permitted(true)? }; match update_permitted { HardFail => { // TODO: Detect which package manager and be more useful. err!("self-update is disabled for this build of rustup"); err!("you should probably use your system package manager to update rustup"); return Ok(utils::ExitCode(1)); } Skip => { info!("Skipping self-update at this time"); return Ok(utils::ExitCode(0)); } Permit => {} } match prepare_update()? { Some(setup_path) => { let version = match get_new_rustup_version(&setup_path) { Some(new_version) => parse_new_rustup_version(new_version), None => { err!("failed to get rustup version"); return Ok(utils::ExitCode(1)); } }; let _ = common::show_channel_update(cfg, "rustup", Ok(UpdateStatus::Updated(version))); return run_update(&setup_path); } None => { let _ = common::show_channel_update(cfg, "rustup", Ok(UpdateStatus::Unchanged)); // Try again in case we emitted "tool `{}` is already installed" last time. install_proxies()? } } Ok(utils::ExitCode(0)) } fn get_new_rustup_version(path: &Path) -> Option { match Command::new(path).arg("--version").output() { Err(_) => None, Ok(output) => match String::from_utf8(output.stdout) { Ok(version) => Some(version), Err(_) => None, }, } } fn parse_new_rustup_version(version: String) -> String { use lazy_static::lazy_static; use regex::Regex; lazy_static! { static ref RE: Regex = Regex::new(r"\d+.\d+.\d+[0-9a-zA-Z-]*").unwrap(); } let capture = RE.captures(&version); let matched_version = match capture { Some(cap) => cap.get(0).unwrap().as_str(), None => "(unknown)", }; String::from(matched_version) } pub(crate) fn prepare_update() -> Result> { let cargo_home = utils::cargo_home()?; let rustup_path = cargo_home.join(format!("bin{MAIN_SEPARATOR}rustup{EXE_SUFFIX}")); let setup_path = cargo_home.join(format!("bin{MAIN_SEPARATOR}rustup-init{EXE_SUFFIX}")); if !rustup_path.exists() { return Err(CLIError::NotSelfInstalled { p: cargo_home }.into()); } if setup_path.exists() { utils::remove_file("setup", &setup_path)?; } // Get build triple let triple = dist::TargetTriple::from_build(); // For windows x86 builds seem slow when used with windows defender. // The website defaulted to i686-windows-gnu builds for a long time. // This ensures that we update to a version thats appropriate for users // and also works around if the website messed up the detection. // If someone really wants to use another version, they still can enforce // that using the environment variable RUSTUP_OVERRIDE_HOST_TRIPLE. #[cfg(windows)] let triple = dist::TargetTriple::from_host().unwrap_or(triple); // Get update root. let update_root = process() .var("RUSTUP_UPDATE_ROOT") .unwrap_or_else(|_| String::from(UPDATE_ROOT)); // Get current version let current_version = env!("CARGO_PKG_VERSION"); // Get available version info!("checking for self-update"); let available_version = get_available_rustup_version()?; // If up-to-date if available_version == current_version { return Ok(None); } // Get download URL let url = format!("{update_root}/archive/{available_version}/{triple}/rustup-init{EXE_SUFFIX}"); // Get download path let download_url = utils::parse_url(&url)?; // Download new version info!("downloading self-update"); utils::download_file(&download_url, &setup_path, None, &|_| ())?; // Mark as executable utils::make_executable(&setup_path)?; Ok(Some(setup_path)) } pub(crate) fn get_available_rustup_version() -> Result { let update_root = process() .var("RUSTUP_UPDATE_ROOT") .unwrap_or_else(|_| String::from(UPDATE_ROOT)); let tempdir = tempfile::Builder::new() .prefix("rustup-update") .tempdir() .context("error creating temp directory")?; // Parse the release file. let release_file_url = format!("{update_root}/release-stable.toml"); let release_file_url = utils::parse_url(&release_file_url)?; let release_file = tempdir.path().join("release-stable.toml"); utils::download_file(&release_file_url, &release_file, None, &|_| ())?; let release_toml_str = utils::read_file("rustup release", &release_file)?; let release_toml: toml::Value = toml::from_str(&release_toml_str).context("unable to parse rustup release file")?; // Check the release file schema. let schema = release_toml .get("schema-version") .ok_or_else(|| anyhow!("no schema key in rustup release file"))? .as_str() .ok_or_else(|| anyhow!("invalid schema key in rustup release file"))?; if schema != "1" { return Err(anyhow!(format!( "unknown schema version '{schema}' in rustup release file" ))); } // Get the version. let available_version = release_toml .get("version") .ok_or_else(|| anyhow!("no version key in rustup release file"))? .as_str() .ok_or_else(|| anyhow!("invalid version key in rustup release file"))?; Ok(String::from(available_version)) } pub(crate) fn check_rustup_update() -> Result<()> { let mut t = term2::stdout(); // Get current rustup version let current_version = env!("CARGO_PKG_VERSION"); // Get available rustup version let available_version = get_available_rustup_version()?; let _ = t.attr(term2::Attr::Bold); write!(t, "rustup - ")?; if current_version != available_version { let _ = t.fg(term2::color::YELLOW); write!(t, "Update available")?; let _ = t.reset(); writeln!(t, " : {current_version} -> {available_version}")?; } else { let _ = t.fg(term2::color::GREEN); write!(t, "Up to date")?; let _ = t.reset(); writeln!(t, " : {current_version}")?; } Ok(()) } pub(crate) fn cleanup_self_updater() -> Result<()> { let cargo_home = utils::cargo_home()?; let setup = cargo_home.join(format!("bin/rustup-init{EXE_SUFFIX}")); if setup.exists() { utils::remove_file("setup", &setup)?; } Ok(()) } pub(crate) fn valid_self_update_modes() -> String { SelfUpdateMode::modes() .iter() .map(|s| format!("'{s}'")) .collect::>() .join(", ") } #[cfg(test)] mod tests { use std::collections::HashMap; use anyhow::Result; use crate::cli::common; use crate::dist::dist::ToolchainDesc; use crate::test::{test_dir, with_rustup_home, Env}; use crate::{currentprocess, for_host}; #[test] fn default_toolchain_is_stable() { with_rustup_home(|home| { let mut vars = HashMap::new(); home.apply(&mut vars); let tp = Box::new(currentprocess::TestProcess { vars, ..Default::default() }); currentprocess::with(tp.clone(), || -> Result<()> { // TODO: we could pass in a custom cfg to get notification // callbacks rather than output to the tp sink. let mut cfg = common::set_globals(false, false).unwrap(); assert_eq!( "stable", super::_install_selection( &mut cfg, None, // No toolchain specified "default", // default profile None, true, &[], &[], ) .unwrap() // result .unwrap() // option .name() .parse::() .unwrap() .channel ); Ok(()) })?; assert_eq!( for_host!( r"info: profile set to 'default' info: default host triple is {0} " ), &String::from_utf8(tp.get_stderr()).unwrap() ); Ok(()) }) .unwrap(); } #[test] fn install_bins_creates_cargo_home() { let root_dir = test_dir().unwrap(); let cargo_home = root_dir.path().join("cargo"); let mut vars = HashMap::new(); vars.env("CARGO_HOME", cargo_home.to_string_lossy().to_string()); let tp = Box::new(currentprocess::TestProcess { vars, ..Default::default() }); currentprocess::with(tp, || -> Result<()> { super::install_bins().unwrap(); Ok(()) }) .unwrap(); assert!(cargo_home.exists()); } } rustup-1.26.0/src/cli/self_update/000077500000000000000000000000001441327105200167725ustar00rootroot00000000000000rustup-1.26.0/src/cli/self_update/env.sh000066400000000000000000000004421441327105200201160ustar00rootroot00000000000000#!/bin/sh # rustup shell setup # affix colons on either side of $PATH to simplify matching case ":${PATH}:" in *:"{cargo_bin}":*) ;; *) # Prepending path in case a system-installed rustc needs to be overridden export PATH="{cargo_bin}:$PATH" ;; esac rustup-1.26.0/src/cli/self_update/shell.rs000066400000000000000000000152621441327105200204550ustar00rootroot00000000000000//! Paths and Unix shells //! //! MacOS, Linux, FreeBSD, and many other OS model their design on Unix, //! so handling them is relatively consistent. But only relatively. //! POSIX postdates Unix by 20 years, and each "Unix-like" shell develops //! unique quirks over time. //! //! //! Windowing Managers, Desktop Environments, GUI Terminals, and PATHs //! //! Duplicating paths in PATH can cause performance issues when the OS searches //! the same place multiple times. Traditionally, Unix configurations have //! resolved this by setting up PATHs in the shell's login profile. //! //! This has its own issues. Login profiles are only intended to run once, but //! changing the PATH is common enough that people may run it twice. Desktop //! environments often choose to NOT start login shells in GUI terminals. Thus, //! a trend has emerged to place PATH updates in other run-commands (rc) files, //! leaving Rustup with few assumptions to build on for fulfilling its promise //! to set up PATH appropriately. //! //! Rustup addresses this by: //! 1) using a shell script that updates PATH if the path is not in PATH //! 2) sourcing this script (`. /path/to/script`) in any appropriate rc file use std::borrow::Cow; use std::path::PathBuf; use anyhow::{bail, Result}; use super::utils; use crate::process; pub(crate) type Shell = Box; #[derive(Debug, PartialEq)] pub(crate) struct ShellScript { content: &'static str, name: &'static str, } impl ShellScript { pub(crate) fn write(&self) -> Result<()> { let home = utils::cargo_home()?; let cargo_bin = format!("{}/bin", cargo_home_str()?); let env_name = home.join(self.name); let env_file = self.content.replace("{cargo_bin}", &cargo_bin); utils::write_file(self.name, &env_name, &env_file)?; Ok(()) } } // TODO: Update into a bytestring. pub(crate) fn cargo_home_str() -> Result> { let path = utils::cargo_home()?; let default_cargo_home = utils::home_dir() .unwrap_or_else(|| PathBuf::from(".")) .join(".cargo"); Ok(if default_cargo_home == path { "$HOME/.cargo".into() } else { match path.to_str() { Some(p) => p.to_owned().into(), None => bail!("Non-Unicode path!"), } }) } // TODO: Tcsh (BSD) // TODO?: Make a decision on Ion Shell, Power Shell, Nushell // Cross-platform non-POSIX shells have not been assessed for integration yet fn enumerate_shells() -> Vec { vec![Box::new(Posix), Box::new(Bash), Box::new(Zsh)] } pub(crate) fn get_available_shells() -> impl Iterator { enumerate_shells().into_iter().filter(|sh| sh.does_exist()) } pub(crate) trait UnixShell { // Detects if a shell "exists". Users have multiple shells, so an "eager" // heuristic should be used, assuming shells exist if any traces do. fn does_exist(&self) -> bool; // Gives all rcfiles of a given shell that Rustup is concerned with. // Used primarily in checking rcfiles for cleanup. fn rcfiles(&self) -> Vec; // Gives rcs that should be written to. fn update_rcs(&self) -> Vec; // Writes the relevant env file. fn env_script(&self) -> ShellScript { ShellScript { name: "env", content: include_str!("env.sh"), } } fn source_string(&self) -> Result { Ok(format!(r#". "{}/env""#, cargo_home_str()?)) } } struct Posix; impl UnixShell for Posix { fn does_exist(&self) -> bool { true } fn rcfiles(&self) -> Vec { match utils::home_dir() { Some(dir) => vec![dir.join(".profile")], _ => vec![], } } fn update_rcs(&self) -> Vec { // Write to .profile even if it doesn't exist. It's the only rc in the // POSIX spec so it should always be set up. self.rcfiles() } } struct Bash; impl UnixShell for Bash { fn does_exist(&self) -> bool { !self.update_rcs().is_empty() } fn rcfiles(&self) -> Vec { // Bash also may read .profile, however Rustup already includes handling // .profile as part of POSIX and always does setup for POSIX shells. [".bash_profile", ".bash_login", ".bashrc"] .iter() .filter_map(|rc| utils::home_dir().map(|dir| dir.join(rc))) .collect() } fn update_rcs(&self) -> Vec { self.rcfiles() .into_iter() .filter(|rc| rc.is_file()) .collect() } } struct Zsh; impl Zsh { fn zdotdir() -> Result { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; if matches!(process().var("SHELL"), Ok(sh) if sh.contains("zsh")) { match process().var("ZDOTDIR") { Ok(dir) if !dir.is_empty() => Ok(PathBuf::from(dir)), _ => bail!("Zsh setup failed."), } } else { match std::process::Command::new("zsh") .args(["-c", "'echo $ZDOTDIR'"]) .output() { Ok(io) if !io.stdout.is_empty() => Ok(PathBuf::from(OsStr::from_bytes(&io.stdout))), _ => bail!("Zsh setup failed."), } } } } impl UnixShell for Zsh { fn does_exist(&self) -> bool { // zsh has to either be the shell or be callable for zsh setup. matches!(process().var("SHELL"), Ok(sh) if sh.contains("zsh")) || matches!(utils::find_cmd(&["zsh"]), Some(_)) } fn rcfiles(&self) -> Vec { [Zsh::zdotdir().ok(), utils::home_dir()] .iter() .filter_map(|dir| dir.as_ref().map(|p| p.join(".zshenv"))) .collect() } fn update_rcs(&self) -> Vec { // zsh can change $ZDOTDIR both _before_ AND _during_ reading .zshenv, // so we: write to $ZDOTDIR/.zshenv if-exists ($ZDOTDIR changes before) // OR write to $HOME/.zshenv if it exists (change-during) // if neither exist, we create it ourselves, but using the same logic, // because we must still respond to whether $ZDOTDIR is set or unset. // In any case we only write once. self.rcfiles() .into_iter() .filter(|env| env.is_file()) .chain(self.rcfiles().into_iter()) .take(1) .collect() } } pub(crate) fn legacy_paths() -> impl Iterator { let zprofiles = Zsh::zdotdir() .into_iter() .chain(utils::home_dir()) .map(|d| d.join(".zprofile")); let profiles = [".bash_profile", ".profile"] .iter() .filter_map(|rc| utils::home_dir().map(|d| d.join(rc))); profiles.chain(zprofiles) } rustup-1.26.0/src/cli/self_update/test.rs000066400000000000000000000032351441327105200203220ustar00rootroot00000000000000//! Support for functional tests. use std::sync::Mutex; use lazy_static::lazy_static; #[cfg(not(unix))] use winreg::{ enums::{HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}, RegKey, RegValue, }; #[cfg(not(unix))] pub fn get_path() -> std::io::Result> { let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); match environment.get_raw_value("PATH") { Ok(val) => Ok(Some(val)), Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), Err(e) => Err(e), } } #[cfg(not(unix))] fn restore_path(p: Option) { let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); if let Some(p) = p.as_ref() { environment.set_raw_value("PATH", p).unwrap(); } else { let _ = environment.delete_value("PATH"); } } /// Support testing of code that mutates global path state pub fn with_saved_path(f: &mut dyn FnMut()) { // Lock protects concurrent mutation of registry lazy_static! { static ref LOCK: Mutex<()> = Mutex::new(()); } let _g = LOCK.lock(); // On windows these tests mess with the user's PATH. Save // and restore them here to keep from trashing things. let saved_path = get_path().expect("Error getting PATH: Better abort to avoid trashing it."); let _g = scopeguard::guard(saved_path, restore_path); f(); } #[cfg(unix)] pub fn get_path() -> std::io::Result> { Ok(None) } #[cfg(unix)] fn restore_path(_: Option<()>) {} rustup-1.26.0/src/cli/self_update/unix.rs000066400000000000000000000143361441327105200203320ustar00rootroot00000000000000use std::path::{Path, PathBuf}; use std::process::Command; use anyhow::{bail, Context, Result}; use super::install_bins; use super::shell; use crate::process; use crate::utils::utils; use crate::utils::Notification; // If the user is trying to install with sudo, on some systems this will // result in writing root-owned files to the user's home directory, because // sudo is configured not to change $HOME. Don't let that bogosity happen. pub(crate) fn do_anti_sudo_check(no_prompt: bool) -> Result { pub(crate) fn home_mismatch() -> (bool, PathBuf, PathBuf) { let fallback = || (false, PathBuf::new(), PathBuf::new()); // test runner should set this, nothing else if process() .var_os("RUSTUP_INIT_SKIP_SUDO_CHECK") .map_or(false, |s| s == "yes") { return fallback(); } match (utils::home_dir_from_passwd(), process().var_os("HOME")) { (Some(pw), Some(eh)) if eh != pw => return (true, PathBuf::from(eh), pw), (None, _) => warn!("getpwuid_r: couldn't get user data"), _ => {} } fallback() } match home_mismatch() { (false, _, _) => {} (true, env_home, euid_home) => { err!("$HOME differs from euid-obtained home directory: you may be using sudo"); err!("$HOME directory: {}", env_home.display()); err!("euid-obtained home directory: {}", euid_home.display()); if !no_prompt { err!("if this is what you want, restart the installation with `-y'"); return Ok(utils::ExitCode(1)); } } } Ok(utils::ExitCode(0)) } pub(crate) fn delete_rustup_and_cargo_home() -> Result<()> { let cargo_home = utils::cargo_home()?; utils::remove_dir("cargo_home", &cargo_home, &|_: Notification<'_>| ()) } pub(crate) fn do_remove_from_path() -> Result<()> { for sh in shell::get_available_shells() { let source_bytes = format!("{}\n", sh.source_string()?).into_bytes(); // Check more files for cleanup than normally are updated. for rc in sh.rcfiles().iter().filter(|rc| rc.is_file()) { 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()) { // 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()..]); let new_file = String::from_utf8(new_bytes).unwrap(); utils::write_file("rcfile", rc, &new_file)?; } } } remove_legacy_paths()?; Ok(()) } pub(crate) fn do_add_to_path() -> Result<()> { for sh in shell::get_available_shells() { let source_cmd = sh.source_string()?; let source_cmd_with_newline = format!("\n{}", &source_cmd); for rc in sh.update_rcs() { let cmd_to_write = match utils::read_file("rcfile", &rc) { Ok(contents) if contents.contains(&source_cmd) => continue, Ok(contents) if !contents.ends_with('\n') => &source_cmd_with_newline, _ => &source_cmd, }; utils::append_file("rcfile", &rc, cmd_to_write) .with_context(|| format!("could not amend shell profile: '{}'", rc.display()))?; } } remove_legacy_paths()?; Ok(()) } pub(crate) fn do_write_env_files() -> Result<()> { let mut written = vec![]; for sh in shell::get_available_shells() { let script = sh.env_script(); // Only write each possible script once. if !written.contains(&script) { script.write()?; written.push(script); } } Ok(()) } pub(crate) fn do_add_to_programs() -> Result<()> { Ok(()) } pub(crate) fn do_remove_from_programs() -> Result<()> { Ok(()) } /// Tell the upgrader to replace the rustup bins, then delete /// itself. pub(crate) fn run_update(setup_path: &Path) -> Result { let status = Command::new(setup_path) .arg("--self-replace") .status() .context("unable to run updater")?; if !status.success() { bail!("self-updated failed to replace rustup executable"); } Ok(utils::ExitCode(0)) } /// This function is as the final step of a self-upgrade. It replaces /// `CARGO_HOME`/bin/rustup with the running exe, and updates the the /// links to it. pub(crate) fn self_replace() -> Result { install_bins()?; Ok(utils::ExitCode(0)) } fn remove_legacy_source_command(source_cmd: String) -> Result<()> { let cmd_bytes = source_cmd.into_bytes(); for rc in shell::legacy_paths().filter(|rc| rc.is_file()) { 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()) { // 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()..]); let new_file = String::from_utf8(new_bytes).unwrap(); utils::write_file("rcfile", &rc, &new_file)?; } } Ok(()) } fn remove_legacy_paths() -> 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 // now considered legacy remove_legacy_source_command(format!( "export PATH=\"{}/bin:$PATH\"\n", shell::cargo_home_str()? ))?; // Unfortunately in 1.23, we accidentally used `source` rather than `.` // which, while widely supported, isn't actually POSIX, so we also // clean that up here. This issue was filed as #2623. remove_legacy_source_command(format!("source \"{}/env\"\n", shell::cargo_home_str()?))?; Ok(()) } rustup-1.26.0/src/cli/self_update/windows.rs000066400000000000000000000747361441327105200210530ustar00rootroot00000000000000use std::cell::RefCell; use std::env::{consts::EXE_SUFFIX, split_paths}; use std::ffi::{OsStr, OsString}; use std::fmt; use std::io::Write; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::path::Path; use std::process::Command; use anyhow::{anyhow, Context, Result}; use super::super::errors::*; use super::common; use super::{install_bins, InstallOpts}; use crate::cli::download_tracker::DownloadTracker; use crate::dist::dist::TargetTriple; use crate::process; use crate::utils::utils; use crate::utils::Notification; use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; use winreg::{RegKey, RegValue}; pub(crate) fn ensure_prompt() -> Result<()> { writeln!(process().stdout(),)?; writeln!(process().stdout(), "Press the Enter key to continue.")?; common::read_line()?; Ok(()) } fn choice(max: u8) -> Result> { write!(process().stdout(), ">")?; let _ = std::io::stdout().flush(); let input = common::read_line()?; let r = match str::parse(&input) { Ok(n) if n <= max => Some(n), _ => None, }; writeln!(process().stdout())?; Ok(r) } pub(crate) fn choose_vs_install() -> Result> { writeln!( process().stdout(), "\n1) Quick install via the Visual Studio Community installer" )?; writeln!( process().stdout(), " (free for individuals, academic uses, and open source)." )?; writeln!( process().stdout(), "\n2) Manually install the prerequisites" )?; writeln!( process().stdout(), " (for enterprise and advanced users)." )?; writeln!(process().stdout(), "\n3) Don't install the prerequisites")?; writeln!( process().stdout(), " (if you're targeting the GNU ABI).\n" )?; let choice = loop { if let Some(n) = choice(3)? { break n; } writeln!(process().stdout(), "Select option 1, 2 or 3")?; }; let plan = match choice { 1 => Some(VsInstallPlan::Automatic), 2 => Some(VsInstallPlan::Manual), _ => None, }; Ok(plan) } #[derive(PartialEq, Eq)] pub(crate) enum VsInstallPlan { Automatic, Manual, } // Provide guidance about setting up MSVC if it doesn't appear to be // installed pub(crate) fn do_msvc_check(opts: &InstallOpts<'_>) -> Option { // Test suite skips this since it's env dependent if process().var("RUSTUP_INIT_SKIP_MSVC_CHECK").is_ok() { return None; } use cc::windows_registry; let host_triple = if let Some(trip) = opts.default_host_triple.as_ref() { trip.to_owned() } else { TargetTriple::from_host_or_build().to_string() }; let installing_msvc = host_triple.contains("msvc"); let have_msvc = windows_registry::find_tool(&host_triple, "cl.exe").is_some(); if installing_msvc && !have_msvc { // Visual Studio build tools are required. // If the user does not have Visual Studio installed and their host // machine is i686 or x86_64 then it's OK to try an auto install. // Otherwise a manual install will be required. let has_any_vs = windows_registry::find_vs_version().is_ok(); let is_x86 = host_triple.contains("i686") || host_triple.contains("x86_64"); if is_x86 && !has_any_vs { Some(VsInstallPlan::Automatic) } else { Some(VsInstallPlan::Manual) } } else { None } } #[derive(Debug, Eq, PartialEq)] struct VsInstallError(i32); impl std::error::Error for VsInstallError {} impl fmt::Display for VsInstallError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // See https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio?view=vs-2022#error-codes let message = match self.0 { 740 => "elevation required", 1001 => "Visual Studio installer process is running", 1003 => "Visual Studio is in use", 1602 => "operation was canceled", 1618 => "another installation running", 1641 => "operation completed successfully, and reboot was initiated", 3010 => "operation completed successfully, but install requires reboot before it can be used", 5003 => "bootstrapper failed to download installer", 5004 => "operation was canceled", 5005 => "bootstrapper command-line parse error", 5007 => "operation was blocked - the computer does not meet the requirements", 8001 => "arm machine check failure", 8002 => "background download precheck failure", 8003 => "out of support selectable failure", 8004 => "target directory failure", 8005 => "verifying source payloads failure", 8006 => "Visual Studio processes running", -1073720687 => "connectivity failure", -1073741510 => "Microsoft Visual Studio Installer was terminated", _ => "error installing Visual Studio" }; write!(f, "{} (exit code {})", message, self.0) } } impl VsInstallError { const REBOOTING_NOW: Self = Self(1641); const REBOOT_REQUIRED: Self = Self(3010); } pub(crate) enum ContinueInstall { Yes, No, } /// Tries to install the needed Visual Studio components. /// /// Returns `Ok(ContinueInstall::No)` if installing Visual Studio was successful /// but the rustup install should not be continued at this time. pub(crate) fn try_install_msvc(opts: &InstallOpts<'_>) -> Result { // download the installer let visual_studio_url = utils::parse_url("https://aka.ms/vs/17/release/vs_community.exe")?; let tempdir = tempfile::Builder::new() .prefix("rustup-visualstudio") .tempdir() .context("error creating temp directory")?; let visual_studio = tempdir.path().join("vs_setup.exe"); let download_tracker = RefCell::new(DownloadTracker::new().with_display_progress(true)); download_tracker.borrow_mut().download_finished(); info!("downloading Visual Studio installer"); utils::download_file(&visual_studio_url, &visual_studio, None, &move |n| { download_tracker .borrow_mut() .handle_notification(&crate::Notification::Install( crate::dist::Notification::Utils(n), )); })?; // Run the installer. Arguments are documented at: // https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio let mut cmd = Command::new(visual_studio); cmd.arg("--wait") // Display an interactive GUI focused on installing just the selected components. .arg("--focusedUi") // Add the English language pack .args(["--addProductLang", "En-us"]) // Add the linker and C runtime libraries. .args(["--add", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"]); // 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() { cmd.args([ "--add", "Microsoft.VisualStudio.Component.Windows11SDK.22000", ]); } info!("running the Visual Studio install"); info!("rustup will continue once Visual Studio installation is complete\n"); let exit_status = cmd .spawn() .and_then(|mut child| child.wait()) .context("error running Visual Studio installer")?; if exit_status.success() { Ok(ContinueInstall::Yes) } else { match VsInstallError(exit_status.code().unwrap()) { err @ VsInstallError::REBOOT_REQUIRED => { // A reboot is required but the user opted to delay it. warn!("{}", err); Ok(ContinueInstall::Yes) } err @ VsInstallError::REBOOTING_NOW => { // The user is wanting to reboot right now, so we should // not continue the install. warn!("{}", err); info!("\nRun rustup-init after restart to continue install"); Ok(ContinueInstall::No) } err => { // 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).is_none(); let has_libs = has_windows_sdk_libs(); if have_msvc && has_libs { warn!("Visual Studio is installed but a problem occurred during installation"); warn!("{}", err); Ok(ContinueInstall::Yes) } else { Err(err).context("failed to install Visual Studio") } } } } } fn has_windows_sdk_libs() -> bool { if let Some(paths) = process().var_os("lib") { for mut path in split_paths(&paths) { path.push("kernel32.lib"); if path.exists() { return true; } } }; false } /// Run by rustup-gc-$num.exe to delete CARGO_HOME pub fn complete_windows_uninstall() -> Result { use std::process::Stdio; wait_for_parent()?; // Now that the parent has exited there are hopefully no more files open in CARGO_HOME let cargo_home = utils::cargo_home()?; utils::remove_dir("cargo_home", &cargo_home, &|_: Notification<'_>| ())?; // Now, run a *system* binary to inherit the DELETE_ON_CLOSE // handle to *this* process, then exit. The OS will delete the gc // exe when it exits. let rm_gc_exe = OsStr::new("net"); Command::new(rm_gc_exe) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn() .context(CLIError::WindowsUninstallMadness)?; Ok(utils::ExitCode(0)) } pub(crate) fn wait_for_parent() -> Result<()> { use std::io; use std::mem; use winapi::shared::minwindef::DWORD; use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; use winapi::um::processthreadsapi::{GetCurrentProcessId, OpenProcess}; use winapi::um::synchapi::WaitForSingleObject; use winapi::um::tlhelp32::{ CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS, }; use winapi::um::winbase::{INFINITE, WAIT_OBJECT_0}; use winapi::um::winnt::SYNCHRONIZE; unsafe { // Take a snapshot of system processes, one of which is ours // and contains our parent's pid let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if snapshot == INVALID_HANDLE_VALUE { let err = io::Error::last_os_error(); return Err(err).context(CLIError::WindowsUninstallMadness); } let snapshot = scopeguard::guard(snapshot, |h| { let _ = CloseHandle(h); }); let mut entry: PROCESSENTRY32 = mem::zeroed(); entry.dwSize = mem::size_of::() as DWORD; // Iterate over system processes looking for ours let success = Process32First(*snapshot, &mut entry); if success == 0 { let err = io::Error::last_os_error(); return Err(err).context(CLIError::WindowsUninstallMadness); } let this_pid = GetCurrentProcessId(); while entry.th32ProcessID != this_pid { let success = Process32Next(*snapshot, &mut entry); if success == 0 { let err = io::Error::last_os_error(); return Err(err).context(CLIError::WindowsUninstallMadness); } } // FIXME: Using the process ID exposes a race condition // wherein the parent process already exited and the OS // reassigned its ID. let parent_id = entry.th32ParentProcessID; // Get a handle to the parent process let parent = OpenProcess(SYNCHRONIZE, 0, parent_id); if parent.is_null() { // This just means the parent has already exited. return Ok(()); } let parent = scopeguard::guard(parent, |h| { let _ = CloseHandle(h); }); // Wait for our parent to exit let res = WaitForSingleObject(*parent, INFINITE); if res != WAIT_OBJECT_0 { let err = io::Error::last_os_error(); return Err(err).context(CLIError::WindowsUninstallMadness); } } Ok(()) } pub(crate) fn do_add_to_path() -> Result<()> { let new_path = _with_path_cargo_home_bin(_add_to_path)?; _apply_new_path(new_path) } fn _apply_new_path(new_path: Option>) -> Result<()> { use std::ptr; use winapi::shared::minwindef::*; use winapi::um::winuser::{ SendMessageTimeoutA, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE, }; let new_path = match new_path { Some(new_path) => new_path, None => return Ok(()), // No need to set the path }; let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)?; if new_path.is_empty() { environment.delete_value("PATH")?; } else { let reg_value = RegValue { bytes: to_winreg_bytes(new_path), vtype: RegType::REG_EXPAND_SZ, }; environment.set_raw_value("PATH", ®_value)?; } // Tell other processes to update their environment #[allow(clippy::unnecessary_cast)] unsafe { SendMessageTimeoutA( HWND_BROADCAST, WM_SETTINGCHANGE, 0 as WPARAM, "Environment\0".as_ptr() as LPARAM, SMTO_ABORTIFHUNG, 5000, ptr::null_mut(), ); } Ok(()) } // 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>> { use std::io; let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .context("Failed opening Environment key")?; let reg_value = environment.get_raw_value("PATH"); match reg_value { Ok(val) => { if let Some(s) = from_winreg_value(&val) { Ok(Some(s)) } else { warn!( "the registry key HKEY_CURRENT_USER\\Environment\\PATH is not a string. \ Not modifying the PATH variable" ); Ok(None) } } Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(Some(Vec::new())), Err(e) => Err(e).context(CLIError::WindowsUninstallMadness), } } // Returns None if the existing old_path does not need changing, otherwise // prepends the path_str to old_path, handling empty old_path appropriately. fn _add_to_path(old_path: Vec, path_str: Vec) -> Option> { if old_path.is_empty() { Some(path_str) } else if old_path .windows(path_str.len()) .any(|path| path == path_str) { None } else { let mut new_path = path_str; new_path.push(b';' as u16); new_path.extend_from_slice(&old_path); Some(new_path) } } // Returns None if the existing old_path does not need changing fn _remove_from_path(old_path: Vec, path_str: Vec) -> Option> { let idx = old_path .windows(path_str.len()) .position(|path| path == path_str)?; // If there's a trailing semicolon (likely, since we probably added one // during install), include that in the substring to remove. We don't search // for that to find the string, because if it's the last string in the path, // there may not be. let mut len = path_str.len(); if old_path.get(idx + path_str.len()) == Some(&(b';' as u16)) { len += 1; } let mut new_path = old_path[..idx].to_owned(); new_path.extend_from_slice(&old_path[idx + len..]); // Don't leave a trailing ; though, we don't want an empty string in the // path. if new_path.last() == Some(&(b';' as u16)) { new_path.pop(); } Some(new_path) } fn _with_path_cargo_home_bin(f: F) -> Result>> where F: FnOnce(Vec, Vec) -> Option>, { let windows_path = get_windows_path_var()?; let mut path_str = utils::cargo_home()?; path_str.push("bin"); Ok(windows_path .and_then(|old_path| f(old_path, OsString::from(path_str).encode_wide().collect()))) } pub(crate) fn do_remove_from_path() -> Result<()> { let new_path = _with_path_cargo_home_bin(_remove_from_path)?; _apply_new_path(new_path) } const RUSTUP_UNINSTALL_ENTRY: &str = r"Software\Microsoft\Windows\CurrentVersion\Uninstall\Rustup"; pub(crate) fn do_add_to_programs() -> Result<()> { use std::path::PathBuf; let key = RegKey::predef(HKEY_CURRENT_USER) .create_subkey(RUSTUP_UNINSTALL_ENTRY) .context("Failed creating uninstall key")? .0; // Don't overwrite registry if Rustup is already installed let prev = key .get_raw_value("UninstallString") .map(|val| from_winreg_value(&val)); if let Ok(Some(s)) = prev { let mut path = PathBuf::from(OsString::from_wide(&s)); path.pop(); if path.exists() { return Ok(()); } } let mut path = utils::cargo_home()?; path.push("bin\\rustup.exe"); let mut uninstall_cmd = OsString::from("\""); uninstall_cmd.push(path); uninstall_cmd.push("\" self uninstall"); let reg_value = RegValue { bytes: to_winreg_bytes(uninstall_cmd.encode_wide().collect()), vtype: RegType::REG_SZ, }; let current_version: &str = env!("CARGO_PKG_VERSION"); key.set_raw_value("UninstallString", ®_value) .context("Failed to set uninstall string")?; key.set_value("DisplayName", &"Rustup: the Rust toolchain installer") .context("Failed to set display name")?; key.set_value("DisplayVersion", ¤t_version) .context("Failed to set display version")?; Ok(()) } pub(crate) fn do_remove_from_programs() -> Result<()> { match RegKey::predef(HKEY_CURRENT_USER).delete_subkey_all(RUSTUP_UNINSTALL_ENTRY) { Ok(()) => Ok(()), Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), Err(e) => Err(anyhow!(e)), } } /// Convert a vector UCS-2 chars to a null-terminated UCS-2 string in bytes pub(crate) fn to_winreg_bytes(mut v: Vec) -> Vec { v.push(0); unsafe { std::slice::from_raw_parts(v.as_ptr().cast::(), v.len() * 2).to_vec() } } /// This is used to decode the value of HKCU\Environment\PATH. If that key is /// not REG_SZ | REG_EXPAND_SZ then this returns None. The winreg library itself /// does a lossy unicode conversion. pub(crate) fn from_winreg_value(val: &winreg::RegValue) -> Option> { use std::slice; match val.vtype { RegType::REG_SZ | RegType::REG_EXPAND_SZ => { // Copied from winreg let mut words = unsafe { #[allow(clippy::cast_ptr_alignment)] slice::from_raw_parts(val.bytes.as_ptr().cast::(), val.bytes.len() / 2) .to_owned() }; while words.last() == Some(&0) { words.pop(); } Some(words) } _ => None, } } pub(crate) fn run_update(setup_path: &Path) -> Result { Command::new(setup_path) .arg("--self-replace") .spawn() .context("unable to run updater")?; Ok(utils::ExitCode(0)) } pub(crate) fn self_replace() -> Result { wait_for_parent()?; install_bins()?; Ok(utils::ExitCode(0)) } // The last step of uninstallation is to delete *this binary*, // rustup.exe and the CARGO_HOME that contains it. On Unix, this // works fine. On Windows you can't delete files while they are open, // like when they are running. // // Here's what we're going to do: // - Copy rustup.exe to a temporary file in // CARGO_HOME/../rustup-gc-$random.exe. // - Open the gc exe with the FILE_FLAG_DELETE_ON_CLOSE and // FILE_SHARE_DELETE flags. This is going to be the last // file to remove, and the OS is going to do it for us. // This file is opened as inheritable so that subsequent // processes created with the option to inherit handles // will also keep them open. // - Run the gc exe, which waits for the original rustup.exe // process to close, then deletes CARGO_HOME. This process // has inherited a FILE_FLAG_DELETE_ON_CLOSE handle to itself. // - Finally, spawn yet another system binary with the inherit handles // flag, so *it* inherits the FILE_FLAG_DELETE_ON_CLOSE handle to // the gc exe. If the gc exe exits before the system exe then at // last it will be deleted when the handle closes. // // This is the DELETE_ON_CLOSE method from // https://www.catch22.net/tuts/win32/self-deleting-executables // // ... which doesn't actually work because Windows won't really // delete a FILE_FLAG_DELETE_ON_CLOSE process when it exits. // // .. augmented with this SO answer // https://stackoverflow.com/questions/10319526/understanding-a-self-deleting-program-in-c pub(crate) fn delete_rustup_and_cargo_home() -> Result<()> { use std::io; use std::mem; use std::ptr; use std::thread; use std::time::Duration; use winapi::shared::minwindef::DWORD; use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING}; use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; use winapi::um::minwinbase::SECURITY_ATTRIBUTES; use winapi::um::winbase::FILE_FLAG_DELETE_ON_CLOSE; use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, GENERIC_READ}; // CARGO_HOME, hopefully empty except for bin/rustup.exe let cargo_home = utils::cargo_home()?; // The rustup.exe bin let rustup_path = cargo_home.join(format!("bin/rustup{EXE_SUFFIX}")); // The directory containing CARGO_HOME let work_path = cargo_home .parent() .expect("CARGO_HOME doesn't have a parent?"); // Generate a unique name for the files we're about to move out // of CARGO_HOME. let numbah: u32 = rand::random(); let gc_exe = work_path.join(format!("rustup-gc-{numbah:x}.exe")); // Copy rustup (probably this process's exe) to the gc exe utils::copy_file(&rustup_path, &gc_exe)?; let gc_exe_win: Vec<_> = gc_exe.as_os_str().encode_wide().chain(Some(0)).collect(); // Make the sub-process opened by gc exe inherit its attribute. let mut sa = SECURITY_ATTRIBUTES { nLength: mem::size_of::() as DWORD, lpSecurityDescriptor: ptr::null_mut(), bInheritHandle: 1, }; let _g = unsafe { // Open an inheritable handle to the gc exe marked // FILE_FLAG_DELETE_ON_CLOSE. let gc_handle = CreateFileW( gc_exe_win.as_ptr(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, &mut sa, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, ptr::null_mut(), ); if gc_handle == INVALID_HANDLE_VALUE { let err = io::Error::last_os_error(); return Err(err).context(CLIError::WindowsUninstallMadness); } scopeguard::guard(gc_handle, |h| { let _ = CloseHandle(h); }) }; Command::new(gc_exe) .spawn() .context(CLIError::WindowsUninstallMadness)?; // The catch 22 article says we must sleep here to give // Windows a chance to bump the processes file reference // count. acrichto though is in disbelief and *demanded* that // we not insert a sleep. If Windows failed to uninstall // correctly it is because of him. // (.. and months later acrichto owes me a beer). thread::sleep(Duration::from_millis(100)); Ok(()) } #[cfg(test)] mod tests { use std::ffi::OsString; use std::os::windows::ffi::OsStrExt; use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; use winreg::{RegKey, RegValue}; use crate::currentprocess; use crate::test::with_saved_path; fn wide(str: &str) -> Vec { OsString::from(str).encode_wide().collect() } #[test] fn windows_install_does_not_add_path_twice() { assert_eq!( None, super::_add_to_path( wide(r"c:\users\example\.cargo\bin;foo"), wide(r"c:\users\example\.cargo\bin") ) ); } #[test] fn windows_handle_non_unicode_path() { let initial_path = vec![ 0xD800, // leading surrogate 0x0101, // bogus trailing surrogate 0x0000, // null ]; let cargo_home = wide(r"c:\users\example\.cargo\bin"); let final_path = [&cargo_home, &[b';' as u16][..], &initial_path].join(&[][..]); assert_eq!( &final_path, &super::_add_to_path(initial_path.clone(), cargo_home.clone(),).unwrap() ); assert_eq!( &initial_path, &super::_remove_from_path(final_path, cargo_home,).unwrap() ); } #[test] fn windows_path_regkey_type() { // per issue #261, setting PATH should use REG_EXPAND_SZ. let tp = Box::new(currentprocess::TestProcess::default()); with_saved_path(&mut || { currentprocess::with(tp.clone(), || { let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); environment.delete_value("PATH").unwrap(); { // Can't compare the Results as Eq isn't derived; thanks error-chain. #![allow(clippy::unit_cmp)] assert_eq!((), super::_apply_new_path(Some(wide("foo"))).unwrap()); } let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); let path = environment.get_raw_value("PATH").unwrap(); assert_eq!(path.vtype, RegType::REG_EXPAND_SZ); assert_eq!(super::to_winreg_bytes(wide("foo")), &path.bytes[..]); }) }); } #[test] fn windows_path_delete_key_when_empty() { use std::io; // during uninstall the PATH key may end up empty; if so we should // delete it. let tp = Box::new(currentprocess::TestProcess::default()); with_saved_path(&mut || { currentprocess::with(tp.clone(), || { let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); environment .set_raw_value( "PATH", &RegValue { bytes: super::to_winreg_bytes(wide("foo")), vtype: RegType::REG_EXPAND_SZ, }, ) .unwrap(); { // Can't compare the Results as Eq isn't derived; thanks error-chain. #![allow(clippy::unit_cmp)] assert_eq!((), super::_apply_new_path(Some(Vec::new())).unwrap()); } let reg_value = environment.get_raw_value("PATH"); match reg_value { Ok(_) => panic!("key not deleted"), Err(ref e) if e.kind() == io::ErrorKind::NotFound => {} Err(ref e) => panic!("error {e}"), } }) }); } #[test] fn windows_doesnt_mess_with_a_non_string_path() { // This writes an error, so we want a sink for it. let tp = Box::new(currentprocess::TestProcess { vars: [("HOME".to_string(), "/unused".to_string())] .iter() .cloned() .collect(), ..Default::default() }); with_saved_path(&mut || { currentprocess::with(tp.clone(), || { let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); let reg_value = RegValue { bytes: vec![0x12, 0x34], vtype: RegType::REG_BINARY, }; environment.set_raw_value("PATH", ®_value).unwrap(); // Ok(None) signals no change to the PATH setting layer assert_eq!( None, super::_with_path_cargo_home_bin(|_, _| panic!("called")).unwrap() ); }) }); assert_eq!( r"warning: the registry key HKEY_CURRENT_USER\Environment\PATH is not a string. Not modifying the PATH variable ", String::from_utf8(tp.get_stderr()).unwrap() ); } #[test] fn windows_treat_missing_path_as_empty() { // during install the PATH key may be missing; treat it as empty let tp = Box::new(currentprocess::TestProcess::default()); with_saved_path(&mut || { currentprocess::with(tp.clone(), || { let root = RegKey::predef(HKEY_CURRENT_USER); let environment = root .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap(); environment.delete_value("PATH").unwrap(); assert_eq!(Some(Vec::new()), super::get_windows_path_var().unwrap()); }) }); } #[test] fn windows_uninstall_removes_semicolon_from_path_prefix() { assert_eq!( wide("foo"), super::_remove_from_path( wide(r"c:\users\example\.cargo\bin;foo"), wide(r"c:\users\example\.cargo\bin"), ) .unwrap() ) } #[test] fn windows_uninstall_removes_semicolon_from_path_suffix() { assert_eq!( wide("foo"), super::_remove_from_path( wide(r"foo;c:\users\example\.cargo\bin"), wide(r"c:\users\example\.cargo\bin"), ) .unwrap() ) } } rustup-1.26.0/src/cli/setup_mode.rs000066400000000000000000000121151441327105200172110ustar00rootroot00000000000000use anyhow::Result; use clap::{builder::PossibleValuesParser, AppSettings, Arg, ArgAction, Command}; use super::common; use super::self_update::{self, InstallOpts}; use crate::dist::dist::Profile; use crate::process; use crate::utils::utils; pub fn main() -> Result { let args: Vec<_> = process().args().collect(); let arg1 = args.get(1).map(|a| &**a); // Secret command used during self-update. Not for users. if arg1 == Some("--self-replace") { return self_update::self_replace(); } // Internal testament dump used during CI. Not for users. if arg1 == Some("--dump-testament") { common::dump_testament()?; return Ok(utils::ExitCode(0)); } // NOTICE: If you change anything here, please make the same changes in rustup-init.sh let cli = Command::new("rustup-init") .version(common::version()) .about("The installer for rustup") .setting(AppSettings::DeriveDisplayOrder) .arg( Arg::new("verbose") .short('v') .long("verbose") .help("Enable verbose output") .action(ArgAction::SetTrue), ) .arg( Arg::new("quiet") .conflicts_with("verbose") .short('q') .long("quiet") .help("Disable progress output") .action(ArgAction::SetTrue), ) .arg( Arg::new("no-prompt") .short('y') .help("Disable confirmation prompt.") .action(ArgAction::SetTrue), ) .arg( Arg::new("default-host") .long("default-host") .takes_value(true) .help("Choose a default host triple"), ) .arg( Arg::new("default-toolchain") .long("default-toolchain") .takes_value(true) .help("Choose a default toolchain to install. Use 'none' to not install any toolchains at all"), ) .arg( Arg::new("profile") .long("profile") .value_parser(PossibleValuesParser::new(Profile::names())) .default_value(Profile::default_name()), ) .arg( Arg::new("components") .help("Component name to also install") .long("component") .short('c') .takes_value(true) .multiple_values(true) .use_value_delimiter(true) .action(ArgAction::Append), ) .arg( Arg::new("targets") .help("Target name to also install") .long("target") .short('t') .takes_value(true) .multiple_values(true) .use_value_delimiter(true) .action(ArgAction::Append), ) .arg( Arg::new("no-update-default-toolchain") .long("no-update-default-toolchain") .help("Don't update any existing default toolchain after install") .action(ArgAction::SetTrue), ) .arg( Arg::new("no-modify-path") .long("no-modify-path") .help("Don't configure the PATH environment variable") .action(ArgAction::SetTrue), ); let matches = match cli.try_get_matches_from(process().args_os()) { Ok(matches) => matches, Err(e) if e.kind() == clap::ErrorKind::DisplayHelp || e.kind() == clap::ErrorKind::DisplayVersion => { write!(process().stdout().lock(), "{e}")?; return Ok(utils::ExitCode(0)); } Err(e) => return Err(e.into()), }; let no_prompt = matches.get_flag("no-prompt"); let verbose = matches.get_flag("verbose"); let quiet = matches.get_flag("quiet"); let default_host = matches .get_one::("default-host") .map(ToOwned::to_owned); let default_toolchain = matches .get_one::("default-toolchain") .map(ToOwned::to_owned); let profile = matches .get_one::("profile") .expect("Unreachable: Clap should supply a default"); let no_modify_path = matches.get_flag("no-modify-path"); let no_update_toolchain = matches.get_flag("no-update-default-toolchain"); let components: Vec<_> = matches .get_many::("components") .map(|v| v.map(|s| &**s).collect()) .unwrap_or_else(Vec::new); let targets: Vec<_> = matches .get_many::("targets") .map(|v| v.map(|s| &**s).collect()) .unwrap_or_else(Vec::new); let opts = InstallOpts { default_host_triple: default_host, default_toolchain, profile: profile.to_owned(), no_modify_path, no_update_toolchain, components: &components, targets: &targets, }; if profile == "complete" { warn!("{}", common::WARN_COMPLETE_PROFILE); } self_update::install(no_prompt, verbose, quiet, opts) } rustup-1.26.0/src/cli/term2.rs000066400000000000000000000161541441327105200161050ustar00rootroot00000000000000//! This provides wrappers around the `StdoutTerminal` and `StderrTerminal` types //! that does not fail if `StdoutTerminal` etc can't be constructed, which happens //! if TERM isn't defined. use std::io; use std::ops::Deref; use std::sync::Mutex; use lazy_static::lazy_static; pub(crate) use term::color; pub(crate) use term::Attr; pub(crate) use term::Terminal; use crate::currentprocess::filesource::{Isatty, Writer}; use crate::process; mod termhack { // Things we should submit to term as improvements: here temporarily. use std::collections::HashMap; use std::io; use term::terminfo::TermInfo; use term::{StderrTerminal, StdoutTerminal, Terminal, TerminfoTerminal}; /// Return a Terminal object for T on this platform. /// If there is no terminfo and the platform requires terminfo, then None is returned. fn make_terminal( terminfo: Option, source: F, ) -> Option + Send>> where T: 'static + io::Write + Send, // Works around stdio instances being unclonable. F: Fn() -> T + Copy, { let result = terminfo .map(move |ti| TerminfoTerminal::new_with_terminfo(source(), ti)) .map(|t| Box::new(t) as Box + Send>); #[cfg(windows)] { result.or_else(|| { term::WinConsole::new(source()) .ok() .map(|t| Box::new(t) as Box + Send>) }) } #[cfg(not(windows))] { result } } pub(crate) fn make_terminal_with_fallback( terminfo: Option, source: F, ) -> Box + Send> where T: 'static + io::Write + Send, // Works around stdio instances being unclonable. F: Fn() -> T + Copy, { make_terminal(terminfo, source) .or_else(|| { let ti = TermInfo { names: vec![], bools: HashMap::new(), numbers: HashMap::new(), strings: HashMap::new(), }; let t = TerminfoTerminal::new_with_terminfo(source(), ti); Some(Box::new(t) as Box + Send>) }) .unwrap() } /// Return a Terminal wrapping stdout, or None if a terminal couldn't be /// opened. #[allow(unused)] pub(crate) fn stdout(terminfo: Option) -> Option> { make_terminal(terminfo, io::stdout) } /// Return a Terminal wrapping stderr, or None if a terminal couldn't be /// opened. #[allow(unused)] pub(crate) fn stderr(terminfo: Option) -> Option> { make_terminal(terminfo, io::stderr) } /// Return a Terminal wrapping stdout. #[allow(unused)] pub(crate) fn stdout_with_fallback(terminfo: Option) -> Box { make_terminal_with_fallback(terminfo, io::stdout) } /// Return a Terminal wrapping stderr. #[allow(unused)] pub(crate) fn stderr_with_fallback(terminfo: Option) -> Box { make_terminal_with_fallback(terminfo, io::stderr) } } // Decorator to: // - Disable all terminal controls on non-tty's // - Swallow errors when we try to use features a terminal doesn't have // such as setting colours when no TermInfo DB is present pub(crate) struct AutomationFriendlyTerminal(Box + Send>) where T: Isatty + io::Write; pub(crate) type StdoutTerminal = AutomationFriendlyTerminal>; pub(crate) type StderrTerminal = AutomationFriendlyTerminal>; macro_rules! swallow_unsupported { ( $call:expr ) => {{ use term::Error::*; match $call { Ok(()) | Err(ColorOutOfRange) | Err(NotSupported) => Ok(()), Err(e) => Err(e), } }}; } impl Isatty for Box { fn isatty(&self) -> bool { self.deref().isatty() } } impl term::Terminal for AutomationFriendlyTerminal where T: io::Write + Isatty, { type Output = T; fn fg(&mut self, color: color::Color) -> term::Result<()> { if !self.get_ref().isatty() { return Ok(()); } swallow_unsupported!(self.0.fg(color)) } fn bg(&mut self, color: color::Color) -> term::Result<()> { if !self.get_ref().isatty() { return Ok(()); } swallow_unsupported!(self.0.bg(color)) } fn attr(&mut self, attr: Attr) -> term::Result<()> { if !self.get_ref().isatty() { return Ok(()); } swallow_unsupported!(self.0.attr(attr)) } fn supports_attr(&self, attr: Attr) -> bool { self.0.supports_attr(attr) } fn reset(&mut self) -> term::Result<()> { if !self.get_ref().isatty() { return Ok(()); } swallow_unsupported!(self.0.reset()) } /// Returns true if reset is supported. fn supports_reset(&self) -> bool { self.0.supports_reset() } fn supports_color(&self) -> bool { self.0.supports_color() } fn cursor_up(&mut self) -> term::Result<()> { if !self.get_ref().isatty() { return Ok(()); } swallow_unsupported!(self.0.cursor_up()) } fn delete_line(&mut self) -> term::Result<()> { swallow_unsupported!(self.0.delete_line()) } fn carriage_return(&mut self) -> term::Result<()> { // This might leak control chars in !isatty? needs checking. swallow_unsupported!(self.0.carriage_return()) } fn get_ref(&self) -> &Self::Output { self.0.get_ref() } fn get_mut(&mut self) -> &mut Self::Output { self.0.get_mut() } /// Returns the contained stream, destroying the `Terminal` fn into_inner(self) -> Self::Output where Self: Sized, { unimplemented!() // self.0.into_inner().into_inner() } } impl io::Write for AutomationFriendlyTerminal { fn write(&mut self, buf: &[u8]) -> Result { self.0.write(buf) } fn flush(&mut self) -> Result<(), io::Error> { self.0.flush() } } lazy_static! { // Cache the terminfo database for performance. // Caching the actual terminals may be better, as on Windows terminal // detection is per-fd, but this at least avoids the IO subsystem and // caching the stdout instances is more complex static ref TERMINFO: Mutex> = Mutex::new(term::terminfo::TermInfo::from_env().ok()); } pub(crate) fn stdout() -> StdoutTerminal { let info_result = TERMINFO.lock().unwrap().clone(); AutomationFriendlyTerminal(termhack::make_terminal_with_fallback(info_result, || { process().stdout() })) } pub(crate) fn stderr() -> StderrTerminal { let info_result = TERMINFO.lock().unwrap().clone(); AutomationFriendlyTerminal(termhack::make_terminal_with_fallback(info_result, || { process().stderr() })) } rustup-1.26.0/src/cli/topical_doc.rs000066400000000000000000000124271441327105200173330ustar00rootroot00000000000000use std::ffi::OsString; use std::fs; use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context, Result}; struct DocData<'a> { topic: &'a str, subtopic: &'a str, root: &'a Path, } fn index_html(doc: &DocData<'_>, wpath: &Path) -> Option { let indexhtml = wpath.join("index.html"); match &doc.root.join(&indexhtml).exists() { true => Some(indexhtml), false => None, } } fn dir_into_vec(dir: &Path) -> Result> { let entries = fs::read_dir(dir).with_context(|| format!("Failed to read_dir {dir:?}"))?; let mut v = Vec::new(); for entry in entries { let entry = entry?; v.push(entry.file_name()); } Ok(v) } fn search_path(doc: &DocData<'_>, wpath: &Path, keywords: &[&str]) -> Result { let dir = &doc.root.join(wpath); if dir.is_dir() { let entries = dir_into_vec(dir)?; for k in keywords { let filename = &format!("{}.{}.html", k, doc.subtopic); if entries.contains(&OsString::from(filename)) { return Ok(dir.join(filename)); } } } Err(anyhow!(format!("No document for '{}'", doc.topic))) } pub(crate) fn local_path(root: &Path, topic: &str) -> Result { // The ORDER of keywords_top is used for the default search and should not // be changed. // https://github.com/rust-lang/rustup/issues/2076#issuecomment-546613036 let keywords_top = vec!["keyword", "primitive", "macro"]; let keywords_mod = ["fn", "struct", "trait", "enum", "type", "constant"]; let topic_vec: Vec<&str> = topic.split("::").collect(); let work_path = topic_vec.iter().fold(PathBuf::new(), |acc, e| acc.join(e)); let mut subtopic = topic_vec[topic_vec.len() - 1]; let mut forced_keyword = None; if topic_vec.len() == 1 { let split: Vec<&str> = topic.splitn(2, ':').collect(); if split.len() == 2 { forced_keyword = Some(vec![split[0]]); subtopic = split[1]; } } let doc = DocData { topic, subtopic, root, }; /************************** * Please ensure tests/mock/topical_doc_data.rs is UPDATED to reflect * any change in functionality. Argument File directory # len() == 1 Return index.html std std/index.html root/std core core/index.html root/core alloc alloc/index.html root/core KKK std/keyword.KKK.html root/std PPP std/primitive.PPP.html root/std MMM std/macro.MMM.html root/std # len() == 2 not ending in :: MMM std/macro.MMM.html root/std KKK std/keyword.KKK.html root/std PPP std/primitive.PPP.html root/std MMM core/macro.MMM.html root/core MMM alloc/macro.MMM.html root/alloc # If above fail, try module std::module std/module/index.html root/std/module core::module core/module/index.html root/core/module alloc::module alloc/module/index.html alloc/core/module # len() == 2, ending with :: std::module std/module/index.html root/std/module core::module core/module/index.html root/core/module alloc::module alloc/module/index.html alloc/core/module # len() > 2 # search for index.html in rel_path std::AAA::MMM std/AAA/MMM/index.html root/std/AAA/MMM # OR check if parent() dir exists and search for fn/struct/etc std::AAA::FFF std/AAA/fn.FFF9.html root/std/AAA std::AAA::SSS std/AAA/struct.SSS.html root/std/AAA core:AAA::SSS std/AAA/struct.SSS.html root/coreAAA alloc:AAA::SSS std/AAA/struct.SSS.html root/coreAAA std::AAA::TTT std/2222/trait.TTT.html root/std/AAA std::AAA::EEE std/2222/enum.EEE.html root/std/AAA std::AAA::TTT std/2222/type.TTT.html root/std/AAA std::AAA::CCC std/2222/constant.CCC.html root/std/AAA **************************/ // topic.split.count cannot be 0 let subpath_os_path = match topic_vec.len() { 1 => match topic { "std" | "core" | "alloc" => match index_html(&doc, &work_path) { Some(f) => f, None => bail!(format!("No document for '{}'", doc.topic)), }, _ => { let std = PathBuf::from("std"); let search_keywords = match forced_keyword { Some(k) => k, None => keywords_top, }; search_path(&doc, &std, &search_keywords)? } }, 2 => match index_html(&doc, &work_path) { Some(f) => f, None => { let parent = work_path.parent().unwrap(); search_path(&doc, parent, &keywords_top)? } }, _ => match index_html(&doc, &work_path) { Some(f) => f, None => { // len > 2, guaranteed to have a parent, safe to unwrap let parent = work_path.parent().unwrap(); search_path(&doc, parent, &keywords_mod)? } }, }; // The path and filename were validated to be existing on the filesystem. // It should be safe to unwrap, or worth panicking. Ok(subpath_os_path) } rustup-1.26.0/src/command.rs000066400000000000000000000031111441327105200157100ustar00rootroot00000000000000use std::ffi::OsStr; use std::io; use std::process::{self, Command}; use anyhow::{Context, Result}; use crate::errors::*; use crate::utils::utils::ExitCode; pub(crate) fn run_command_for_dir>( mut cmd: Command, arg0: &str, args: &[S], ) -> Result { cmd.args(args); // FIXME rust-lang/rust#32254. It's not clear to me // when and why this is needed. // TODO: currentprocess support for mocked file descriptor inheritance here: until // then tests that depend on rustups stdin being inherited won't work in-process. cmd.stdin(process::Stdio::inherit()); return exec(&mut cmd).with_context(|| RustupError::RunningCommand { name: OsStr::new(arg0).to_owned(), }); #[cfg(unix)] fn exec(cmd: &mut Command) -> io::Result { use std::os::unix::prelude::*; Err(cmd.exec()) } #[cfg(windows)] fn exec(cmd: &mut Command) -> io::Result { use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE}; use winapi::um::consoleapi::SetConsoleCtrlHandler; unsafe extern "system" fn ctrlc_handler(_: DWORD) -> BOOL { // Do nothing. Let the child process handle it. TRUE } unsafe { if SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) == FALSE { return Err(io::Error::new( io::ErrorKind::Other, "Unable to set console handler", )); } } let status = cmd.status()?; Ok(ExitCode(status.code().unwrap())) } } rustup-1.26.0/src/config.rs000066400000000000000000001150621441327105200155500ustar00rootroot00000000000000use std::borrow::Cow; use std::fmt::{self, Display}; use std::io; use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; use std::sync::Arc; use anyhow::{anyhow, bail, Context, Result}; use serde::Deserialize; use thiserror::Error as ThisError; use crate::cli::self_update::SelfUpdateMode; use crate::dist::download::DownloadCfg; use crate::dist::{ dist::{self, Profile}, temp, }; use crate::errors::RustupError; use crate::fallback_settings::FallbackSettings; use crate::notifications::*; use crate::process; use crate::settings::{Settings, SettingsFile, DEFAULT_METADATA_VERSION}; use crate::toolchain::{DistributableToolchain, Toolchain, UpdateStatus}; use crate::utils::utils; #[derive(Debug, ThisError)] enum OverrideFileConfigError { #[error("empty toolchain override file detected. Please remove it, or else specify the desired toolchain properties in the file")] Empty, #[error("missing toolchain properties in toolchain override file")] Invalid, #[error("error parsing override file")] Parsing, } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] struct OverrideFile { toolchain: ToolchainSection, } impl OverrideFile { fn is_empty(&self) -> bool { self.toolchain.is_empty() } } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] struct ToolchainSection { channel: Option, path: Option, components: Option>, targets: Option>, profile: Option, } impl ToolchainSection { fn is_empty(&self) -> bool { self.channel.is_none() && self.components.is_none() && self.targets.is_none() && self.path.is_none() } } impl> From for OverrideFile { fn from(channel: T) -> Self { let override_ = channel.into(); if Path::new(&override_).is_absolute() { Self { toolchain: ToolchainSection { path: Some(PathBuf::from(override_)), ..Default::default() }, } } else { Self { toolchain: ToolchainSection { channel: Some(override_), ..Default::default() }, } } } } #[derive(Debug)] pub(crate) enum OverrideReason { Environment, CommandLine, OverrideDB(PathBuf), ToolchainFile(PathBuf), } impl Display for OverrideReason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { match self { Self::Environment => write!(f, "environment override by RUSTUP_TOOLCHAIN"), Self::CommandLine => write!(f, "overridden by +toolchain on the command line"), Self::OverrideDB(path) => write!(f, "directory override for '{}'", path.display()), Self::ToolchainFile(path) => write!(f, "overridden by '{}'", path.display()), } } } #[derive(Default, Debug)] struct OverrideCfg<'a> { toolchain: Option>, components: Vec, targets: Vec, profile: Option, } impl<'a> OverrideCfg<'a> { fn from_file( cfg: &'a Cfg, cfg_path: Option>, file: OverrideFile, ) -> Result { Ok(Self { toolchain: match (file.toolchain.channel, file.toolchain.path) { (Some(name), None) => Some(Toolchain::from(cfg, &name)?), (None, Some(path)) => { if file.toolchain.targets.is_some() || file.toolchain.components.is_some() || file.toolchain.profile.is_some() { bail!( "toolchain options are ignored for path toolchain ({})", path.display() ) } Some(Toolchain::from_path(cfg, cfg_path, &path)?) } (Some(channel), Some(path)) => { bail!( "cannot specify both channel ({}) and path ({}) simultaneously", channel, path.display() ) } (None, None) => None, }, components: file.toolchain.components.unwrap_or_default(), targets: file.toolchain.targets.unwrap_or_default(), profile: file .toolchain .profile .as_deref() .map(dist::Profile::from_str) .transpose()?, }) } } pub(crate) const UNIX_FALLBACK_SETTINGS: &str = "/etc/rustup/settings.toml"; pub struct Cfg { profile_override: Option, pub rustup_dir: PathBuf, pub settings_file: SettingsFile, pub fallback_settings: Option, pub toolchains_dir: PathBuf, pub update_hash_dir: PathBuf, pub download_dir: PathBuf, pub temp_cfg: temp::Cfg, pub toolchain_override: Option, pub env_override: Option, pub dist_root_url: String, pub notify_handler: Arc)>, } impl Cfg { pub(crate) fn from_env(notify_handler: Arc)>) -> Result { // Set up the rustup home directory let rustup_dir = utils::rustup_home()?; utils::ensure_dir_exists("home", &rustup_dir, notify_handler.as_ref())?; let settings_file = SettingsFile::new(rustup_dir.join("settings.toml")); // Centralised file for multi-user systems to provide admin/distributor set initial values. let fallback_settings = if cfg!(not(windows)) { // If present, use the RUSTUP_OVERRIDE_UNIX_FALLBACK_SETTINGS environment // variable as settings path, or UNIX_FALLBACK_SETTINGS otherwise FallbackSettings::new( match process().var("RUSTUP_OVERRIDE_UNIX_FALLBACK_SETTINGS") { Ok(s) => PathBuf::from(s), Err(_) => PathBuf::from(UNIX_FALLBACK_SETTINGS), }, )? } else { None }; let toolchains_dir = rustup_dir.join("toolchains"); let update_hash_dir = rustup_dir.join("update-hashes"); let download_dir = rustup_dir.join("downloads"); // Environment override let env_override = process() .var("RUSTUP_TOOLCHAIN") .ok() .and_then(utils::if_not_empty); let dist_root_server = match process().var("RUSTUP_DIST_SERVER") { Ok(ref s) if !s.is_empty() => s.clone(), _ => { // For backward compatibility process() .var("RUSTUP_DIST_ROOT") .ok() .and_then(utils::if_not_empty) .map_or(Cow::Borrowed(dist::DEFAULT_DIST_ROOT), Cow::Owned) .as_ref() .trim_end_matches("/dist") .to_owned() } }; let notify_clone = notify_handler.clone(); let temp_cfg = temp::Cfg::new( rustup_dir.join("tmp"), dist_root_server.as_str(), Box::new(move |n| (notify_clone)(n.into())), ); let dist_root = dist_root_server + "/dist"; let cfg = Self { profile_override: None, rustup_dir, settings_file, fallback_settings, toolchains_dir, update_hash_dir, download_dir, temp_cfg, notify_handler, toolchain_override: None, env_override, dist_root_url: dist_root, }; // Run some basic checks against the constructed configuration // For now, that means simply checking that 'stable' can resolve // for the current configuration. cfg.resolve_toolchain("stable") .context("Unable parse configuration")?; Ok(cfg) } /// construct a download configuration pub(crate) fn download_cfg<'a>( &'a self, notify_handler: &'a dyn Fn(crate::dist::Notification<'_>), ) -> DownloadCfg<'a> { DownloadCfg { dist_root: &self.dist_root_url, temp_cfg: &self.temp_cfg, download_dir: &self.download_dir, notify_handler, } } pub(crate) fn set_profile_override(&mut self, profile: dist::Profile) { self.profile_override = Some(profile); } pub(crate) fn set_default(&self, toolchain: &str) -> Result<()> { self.settings_file.with_mut(|s| { s.default_toolchain = if toolchain == "none" { None } else { Some(toolchain.to_owned()) }; Ok(()) })?; (self.notify_handler)(Notification::SetDefaultToolchain(toolchain)); Ok(()) } pub(crate) fn set_profile(&mut self, profile: &str) -> Result<()> { match Profile::from_str(profile) { Ok(p) => { self.profile_override = None; self.settings_file.with_mut(|s| { s.profile = Some(p); Ok(()) })?; (self.notify_handler)(Notification::SetProfile(profile)); Ok(()) } Err(err) => Err(err), } } pub(crate) fn set_auto_self_update(&mut self, mode: &str) -> Result<()> { match SelfUpdateMode::from_str(mode) { Ok(update_mode) => { self.settings_file.with_mut(|s| { s.auto_self_update = Some(update_mode); Ok(()) })?; (self.notify_handler)(Notification::SetSelfUpdate(mode)); Ok(()) } Err(err) => Err(err), } } pub(crate) fn set_toolchain_override(&mut self, toolchain_override: &str) { self.toolchain_override = Some(toolchain_override.to_owned()); } // Returns a profile, if one exists in the settings file. // // Returns `Err` if the settings file could not be read or the profile is // invalid. Returns `Ok(...)` if there is a valid profile, and `Ok(Profile::Default)` // if there is no profile in the settings file. The last variant happens when // a user upgrades from a version of Rustup without profiles to a version of // Rustup with profiles. pub(crate) fn get_profile(&self) -> Result { if let Some(p) = self.profile_override { return Ok(p); } self.settings_file.with(|s| { let p = match s.profile { Some(p) => p, None => Profile::Default, }; Ok(p) }) } pub(crate) fn get_self_update_mode(&self) -> Result { self.settings_file.with(|s| { let mode = match &s.auto_self_update { Some(mode) => mode.clone(), None => SelfUpdateMode::Enable, }; Ok(mode) }) } pub(crate) fn get_toolchain(&self, name: &str, create_parent: bool) -> Result> { if create_parent { utils::ensure_dir_exists("toolchains", &self.toolchains_dir, &|n| { (self.notify_handler)(n) })?; } if name.is_empty() { anyhow::bail!("toolchain names must not be empty"); } Toolchain::from(self, name) } pub(crate) fn get_hash_file(&self, toolchain: &str, create_parent: bool) -> Result { if create_parent { utils::ensure_dir_exists( "update-hash", &self.update_hash_dir, self.notify_handler.as_ref(), )?; } Ok(self.update_hash_dir.join(toolchain)) } pub(crate) fn which_binary_by_toolchain( &self, toolchain_name: &str, binary: &str, ) -> Result { let toolchain = self.get_toolchain(toolchain_name, false)?; if toolchain.exists() { Ok(toolchain.binary_file(binary)) } else { Err(RustupError::ToolchainNotInstalled(toolchain_name.to_string()).into()) } } pub(crate) fn which_binary(&self, path: &Path, binary: &str) -> Result { let (toolchain, _) = self.find_or_install_override_toolchain_or_default(path)?; Ok(toolchain.binary_file(binary)) } pub(crate) fn upgrade_data(&self) -> Result<()> { let current_version = self.settings_file.with(|s| Ok(s.version.clone()))?; if current_version == DEFAULT_METADATA_VERSION { (self.notify_handler)(Notification::MetadataUpgradeNotNeeded(¤t_version)); return Ok(()); } (self.notify_handler)(Notification::UpgradingMetadata( ¤t_version, DEFAULT_METADATA_VERSION, )); match &*current_version { "2" => { // The toolchain installation format changed. Just delete them all. (self.notify_handler)(Notification::UpgradeRemovesToolchains); let dirs = utils::read_dir("toolchains", &self.toolchains_dir)?; for dir in dirs { let dir = dir.context("IO Error reading toolchains")?; utils::remove_dir("toolchain", &dir.path(), self.notify_handler.as_ref())?; } // Also delete the update hashes let files = utils::read_dir("update hashes", &self.update_hash_dir)?; for file in files { let file = file.context("IO Error reading update hashes")?; utils::remove_file("update hash", &file.path())?; } self.settings_file.with_mut(|s| { s.version = DEFAULT_METADATA_VERSION.to_owned(); Ok(()) }) } _ => Err(RustupError::UnknownMetadataVersion(current_version).into()), } } pub(crate) fn find_default(&self) -> Result>> { let opt_name = self.get_default()?; if let Some(name) = opt_name { let toolchain = Toolchain::from(self, &name)?; Ok(Some(toolchain)) } else { Ok(None) } } pub(crate) fn find_override( &self, path: &Path, ) -> Result, OverrideReason)>> { self.find_override_config(path).map(|opt| { opt.and_then(|(override_cfg, reason)| { override_cfg.toolchain.map(|toolchain| (toolchain, reason)) }) }) } fn find_override_config( &self, path: &Path, ) -> Result, OverrideReason)>> { let mut override_ = None; // First check toolchain override from command if let Some(ref name) = self.toolchain_override { override_ = Some((name.into(), OverrideReason::CommandLine)); } // Check RUSTUP_TOOLCHAIN if let Some(ref name) = self.env_override { override_ = Some((name.into(), OverrideReason::Environment)); } // Then walk up the directory tree from 'path' looking for either the // directory in override database, or a `rust-toolchain` file. if override_.is_none() { self.settings_file.with(|s| { override_ = self.find_override_from_dir_walk(path, s)?; Ok(()) })?; } if let Some((file, reason)) = override_ { // This is hackishly using the error chain to provide a bit of // extra context about what went wrong. The CLI will display it // on a line after the proximate error. let reason_err = match reason { OverrideReason::Environment => { "the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain" .to_string() } OverrideReason::CommandLine => { "the +toolchain on the command line specifies an uninstalled toolchain" .to_string() } OverrideReason::OverrideDB(ref path) => format!( "the directory override for '{}' specifies an uninstalled toolchain", utils::canonicalize_path(path, self.notify_handler.as_ref()).display(), ), OverrideReason::ToolchainFile(ref path) => format!( "the toolchain file at '{}' specifies an uninstalled toolchain", utils::canonicalize_path(path, self.notify_handler.as_ref()).display(), ), }; let cfg_file = if let OverrideReason::ToolchainFile(ref path) = reason { Some(path) } else { None }; let override_cfg = OverrideCfg::from_file(self, cfg_file, file)?; if let Some(toolchain) = &override_cfg.toolchain { // Overridden toolchains can be literally any string, but only // distributable toolchains will be auto-installed by the wrapping // code; provide a nice error for this common case. (default could // be set badly too, but that is much less common). if !toolchain.exists() && toolchain.is_custom() { // Strip the confusing NotADirectory error and only mention that the // override toolchain is not installed. return Err(anyhow!(reason_err)).with_context(|| { format!("override toolchain '{}' is not installed", toolchain.name()) }); } } Ok(Some((override_cfg, reason))) } else { Ok(None) } } fn find_override_from_dir_walk( &self, dir: &Path, settings: &Settings, ) -> Result> { let notify = self.notify_handler.as_ref(); let mut dir = Some(dir); while let Some(d) = dir { // First check the override database if let Some(name) = settings.dir_override(d, notify) { let reason = OverrideReason::OverrideDB(d.to_owned()); return Ok(Some((name.into(), reason))); } // Then look for 'rust-toolchain' or 'rust-toolchain.toml' let path_rust_toolchain = d.join("rust-toolchain"); let path_rust_toolchain_toml = d.join("rust-toolchain.toml"); let (toolchain_file, contents, parse_mode) = match ( utils::read_file("toolchain file", &path_rust_toolchain), utils::read_file("toolchain file", &path_rust_toolchain_toml), ) { (contents, Err(_)) => { // no `rust-toolchain.toml` exists (path_rust_toolchain, contents, ParseMode::Both) } (Err(_), Ok(contents)) => { // only `rust-toolchain.toml` exists (path_rust_toolchain_toml, Ok(contents), ParseMode::OnlyToml) } (Ok(contents), Ok(_)) => { // both `rust-toolchain` and `rust-toolchain.toml` exist notify(Notification::DuplicateToolchainFile { rust_toolchain: &path_rust_toolchain, rust_toolchain_toml: &path_rust_toolchain_toml, }); (path_rust_toolchain, Ok(contents), ParseMode::Both) } }; if let Ok(contents) = contents { let add_file_context = || format!("in {}", toolchain_file.to_string_lossy()); let override_file = Cfg::parse_override_file(contents, parse_mode) .with_context(add_file_context)?; if let Some(toolchain_name) = &override_file.toolchain.channel { let all_toolchains = self.list_toolchains().with_context(add_file_context)?; if !all_toolchains.iter().any(|s| s == toolchain_name) { // The given name is not resolvable as a toolchain, so // instead check it's plausible for installation later dist::validate_channel_name(toolchain_name) .with_context(add_file_context)?; } } let reason = OverrideReason::ToolchainFile(toolchain_file); return Ok(Some((override_file, reason))); } dir = d.parent(); } Ok(None) } fn parse_override_file>( contents: S, parse_mode: ParseMode, ) -> Result { let contents = contents.as_ref(); match (contents.lines().count(), parse_mode) { (0, _) => Err(anyhow!(OverrideFileConfigError::Empty)), (1, ParseMode::Both) => { let channel = contents.trim(); if channel.is_empty() { Err(anyhow!(OverrideFileConfigError::Empty)) } else { Ok(channel.into()) } } _ => { let override_file = toml::from_str::(contents) .context(OverrideFileConfigError::Parsing)?; if override_file.is_empty() { Err(anyhow!(OverrideFileConfigError::Invalid)) } else { Ok(override_file) } } } } pub(crate) fn find_or_install_override_toolchain_or_default( &self, path: &Path, ) -> Result<(Toolchain<'_>, Option)> { fn components_exist( distributable: &DistributableToolchain<'_>, components: &[&str], targets: &[&str], ) -> Result { let components_requested = !components.is_empty() || !targets.is_empty(); // If we're here, the toolchain exists on disk and is a dist toolchain // so we should attempt to load its manifest let desc = if let Some(desc) = distributable.get_toolchain_desc_with_manifest()? { desc } else { // We can't read the manifest. If this is a v1 install that's understandable // and we assume the components are all good, otherwise we need to have a go // at re-fetching the manifest to try again. return Ok(distributable.guess_v1_manifest()); }; match (desc.list_components(), components_requested) { // If the toolchain does not support components but there were components requested, bubble up the error (Err(e), true) => Err(e), // Otherwise check if all the components we want are installed (Ok(installed_components), _) => Ok(components.iter().all(|name| { installed_components.iter().any(|status| { let cname = status.component.short_name(&desc.manifest); let cname = cname.as_str(); let cnameim = status.component.short_name_in_manifest(); let cnameim = cnameim.as_str(); (cname == *name || cnameim == *name) && status.installed }) }) // And that all the targets we want are installed && targets.iter().all(|name| { installed_components .iter() .filter(|c| c.component.short_name_in_manifest() == "rust-std") .any(|status| { let ctarg = status.component.target(); (ctarg == *name) && status.installed }) })), _ => Ok(true), } } if let Some((toolchain, components, targets, reason, profile)) = match self.find_override_config(path)? { Some(( OverrideCfg { toolchain, components, targets, profile, }, reason, )) => { let default = if toolchain.is_none() { self.find_default()? } else { None }; toolchain .or(default) .map(|toolchain| (toolchain, components, targets, Some(reason), profile)) } None => self .find_default()? .map(|toolchain| (toolchain, vec![], vec![], None, None)), } { if toolchain.is_custom() { if !toolchain.exists() { return Err( RustupError::ToolchainNotInstalled(toolchain.name().to_string()).into(), ); } } else { let components: Vec<_> = components.iter().map(AsRef::as_ref).collect(); let targets: Vec<_> = targets.iter().map(AsRef::as_ref).collect(); let distributable = DistributableToolchain::new(&toolchain)?; if !toolchain.exists() || !components_exist(&distributable, &components, &targets)? { distributable.install_from_dist(true, false, &components, &targets, profile)?; } } Ok((toolchain, reason)) } else { // No override and no default set Err(RustupError::ToolchainNotSelected.into()) } } pub(crate) fn get_default(&self) -> Result> { let user_opt = self.settings_file.with(|s| Ok(s.default_toolchain.clone())); if let Some(fallback_settings) = &self.fallback_settings { match user_opt { Err(_) | Ok(None) => return Ok(fallback_settings.default_toolchain.clone()), _ => {} }; }; user_opt } pub(crate) fn list_toolchains(&self) -> Result> { if utils::is_directory(&self.toolchains_dir) { let mut toolchains: Vec<_> = utils::read_dir("toolchains", &self.toolchains_dir)? .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()) .collect(); utils::toolchain_sort(&mut toolchains); Ok(toolchains) } else { Ok(Vec::new()) } } pub(crate) fn list_channels(&self) -> Result>)>> { let toolchains = self.list_toolchains()?; // Convert the toolchain strings to Toolchain values let toolchains = toolchains.into_iter(); let toolchains = toolchains.map(|n| (n.clone(), self.get_toolchain(&n, true))); // Filter out toolchains that don't track a release channel Ok(toolchains .filter(|(_, ref t)| t.as_ref().map(Toolchain::is_tracking).unwrap_or(false)) .collect()) } pub(crate) fn update_all_channels( &self, force_update: bool, ) -> Result)>> { let channels = self.list_channels()?; let channels = channels.into_iter(); // Update toolchains and collect the results let channels = channels.map(|(n, t)| { let st = t.and_then(|t| { let distributable = DistributableToolchain::new(&t)?; let st = distributable.install_from_dist(force_update, false, &[], &[], None); if let Err(ref e) = st { (self.notify_handler)(Notification::NonFatalError(e)); } st }); (n, st) }); Ok(channels.collect()) } pub(crate) fn check_metadata_version(&self) -> Result<()> { utils::assert_is_directory(&self.rustup_dir)?; self.settings_file.with(|s| { (self.notify_handler)(Notification::ReadMetadataVersion(&s.version)); if s.version == DEFAULT_METADATA_VERSION { Ok(()) } else { Err(anyhow!( "rustup's metadata is out of date. run `rustup self upgrade-data`" )) } }) } pub(crate) fn toolchain_for_dir( &self, path: &Path, ) -> Result<(Toolchain<'_>, Option)> { self.find_or_install_override_toolchain_or_default(path) } pub(crate) fn create_command_for_dir(&self, path: &Path, binary: &str) -> Result { let (ref toolchain, _) = self.toolchain_for_dir(path)?; if let Some(cmd) = self.maybe_do_cargo_fallback(toolchain, binary)? { Ok(cmd) } else { // NB this can only fail in race conditions since we used toolchain // for dir. let installed = toolchain.as_installed_common()?; installed.create_command(binary) } } pub(crate) fn create_command_for_toolchain( &self, toolchain: &str, install_if_missing: bool, binary: &str, ) -> Result { let toolchain = self.get_toolchain(toolchain, false)?; if install_if_missing && !toolchain.exists() { let distributable = DistributableToolchain::new(&toolchain)?; distributable.install_from_dist(true, false, &[], &[], None)?; } if let Some(cmd) = self.maybe_do_cargo_fallback(&toolchain, binary)? { Ok(cmd) } else { // NB note this really can't fail due to to having installed the toolchain if needed let installed = toolchain.as_installed_common()?; installed.create_command(binary) } } // Custom toolchains don't have cargo, so here we detect that situation and // try to find a different cargo. fn maybe_do_cargo_fallback( &self, toolchain: &Toolchain<'_>, binary: &str, ) -> Result> { if !toolchain.is_custom() { return Ok(None); } if binary != "cargo" && binary != "cargo.exe" { return Ok(None); } let cargo_path = toolchain.path().join("bin/cargo"); let cargo_exe_path = toolchain.path().join("bin/cargo.exe"); if cargo_path.exists() || cargo_exe_path.exists() { return Ok(None); } // XXX: This could actually consider all distributable toolchains in principle. for fallback in &["nightly", "beta", "stable"] { let fallback = self.get_toolchain(fallback, false)?; if fallback.exists() { let distributable = DistributableToolchain::new(&fallback)?; let cmd = distributable.create_fallback_command("cargo", toolchain)?; return Ok(Some(cmd)); } } Ok(None) } pub(crate) fn set_default_host_triple(&self, host_triple: &str) -> Result<()> { // Ensure that the provided host_triple is capable of resolving // against the 'stable' toolchain. This provides early errors // if the supplied triple is insufficient / bad. dist::PartialToolchainDesc::from_str("stable")? .resolve(&dist::TargetTriple::new(host_triple))?; self.settings_file.with_mut(|s| { s.default_host_triple = Some(host_triple.to_owned()); Ok(()) }) } pub(crate) fn get_default_host_triple(&self) -> Result { Ok(self .settings_file .with(|s| { Ok(s.default_host_triple .as_ref() .map(|s| dist::TargetTriple::new(s))) })? .unwrap_or_else(dist::TargetTriple::from_host_or_build)) } pub(crate) fn resolve_toolchain(&self, name: &str) -> Result { // remove trailing slashes in toolchain name let normalized_name = name.trim_end_matches('/'); if let Ok(desc) = dist::PartialToolchainDesc::from_str(normalized_name) { let host = self.get_default_host_triple()?; Ok(desc.resolve(&host)?.to_string()) } else { Ok(normalized_name.to_owned()) } } } /// Specifies how a `rust-toolchain`/`rust-toolchain.toml` configuration file should be parsed. enum ParseMode { /// Only permit TOML format in a configuration file. /// /// This variant is used for `rust-toolchain.toml` files (with `.toml` extension). OnlyToml, /// Permit both the legacy format (i.e. just the channel name) and the TOML format in /// a configuration file. /// /// This variant is used for `rust-toolchain` files (no file extension) for backwards /// compatibility. Both, } #[cfg(test)] mod tests { use super::*; #[test] fn parse_legacy_toolchain_file() { let contents = "nightly-2020-07-10"; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: Some(contents.into()), path: None, components: None, targets: None, profile: None, } } ); } #[test] fn parse_toml_toolchain_file() { let contents = r#"[toolchain] channel = "nightly-2020-07-10" components = [ "rustfmt", "rustc-dev" ] targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ] profile = "default" "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), path: None, components: Some(vec!["rustfmt".into(), "rustc-dev".into()]), targets: Some(vec![ "wasm32-unknown-unknown".into(), "thumbv2-none-eabi".into() ]), profile: Some("default".into()), } } ); } #[test] fn parse_toml_toolchain_file_only_channel() { let contents = r#"[toolchain] channel = "nightly-2020-07-10" "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), path: None, components: None, targets: None, profile: None, } } ); } #[test] fn parse_toml_toolchain_file_only_path() { let contents = r#"[toolchain] path = "foobar" "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: None, path: Some("foobar".into()), components: None, targets: None, profile: None, } } ); } #[test] fn parse_toml_toolchain_file_empty_components() { let contents = r#"[toolchain] channel = "nightly-2020-07-10" components = [] "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), path: None, components: Some(vec![]), targets: None, profile: None, } } ); } #[test] fn parse_toml_toolchain_file_empty_targets() { let contents = r#"[toolchain] channel = "nightly-2020-07-10" targets = [] "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), path: None, components: None, targets: Some(vec![]), profile: None, } } ); } #[test] fn parse_toml_toolchain_file_no_channel() { let contents = r#"[toolchain] components = [ "rustfmt" ] "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert_eq!( result.unwrap(), OverrideFile { toolchain: ToolchainSection { channel: None, path: None, components: Some(vec!["rustfmt".into()]), targets: None, profile: None, } } ); } #[test] fn parse_empty_toml_toolchain_file() { let contents = r#" [toolchain] "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert!(matches!( result.unwrap_err().downcast::(), Ok(OverrideFileConfigError::Invalid) )); } #[test] fn parse_empty_toolchain_file() { let contents = ""; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert!(matches!( result.unwrap_err().downcast::(), Ok(OverrideFileConfigError::Empty) )); } #[test] fn parse_whitespace_toolchain_file() { let contents = " "; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert!(matches!( result.unwrap_err().downcast::(), Ok(OverrideFileConfigError::Empty) )); } #[test] fn parse_toml_syntax_error() { let contents = r#"[toolchain] channel = nightly "#; let result = Cfg::parse_override_file(contents, ParseMode::Both); assert!(matches!( result.unwrap_err().downcast::(), Ok(OverrideFileConfigError::Parsing) )); } } rustup-1.26.0/src/currentprocess.rs000066400000000000000000000174041441327105200173650ustar00rootroot00000000000000use std::boxed::Box; use std::cell::RefCell; use std::collections::HashMap; use std::default::Default; use std::fmt::Debug; use std::io::Cursor; use std::panic; use std::path::{Path, PathBuf}; use std::sync::Once; use std::sync::{Arc, Mutex}; use home::env as home; use rand::{thread_rng, Rng}; pub(crate) mod argsource; pub(crate) mod cwdsource; pub(crate) mod filesource; mod homethunk; pub(crate) mod varsource; use argsource::*; use cwdsource::*; use filesource::*; use varsource::*; /// An abstraction for the current process /// /// This provides replacements env::arg*, env::var*, and the standard files /// io::std* with traits that are customisable for tests. As a result any macros /// or code that have non-pluggable usage of those are incompatible with /// CurrentProcess and must not be used. That includes \[e\]println! as well as /// third party crates. /// /// CurrentProcess is used via an instance in a thread local variable; when /// making new threads, be sure to copy CurrentProcess::process() into the new /// thread before calling any code that may need to use a CurrentProcess /// function. /// /// Run some code using with: this will set the current instance, call your /// function, then finally reset the instance at the end before returning. /// /// Testing level interoperation with external code that depends on environment /// variables could be possible with a hypothetical `with_projected()` which /// would be a zero-cost operation in real processes, but in test processes will /// take a lock out to mutually exclude other code, then overwrite the current /// value of std::env::vars, restoring it at the end. However, the only use for /// that today is a test of cargo::home, which is now implemented in a separate /// crate, so we've just deleted the test. /// /// A thread local is used to permit the instance to be available to the entire /// rustup library without needing to explicitly wire this normally global state /// everywhere; and a trait object with dyn dispatch is likewise used to avoid /// needing to thread trait parameters across the entire code base: none of the /// methods are in performance critical loops (except perhaps progress bars - /// and even there we should be doing debouncing and managing update rates). /// The real trait is CurrentProcess; HomeProcess is a single trait because /// Box only allows autotraits to be added to it; so we use a subtrait to add /// home::Env in. pub trait HomeProcess: CurrentProcess + home::Env { fn clone_boxed(&self) -> Box; } // Machinery for Cloning boxes impl HomeProcess for T where T: 'static + CurrentProcess + home::Env + Clone, { fn clone_boxed(&self) -> Box { Box::new(T::clone(self)) } } impl Clone for Box { fn clone(&self) -> Self { HomeProcess::clone_boxed(self.as_ref()) } } pub trait CurrentProcess: ArgSource + CurrentDirSource + VarSource + StdoutSource + StderrSource + StdinSource + ProcessSource + Debug { fn clone_boxed(&self) -> Box; fn name(&self) -> Option; } // Machinery for Cloning boxes impl CurrentProcess for T where T: 'static + Clone + Debug + ArgSource + CurrentDirSource + VarSource + StdoutSource + StderrSource + StdinSource + ProcessSource, { fn clone_boxed(&self) -> Box { Box::new(T::clone(self)) } fn name(&self) -> Option { let arg0 = match self.var("RUSTUP_FORCE_ARG0") { Ok(v) => Some(v), Err(_) => self.args().next(), } .map(PathBuf::from); arg0.as_ref() .and_then(|a| a.file_stem()) .and_then(std::ffi::OsStr::to_str) .map(String::from) } } impl Clone for Box { fn clone(&self) -> Self { self.as_ref().clone_boxed() } } /// Obtain the current instance of CurrentProcess pub fn process() -> Box { CurrentProcess::clone_boxed(&*home_process()) } /// Obtain the current instance of HomeProcess pub(crate) fn home_process() -> Box { match PROCESS.with(|p| p.borrow().clone()) { None => panic!("No process instance"), Some(p) => p, } } static HOOK_INSTALLED: Once = Once::new(); /// Run a function in the context of a process definition. /// /// If the function panics, the process definition *in that thread* is cleared /// by an implicitly installed global panic hook. pub fn with(process: Box, f: F) -> R where F: FnOnce() -> R, { HOOK_INSTALLED.call_once(|| { let orig_hook = panic::take_hook(); panic::set_hook(Box::new(move |info| { clear_process(); orig_hook(info); })); }); PROCESS.with(|p| { if let Some(old_p) = &*p.borrow() { panic!("current process already set {old_p:?}"); } *p.borrow_mut() = Some(process); let result = f(); *p.borrow_mut() = None; result }) } /// Internal - for the panic hook only fn clear_process() { PROCESS.with(|p| p.replace(None)); } thread_local! { pub(crate) static PROCESS:RefCell>> = RefCell::new(None); } // PID related things pub trait ProcessSource { /// Returns a unique id for the process. /// /// Real process ids are <= u32::MAX. /// Test process ids are > u32::MAX fn id(&self) -> u64; } // ----------- real process ----------------- #[derive(Clone, Debug, Default)] pub struct OSProcess {} impl ProcessSource for OSProcess { fn id(&self) -> u64 { std::process::id() as u64 } } // ------------ test process ---------------- #[derive(Clone, Debug, Default)] pub struct TestProcess { pub cwd: PathBuf, pub args: Vec, pub vars: HashMap, pub id: u64, pub stdin: TestStdinInner, pub stdout: TestWriterInner, pub stderr: TestWriterInner, } impl TestProcess { pub fn new, A: AsRef>( cwd: P, args: &[A], vars: HashMap, stdin: &str, ) -> Self { TestProcess { cwd: cwd.as_ref().to_path_buf(), args: args.iter().map(|s| s.as_ref().to_string()).collect(), vars, id: TestProcess::new_id(), stdin: Arc::new(Mutex::new(Cursor::new(stdin.to_string()))), stdout: Arc::new(Mutex::new(Vec::new())), stderr: Arc::new(Mutex::new(Vec::new())), } } fn new_id() -> u64 { let low_bits: u64 = std::process::id() as u64; let mut rng = thread_rng(); let high_bits = rng.gen_range(0..u32::MAX) as u64; high_bits << 32 | low_bits } /// Extracts the stdout from the process pub fn get_stdout(&self) -> Vec { self.stdout .lock() .unwrap_or_else(|e| e.into_inner()) .clone() } /// Extracts the stderr from the process pub fn get_stderr(&self) -> Vec { self.stderr .lock() .unwrap_or_else(|e| e.into_inner()) .clone() } } impl ProcessSource for TestProcess { fn id(&self) -> u64 { self.id } } #[cfg(test)] mod tests { use std::collections::HashMap; use std::env; use super::{process, with, ProcessSource, TestProcess}; #[test] fn test_instance() { let proc = TestProcess::new( env::current_dir().unwrap(), &["foo", "bar", "baz"], HashMap::default(), "", ); with(Box::new(proc.clone()), || { assert_eq!(proc.id(), process().id(), "{:?} != {:?}", proc, process()) }); } } rustup-1.26.0/src/currentprocess/000077500000000000000000000000001441327105200170115ustar00rootroot00000000000000rustup-1.26.0/src/currentprocess/argsource.rs000066400000000000000000000031651441327105200213560ustar00rootroot00000000000000/// Abstracts over reading the current process environment variables as a /// zero-cost abstraction to support threaded in-process testing. use std::env; use std::ffi::OsString; use std::marker::PhantomData; pub trait ArgSource { fn args(&self) -> Box>; fn args_os(&self) -> Box>; } /// Implements ArgSource with `std::env::args` impl ArgSource for super::OSProcess { fn args(&self) -> Box> { Box::new(env::args()) } fn args_os(&self) -> Box> { Box::new(env::args_os()) } } /// Helper for ArgSource over `Vec` pub(crate) struct VecArgs { v: Vec, i: usize, _marker: PhantomData, } impl From<&Vec> for VecArgs { fn from(source: &Vec) -> Self { let v = source.clone(); VecArgs { v, i: 0, _marker: PhantomData, } } } impl> Iterator for VecArgs { type Item = T; fn next(&mut self) -> Option { if self.i == self.v.len() { return None; } let i = self.i; self.i += 1; Some(T::from(self.v[i].clone())) } fn size_hint(&self) -> (usize, Option) { (self.v.len(), Some(self.v.len())) } } impl ArgSource for super::TestProcess { fn args(&self) -> Box> { Box::new(VecArgs::::from(&self.args)) } fn args_os(&self) -> Box> { Box::new(VecArgs::::from(&self.args)) } } rustup-1.26.0/src/currentprocess/cwdsource.rs000066400000000000000000000010711441327105200213540ustar00rootroot00000000000000/// Abstracts over reading the current process cwd as a zero-cost abstraction to /// support threaded in-process testing. use std::env; use std::io; use std::path::PathBuf; pub trait CurrentDirSource { fn current_dir(&self) -> io::Result; } /// Implements VarSource with `std::env::env` impl CurrentDirSource for super::OSProcess { fn current_dir(&self) -> io::Result { env::current_dir() } } impl CurrentDirSource for super::TestProcess { fn current_dir(&self) -> io::Result { Ok(self.cwd.clone()) } } rustup-1.26.0/src/currentprocess/filesource.rs000066400000000000000000000107701441327105200215240ustar00rootroot00000000000000use std::io::{self, BufRead, Cursor, Read, Result, Write}; use std::sync::{Arc, Mutex, MutexGuard}; use crate::utils::tty; /// Stand-in for std::io::Stdin pub trait Stdin { fn lock(&self) -> Box; fn read_line(&self, buf: &mut String) -> Result; } /// Stand-in for std::io::StdinLock pub trait StdinLock: Read + BufRead {} /// Stand-in for std::io::stdin pub trait StdinSource { fn stdin(&self) -> Box; } // ----------------- OS support for stdin ----------------- impl StdinLock for io::StdinLock<'_> {} impl Stdin for io::Stdin { fn lock(&self) -> Box { Box::new(io::Stdin::lock(self)) } fn read_line(&self, buf: &mut String) -> Result { io::Stdin::read_line(self, buf) } } impl StdinSource for super::OSProcess { fn stdin(&self) -> Box { Box::new(io::stdin()) } } // ----------------------- test support for stdin ------------------ struct TestStdinLock<'a> { inner: MutexGuard<'a, Cursor>, } impl StdinLock for TestStdinLock<'_> {} impl Read for TestStdinLock<'_> { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.inner.read(buf) } } impl BufRead for TestStdinLock<'_> { fn fill_buf(&mut self) -> io::Result<&[u8]> { self.inner.fill_buf() } fn consume(&mut self, n: usize) { self.inner.consume(n) } } pub(crate) type TestStdinInner = Arc>>; struct TestStdin(TestStdinInner); impl Stdin for TestStdin { fn lock(&self) -> Box { Box::new(TestStdinLock { inner: self.0.lock().unwrap_or_else(|e| e.into_inner()), }) } fn read_line(&self, buf: &mut String) -> Result { self.lock().read_line(buf) } } impl StdinSource for super::TestProcess { fn stdin(&self) -> Box { Box::new(TestStdin(self.stdin.clone())) } } // -------------- stdout ------------------------------- pub trait Isatty { fn isatty(&self) -> bool; } /// Stand-in for std::io::StdoutLock pub trait WriterLock: Write {} /// Stand-in for std::io::Stdout pub trait Writer: Write + Isatty + Send { fn lock(&self) -> Box; } /// Stand-in for std::io::stdout pub trait StdoutSource { fn stdout(&self) -> Box; } // -------------- stderr ------------------------------- /// Stand-in for std::io::stderr pub trait StderrSource { fn stderr(&self) -> Box; } // ----------------- OS support for writers ----------------- impl WriterLock for io::StdoutLock<'_> {} impl Writer for io::Stdout { fn lock(&self) -> Box { Box::new(io::Stdout::lock(self)) } } impl Isatty for io::Stdout { fn isatty(&self) -> bool { tty::stdout_isatty() } } impl StdoutSource for super::OSProcess { fn stdout(&self) -> Box { Box::new(io::stdout()) } } impl WriterLock for io::StderrLock<'_> {} impl Writer for io::Stderr { fn lock(&self) -> Box { Box::new(io::Stderr::lock(self)) } } impl Isatty for io::Stderr { fn isatty(&self) -> bool { tty::stderr_isatty() } } impl StderrSource for super::OSProcess { fn stderr(&self) -> Box { Box::new(io::stderr()) } } // ----------------------- test support for writers ------------------ struct TestWriterLock<'a> { inner: MutexGuard<'a, Vec>, } impl WriterLock for TestWriterLock<'_> {} impl Write for TestWriterLock<'_> { fn write(&mut self, buf: &[u8]) -> Result { self.inner.write(buf) } fn flush(&mut self) -> Result<()> { Ok(()) } } pub(crate) type TestWriterInner = Arc>>; struct TestWriter(TestWriterInner); impl Writer for TestWriter { fn lock(&self) -> Box { Box::new(TestWriterLock { inner: self.0.lock().unwrap_or_else(|e| e.into_inner()), }) } } impl Write for TestWriter { fn write(&mut self, buf: &[u8]) -> Result { self.lock().write(buf) } fn flush(&mut self) -> Result<()> { Ok(()) } } impl Isatty for TestWriter { fn isatty(&self) -> bool { false } } impl StdoutSource for super::TestProcess { fn stdout(&self) -> Box { Box::new(TestWriter(self.stdout.clone())) } } impl StderrSource for super::TestProcess { fn stderr(&self) -> Box { Box::new(TestWriter(self.stderr.clone())) } } rustup-1.26.0/src/currentprocess/homethunk.rs000066400000000000000000000024011441327105200213560ustar00rootroot00000000000000/// Adapts currentprocess to the trait home::Env use std::ffi::OsString; use std::io; use std::ops::Deref; use std::path::PathBuf; use home::env as home; use super::CurrentDirSource; use super::HomeProcess; use super::OSProcess; use super::TestProcess; use super::VarSource; impl home::Env for Box { fn home_dir(&self) -> Option { (**self).home_dir() } fn current_dir(&self) -> Result { home::Env::current_dir(&(**self)) } fn var_os(&self, key: &str) -> Option { home::Env::var_os(&(**self), key) } } impl home::Env for TestProcess { fn home_dir(&self) -> Option { self.var("HOME").ok().map(|v| v.into()) } fn current_dir(&self) -> Result { CurrentDirSource::current_dir(self.deref()) } fn var_os(&self, key: &str) -> Option { VarSource::var_os(self.deref(), key) } } impl home::Env for OSProcess { fn home_dir(&self) -> Option { home::OS_ENV.home_dir() } fn current_dir(&self) -> Result { home::OS_ENV.current_dir() } fn var_os(&self, key: &str) -> Option { home::OS_ENV.var_os(key) } } rustup-1.26.0/src/currentprocess/varsource.rs000066400000000000000000000022721441327105200213730ustar00rootroot00000000000000/// Abstracts over reading the current process environment variables as a /// zero-cost abstraction to support threaded in-process testing. use std::env; use std::ffi::OsString; pub trait VarSource { // In order to support dyn dispatch we use concrete types rather than the // stdlib signature. fn var(&self, key: &str) -> std::result::Result; fn var_os(&self, key: &str) -> Option; } /// Implements VarSource with `std::env::env` impl VarSource for super::OSProcess { fn var(&self, key: &str) -> std::result::Result { env::var(key) } fn var_os(&self, key: &str) -> Option { env::var_os(key) } } impl VarSource for super::TestProcess { fn var(&self, key: &str) -> std::result::Result { match self.var_os(key) { None => Err(env::VarError::NotPresent), // safe because we know this only has String in it. Some(key) => Ok(key.into_string().unwrap()), } } fn var_os(&self, key: &str) -> Option { self.vars .get(key) .map(|s_ref| OsString::from(s_ref.clone())) } } rustup-1.26.0/src/diskio/000077500000000000000000000000001441327105200152125ustar00rootroot00000000000000rustup-1.26.0/src/diskio/immediate.rs000066400000000000000000000164271441327105200175300ustar00rootroot00000000000000/// Immediate IO model: performs IO in the current thread. /// /// Use for diagnosing bugs or working around any unexpected issues with the /// threaded code paths. use std::{ fmt::Debug, fs::{File, OpenOptions}, io::{self, Write}, path::Path, sync::{Arc, Mutex}, time::Instant, }; use super::{CompletedIo, Executor, FileBuffer, Item}; #[derive(Debug)] pub(crate) struct _IncrementalFileState { completed_chunks: Vec, err: Option>, item: Option, finished: bool, } pub(super) type IncrementalFileState = Arc>>; #[derive(Default, Debug)] pub(crate) struct ImmediateUnpacker { incremental_state: IncrementalFileState, } impl ImmediateUnpacker { pub(crate) fn new() -> Self { Self { ..Default::default() } } fn deque(&self) -> Box> { let mut guard = self.incremental_state.lock().unwrap(); // incremental file in progress if let Some(ref mut state) = *guard { // Case 1: pending errors if state.finished { let mut item = state.item.take().unwrap(); if state.err.is_some() { let err = state.err.take().unwrap(); item.result = err; } item.finish = item .start .map(|s| Instant::now().saturating_duration_since(s)); if state.finished { *guard = None; } Box::new(Some(CompletedIo::Item(item)).into_iter()) } else { // Case 2: pending chunks (which might be empty) let mut completed_chunks = vec![]; completed_chunks.append(&mut state.completed_chunks); Box::new(completed_chunks.into_iter().map(CompletedIo::Chunk)) } } else { Box::new(None.into_iter()) } } } impl Executor for ImmediateUnpacker { fn dispatch(&self, mut item: Item) -> Box + '_> { item.result = match &mut item.kind { super::Kind::Directory => super::create_dir(&item.full_path), super::Kind::File(ref contents) => { if let super::FileBuffer::Immediate(ref contents) = &contents { super::write_file(&item.full_path, contents, item.mode) } else { unreachable!() } } super::Kind::IncrementalFile(_incremental_file) => { return { // If there is a pending error, return it, otherwise stash the // Item for eventual return when the file is finished. let mut guard = self.incremental_state.lock().unwrap(); if let Some(ref mut state) = *guard { if state.err.is_some() { let err = state.err.take().unwrap(); item.result = err; item.finish = item .start .map(|s| Instant::now().saturating_duration_since(s)); *guard = None; Box::new(Some(CompletedIo::Item(item)).into_iter()) } else { state.item = Some(item); Box::new(None.into_iter()) } } else { unreachable!(); } }; } }; item.finish = item .start .map(|s| Instant::now().saturating_duration_since(s)); Box::new(Some(CompletedIo::Item(item)).into_iter()) } fn join(&mut self) -> Box> { self.deque() } fn completed(&self) -> Box> { self.deque() } fn incremental_file_state(&self) -> super::IncrementalFileState { let mut state = self.incremental_state.lock().unwrap(); if state.is_some() { unreachable!(); } else { *state = Some(_IncrementalFileState { completed_chunks: vec![], err: None, item: None, finished: false, }); super::IncrementalFileState::Immediate(self.incremental_state.clone()) } } fn get_buffer(&mut self, capacity: usize) -> super::FileBuffer { super::FileBuffer::Immediate(Vec::with_capacity(capacity)) } fn buffer_available(&self, _len: usize) -> bool { true } #[cfg(test)] fn buffer_used(&self) -> usize { 0 } } /// The non-shared state for writing a file incrementally #[derive(Debug)] pub(super) struct IncrementalFileWriter { state: IncrementalFileState, file: Option, path_display: String, } impl IncrementalFileWriter { #[allow(unused_variables)] pub(crate) fn new>( path: P, mode: u32, state: IncrementalFileState, ) -> std::result::Result { let mut opts = OpenOptions::new(); #[cfg(unix)] { use std::os::unix::fs::OpenOptionsExt; opts.mode(mode); } let path = path.as_ref(); let path_display = format!("{}", path.display()); let file = Some({ trace_scoped!("creat", "name": path_display); opts.write(true).create(true).truncate(true).open(path)? }); Ok(IncrementalFileWriter { state, file, path_display, }) } pub(crate) fn chunk_submit(&mut self, chunk: FileBuffer) -> bool { if (self.state.lock().unwrap()).is_none() { return false; } let chunk = if let FileBuffer::Immediate(v) = chunk { v } else { unreachable!() }; match self.write(chunk) { Ok(v) => v, Err(e) => { let mut state = self.state.lock().unwrap(); if let Some(ref mut state) = *state { state.err.replace(Err(e)); state.finished = true; false } else { false } } } } fn write(&mut self, chunk: Vec) -> std::result::Result { let mut state = self.state.lock().unwrap(); if let Some(ref mut state) = *state { if let Some(ref mut file) = self.file.as_mut() { // Length 0 vector is used for clean EOF signalling. if chunk.is_empty() { trace_scoped!("close", "name:": self.path_display); drop(std::mem::take(&mut self.file)); state.finished = true; } else { trace_scoped!("write_segment", "name": self.path_display, "len": chunk.len()); file.write_all(&chunk)?; state.completed_chunks.push(chunk.len()); } Ok(true) } else { Ok(false) } } else { unreachable!(); } } } rustup-1.26.0/src/diskio/mod.rs000066400000000000000000000411141441327105200163400ustar00rootroot00000000000000/// Disk IO abstraction for rustup. /// /// This exists to facilitate high performance extraction even though OS's are /// imperfect beasts. For detailed design notes see the module source. // // When performing IO we have a choice: // - perform some IO in this thread // - dispatch some or all IO to another thread // known tradeoffs: // NFS: network latency incurred on create, chmod, close calls // WSLv1: Defender latency incurred on close calls; mutex shared with create calls // Windows: Defender latency incurred on close calls // Unix: limited open file count // Defender : CPU limited, so more service points than cores brings no gain. // Some machines: IO limited, more service points than cores brings more efficient // Hello world footprint ~350MB, so around 400MB to install is considered ok. // IO utilisation. // All systems: dispatching to a thread has some overhead. // Basic idea then is a locally measured congestion control problem. // Underlying system has two // dimensions - how much work we have queued, and how much work we execute // at once. Queued work is both memory footprint, and unless each executor // is performing complex logic, potentially concurrent work. // Single core machines - thread anyway, they probably don't have SSDs? // How many service points? Blocking latency due to networks and disks // is independent of CPU: more threads will garner more throughput up // to actual resource service capability. // so: // a) measure time around each IO op from dispatch to completion. // b) create more threads than CPUs - 2x for now (because threadpool // doesn't allow creating dynamically), with very shallow stacks // (say 1MB) // c) keep adding work while the P95? P80? of completion stays the same // when pNN starts to increase either (i) we've saturated the system // or (ii) other work coming in has saturated the system or (iii) this // sort of work is a lot harder to complete. We use NN<100 to avoid // having jitter throttle us inappropriately. We use a high NN to // avoid making the system perform poorly for the user / other users // on shared components. Perhaps time-to-completion should be scaled by size. // d) if we have a lot of (iii) we should respond to it the same as (i), so // lets reduce this to (i) and (ii). Being unable to tell the difference // between load we created and others, we have to throttle back when // the system saturates. Our most throttled position will be one service // worker: dispatch IO, extract the next text, wait for IO completion, // repeat. // e) scaling up and down: TCP's lessons here are pretty good. So exponential // up - single thread and measure. two, 4 etc. When Pnn goes bad back off // for a time and then try again with linear increase (it could be case (ii) // - lots of room to experiment here; working with a time based approach is important // as that is the only way we can detect saturation: we are not facing // loss or errors in this model. // f) data gathering: record (name, bytes, start, duration) // write to disk afterwards as a csv file? pub(crate) mod immediate; #[cfg(test)] mod test; pub(crate) mod threaded; use std::io::{self, Write}; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::sync::mpsc::Receiver; use std::time::{Duration, Instant}; use std::{fmt::Debug, fs::OpenOptions}; use anyhow::{Context, Result}; use crate::process; use crate::utils::notifications::Notification; use threaded::PoolReference; /// Carries the implementation specific data for complete file transfers into the executor. #[derive(Debug)] pub(crate) enum FileBuffer { Immediate(Vec), // A reference to the object in the pool, and a handle to write to it Threaded(PoolReference), } impl FileBuffer { /// All the buffers space to be re-used when the last reference to it is dropped. pub(crate) fn clear(&mut self) { if let FileBuffer::Threaded(ref mut contents) = self { contents.clear() } } pub(crate) fn len(&self) -> usize { match self { FileBuffer::Immediate(ref vec) => vec.len(), FileBuffer::Threaded(PoolReference::Owned(owned, _)) => owned.len(), FileBuffer::Threaded(PoolReference::Mut(mutable, _)) => mutable.len(), } } pub(crate) fn finished(self) -> Self { match self { FileBuffer::Threaded(PoolReference::Mut(mutable, pool)) => { FileBuffer::Threaded(PoolReference::Owned(mutable.downgrade(), pool)) } _ => self, } } } impl Deref for FileBuffer { type Target = Vec; fn deref(&self) -> &Self::Target { match self { FileBuffer::Immediate(ref vec) => vec, FileBuffer::Threaded(PoolReference::Owned(owned, _)) => owned, FileBuffer::Threaded(PoolReference::Mut(mutable, _)) => mutable, } } } impl DerefMut for FileBuffer { fn deref_mut(&mut self) -> &mut Self::Target { match self { FileBuffer::Immediate(ref mut vec) => vec, FileBuffer::Threaded(PoolReference::Owned(_, _)) => { unimplemented!() } FileBuffer::Threaded(PoolReference::Mut(mutable, _)) => mutable, } } } pub(crate) const IO_CHUNK_SIZE: usize = 16_777_216; /// Carries the implementation specific channel data into the executor. #[derive(Debug)] pub(crate) enum IncrementalFile { ImmediateReceiver, ThreadedReceiver(Receiver), } // The basic idea is that in single threaded mode we get this pattern: // package budget io-layer // +<-claim-> // +-submit--------+ | write // +-complete------+ // + // .. loop .. // In thread mode with lots of memory we want the following: // +<-claim-> // +-submit--------+ // +<-claim-> // +-submit--------+ // .. loop .. | writes // +-complete------+ // + // +-complete------+ // + // In thread mode with limited memory we want the following: // +<-claim-> // +-submit--------+ // +<-claim-> // +-submit--------+ // .. loop up to budget .. | writes // +-complete------+ // + // +<-claim-> // +-submit--------+ // .. loop etc .. // // lastly we want pending IOs such as directory creation to be able to complete in the same way, so a chunk completion // needs to be able to report back in the same fashion; folding it into the same enum will make the driver code easier to write. // // The implementation is done via a pair of MPSC channels. One to send data to write. In // the immediate model, acknowledgements are sent after doing the write immediately. In the threaded model, // acknowledgements are sent after the write completes in the thread pool handler. In the packages code the inner that // handles iops and continues processing incremental mode files handles the connection between the acks and the budget. // Error reporting is passed through the regular completion port, to avoid creating a new special case. /// What kind of IO operation to perform #[derive(Debug)] pub(crate) enum Kind { Directory, File(FileBuffer), IncrementalFile(IncrementalFile), } /// The details of the IO operation #[derive(Debug)] pub(crate) struct Item { /// The path to operate on pub(crate) full_path: PathBuf, /// The operation to perform pub(crate) kind: Kind, /// When the operation started start: Option, /// Amount of time the operation took to finish finish: Option, /// The result of the operation (could now be factored into CompletedIO...) pub(crate) result: io::Result<()>, /// The mode to apply mode: u32, } #[derive(Debug)] pub(crate) enum CompletedIo { /// A submitted Item has completed Item(Item), /// An IncrementalFile has completed a single chunk Chunk(usize), } impl Item { pub(crate) fn make_dir(full_path: PathBuf, mode: u32) -> Self { Self { full_path, kind: Kind::Directory, start: None, finish: None, result: Ok(()), mode, } } pub(crate) fn write_file(full_path: PathBuf, mode: u32, content: FileBuffer) -> Self { Self { full_path, kind: Kind::File(content), start: None, finish: None, result: Ok(()), mode, } } pub(crate) fn write_file_segmented<'a>( full_path: PathBuf, mode: u32, state: IncrementalFileState, ) -> Result<(Self, Box bool + 'a>)> { let (chunk_submit, content_callback) = state.incremental_file_channel(&full_path, mode)?; let result = Self { full_path, kind: Kind::IncrementalFile(content_callback), start: None, finish: None, result: Ok(()), mode, }; Ok((result, Box::new(chunk_submit))) } } // This could be a boxed trait object perhaps... but since we're looking at // rewriting this all into an aio layer anyway, and not looking at plugging // different backends in at this time, it can keep. /// Implementation specific state for incremental file writes. This effectively /// just allows the immediate codepath to get access to the Arc referenced state /// without holding a lifetime reference to the executor, as the threaded code /// path is all message passing. pub(crate) enum IncrementalFileState { Threaded, Immediate(immediate::IncrementalFileState), } impl IncrementalFileState { /// Get a channel for submitting incremental file chunks to the executor fn incremental_file_channel( &self, path: &Path, mode: u32, ) -> Result<(Box bool>, IncrementalFile)> { use std::sync::mpsc::channel; match *self { IncrementalFileState::Threaded => { let (tx, rx) = channel::(); let content_callback = IncrementalFile::ThreadedReceiver(rx); let chunk_submit = move |chunk: FileBuffer| tx.send(chunk).is_ok(); Ok((Box::new(chunk_submit), content_callback)) } IncrementalFileState::Immediate(ref state) => { let content_callback = IncrementalFile::ImmediateReceiver; let mut writer = immediate::IncrementalFileWriter::new(path, mode, state.clone())?; let chunk_submit = move |chunk: FileBuffer| writer.chunk_submit(chunk); Ok((Box::new(chunk_submit), content_callback)) } } } } /// Trait object for performing IO. At this point the overhead /// of trait invocation is not a bottleneck, but if it becomes /// one we could consider an enum variant based approach instead. pub(crate) trait Executor { /// Perform a single operation. /// During overload situations previously queued items may /// need to be completed before the item is accepted: /// consume the returned iterator. fn execute(&self, mut item: Item) -> Box + '_> { item.start = Some(Instant::now()); self.dispatch(item) } /// Actually dispatch an operation. /// This is called by the default execute() implementation and /// should not be called directly. fn dispatch(&self, item: Item) -> Box + '_>; /// Wrap up any pending operations and iterate over them. /// All operations submitted before the join will have been /// returned either through ready/complete or join once join /// returns. fn join(&mut self) -> Box + '_>; /// Iterate over completed items. fn completed(&self) -> Box + '_>; /// Get any state needed for incremental file processing fn incremental_file_state(&self) -> IncrementalFileState; /// Get a disk buffer E.g. this gets the right sized pool object for /// optimized situations, or just a malloc when optimisations are off etc /// etc. fn get_buffer(&mut self, len: usize) -> FileBuffer; /// Query the memory budget to see if a particular size buffer is available fn buffer_available(&self, len: usize) -> bool; #[cfg(test)] /// Query the memory budget to see how much of the buffer pool is in use fn buffer_used(&self) -> usize; } /// Trivial single threaded IO to be used from executors. /// (Crazy sophisticated ones can obviously ignore this) pub(crate) fn perform(item: &mut Item, chunk_complete_callback: F) { // directories: make them, TODO: register with the dir existence cache. // Files, write them. item.result = match &mut item.kind { Kind::Directory => create_dir(&item.full_path), Kind::File(ref mut contents) => { contents.clear(); match contents { FileBuffer::Immediate(ref contents) => { write_file(&item.full_path, contents, item.mode) } FileBuffer::Threaded(ref mut contents) => { write_file(&item.full_path, contents, item.mode) } } } Kind::IncrementalFile(incremental_file) => write_file_incremental( &item.full_path, incremental_file, item.mode, chunk_complete_callback, ), }; item.finish = item .start .map(|s| Instant::now().saturating_duration_since(s)); } #[allow(unused_variables)] pub(crate) fn write_file, C: AsRef<[u8]>>( path: P, contents: C, mode: u32, ) -> io::Result<()> { let mut opts = OpenOptions::new(); #[cfg(unix)] { use std::os::unix::fs::OpenOptionsExt; opts.mode(mode); } let path = path.as_ref(); let path_display = format!("{}", path.display()); let mut f = { trace_scoped!("creat", "name": path_display); opts.write(true).create(true).truncate(true).open(path)? }; let contents = contents.as_ref(); let len = contents.len(); { trace_scoped!("write", "name": path_display, "len": len); f.write_all(contents)?; } { trace_scoped!("close", "name:": path_display); drop(f); } Ok(()) } #[allow(unused_variables)] pub(crate) fn write_file_incremental, F: Fn(usize)>( path: P, content_callback: &mut IncrementalFile, mode: u32, chunk_complete_callback: F, ) -> io::Result<()> { let mut opts = OpenOptions::new(); #[cfg(unix)] { use std::os::unix::fs::OpenOptionsExt; opts.mode(mode); } let path = path.as_ref(); let path_display = format!("{}", path.display()); let mut f = { trace_scoped!("creat", "name": path_display); opts.write(true).create(true).truncate(true).open(path)? }; if let IncrementalFile::ThreadedReceiver(recv) = content_callback { loop { // We unwrap here because the documented only reason for recv to fail is a close by the sender, which is reading // from the tar file: a failed read there will propagate the error in the main thread directly. let contents = recv.recv().unwrap(); let len = contents.len(); // Length 0 vector is used for clean EOF signalling. if len == 0 { trace_scoped!("EOF_chunk", "name": path_display, "len": len); drop(contents); chunk_complete_callback(len); break; } else { trace_scoped!("write_segment", "name": path_display, "len": len); f.write_all(&contents)?; drop(contents); chunk_complete_callback(len); } } } else { unreachable!(); } { trace_scoped!("close", "name:": path_display); drop(f); } Ok(()) } pub(crate) fn create_dir>(path: P) -> io::Result<()> { let path = path.as_ref(); let path_display = format!("{}", path.display()); trace_scoped!("create_dir", "name": path_display); std::fs::create_dir(path) } /// Get the executor for disk IO. pub(crate) fn get_executor<'a>( notify_handler: Option<&'a dyn Fn(Notification<'_>)>, ram_budget: usize, ) -> Result> { // If this gets lots of use, consider exposing via the config file. let thread_count = match process().var("RUSTUP_IO_THREADS") { Err(_) => num_cpus::get(), Ok(n) => n .parse::() .context("invalid value in RUSTUP_IO_THREADS. Must be a natural number")?, }; Ok(match thread_count { 0 | 1 => Box::new(immediate::ImmediateUnpacker::new()), n => Box::new(threaded::Threaded::new(notify_handler, n, ram_budget)), }) } rustup-1.26.0/src/diskio/test.rs000066400000000000000000000130161441327105200165400ustar00rootroot00000000000000use std::collections::HashMap; use anyhow::Result; use crate::test::test_dir; use super::{get_executor, Executor, Item, Kind}; use crate::currentprocess; impl Item { /// The length of the file, for files (for stats) fn size(&self) -> Option { match &self.kind { Kind::File(buf) => Some(buf.len()), _ => None, } } } fn test_incremental_file(io_threads: &str) -> Result<()> { let work_dir = test_dir()?; let mut vars = HashMap::new(); vars.insert("RUSTUP_IO_THREADS".to_string(), io_threads.to_string()); let tp = Box::new(currentprocess::TestProcess { vars, ..Default::default() }); currentprocess::with(tp, || -> Result<()> { let mut written = 0; let mut file_finished = false; let mut io_executor: Box = get_executor(None, 32 * 1024 * 1024)?; let (item, mut sender) = Item::write_file_segmented( work_dir.path().join("scratch"), 0o666, io_executor.incremental_file_state(), )?; for _ in io_executor.execute(item).collect::>() { // The file should be open and incomplete, and no completed chunks unreachable!(); } let mut chunk = io_executor.get_buffer(super::IO_CHUNK_SIZE); chunk.extend(b"0123456789"); chunk = chunk.finished(); sender(chunk); let mut chunk = io_executor.get_buffer(super::IO_CHUNK_SIZE); chunk.extend(b"0123456789"); chunk = chunk.finished(); sender(chunk); loop { for work in io_executor.completed().collect::>() { match work { super::CompletedIo::Chunk(size) => written += size, super::CompletedIo::Item(item) => unreachable!("{:?}", item), } } if written == 20 { break; } } // sending a zero length chunk closes the file let mut chunk = io_executor.get_buffer(super::IO_CHUNK_SIZE); chunk = chunk.finished(); sender(chunk); loop { for work in io_executor.completed().collect::>() { match work { super::CompletedIo::Chunk(_) => {} super::CompletedIo::Item(_) => { file_finished = true; } } } if file_finished { break; } } assert!(file_finished); for _ in io_executor.join().collect::>() { // no more work should be outstanding unreachable!(); } assert_eq!(io_executor.buffer_used(), 0); Ok(()) })?; // We should be able to read back the file assert_eq!( std::fs::read_to_string(work_dir.path().join("scratch"))?, "01234567890123456789".to_string() ); Ok(()) } fn test_complete_file(io_threads: &str) -> Result<()> { let work_dir = test_dir()?; let mut vars = HashMap::new(); vars.insert("RUSTUP_IO_THREADS".to_string(), io_threads.to_string()); let tp = Box::new(currentprocess::TestProcess { vars, ..Default::default() }); currentprocess::with(tp, || -> Result<()> { let mut io_executor: Box = get_executor(None, 32 * 1024 * 1024)?; let mut chunk = io_executor.get_buffer(10); chunk.extend(b"0123456789"); assert_eq!(chunk.len(), 10); chunk = chunk.finished(); let item = Item::write_file(work_dir.path().join("scratch"), 0o666, chunk); assert_eq!(item.size(), Some(10)); let mut items = 0; let mut check_item = |item: Item| { assert_eq!(item.size(), Some(10)); items += 1; assert_eq!(1, items); }; let mut finished = false; for work in io_executor.execute(item).collect::>() { // The file might complete immediately match work { super::CompletedIo::Chunk(size) => unreachable!("{:?}", size), super::CompletedIo::Item(item) => { check_item(item); finished = true; } } } if !finished { loop { for work in io_executor.completed().collect::>() { match work { super::CompletedIo::Chunk(size) => unreachable!("{:?}", size), super::CompletedIo::Item(item) => { check_item(item); finished = true; } } } if finished { break; } } } assert!(items > 0); for _ in io_executor.join().collect::>() { // no more work should be outstanding unreachable!(); } Ok(()) })?; // We should be able to read back the file with correct content assert_eq!( std::fs::read_to_string(work_dir.path().join("scratch"))?, "0123456789".to_string() ); Ok(()) } #[test] fn test_incremental_file_immediate() -> Result<()> { test_incremental_file("1") } #[test] fn test_incremental_file_threaded() -> Result<()> { test_incremental_file("2") } #[test] fn test_complete_file_immediate() -> Result<()> { test_complete_file("1") } #[test] fn test_complete_file_threaded() -> Result<()> { test_complete_file("2") } rustup-1.26.0/src/diskio/threaded.rs000066400000000000000000000365741441327105200173570ustar00rootroot00000000000000/// Threaded IO model: A pool of threads is used so that syscall latencies /// due to (nonexhaustive list) Network file systems, virus scanners, and /// operating system design, do not cause rustup to be significantly slower /// than desired. In particular the docs workload with 20K files requires /// very low latency per file, which even a few ms per syscall per file /// will cause minutes of wall clock time. use std::cell::{Cell, RefCell}; use std::fmt; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::Arc; use enum_map::{enum_map, Enum, EnumMap}; use sharded_slab::pool::{OwnedRef, OwnedRefMut}; use super::{perform, CompletedIo, Executor, Item}; use crate::utils::notifications::Notification; use crate::utils::units::Unit; #[derive(Copy, Clone, Debug, Enum)] pub(crate) enum Bucket { FourK, EightK, OneM, EightM, SixteenM, } #[derive(Debug)] pub(crate) enum PoolReference { Owned(OwnedRef>, Arc>>), Mut(OwnedRefMut>, Arc>>), } impl PoolReference { pub(crate) fn clear(&mut self) { match self { PoolReference::Mut(orm, pool) => { pool.clear(orm.key()); } PoolReference::Owned(rm, pool) => { pool.clear(rm.key()); } } } } impl AsRef<[u8]> for PoolReference { fn as_ref(&self) -> &[u8] { match self { PoolReference::Owned(owned, _) => owned, PoolReference::Mut(mutable, _) => mutable, } } } enum Task { Request(CompletedIo), // Used to synchronise in the join method. Sentinel, } impl Default for Task { fn default() -> Self { Self::Sentinel } } struct Pool { pool: Arc>>, high_watermark: RefCell, in_use: RefCell, size: usize, } impl Pool { fn claim(&self) { if self.in_use == self.high_watermark { *self.high_watermark.borrow_mut() += self.size; } *self.in_use.borrow_mut() += self.size; } fn reclaim(&self) { *self.in_use.borrow_mut() -= self.size; } } impl fmt::Debug for Pool { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Pool") .field("size", &self.size) .field("in_use", &self.in_use) .field("high_watermark", &self.high_watermark) .finish() } } pub(crate) struct Threaded<'a> { n_files: Arc, pool: threadpool::ThreadPool, notify_handler: Option<&'a dyn Fn(Notification<'_>)>, rx: Receiver, tx: Sender, vec_pools: EnumMap, ram_budget: usize, } impl<'a> Threaded<'a> { /// Construct a new Threaded executor. pub(crate) fn new( notify_handler: Option<&'a dyn Fn(Notification<'_>)>, thread_count: usize, ram_budget: usize, ) -> Self { // Defaults to hardware thread count threads; this is suitable for // our needs as IO bound operations tend to show up as write latencies // rather than close latencies, so we don't need to look at // more threads to get more IO dispatched at this stage in the process. let pool = threadpool::Builder::new() .thread_name("CloseHandle".into()) .num_threads(thread_count) .thread_stack_size(1_048_576) .build(); let (tx, rx) = channel(); let vec_pools = enum_map! { Bucket::FourK => Pool{ pool: Arc::new(sharded_slab::Pool::new()), high_watermark: RefCell::new(4096), in_use: RefCell::new(0), size:4096 }, Bucket::EightK=> Pool{ pool: Arc::new(sharded_slab::Pool::new()), high_watermark: RefCell::new(8192), in_use: RefCell::new(0), size:8192 }, Bucket::OneM=> Pool{ pool: Arc::new(sharded_slab::Pool::new()), high_watermark: RefCell::new(1024*1024), in_use: RefCell::new(0), size:1024*1024 }, Bucket::EightM=> Pool{ pool: Arc::new(sharded_slab::Pool::new()), high_watermark: RefCell::new(8*1024*1024), in_use: RefCell::new(0), size:8*1024*1024 }, Bucket::SixteenM=> Pool{ pool: Arc::new(sharded_slab::Pool::new()), high_watermark: RefCell::new(16*1024*1024), in_use: RefCell::new(0), size: 16*1024*1024 }, }; // Ensure there is at least one each size buffer, so we can always make forward progress. for (_, pool) in &vec_pools { let key = pool .pool .create_with(|vec| vec.reserve_exact(pool.size - vec.len())) .unwrap(); pool.pool.clear(key); } // Since we've just *used* this memory, we had better have been allowed to! assert!(Threaded::ram_highwater(&vec_pools) < ram_budget); Self { n_files: Arc::new(AtomicUsize::new(0)), pool, notify_handler, rx, tx, vec_pools, ram_budget, } } /// How much RAM is allocated across all the pools right now fn ram_highwater(vec_pools: &EnumMap) -> usize { vec_pools .iter() .map(|(_, pool)| *pool.high_watermark.borrow()) .sum() } fn reclaim(&self, op: &CompletedIo) { let size = match &op { CompletedIo::Item(op) => match &op.kind { super::Kind::Directory => return, super::Kind::File(content) => content.len(), super::Kind::IncrementalFile(_) => return, }, CompletedIo::Chunk(_) => super::IO_CHUNK_SIZE, }; let bucket = self.find_bucket(size); let pool = &self.vec_pools[bucket]; pool.reclaim(); } fn submit(&self, mut item: Item) { let tx = self.tx.clone(); self.n_files.fetch_add(1, Ordering::Relaxed); let n_files = self.n_files.clone(); self.pool.execute(move || { let chunk_complete_callback = |size| { tx.send(Task::Request(CompletedIo::Chunk(size))) .expect("receiver should be listening") }; perform(&mut item, chunk_complete_callback); n_files.fetch_sub(1, Ordering::Relaxed); tx.send(Task::Request(CompletedIo::Item(item))) .expect("receiver should be listening"); }); } fn find_bucket(&self, capacity: usize) -> Bucket { let mut bucket = Bucket::FourK; for (next_bucket, pool) in &self.vec_pools { bucket = next_bucket; if pool.size >= capacity { break; } } let pool = &self.vec_pools[bucket]; assert!( capacity <= pool.size, "capacity <= pool.size: {} > {}", capacity, pool.size ); bucket } } impl<'a> Executor for Threaded<'a> { fn dispatch(&self, item: Item) -> Box + '_> { // Yield any completed work before accepting new work - keep memory // pressure under control // - return an iterator that runs until we can submit and then submits // as its last action Box::new(SubmitIterator { executor: self, item: Cell::new(Some(item)), }) } fn join(&mut self) -> Box + '_> { // Some explanation is in order. Even though the tar we are reading from (if // any) will have had its FileWithProgress download tracking // completed before we hit drop, that is not true if we are unwinding due to a // failure, where the logical ownership of the progress bar is // ambiguous, and as the tracker itself is abstracted out behind // notifications etc we cannot just query for that. So: we assume no // more reads of the underlying tar will take place: either the // error unwinding will stop reads, or we completed; either way, we // notify finished to the tracker to force a reset to zero; we set // the units to files, show our progress, and set our units back // afterwards. The largest archives today - rust docs - have ~20k // items, and the download tracker's progress is confounded with // actual handling of data today, we synthesis a data buffer and // pretend to have bytes to deliver. let mut prev_files = self.n_files.load(Ordering::Relaxed); if let Some(handler) = self.notify_handler { handler(Notification::DownloadFinished); handler(Notification::DownloadPushUnit(Unit::IO)); handler(Notification::DownloadContentLengthReceived( prev_files as u64, )); } if prev_files > 50 { eprintln!("{prev_files} deferred IO operations"); } let buf: Vec = vec![0; prev_files]; // Cheap wrap-around correctness check - we have 20k files, more than // 32K means we subtracted from 0 somewhere. assert!(32767 > prev_files); let mut current_files = prev_files; while current_files != 0 { use std::thread::sleep; sleep(std::time::Duration::from_millis(100)); prev_files = current_files; current_files = self.n_files.load(Ordering::Relaxed); let step_count = prev_files - current_files; if let Some(handler) = self.notify_handler { handler(Notification::DownloadDataReceived(&buf[0..step_count])); } } self.pool.join(); if let Some(handler) = self.notify_handler { handler(Notification::DownloadFinished); handler(Notification::DownloadPopUnit); } // close the feedback channel so that blocking reads on it can // complete. send is atomic, and we know the threads completed from the // pool join, so this is race-free. It is possible that try_iter is safe // but the documentation is not clear: it says it will not wait, but not // whether a put done by another thread on a NUMA machine before (say) // the mutex in the thread pool is entirely synchronised; since this is // largely hidden from the clients, digging into check whether we can // make this tidier (e.g. remove the Marker variant) is left for another // day. I *have* checked that insertion is barried and ordered such that // sending the marker cannot come in before markers sent from other // threads we just joined. self.tx .send(Task::Sentinel) .expect("must still be listening"); if crate::currentprocess::process().var("RUSTUP_DEBUG").is_ok() { // debug! is in the cli layer. erg. And notification stack is still terrible. debug!(""); for (bucket, pool) in &self.vec_pools { debug!("{:?}: {:?}", bucket, pool); } } Box::new(JoinIterator { executor: self, consume_sentinel: false, }) } fn completed(&self) -> Box + '_> { Box::new(JoinIterator { executor: self, consume_sentinel: true, }) } fn incremental_file_state(&self) -> super::IncrementalFileState { super::IncrementalFileState::Threaded } fn get_buffer(&mut self, capacity: usize) -> super::FileBuffer { let bucket = self.find_bucket(capacity); let pool = &mut self.vec_pools[bucket]; let mut item = pool.pool.clone().create_owned().unwrap(); item.reserve_exact(pool.size); pool.claim(); super::FileBuffer::Threaded(PoolReference::Mut(item, pool.pool.clone())) } fn buffer_available(&self, len: usize) -> bool { // if either: there is room in the budget to assign a new slab entry of // this size, or there is an unused slab entry of this size. let bucket = self.find_bucket(len); let pool = &self.vec_pools[bucket]; if pool.in_use < pool.high_watermark { return true; } let size = pool.size; let total_used = Threaded::ram_highwater(&self.vec_pools); total_used + size < self.ram_budget } #[cfg(test)] fn buffer_used(&self) -> usize { self.vec_pools.iter().map(|(_, p)| *p.in_use.borrow()).sum() } } impl<'a> Drop for Threaded<'a> { fn drop(&mut self) { // We are not permitted to fail - consume but do not handle the items. self.join().for_each(drop); } } struct JoinIterator<'a, 'b> { executor: &'a Threaded<'b>, consume_sentinel: bool, } impl<'a, 'b> JoinIterator<'a, 'b> { fn inner>(&self, mut iter: T) -> Option { loop { let task_o = iter.next(); match task_o { None => break None, Some(task) => match task { Task::Sentinel => { if self.consume_sentinel { continue; } else { break None; } } Task::Request(item) => { self.executor.reclaim(&item); break Some(item); } }, } } } } impl<'a, 'b> Iterator for JoinIterator<'a, 'b> { type Item = CompletedIo; fn next(&mut self) -> Option { if self.consume_sentinel { self.inner(self.executor.rx.try_iter()) } else { self.inner(self.executor.rx.iter()) } } } struct SubmitIterator<'a, 'b> { executor: &'a Threaded<'b>, item: Cell>, } impl<'a, 'b> Iterator for SubmitIterator<'a, 'b> { type Item = CompletedIo; fn next(&mut self) -> Option { // The number here is arbitrary; just a number to stop exhausting fd's on linux // and still allow rapid decompression to generate work to dispatch // This function could perhaps be tuned: e.g. it may wait in rx.iter() // unnecessarily blocking if many items complete at once but threads do // not pick up work quickly for some reason, until another thread // actually completes; however, results are presently ok. let threshold = 5; if self.executor.pool.queued_count() < threshold { if let Some(item) = self.item.take() { self.executor.submit(item); }; None } else { for task in self.executor.rx.iter() { if let Task::Request(item) = task { self.executor.reclaim(&item); return Some(item); } if self.executor.pool.queued_count() < threshold { if let Some(item) = self.item.take() { self.executor.submit(item); }; return None; } } unreachable!(); } } } rustup-1.26.0/src/dist/000077500000000000000000000000001441327105200146735ustar00rootroot00000000000000rustup-1.26.0/src/dist/component/000077500000000000000000000000001441327105200166755ustar00rootroot00000000000000rustup-1.26.0/src/dist/component/components.rs000066400000000000000000000250661441327105200214410ustar00rootroot00000000000000//! The representation of the installed toolchain and its components. //! `Components` and `DirectoryPackage` are the two sides of the //! installation / uninstallation process. use std::path::{Path, PathBuf}; use anyhow::{bail, Result}; use crate::dist::component::package::{INSTALLER_VERSION, VERSION_FILE}; use crate::dist::component::transaction::Transaction; use crate::dist::prefix::InstallPrefix; use crate::errors::RustupError; use crate::utils::utils; const COMPONENTS_FILE: &str = "components"; #[derive(Clone, Debug)] pub struct Components { prefix: InstallPrefix, } impl Components { pub fn open(prefix: InstallPrefix) -> Result { let c = Self { prefix }; // Validate that the metadata uses a format we know if let Some(v) = c.read_version()? { if v != INSTALLER_VERSION { bail!( "unsupported metadata version in existing installation: {}", v ); } } Ok(c) } fn rel_components_file(&self) -> PathBuf { self.prefix.rel_manifest_file(COMPONENTS_FILE) } fn rel_component_manifest(&self, name: &str) -> PathBuf { self.prefix.rel_manifest_file(&format!("manifest-{name}")) } fn read_version(&self) -> Result> { let p = self.prefix.manifest_file(VERSION_FILE); if utils::is_file(&p) { Ok(Some(utils::read_file(VERSION_FILE, &p)?.trim().to_string())) } else { Ok(None) } } fn write_version(&self, tx: &mut Transaction<'_>) -> Result<()> { tx.modify_file(self.prefix.rel_manifest_file(VERSION_FILE))?; utils::write_file( VERSION_FILE, &self.prefix.manifest_file(VERSION_FILE), INSTALLER_VERSION, )?; Ok(()) } pub fn list(&self) -> Result> { let path = self.prefix.abs_path(self.rel_components_file()); if !utils::is_file(&path) { return Ok(Vec::new()); } let content = utils::read_file("components", &path)?; Ok(content .lines() .map(|s| Component { components: self.clone(), name: s.to_owned(), }) .collect()) } pub(crate) fn add<'a>(&self, name: &str, tx: Transaction<'a>) -> ComponentBuilder<'a> { ComponentBuilder { components: self.clone(), name: name.to_owned(), parts: Vec::new(), tx, } } pub fn find(&self, name: &str) -> Result> { let result = self.list()?; Ok(result.into_iter().find(|c| (c.name() == name))) } pub(crate) fn prefix(&self) -> InstallPrefix { self.prefix.clone() } } pub(crate) struct ComponentBuilder<'a> { components: Components, name: String, parts: Vec, tx: Transaction<'a>, } impl<'a> ComponentBuilder<'a> { pub(crate) fn copy_file(&mut self, path: PathBuf, src: &Path) -> Result<()> { self.parts .push(ComponentPart("file".to_owned(), path.clone())); self.tx.copy_file(&self.name, path, src) } pub(crate) fn copy_dir(&mut self, path: PathBuf, src: &Path) -> Result<()> { self.parts .push(ComponentPart("dir".to_owned(), path.clone())); self.tx.copy_dir(&self.name, path, src) } pub(crate) fn move_file(&mut self, path: PathBuf, src: &Path) -> Result<()> { self.parts .push(ComponentPart("file".to_owned(), path.clone())); self.tx.move_file(&self.name, path, src) } pub(crate) fn move_dir(&mut self, path: PathBuf, src: &Path) -> Result<()> { self.parts .push(ComponentPart("dir".to_owned(), path.clone())); self.tx.move_dir(&self.name, path, src) } pub(crate) fn finish(mut self) -> Result> { // Write component manifest let path = self.components.rel_component_manifest(&self.name); let abs_path = self.components.prefix.abs_path(&path); let mut file = self.tx.add_file(&self.name, path)?; for part in self.parts { // FIXME: This writes relative paths to the component manifest, // but rust-installer writes absolute paths. utils::write_line("component", &mut file, &abs_path, &part.encode())?; } // Add component to components file let path = self.components.rel_components_file(); let abs_path = self.components.prefix.abs_path(&path); self.tx.modify_file(path)?; utils::append_file("components", &abs_path, &self.name)?; // Drop in the version file for future use self.components.write_version(&mut self.tx)?; Ok(self.tx) } } #[derive(Debug)] pub struct ComponentPart(pub String, pub PathBuf); impl ComponentPart { pub(crate) fn encode(&self) -> String { format!("{}:{}", &self.0, &self.1.to_string_lossy()) } pub(crate) fn decode(line: &str) -> Option { line.find(':') .map(|pos| Self(line[0..pos].to_owned(), PathBuf::from(&line[(pos + 1)..]))) } } #[derive(Clone, Debug)] pub struct Component { components: Components, name: String, } impl Component { pub(crate) fn manifest_name(&self) -> String { format!("manifest-{}", &self.name) } pub(crate) fn manifest_file(&self) -> PathBuf { self.components.prefix.manifest_file(&self.manifest_name()) } pub(crate) fn rel_manifest_file(&self) -> PathBuf { self.components .prefix .rel_manifest_file(&self.manifest_name()) } pub(crate) fn name(&self) -> &str { &self.name } pub(crate) fn parts(&self) -> Result> { let mut result = Vec::new(); for line in utils::read_file("component", &self.manifest_file())?.lines() { result.push( ComponentPart::decode(line) .ok_or_else(|| RustupError::CorruptComponent(self.name.clone()))?, ); } Ok(result) } pub fn uninstall<'a>(&self, mut tx: Transaction<'a>) -> Result> { // Update components file let path = self.components.rel_components_file(); let abs_path = self.components.prefix.abs_path(&path); let temp = tx.temp().new_file()?; utils::filter_file("components", &abs_path, &temp, |l| l != self.name)?; tx.modify_file(path)?; utils::rename_file("components", &temp, &abs_path, tx.notify_handler())?; // TODO: If this is the last component remove the components file // and the version file. // Track visited directories use std::collections::hash_set::IntoIter; use std::collections::HashSet; use std::fs::read_dir; // dirs will contain the set of longest disjoint directory paths seen // ancestors help in filtering seen paths and constructing dirs // All seen paths must be relative to avoid surprises struct PruneSet { dirs: HashSet, ancestors: HashSet, prefix: PathBuf, } impl PruneSet { fn seen(&mut self, mut path: PathBuf) { if !path.is_relative() || !path.pop() { return; } if self.dirs.contains(&path) || self.ancestors.contains(&path) { return; } self.dirs.insert(path.clone()); while path.pop() { if path.file_name().is_none() { break; } if self.dirs.contains(&path) { self.dirs.remove(&path); } if self.ancestors.contains(&path) { break; } self.ancestors.insert(path.clone()); } } } struct PruneIter { iter: IntoIter, path_buf: Option, prefix: PathBuf, } impl IntoIterator for PruneSet { type Item = PathBuf; type IntoIter = PruneIter; fn into_iter(self) -> Self::IntoIter { PruneIter { iter: self.dirs.into_iter(), path_buf: None, prefix: self.prefix, } } } // Returns only empty directories impl Iterator for PruneIter { type Item = PathBuf; fn next(&mut self) -> Option { self.path_buf = match self.path_buf { None => self.iter.next(), Some(_) => { let mut path_buf = self.path_buf.take().unwrap(); match path_buf.file_name() { Some(_) => { if path_buf.pop() { Some(path_buf) } else { None } } None => self.iter.next(), } } }; self.path_buf.as_ref()?; let full_path = self.prefix.join(self.path_buf.as_ref().unwrap()); let empty = match read_dir(full_path) { Ok(dir) => dir.count() == 0, Err(_) => false, }; if empty { self.path_buf.clone() } else { // No dir above can be empty, go to next path in dirs self.path_buf = None; self.next() } } } // Remove parts let mut pset = PruneSet { dirs: HashSet::new(), ancestors: HashSet::new(), prefix: self.components.prefix.abs_path(""), }; for part in self.parts()?.into_iter().rev() { match &*part.0 { "file" => tx.remove_file(&self.name, part.1.clone())?, "dir" => tx.remove_dir(&self.name, part.1.clone())?, _ => return Err(RustupError::CorruptComponent(self.name.clone()).into()), } pset.seen(part.1); } for empty_dir in pset { tx.remove_dir(&self.name, empty_dir)?; } // Remove component manifest tx.remove_file(&self.name, self.rel_manifest_file())?; Ok(tx) } } rustup-1.26.0/src/dist/component/mod.rs000066400000000000000000000006471441327105200200310ustar00rootroot00000000000000pub use self::components::*; pub use self::package::*; /// An interpreter for the rust-installer [1] installation format. /// /// https://github.com/rust-lang/rust-installer pub use self::transaction::*; // Transactional file system tools mod transaction; // The representation of a package, its components, and installation mod package; // The representation of *installed* components, and uninstallation mod components; rustup-1.26.0/src/dist/component/package.rs000066400000000000000000000544631441327105200206520ustar00rootroot00000000000000//! An interpreter for the rust-installer package format. Responsible //! for installing from a directory or tarball to an installation //! prefix, represented by a `Components` instance. use std::collections::{HashMap, HashSet}; use std::fmt; use std::io::{self, ErrorKind as IOErrorKind, Read}; use std::mem; use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context, Result}; use tar::EntryType; use crate::diskio::{get_executor, CompletedIo, Executor, FileBuffer, Item, Kind, IO_CHUNK_SIZE}; use crate::dist::component::components::*; use crate::dist::component::transaction::*; use crate::dist::temp; use crate::errors::*; use crate::process; use crate::utils::notifications::Notification; use crate::utils::utils; /// The current metadata revision used by rust-installer pub(crate) const INSTALLER_VERSION: &str = "3"; pub(crate) const VERSION_FILE: &str = "rust-installer-version"; pub trait Package: fmt::Debug { fn contains(&self, component: &str, short_name: Option<&str>) -> bool; fn install<'a>( &self, target: &Components, component: &str, short_name: Option<&str>, tx: Transaction<'a>, ) -> Result>; fn components(&self) -> Vec; } #[derive(Debug)] pub struct DirectoryPackage { path: PathBuf, components: HashSet, copy: bool, } impl DirectoryPackage { pub fn new(path: PathBuf, copy: bool) -> Result { validate_installer_version(&path)?; let content = utils::read_file("package components", &path.join("components"))?; let components = content .lines() .map(std::borrow::ToOwned::to_owned) .collect(); Ok(Self { path, components, copy, }) } } fn validate_installer_version(path: &Path) -> Result<()> { let file = utils::read_file("installer version", &path.join(VERSION_FILE))?; let v = file.trim(); if v == INSTALLER_VERSION { Ok(()) } else { Err(anyhow!(format!("unsupported installer version: {v}"))) } } impl Package for DirectoryPackage { fn contains(&self, component: &str, short_name: Option<&str>) -> bool { self.components.contains(component) || if let Some(n) = short_name { self.components.contains(n) } else { false } } fn install<'a>( &self, target: &Components, name: &str, short_name: Option<&str>, tx: Transaction<'a>, ) -> Result> { let actual_name = if self.components.contains(name) { name } else if let Some(n) = short_name { n } else { name }; let root = self.path.join(actual_name); let manifest = utils::read_file("package manifest", &root.join("manifest.in"))?; let mut builder = target.add(name, tx); for l in manifest.lines() { let part = ComponentPart::decode(l) .ok_or_else(|| RustupError::CorruptComponent(name.to_owned()))?; let path = part.1; let src_path = root.join(&path); match &*part.0 { "file" => { if self.copy { builder.copy_file(path.clone(), &src_path)? } else { builder.move_file(path.clone(), &src_path)? } } "dir" => { if self.copy { builder.copy_dir(path.clone(), &src_path)? } else { builder.move_dir(path.clone(), &src_path)? } } _ => return Err(RustupError::CorruptComponent(name.to_owned()).into()), } } let tx = builder.finish()?; Ok(tx) } fn components(&self) -> Vec { self.components.iter().cloned().collect() } } #[derive(Debug)] pub(crate) struct TarPackage<'a>(DirectoryPackage, temp::Dir<'a>); impl<'a> TarPackage<'a> { pub(crate) fn new( stream: R, temp_cfg: &'a temp::Cfg, notify_handler: Option<&'a dyn Fn(Notification<'_>)>, ) -> Result { let temp_dir = temp_cfg.new_directory()?; let mut archive = tar::Archive::new(stream); // The rust-installer packages unpack to a directory called // $pkgname-$version-$target. Skip that directory when // unpacking. unpack_without_first_dir(&mut archive, &temp_dir, notify_handler) .context("failed to extract package (perhaps you ran out of disk space?)")?; Ok(TarPackage( DirectoryPackage::new(temp_dir.to_owned(), false)?, temp_dir, )) } } // Probably this should live in diskio but ¯\_(ツ)_/¯ fn unpack_ram( io_chunk_size: usize, effective_max_ram: Option, notify_handler: Option<&dyn Fn(Notification<'_>)>, ) -> usize { const RAM_ALLOWANCE_FOR_RUSTUP_AND_BUFFERS: usize = 200 * 1024 * 1024; let minimum_ram = io_chunk_size * 2; let default_max_unpack_ram = if let Some(effective_max_ram) = effective_max_ram { if effective_max_ram > minimum_ram + RAM_ALLOWANCE_FOR_RUSTUP_AND_BUFFERS { effective_max_ram - RAM_ALLOWANCE_FOR_RUSTUP_AND_BUFFERS } else { minimum_ram } } else { // Rustup does not know how much RAM the machine has: use the minimum minimum_ram }; let unpack_ram = match process() .var("RUSTUP_UNPACK_RAM") .ok() .and_then(|budget_str| budget_str.parse::().ok()) { Some(budget) => { if budget < minimum_ram { warn!( "Ignoring RUSTUP_UNPACK_RAM ({}) less than minimum of {}.", budget, minimum_ram ); minimum_ram } else if budget > default_max_unpack_ram { warn!( "Ignoring RUSTUP_UNPACK_RAM ({}) greater than detected available RAM of {}.", budget, default_max_unpack_ram ); default_max_unpack_ram } else { budget } } None => { if let Some(h) = notify_handler { h(Notification::SetDefaultBufferSize(default_max_unpack_ram)) } default_max_unpack_ram } }; if minimum_ram > unpack_ram { panic!("RUSTUP_UNPACK_RAM must be larger than {minimum_ram}"); } else { unpack_ram } } /// Handle the async result of io operations /// Replaces op.result with Ok(()) fn filter_result(op: &mut CompletedIo) -> io::Result<()> { if let CompletedIo::Item(op) = op { let result = mem::replace(&mut op.result, Ok(())); match result { Ok(_) => Ok(()), Err(e) => match e.kind() { IOErrorKind::AlreadyExists => { // mkdir of e.g. ~/.rustup already existing is just fine; // for others it would be better to know whether it is // expected to exist or not -so put a flag in the state. if let Kind::Directory = op.kind { Ok(()) } else { Err(e) } } _ => Err(e), }, } } else { Ok(()) } } /// Dequeue the children of directories queued up waiting for the directory to /// be created. /// /// Currently the volume of queued items does not count as backpressure against /// the main tar extraction process. /// Returns the number of triggered children fn trigger_children( io_executor: &dyn Executor, directories: &mut HashMap, op: CompletedIo, ) -> Result { let mut result = 0; if let CompletedIo::Item(item) = op { if let Kind::Directory = item.kind { let mut pending = Vec::new(); directories .entry(item.full_path) .and_modify(|status| match status { DirStatus::Exists => unreachable!(), DirStatus::Pending(pending_inner) => { pending.append(pending_inner); *status = DirStatus::Exists; } }) .or_insert_with(|| unreachable!()); result += pending.len(); for pending_item in pending.into_iter() { for mut item in io_executor.execute(pending_item).collect::>() { // TODO capture metrics filter_result(&mut item)?; result += trigger_children(io_executor, directories, item)?; } } } }; Ok(result) } /// What is the status of this directory ? enum DirStatus { Exists, Pending(Vec), } fn unpack_without_first_dir( archive: &mut tar::Archive, path: &Path, notify_handler: Option<&dyn Fn(Notification<'_>)>, ) -> Result<()> { let entries = archive.entries()?; let effective_max_ram = match effective_limits::memory_limit() { Ok(ram) => Some(ram as usize), Err(e) => { if let Some(h) = notify_handler { h(Notification::Error(e.to_string())) } None } }; let unpack_ram = unpack_ram(IO_CHUNK_SIZE, effective_max_ram, notify_handler); let mut io_executor: Box = get_executor(notify_handler, unpack_ram)?; let mut directories: HashMap = HashMap::new(); // Path is presumed to exist. Call it a precondition. directories.insert(path.to_owned(), DirStatus::Exists); 'entries: for entry in entries { // drain completed results to keep memory pressure low and respond // rapidly to completed events even if we couldn't submit work (because // our unpacked item is pending dequeue) for mut item in io_executor.completed().collect::>() { // TODO capture metrics filter_result(&mut item)?; trigger_children(&*io_executor, &mut directories, item)?; } let mut entry = entry?; let relpath = { let path = entry.path(); let path = path?; path.into_owned() }; // Reject path components that are not normal (..|/| etc) for part in relpath.components() { match part { // Some very early rust tarballs include a "." segment which we have to // support, despite not liking it. std::path::Component::Normal(_) | std::path::Component::CurDir => {} _ => bail!(format!("tar path '{}' is not supported", relpath.display())), } } let mut components = relpath.components(); // Throw away the first path component: our root was supplied. components.next(); let full_path = path.join(components.as_path()); if full_path == path { // The tmp dir code makes the root dir for us. continue; } struct SenderEntry<'a, 'b, R: std::io::Read> { sender: Box bool + 'a>, entry: tar::Entry<'b, R>, } /// true if either no sender_entry was provided, or the incremental file /// has been fully dispatched. fn flush_ios>( io_executor: &mut dyn Executor, directories: &mut HashMap, mut sender_entry: Option<&mut SenderEntry<'_, '_, R>>, full_path: P, ) -> Result { let mut result = sender_entry.is_none(); for mut op in io_executor.completed().collect::>() { // TODO capture metrics filter_result(&mut op)?; trigger_children(&*io_executor, directories, op)?; } // Maybe stream a file incrementally if let Some(sender) = sender_entry.as_mut() { if io_executor.buffer_available(IO_CHUNK_SIZE) { let mut buffer = io_executor.get_buffer(IO_CHUNK_SIZE); let len = sender .entry .by_ref() .take(IO_CHUNK_SIZE as u64) .read_to_end(&mut buffer)?; buffer = buffer.finished(); if len == 0 { result = true; } if !(sender.sender)(buffer) { bail!(format!( "IO receiver for '{}' disconnected", full_path.as_ref().display() )) } } } Ok(result) } // Bail out if we get hard links, device nodes or any other unusual content // - it is most likely an attack, as rusts cross-platform nature precludes // such artifacts let kind = entry.header().entry_type(); // https://github.com/rust-lang/rustup/issues/1140 and before that // https://github.com/rust-lang/rust/issues/25479 // tl;dr: code got convoluted and we *may* have damaged tarballs out // there. // However the mandate we have is very simple: unpack as the current // user with modes matching the tar contents. No documented tars with // bad modes are in the bug tracker : the previous permission splatting // code was inherited from interactions with sudo that are best // addressed outside of rustup (run with an appropriate effective uid). // THAT SAID: If regressions turn up immediately post release this code - // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a8549057f0827bf3a068d8917256765a // is a translation of the prior helper function into an in-iterator // application. let tar_mode = entry.header().mode().ok().unwrap(); // That said, the tarballs that are shipped way back have single-user // permissions: // -rwx------ rustbuild/rustbuild ..... release/test-release.sh // so we should normalise the mode to match the previous behaviour users // may be expecting where the above file would end up with mode 0o755 let u_mode = tar_mode & 0o700; let g_mode = (u_mode & 0o0500) >> 3; let o_mode = g_mode >> 3; let mode = u_mode | g_mode | o_mode; let file_size = entry.header().size()?; let size = std::cmp::min(IO_CHUNK_SIZE as u64, file_size); while !io_executor.buffer_available(size as usize) { flush_ios::, _>( &mut *io_executor, &mut directories, None, &full_path, )?; } let mut incremental_file_sender: Option bool + '_>> = None; let mut item = match kind { EntryType::Directory => { directories.insert(full_path.to_owned(), DirStatus::Pending(Vec::new())); Item::make_dir(full_path.clone(), mode) } EntryType::Regular => { if file_size > IO_CHUNK_SIZE as u64 { let (item, sender) = Item::write_file_segmented( full_path.clone(), mode, io_executor.incremental_file_state(), )?; incremental_file_sender = Some(sender); item } else { let mut content = io_executor.get_buffer(size as usize); entry.read_to_end(&mut content)?; content = content.finished(); Item::write_file(full_path.clone(), mode, content) } } _ => bail!(format!("tar entry kind '{kind:?}' is not supported")), }; let item = loop { // Create the full path to the entry if it does not exist already if let Some(parent) = item.full_path.to_owned().parent() { match directories.get_mut(parent) { None => { // Tar has item before containing directory // Complain about this so we can see if these exist. writeln!( process().stderr(), "Unexpected: missing parent '{}' for '{}'", parent.display(), entry.path()?.display() )?; directories.insert(parent.to_owned(), DirStatus::Pending(vec![item])); item = Item::make_dir(parent.to_owned(), 0o755); // Check the parent's parent continue; } Some(DirStatus::Exists) => { break Some(item); } Some(DirStatus::Pending(pending)) => { // Parent dir is being made pending.push(item); if incremental_file_sender.is_none() { // take next item from tar continue 'entries; } else { // don't submit a new item for processing, but do be ready to feed data to the incremental file. break None; } } } } else { // We should never see a path with no parent. panic!(); } }; if let Some(item) = item { // Submit the new item for mut item in io_executor.execute(item).collect::>() { // TODO capture metrics filter_result(&mut item)?; trigger_children(&*io_executor, &mut directories, item)?; } } let mut incremental_file_sender = incremental_file_sender.map(|incremental_file_sender| SenderEntry { sender: incremental_file_sender, entry, }); // monitor io queue and feed in the content of the file (if needed) while !flush_ios( &mut *io_executor, &mut directories, incremental_file_sender.as_mut(), &full_path, )? {} } loop { let mut triggered = 0; for mut item in io_executor.join().collect::>() { // handle final IOs // TODO capture metrics filter_result(&mut item)?; triggered += trigger_children(&*io_executor, &mut directories, item)?; } if triggered == 0 { // None of the IO submitted before the prior join triggered any new // submissions break; } } Ok(()) } impl<'a> Package for TarPackage<'a> { fn contains(&self, component: &str, short_name: Option<&str>) -> bool { self.0.contains(component, short_name) } fn install<'b>( &self, target: &Components, component: &str, short_name: Option<&str>, tx: Transaction<'b>, ) -> Result> { self.0.install(target, component, short_name, tx) } fn components(&self) -> Vec { self.0.components() } } #[derive(Debug)] pub(crate) struct TarGzPackage<'a>(TarPackage<'a>); impl<'a> TarGzPackage<'a> { pub(crate) fn new( stream: R, temp_cfg: &'a temp::Cfg, notify_handler: Option<&'a dyn Fn(Notification<'_>)>, ) -> Result { let stream = flate2::read::GzDecoder::new(stream); Ok(TarGzPackage(TarPackage::new( stream, temp_cfg, notify_handler, )?)) } } impl<'a> Package for TarGzPackage<'a> { fn contains(&self, component: &str, short_name: Option<&str>) -> bool { self.0.contains(component, short_name) } fn install<'b>( &self, target: &Components, component: &str, short_name: Option<&str>, tx: Transaction<'b>, ) -> Result> { self.0.install(target, component, short_name, tx) } fn components(&self) -> Vec { self.0.components() } } #[derive(Debug)] pub(crate) struct TarXzPackage<'a>(TarPackage<'a>); impl<'a> TarXzPackage<'a> { pub(crate) fn new( stream: R, temp_cfg: &'a temp::Cfg, notify_handler: Option<&'a dyn Fn(Notification<'_>)>, ) -> Result { let stream = xz2::read::XzDecoder::new(stream); Ok(TarXzPackage(TarPackage::new( stream, temp_cfg, notify_handler, )?)) } } impl<'a> Package for TarXzPackage<'a> { fn contains(&self, component: &str, short_name: Option<&str>) -> bool { self.0.contains(component, short_name) } fn install<'b>( &self, target: &Components, component: &str, short_name: Option<&str>, tx: Transaction<'b>, ) -> Result> { self.0.install(target, component, short_name, tx) } fn components(&self) -> Vec { self.0.components() } } #[derive(Debug)] pub(crate) struct TarZStdPackage<'a>(TarPackage<'a>); impl<'a> TarZStdPackage<'a> { pub(crate) fn new( stream: R, temp_cfg: &'a temp::Cfg, notify_handler: Option<&'a dyn Fn(Notification<'_>)>, ) -> Result { let stream = zstd::stream::read::Decoder::new(stream)?; Ok(TarZStdPackage(TarPackage::new( stream, temp_cfg, notify_handler, )?)) } } impl<'a> Package for TarZStdPackage<'a> { fn contains(&self, component: &str, short_name: Option<&str>) -> bool { self.0.contains(component, short_name) } fn install<'b>( &self, target: &Components, component: &str, short_name: Option<&str>, tx: Transaction<'b>, ) -> Result> { self.0.install(target, component, short_name, tx) } fn components(&self) -> Vec { self.0.components() } } rustup-1.26.0/src/dist/component/transaction.rs000066400000000000000000000304641441327105200215770ustar00rootroot00000000000000//! A transactional interface to file system operations needed by the //! installer. //! //! Installation or uninstallation of a single component is done //! within a Transaction, which supports a few simple file system //! operations. If the Transaction is dropped without committing then //! it will *attempt* to roll back the transaction. //! //! FIXME: This uses ensure_dir_exists in some places but rollback //! does not remove any dirs created by it. use std::fs::File; use std::path::{Path, PathBuf}; use anyhow::{anyhow, Context, Result}; use crate::dist::notifications::*; use crate::dist::prefix::InstallPrefix; use crate::dist::temp; use crate::errors::*; use crate::utils::utils; /// A Transaction tracks changes to the file system, allowing them to /// be rolled back in case of an error. Instead of deleting or /// overwriting file, the old copies are moved to a temporary /// folder. If the transaction is rolled back, they will be moved back /// into place. If the transaction is committed, these files are /// automatically cleaned up using the temp system. /// /// All operations that create files will automatically create any /// intermediate directories in the path to the file if they do not /// already exist. /// /// All operations that create files will fail if the destination /// already exists. pub struct Transaction<'a> { prefix: InstallPrefix, changes: Vec>, temp_cfg: &'a temp::Cfg, notify_handler: &'a dyn Fn(Notification<'_>), committed: bool, } impl<'a> Transaction<'a> { pub fn new( prefix: InstallPrefix, temp_cfg: &'a temp::Cfg, notify_handler: &'a dyn Fn(Notification<'_>), ) -> Self { Transaction { prefix, changes: Vec::new(), temp_cfg, notify_handler, committed: false, } } /// Commit must be called for all successful transactions. If not /// called the transaction will be rolled back on drop. pub fn commit(mut self) { self.committed = true; } fn change(&mut self, item: ChangedItem<'a>) { self.changes.push(item); } /// Add a file at a relative path to the install prefix. Returns a /// `File` that may be used to subsequently write the /// contents. pub fn add_file(&mut self, component: &str, relpath: PathBuf) -> Result { assert!(relpath.is_relative()); let (item, file) = ChangedItem::add_file(&self.prefix, component, relpath)?; self.change(item); Ok(file) } /// Copy a file to a relative path of the install prefix. pub fn copy_file(&mut self, component: &str, relpath: PathBuf, src: &Path) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::copy_file(&self.prefix, component, relpath, src)?; self.change(item); Ok(()) } /// Recursively copy a directory to a relative path of the install prefix. pub fn copy_dir(&mut self, component: &str, relpath: PathBuf, src: &Path) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::copy_dir(&self.prefix, component, relpath, src)?; self.change(item); Ok(()) } /// Remove a file from a relative path to the install prefix. pub fn remove_file(&mut self, component: &str, relpath: PathBuf) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::remove_file( &self.prefix, component, relpath, self.temp_cfg, self.notify_handler(), )?; self.change(item); Ok(()) } /// Recursively remove a directory from a relative path of the /// install prefix. pub fn remove_dir(&mut self, component: &str, relpath: PathBuf) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::remove_dir( &self.prefix, component, relpath, self.temp_cfg, self.notify_handler(), )?; self.change(item); Ok(()) } /// Create a new file with string contents at a relative path to /// the install prefix. pub fn write_file(&mut self, component: &str, relpath: PathBuf, content: String) -> Result<()> { assert!(relpath.is_relative()); let (item, mut file) = ChangedItem::add_file(&self.prefix, component, relpath.clone())?; self.change(item); utils::write_str( "component", &mut file, &self.prefix.abs_path(&relpath), &content, )?; Ok(()) } /// If the file exists back it up for rollback, otherwise ensure that the path /// to it exists so that subsequent calls to `File::create` will succeed. /// /// This is used for arbitrarily manipulating a file. pub fn modify_file(&mut self, relpath: PathBuf) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::modify_file(&self.prefix, relpath, self.temp_cfg)?; self.change(item); Ok(()) } /// Move a file to a relative path of the install prefix. pub(crate) fn move_file( &mut self, component: &str, relpath: PathBuf, src: &Path, ) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::move_file(&self.prefix, component, relpath, src, self.notify_handler())?; self.change(item); Ok(()) } /// Recursively move a directory to a relative path of the install prefix. pub(crate) fn move_dir(&mut self, component: &str, relpath: PathBuf, src: &Path) -> Result<()> { assert!(relpath.is_relative()); let item = ChangedItem::move_dir(&self.prefix, component, relpath, src, self.notify_handler())?; self.change(item); Ok(()) } pub(crate) fn temp(&self) -> &'a temp::Cfg { self.temp_cfg } pub(crate) fn notify_handler(&self) -> &'a dyn Fn(Notification<'_>) { self.notify_handler } } /// If a Transaction is dropped without being committed, the changes /// are automatically rolled back. impl<'a> Drop for Transaction<'a> { fn drop(&mut self) { if !self.committed { (self.notify_handler)(Notification::RollingBack); for item in self.changes.iter().rev() { // ok_ntfy!(self.notify_handler, // Notification::NonFatalError, match item.roll_back(&self.prefix, self.notify_handler()) { Ok(()) => {} Err(e) => { (self.notify_handler)(Notification::NonFatalError(&e)); } } } } } } /// This is the set of fundamental operations supported on a /// Transaction. More complicated operations, such as installing a /// package, or updating a component, distill down into a series of /// these primitives. #[derive(Debug)] enum ChangedItem<'a> { AddedFile(PathBuf), AddedDir(PathBuf), RemovedFile(PathBuf, temp::File<'a>), RemovedDir(PathBuf, temp::Dir<'a>), ModifiedFile(PathBuf, Option>), } impl<'a> ChangedItem<'a> { fn roll_back( &self, prefix: &InstallPrefix, notify: &'a dyn Fn(Notification<'_>), ) -> Result<()> { use self::ChangedItem::*; match self { AddedFile(path) => utils::remove_file("component", &prefix.abs_path(path))?, AddedDir(path) => utils::remove_dir("component", &prefix.abs_path(path), notify)?, RemovedFile(path, tmp) | ModifiedFile(path, Some(tmp)) => { utils::rename_file("component", tmp, &prefix.abs_path(path), notify)? } RemovedDir(path, tmp) => { utils::rename_dir("component", &tmp.join("bk"), &prefix.abs_path(path), notify)? } ModifiedFile(path, None) => { let abs_path = prefix.abs_path(path); if utils::is_file(&abs_path) { utils::remove_file("component", &abs_path)?; } } } Ok(()) } fn dest_abs_path(prefix: &InstallPrefix, component: &str, relpath: &Path) -> Result { let abs_path = prefix.abs_path(relpath); if utils::path_exists(&abs_path) { Err(anyhow!(RustupError::ComponentConflict { name: component.to_owned(), path: relpath.to_path_buf(), })) } else { if let Some(p) = abs_path.parent() { utils::ensure_dir_exists("component", p, &|_: Notification<'_>| ())?; } Ok(abs_path) } } fn add_file(prefix: &InstallPrefix, component: &str, relpath: PathBuf) -> Result<(Self, File)> { let abs_path = ChangedItem::dest_abs_path(prefix, component, &relpath)?; let file = File::create(&abs_path) .with_context(|| format!("error creating file '{}'", abs_path.display()))?; Ok((ChangedItem::AddedFile(relpath), file)) } fn copy_file( prefix: &InstallPrefix, component: &str, relpath: PathBuf, src: &Path, ) -> Result { let abs_path = ChangedItem::dest_abs_path(prefix, component, &relpath)?; utils::copy_file(src, &abs_path)?; Ok(ChangedItem::AddedFile(relpath)) } fn copy_dir( prefix: &InstallPrefix, component: &str, relpath: PathBuf, src: &Path, ) -> Result { let abs_path = ChangedItem::dest_abs_path(prefix, component, &relpath)?; utils::copy_dir(src, &abs_path, &|_: Notification<'_>| ())?; Ok(ChangedItem::AddedDir(relpath)) } fn remove_file( prefix: &InstallPrefix, component: &str, relpath: PathBuf, temp_cfg: &'a temp::Cfg, notify: &'a dyn Fn(Notification<'_>), ) -> Result { let abs_path = prefix.abs_path(&relpath); let backup = temp_cfg.new_file()?; if !utils::path_exists(&abs_path) { Err(RustupError::ComponentMissingFile { name: component.to_owned(), path: relpath, } .into()) } else { utils::rename_file("component", &abs_path, &backup, notify)?; Ok(ChangedItem::RemovedFile(relpath, backup)) } } fn remove_dir( prefix: &InstallPrefix, component: &str, relpath: PathBuf, temp_cfg: &'a temp::Cfg, notify: &'a dyn Fn(Notification<'_>), ) -> Result { let abs_path = prefix.abs_path(&relpath); let backup = temp_cfg.new_directory()?; if !utils::path_exists(&abs_path) { Err(RustupError::ComponentMissingDir { name: component.to_owned(), path: relpath, } .into()) } else { utils::rename_dir("component", &abs_path, &backup.join("bk"), notify)?; Ok(ChangedItem::RemovedDir(relpath, backup)) } } fn modify_file( prefix: &InstallPrefix, relpath: PathBuf, temp_cfg: &'a temp::Cfg, ) -> Result { let abs_path = prefix.abs_path(&relpath); if utils::is_file(&abs_path) { let backup = temp_cfg.new_file()?; utils::copy_file(&abs_path, &backup)?; Ok(ChangedItem::ModifiedFile(relpath, Some(backup))) } else { if let Some(p) = abs_path.parent() { utils::ensure_dir_exists("component", p, &|_: Notification<'_>| {})?; } Ok(ChangedItem::ModifiedFile(relpath, None)) } } fn move_file( prefix: &InstallPrefix, component: &str, relpath: PathBuf, src: &Path, notify: &'a dyn Fn(Notification<'_>), ) -> Result { let abs_path = ChangedItem::dest_abs_path(prefix, component, &relpath)?; utils::rename_file("component", src, &abs_path, notify)?; Ok(ChangedItem::AddedFile(relpath)) } fn move_dir( prefix: &InstallPrefix, component: &str, relpath: PathBuf, src: &Path, notify: &'a dyn Fn(Notification<'_>), ) -> Result { let abs_path = ChangedItem::dest_abs_path(prefix, component, &relpath)?; utils::rename_dir("component", src, &abs_path, notify)?; Ok(ChangedItem::AddedDir(relpath)) } } rustup-1.26.0/src/dist/config.rs000066400000000000000000000050561441327105200165140ustar00rootroot00000000000000use anyhow::{bail, Context, Result}; use super::manifest::Component; use crate::errors::*; use crate::utils::toml_utils::*; pub(crate) const SUPPORTED_CONFIG_VERSIONS: [&str; 1] = ["1"]; pub(crate) const DEFAULT_CONFIG_VERSION: &str = "1"; #[derive(Clone, Debug)] pub struct Config { pub config_version: String, pub components: Vec, } impl Config { pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let config_version = get_string(&mut table, "config_version", path)?; if !SUPPORTED_CONFIG_VERSIONS.contains(&&*config_version) { bail!(RustupError::UnsupportedVersion(config_version)); } let components = get_array(&mut table, "components", path)?; let components = Self::toml_to_components(components, &format!("{}{}.", path, "components"))?; Ok(Self { config_version, components, }) } pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); result.insert( "config_version".to_owned(), toml::Value::String(self.config_version), ); let components = Self::components_to_toml(self.components); if !components.is_empty() { result.insert("components".to_owned(), toml::Value::Array(components)); } result } pub(crate) fn parse(data: &str) -> Result { let value = toml::from_str(data).context("error parsing manifest")?; Self::from_toml(value, "") } pub(crate) fn stringify(self) -> String { toml::Value::Table(self.into_toml()).to_string() } fn toml_to_components(arr: toml::value::Array, path: &str) -> Result> { let mut result = Vec::new(); for (i, v) in arr.into_iter().enumerate() { if let toml::Value::Table(t) = v { let path = format!("{path}[{i}]"); result.push(Component::from_toml(t, &path, false)?); } } Ok(result) } fn components_to_toml(components: Vec) -> toml::value::Array { let mut result = toml::value::Array::new(); for v in components { result.push(toml::Value::Table(v.into_toml())); } result } pub(crate) fn new() -> Self { Default::default() } } impl Default for Config { fn default() -> Self { Self { config_version: DEFAULT_CONFIG_VERSION.to_owned(), components: Vec::new(), } } } rustup-1.26.0/src/dist/dist.rs000066400000000000000000001277731441327105200162250ustar00rootroot00000000000000use std::collections::HashSet; use std::env; use std::fmt; use std::io::Write; use std::ops::Deref; use std::path::Path; use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; use chrono::NaiveDate; use lazy_static::lazy_static; use regex::Regex; use thiserror::Error as ThisError; use crate::dist::download::DownloadCfg; use crate::dist::manifest::{Component, Manifest as ManifestV2}; use crate::dist::manifestation::{Changes, Manifestation, UpdateStatus}; use crate::dist::notifications::*; use crate::dist::prefix::InstallPrefix; use crate::dist::temp; pub(crate) use crate::dist::triple::*; use crate::errors::RustupError; use crate::process; use crate::utils::utils; pub static DEFAULT_DIST_SERVER: &str = "https://static.rust-lang.org"; // Deprecated pub(crate) static DEFAULT_DIST_ROOT: &str = "https://static.rust-lang.org/dist"; // The channel patterns we support static TOOLCHAIN_CHANNELS: &[&str] = &[ "nightly", "beta", "stable", // Allow from 1.0.0 through to 9.999.99 with optional patch version r"\d{1}\.\d{1,3}(?:\.\d{1,2})?", ]; fn components_missing_msg(cs: &[Component], manifest: &ManifestV2, toolchain: &str) -> String { assert!(!cs.is_empty()); let mut buf = vec![]; let suggestion = format!(" rustup toolchain add {toolchain} --profile minimal"); let nightly_tips = "Sometimes not all components are available in any given nightly. "; if cs.len() == 1 { let _ = writeln!( buf, "component {} is unavailable for download for channel '{}'", &cs[0].description(manifest), toolchain, ); if toolchain.starts_with("nightly") { let _ = write!(buf, "{nightly_tips}"); } let _ = write!( buf, "If you don't need the component, you could try a minimal installation with:\n\n{suggestion}" ); } else { let cs_str = cs .iter() .map(|c| c.description(manifest)) .collect::>() .join(", "); let _ = write!( buf, "some components unavailable for download for channel '{toolchain}': {cs_str}" ); if toolchain.starts_with("nightly") { let _ = write!(buf, "{nightly_tips}"); } let _ = write!( buf, "If you don't need the components, you could try a minimal installation with:\n\n{suggestion}" ); } String::from_utf8(buf).unwrap() } #[derive(Debug, ThisError)] enum DistError { #[error("{}", components_missing_msg(.0, .1, .2))] ToolchainComponentsMissing(Vec, Box, String), #[error("no release found for '{0}'")] MissingReleaseForToolchain(String), } #[derive(Debug, PartialEq)] struct ParsedToolchainDesc { channel: String, date: Option, target: Option, } // A toolchain descriptor from rustup's perspective. These contain // 'partial target triples', which allow toolchain names like // 'stable-msvc' to work. Partial target triples though are parsed // from a hardcoded set of known triples, whereas target triples // are nearly-arbitrary strings. #[derive(Debug, Clone)] pub struct PartialToolchainDesc { // Either "nightly", "stable", "beta", or an explicit version number pub channel: String, pub date: Option, pub target: PartialTargetTriple, } // Fully-resolved toolchain descriptors. These always have full target // triples attached to them and are used for canonical identification, // such as naming their installation directory. #[derive(Debug, Clone)] pub struct ToolchainDesc { // Either "nightly", "stable", "beta", or an explicit version number pub channel: String, pub date: Option, pub target: TargetTriple, } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct TargetTriple(String); // Linux hosts don't indicate clib in uname, however binaries only // run on boxes with the same clib, as expected. #[cfg(all(not(windows), not(target_env = "musl")))] const TRIPLE_X86_64_UNKNOWN_LINUX: &str = "x86_64-unknown-linux-gnu"; #[cfg(all(not(windows), target_env = "musl"))] const TRIPLE_X86_64_UNKNOWN_LINUX: &str = "x86_64-unknown-linux-musl"; #[cfg(all(not(windows), not(target_env = "musl")))] const TRIPLE_AARCH64_UNKNOWN_LINUX: &str = "aarch64-unknown-linux-gnu"; #[cfg(all(not(windows), target_env = "musl"))] const TRIPLE_AARCH64_UNKNOWN_LINUX: &str = "aarch64-unknown-linux-musl"; // MIPS platforms don't indicate endianness in uname, however binaries only // run on boxes with the same endianness, as expected. // Hence we could distinguish between the variants with compile-time cfg() // attributes alone. #[cfg(all(not(windows), target_endian = "big"))] static TRIPLE_MIPS_UNKNOWN_LINUX_GNU: &str = "mips-unknown-linux-gnu"; #[cfg(all(not(windows), target_endian = "little"))] static TRIPLE_MIPS_UNKNOWN_LINUX_GNU: &str = "mipsel-unknown-linux-gnu"; #[cfg(all(not(windows), target_endian = "big"))] static TRIPLE_MIPS64_UNKNOWN_LINUX_GNUABI64: &str = "mips64-unknown-linux-gnuabi64"; #[cfg(all(not(windows), target_endian = "little"))] static TRIPLE_MIPS64_UNKNOWN_LINUX_GNUABI64: &str = "mips64el-unknown-linux-gnuabi64"; impl FromStr for ParsedToolchainDesc { type Err = anyhow::Error; fn from_str(desc: &str) -> Result { lazy_static! { static ref TOOLCHAIN_CHANNEL_PATTERN: String = format!( r"^({})(?:-(\d{{4}}-\d{{2}}-\d{{2}}))?(?:-(.+))?$", TOOLCHAIN_CHANNELS.join("|") ); // Note this regex gives you a guaranteed match of the channel (1) // and an optional match of the date (2) and target (3) static ref TOOLCHAIN_CHANNEL_RE: Regex = Regex::new(&TOOLCHAIN_CHANNEL_PATTERN).unwrap(); } let d = TOOLCHAIN_CHANNEL_RE.captures(desc).map(|c| { fn fn_map(s: &str) -> Option { if s.is_empty() { None } else { Some(s.to_owned()) } } // These versions don't have v2 manifests, but they don't have point releases either, // so to make the two-part version numbers work for these versions, specially turn // them into their corresponding ".0" version. let channel = match c.get(1).unwrap().as_str() { "1.0" => "1.0.0", "1.1" => "1.1.0", "1.2" => "1.2.0", "1.3" => "1.3.0", "1.4" => "1.4.0", "1.5" => "1.5.0", "1.6" => "1.6.0", "1.7" => "1.7.0", "1.8" => "1.8.0", other => other, }; Self { channel: channel.to_owned(), date: c.get(2).map(|s| s.as_str()).and_then(fn_map), target: c.get(3).map(|s| s.as_str()).and_then(fn_map), } }); if let Some(d) = d { Ok(d) } else { Err(RustupError::InvalidToolchainName(desc.to_string()).into()) } } } impl Deref for TargetTriple { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } impl TargetTriple { pub fn new(name: &str) -> Self { Self(name.to_string()) } pub(crate) fn from_build() -> Self { if let Some(triple) = option_env!("RUSTUP_OVERRIDE_BUILD_TRIPLE") { Self::new(triple) } else { Self::new(env!("TARGET")) } } pub(crate) fn from_host() -> Option { #[cfg(windows)] fn inner() -> Option { use std::mem; /// Get the host architecture using `IsWow64Process2`. This function /// produces the most accurate results (supports detecting aarch64), but /// it is only available on Windows 10 1511+, so we use `GetProcAddress` /// to maintain backward compatibility with older Windows versions. fn arch_primary() -> Option<&'static str> { use winapi::shared::minwindef::BOOL; use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress}; use winapi::um::processthreadsapi::GetCurrentProcess; use winapi::um::winnt::HANDLE; const IMAGE_FILE_MACHINE_ARM64: u16 = 0xAA64; const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; const IMAGE_FILE_MACHINE_I386: u16 = 0x014c; #[allow(non_snake_case)] let IsWow64Process2: unsafe extern "system" fn( HANDLE, *mut u16, *mut u16, ) -> BOOL = unsafe { let module = GetModuleHandleA(b"kernel32.dll\0" as *const u8 as *const i8); if module.is_null() { return None; } let process = GetProcAddress(module, b"IsWow64Process2\0" as *const u8 as *const i8); if process.is_null() { return None; } mem::transmute(process) }; let mut _machine = 0; let mut native_machine = 0; unsafe { // cannot fail; handle does not need to be closed. let process = GetCurrentProcess(); if IsWow64Process2(process, &mut _machine, &mut native_machine) == 0 { return None; } }; match native_machine { IMAGE_FILE_MACHINE_AMD64 => Some("x86_64"), IMAGE_FILE_MACHINE_I386 => Some("i686"), IMAGE_FILE_MACHINE_ARM64 => Some("aarch64"), _ => None, } } /// Get the host architecture using `GetNativeSystemInfo`. /// Does not support detecting aarch64. fn arch_fallback() -> Option<&'static str> { use winapi::um::sysinfoapi::GetNativeSystemInfo; const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; let mut sys_info; unsafe { sys_info = mem::zeroed(); GetNativeSystemInfo(&mut sys_info); } match unsafe { sys_info.u.s() }.wProcessorArchitecture { PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64"), PROCESSOR_ARCHITECTURE_INTEL => Some("i686"), _ => None, } } // Default to msvc let arch = arch_primary().or_else(arch_fallback)?; let msvc_triple = format!("{arch}-pc-windows-msvc"); Some(TargetTriple(msvc_triple)) } #[cfg(not(windows))] fn inner() -> Option { use std::ffi::CStr; use std::mem; let mut sys_info; let (sysname, machine) = unsafe { sys_info = mem::zeroed(); if libc::uname(&mut sys_info) != 0 { return None; } ( CStr::from_ptr(sys_info.sysname.as_ptr()).to_bytes(), CStr::from_ptr(sys_info.machine.as_ptr()).to_bytes(), ) }; let host_triple = match (sysname, machine) { (_, b"arm") if cfg!(target_os = "android") => Some("arm-linux-androideabi"), (_, b"armv7l") if cfg!(target_os = "android") => Some("armv7-linux-androideabi"), (_, b"armv8l") if cfg!(target_os = "android") => Some("armv7-linux-androideabi"), (_, b"aarch64") if cfg!(target_os = "android") => Some("aarch64-linux-android"), (_, b"i686") if cfg!(target_os = "android") => Some("i686-linux-android"), (_, b"x86_64") if cfg!(target_os = "android") => Some("x86_64-linux-android"), (b"Linux", b"x86_64") => Some(TRIPLE_X86_64_UNKNOWN_LINUX), (b"Linux", b"i686") => Some("i686-unknown-linux-gnu"), (b"Linux", b"mips") => Some(TRIPLE_MIPS_UNKNOWN_LINUX_GNU), (b"Linux", b"mips64") => Some(TRIPLE_MIPS64_UNKNOWN_LINUX_GNUABI64), (b"Linux", b"arm") => Some("arm-unknown-linux-gnueabi"), (b"Linux", b"armv7l") => Some("armv7-unknown-linux-gnueabihf"), (b"Linux", b"armv8l") => Some("armv7-unknown-linux-gnueabihf"), (b"Linux", b"aarch64") => Some(TRIPLE_AARCH64_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"), (b"FreeBSD", b"i686") => Some("i686-unknown-freebsd"), (b"OpenBSD", b"x86_64") => Some("x86_64-unknown-openbsd"), (b"OpenBSD", b"i686") => Some("i686-unknown-openbsd"), (b"NetBSD", b"x86_64") => Some("x86_64-unknown-netbsd"), (b"NetBSD", b"i686") => Some("i686-unknown-netbsd"), (b"DragonFly", b"x86_64") => Some("x86_64-unknown-dragonfly"), (b"SunOS", b"i86pc") => Some("x86_64-unknown-illumos"), _ => None, }; host_triple.map(TargetTriple::new) } if let Ok(triple) = process().var("RUSTUP_OVERRIDE_HOST_TRIPLE") { Some(Self(triple)) } else { inner() } } pub(crate) fn from_host_or_build() -> Self { Self::from_host().unwrap_or_else(Self::from_build) } pub(crate) fn can_run(&self, other: &TargetTriple) -> Result { // Most trivial shortcut of all if self == other { return Ok(true); } // Otherwise we need to parse things let partial_self = PartialTargetTriple::new(&self.0) .ok_or_else(|| anyhow!(format!("Unable to parse target triple: {}", self.0)))?; let partial_other = PartialTargetTriple::new(&other.0) .ok_or_else(|| anyhow!(format!("Unable to parse target triple: {}", other.0)))?; // First obvious check is OS, if that doesn't match there's no chance let ret = if partial_self.os != partial_other.os { false } else if partial_self.os.as_deref() == Some("pc-windows") { // Windows is a special case here: we can run gnu and msvc on the same system, // x86_64 can run i686, and aarch64 can run i686 through emulation (partial_self.arch == partial_other.arch) || (partial_self.arch.as_deref() == Some("x86_64") && partial_other.arch.as_deref() == Some("i686")) || (partial_self.arch.as_deref() == Some("aarch64") && partial_other.arch.as_deref() == Some("i686")) } else { // For other OSes, for now, we assume other toolchains won't run false }; Ok(ret) } } impl std::convert::TryFrom for TargetTriple { type Error = &'static str; fn try_from(value: PartialTargetTriple) -> std::result::Result { if value.arch.is_some() && value.os.is_some() && value.env.is_some() { Ok(Self(format!( "{}-{}-{}", value.arch.unwrap(), value.os.unwrap(), value.env.unwrap() ))) } else { Err("Incomplete / bad target triple") } } } impl FromStr for PartialToolchainDesc { type Err = anyhow::Error; fn from_str(name: &str) -> Result { let parsed: ParsedToolchainDesc = name.parse()?; let target = PartialTargetTriple::new(parsed.target.as_deref().unwrap_or("")); target .map(|target| Self { channel: parsed.channel, date: parsed.date, target, }) .ok_or_else(|| anyhow!(RustupError::InvalidToolchainName(name.to_string()))) } } impl PartialToolchainDesc { pub(crate) fn resolve(self, input_host: &TargetTriple) -> Result { let host = PartialTargetTriple::new(&input_host.0).ok_or_else(|| { anyhow!(format!( "Provided host '{}' couldn't be converted to partial triple", input_host.0 )) })?; let host_arch = host.arch.ok_or_else(|| { anyhow!(format!( "Provided host '{}' did not specify a CPU architecture", input_host.0 )) })?; let host_os = host.os.ok_or_else(|| { anyhow!(format!( "Provided host '{}' did not specify an operating system", input_host.0 )) })?; let host_env = host.env; // If OS was specified, don't default to host environment, even if the OS matches // the host OS, otherwise cannot specify no environment. let env = if self.target.os.is_some() { self.target.env } else { self.target.env.or(host_env) }; let arch = self.target.arch.unwrap_or(host_arch); let os = self.target.os.unwrap_or(host_os); let trip = if let Some(env) = env { format!("{arch}-{os}-{env}") } else { format!("{arch}-{os}") }; Ok(ToolchainDesc { channel: self.channel, date: self.date, target: TargetTriple(trip), }) } pub(crate) fn has_triple(&self) -> bool { self.target.arch.is_some() || self.target.os.is_some() || self.target.env.is_some() } } impl FromStr for ToolchainDesc { type Err = anyhow::Error; fn from_str(name: &str) -> Result { let parsed: ParsedToolchainDesc = name.parse()?; if parsed.target.is_none() { return Err(anyhow!(RustupError::InvalidToolchainName(name.to_string()))); } Ok(Self { channel: parsed.channel, date: parsed.date, target: TargetTriple(parsed.target.unwrap()), }) } } impl ToolchainDesc { pub(crate) fn manifest_v1_url(&self, dist_root: &str) -> String { let do_manifest_staging = process().var("RUSTUP_STAGED_MANIFEST").is_ok(); match (self.date.as_ref(), do_manifest_staging) { (None, false) => format!("{}/channel-rust-{}", dist_root, self.channel), (Some(date), false) => format!("{}/{}/channel-rust-{}", dist_root, date, self.channel), (None, true) => format!("{}/staging/channel-rust-{}", dist_root, self.channel), (Some(_), true) => panic!("not a real-world case"), } } pub(crate) fn manifest_v2_url(&self, dist_root: &str) -> String { format!("{}.toml", self.manifest_v1_url(dist_root)) } /// Either "$channel" or "channel-$date" pub fn manifest_name(&self) -> String { match self.date { None => self.channel.clone(), Some(ref date) => format!("{}-{}", self.channel, date), } } pub(crate) fn package_dir(&self, dist_root: &str) -> String { match self.date { None => dist_root.to_string(), Some(ref date) => format!("{dist_root}/{date}"), } } /// Toolchain channels are considered 'tracking' if it is one of the named channels /// such as `stable`, or is an incomplete version such as `1.48`, and the /// date field is empty. pub(crate) fn is_tracking(&self) -> bool { let channels = ["nightly", "beta", "stable"]; lazy_static! { static ref TRACKING_VERSION: Regex = Regex::new(r"^\d{1}\.\d{1,3}$").unwrap(); } (channels.iter().any(|x| *x == self.channel) || TRACKING_VERSION.is_match(&self.channel)) && self.date.is_none() } } // A little convenience for just parsing a channel name or archived channel name pub(crate) fn validate_channel_name(name: &str) -> Result<()> { let toolchain = PartialToolchainDesc::from_str(name)?; if toolchain.has_triple() { Err(anyhow!(format!("target triple in channel name '{name}'"))) } else { Ok(()) } } #[derive(Debug)] pub(crate) struct Manifest<'a>(temp::File<'a>, String); #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum Profile { Minimal, Default, Complete, } impl FromStr for Profile { type Err = anyhow::Error; fn from_str(name: &str) -> Result { match name { "minimal" | "m" => Ok(Self::Minimal), "default" | "d" | "" => Ok(Self::Default), "complete" | "c" => Ok(Self::Complete), _ => Err(anyhow!(format!( "unknown profile name: '{}'; valid profile names are: {}", name, valid_profile_names() ))), } } } impl Profile { pub(crate) fn names() -> &'static [&'static str] { &["minimal", "default", "complete"] } pub(crate) fn default_name() -> &'static str { "default" } } impl Default for Profile { fn default() -> Self { Self::Default } } impl fmt::Display for TargetTriple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl fmt::Display for PartialToolchainDesc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.channel)?; if let Some(ref date) = self.date { write!(f, "-{date}")?; } if let Some(ref arch) = self.target.arch { write!(f, "-{arch}")?; } if let Some(ref os) = self.target.os { write!(f, "-{os}")?; } if let Some(ref env) = self.target.env { write!(f, "-{env}")?; } Ok(()) } } impl fmt::Display for ToolchainDesc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.channel)?; if let Some(ref date) = self.date { write!(f, "-{date}")?; } write!(f, "-{}", self.target)?; Ok(()) } } impl fmt::Display for Profile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Minimal => write!(f, "minimal"), Self::Default => write!(f, "default"), Self::Complete => write!(f, "complete"), } } } pub(crate) fn valid_profile_names() -> String { Profile::names() .iter() .map(|s| format!("'{s}'")) .collect::>() .join(", ") } // Installs or updates a toolchain from a dist server. If an initial // install then it will be installed with the default components. If // an upgrade then all the existing components will be upgraded. // // Returns the manifest's hash if anything changed. pub(crate) fn update_from_dist( download: DownloadCfg<'_>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, profile: Option, prefix: &InstallPrefix, force_update: bool, allow_downgrade: bool, old_date: Option<&str>, components: &[&str], targets: &[&str], ) -> Result> { let fresh_install = !prefix.path().exists(); let hash_exists = update_hash.map(Path::exists).unwrap_or(false); // fresh_install means the toolchain isn't present, but hash_exists means there is a stray hash file if fresh_install && hash_exists { // It's ok to unwrap, because hash have to exist at this point (download.notify_handler)(Notification::StrayHash(update_hash.unwrap())); std::fs::remove_file(update_hash.unwrap())?; } let res = update_from_dist_( download, update_hash, toolchain, profile, prefix, force_update, allow_downgrade, old_date, components, targets, ); // Don't leave behind an empty / broken installation directory if res.is_err() && fresh_install { // FIXME Ignoring cascading errors let _ = utils::remove_dir("toolchain", prefix.path(), download.notify_handler); } res } fn update_from_dist_( download: DownloadCfg<'_>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, profile: Option, prefix: &InstallPrefix, force_update: bool, allow_downgrade: bool, old_date: Option<&str>, components: &[&str], targets: &[&str], ) -> Result> { let mut toolchain = toolchain.clone(); let mut fetched = String::new(); let mut first_err = None; let backtrack = toolchain.channel == "nightly" && toolchain.date.is_none(); // We want to limit backtracking if we do not already have a toolchain let mut backtrack_limit: Option = if toolchain.date.is_some() { None } else { // We limit the backtracking to 21 days by default (half a release cycle). // The limit of 21 days is an arbitrary selection, so we let the user override it. const BACKTRACK_LIMIT_DEFAULT: i32 = 21; let provided = process() .var("RUSTUP_BACKTRACK_LIMIT") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(BACKTRACK_LIMIT_DEFAULT); Some(if provided < 1 { 1 } else { provided }) }; // In case there is no allow-downgrade option set // we never want to backtrack further back than the nightly that's already installed. // // If no nightly is installed, it makes no sense to backtrack beyond the first ever manifest, // which is 2014-12-20 according to // https://static.rust-lang.org/cargo-dist/index.html. // // We could arguably use the date of the first rustup release here, but that would break a // bunch of the tests, which (inexplicably) use 2015-01-01 as their manifest dates. let first_manifest = date_from_manifest_date("2014-12-20").unwrap(); let old_manifest = old_date .and_then(date_from_manifest_date) .unwrap_or(first_manifest); let last_manifest = if allow_downgrade { first_manifest } else { old_manifest }; let current_manifest = { let manifestation = Manifestation::open(prefix.clone(), toolchain.target.clone())?; manifestation.load_manifest()? }; loop { match try_update_from_dist_( download, update_hash, &toolchain, profile, prefix, force_update, components, targets, &mut fetched, ) { Ok(v) => break Ok(v), Err(e) => { if !backtrack { break Err(e); } let cause = e.downcast_ref::(); match cause { Some(DistError::ToolchainComponentsMissing(components, manifest, ..)) => { (download.notify_handler)(Notification::SkippingNightlyMissingComponent( &toolchain, current_manifest.as_ref().unwrap_or(manifest), components, )); if first_err.is_none() { first_err = Some(e); } // We decrement the backtrack count only on unavailable component errors // so that the limit only applies to nightlies that were indeed available, // and ignores missing ones. backtrack_limit = backtrack_limit.map(|n| n - 1); } Some(DistError::MissingReleaseForToolchain(..)) => { // no need to even print anything for missing nightlies, // since we don't really "skip" them } None => { // All other errors break the loop break Err(e); } }; if let Some(backtrack_limit) = backtrack_limit { if backtrack_limit < 1 { // This unwrap is safe because we can only hit this if we've // had a chance to set first_err break Err(first_err.unwrap()); } } // The user asked to update their nightly, but the latest nightly does not have all // the components that the user currently has installed. Let's try the previous // nightlies in reverse chronological order until we find a nightly that does, // starting at one date earlier than the current manifest's date. let toolchain_date = toolchain.date.as_ref().unwrap_or(&fetched); let try_next = date_from_manifest_date(toolchain_date) .unwrap_or_else(|| panic!("Malformed manifest date: {toolchain_date:?}")) .pred_opt() .unwrap(); if try_next < last_manifest { // Wouldn't be an update if we go further back than the user's current nightly. if let Some(e) = first_err { break Err(e); } else { // In this case, all newer nightlies are missing, which means there are no // updates, so the user is already at the latest nightly. break Ok(None); } } toolchain.date = Some(try_next.format("%Y-%m-%d").to_string()); } } } } fn try_update_from_dist_( download: DownloadCfg<'_>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, profile: Option, prefix: &InstallPrefix, force_update: bool, components: &[&str], targets: &[&str], fetched: &mut String, ) -> Result> { let toolchain_str = toolchain.to_string(); let manifestation = Manifestation::open(prefix.clone(), toolchain.target.clone())?; // TODO: Add a notification about which manifest version is going to be used (download.notify_handler)(Notification::DownloadingManifest(&toolchain_str)); match dl_v2_manifest( download, // 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() { update_hash } else { None }, toolchain, ) { Ok(Some((m, hash))) => { (download.notify_handler)(Notification::DownloadedManifest( &m.date, m.get_rust_version().ok(), )); let profile_components = match profile { Some(profile) => m.get_profile_components(profile, &toolchain.target)?, None => Vec::new(), }; let mut all_components: HashSet = profile_components.into_iter().collect(); let rust_package = m.get_package("rust")?; let rust_target_package = rust_package.get_target(Some(&toolchain.target.clone()))?; for component in components { let mut component = Component::new(component.to_string(), Some(toolchain.target.clone()), false); if let Some(renamed) = m.rename_component(&component) { component = renamed; } // Look up the newly constructed/renamed component and ensure that // if it's a wildcard component we note such, otherwise we end up // exacerbating the problem we thought we'd fixed with #2087 and #2115 if let Some(c) = rust_target_package .components .iter() .find(|c| c.short_name_in_manifest() == component.short_name_in_manifest()) { if c.target.is_none() { component = component.wildcard(); } } all_components.insert(component); } for target in targets { let triple = TargetTriple::new(target); all_components.insert(Component::new("rust-std".to_string(), Some(triple), false)); } let mut explicit_add_components: Vec<_> = all_components.into_iter().collect(); explicit_add_components.sort(); let changes = Changes { explicit_add_components, remove_components: Vec::new(), }; *fetched = m.date.clone(); return match manifestation.update( &m, changes, force_update, &download, &download.notify_handler, &toolchain.manifest_name(), true, ) { Ok(status) => match status { UpdateStatus::Unchanged => Ok(None), UpdateStatus::Changed => Ok(Some(hash)), }, Err(err) => match err.downcast_ref::() { Some(RustupError::RequestedComponentsUnavailable { components, manifest, toolchain, }) => Err(anyhow!(DistError::ToolchainComponentsMissing( components.to_owned(), Box::new(manifest.to_owned()), toolchain.to_owned(), ))), Some(_) | None => Err(err), }, }; } Ok(None) => return Ok(None), Err(any) => { enum Cases { DNE, CF, Other, } let case = match any.downcast_ref::() { Some(RustupError::ChecksumFailed { .. }) => Cases::CF, Some(RustupError::DownloadNotExists { .. }) => Cases::DNE, _ => Cases::Other, }; match case { Cases::CF => return Ok(None), Cases::DNE => { // Proceed to try v1 as a fallback (download.notify_handler)(Notification::DownloadingLegacyManifest); } Cases::Other => return Err(any), } } } // If the v2 manifest is not found then try v1 let manifest = match dl_v1_manifest(download, toolchain) { Ok(m) => m, Err(any) => { enum Cases { DNE, CF, Other, } let case = match any.downcast_ref::() { Some(RustupError::ChecksumFailed { .. }) => Cases::CF, Some(RustupError::DownloadNotExists { .. }) => Cases::DNE, _ => Cases::Other, }; match case { Cases::DNE => { bail!(DistError::MissingReleaseForToolchain( toolchain.manifest_name() )); } Cases::CF => return Err(any), Cases::Other => { return Err(any).with_context(|| { format!( "failed to download manifest for '{}'", toolchain.manifest_name() ) }); } } } }; let result = manifestation.update_v1( &manifest, update_hash, download.temp_cfg, &download.notify_handler, ); // inspect, determine what context to add, then process afterwards. let mut download_not_exists = false; match &result { Ok(_) => (), Err(e) => { if let Some(RustupError::DownloadNotExists { .. }) = e.downcast_ref::() { download_not_exists = true } } } if download_not_exists { result.with_context(|| { format!("could not download nonexistent rust version `{toolchain_str}`") }) } else { result } } pub(crate) fn dl_v2_manifest( download: DownloadCfg<'_>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, ) -> Result> { let manifest_url = toolchain.manifest_v2_url(download.dist_root); match download.download_and_check(&manifest_url, update_hash, ".toml") { Ok(manifest_dl) => { // Downloaded ok! let (manifest_file, manifest_hash) = if let Some(m) = manifest_dl { m } else { return Ok(None); }; let manifest_str = utils::read_file("manifest", &manifest_file)?; let manifest = ManifestV2::parse(&manifest_str)?; Ok(Some((manifest, manifest_hash))) } Err(any) => { if let Some(RustupError::ChecksumFailed { .. }) = any.downcast_ref::() { // Checksum failed - issue warning to try again later (download.notify_handler)(Notification::ManifestChecksumFailedHack); } Err(any) } } } fn dl_v1_manifest(download: DownloadCfg<'_>, toolchain: &ToolchainDesc) -> Result> { let root_url = toolchain.package_dir(download.dist_root); if !["nightly", "beta", "stable"].contains(&&*toolchain.channel) { // This is an explicit version. In v1 there was no manifest, // you just know the file to download, so synthesize one. let installer_name = format!( "{}/rust-{}-{}.tar.gz", root_url, toolchain.channel, toolchain.target ); return Ok(vec![installer_name]); } let manifest_url = toolchain.manifest_v1_url(download.dist_root); let manifest_dl = download.download_and_check(&manifest_url, None, "")?; let (manifest_file, _) = manifest_dl.unwrap(); let manifest_str = utils::read_file("manifest", &manifest_file)?; let urls = manifest_str .lines() .map(|s| format!("{root_url}/{s}")) .collect(); Ok(urls) } fn date_from_manifest_date(date_str: &str) -> Option { NaiveDate::parse_from_str(date_str, "%Y-%m-%d").ok() } #[cfg(test)] mod tests { use super::*; #[test] fn test_parsed_toolchain_desc_parse() { let success_cases = vec![ ("nightly", ("nightly", None, None)), ("beta", ("beta", None, None)), ("stable", ("stable", None, None)), ("0.0", ("0.0", None, None)), ("0.0.0", ("0.0.0", None, None)), ("0.0.0--", ("0.0.0", None, Some("-"))), // possibly a bug? ("9.999.99", ("9.999.99", None, None)), ("0.0.0-anything", ("0.0.0", None, Some("anything"))), ("0.0.0-0000-00-00", ("0.0.0", Some("0000-00-00"), None)), // possibly unexpected behavior, if someone typos a date? ( "0.0.0-00000-000-000", ("0.0.0", None, Some("00000-000-000")), ), // possibly unexpected behavior, if someone forgets to add target after the hyphen? ("0.0.0-0000-00-00-", ("0.0.0", None, Some("0000-00-00-"))), ( "0.0.0-0000-00-00-any-other-thing", ("0.0.0", Some("0000-00-00"), Some("any-other-thing")), ), // special hardcoded cases that only have v1 manifests ("1.0", ("1.0.0", None, None)), ("1.1", ("1.1.0", None, None)), ("1.2", ("1.2.0", None, None)), ("1.3", ("1.3.0", None, None)), ("1.4", ("1.4.0", None, None)), ("1.5", ("1.5.0", None, None)), ("1.6", ("1.6.0", None, None)), ("1.7", ("1.7.0", None, None)), ("1.8", ("1.8.0", None, None)), ]; for (input, (channel, date, target)) in success_cases { let parsed = input.parse::(); assert!( parsed.is_ok(), "expected parsing of `{input}` to succeed: {parsed:?}" ); let expected = ParsedToolchainDesc { channel: channel.into(), date: date.map(String::from), target: target.map(String::from), }; assert_eq!(parsed.unwrap(), expected, "input: `{input}`"); } let failure_cases = vec!["anything", "00.0000.000", "3", "", "--", "0.0.0-"]; for input in failure_cases { let parsed = input.parse::(); assert!( parsed.is_err(), "expected parsing of `{input}` to fail: {parsed:?}" ); let error_message = format!("invalid toolchain name: '{input}'"); assert_eq!( parsed.unwrap_err().to_string(), error_message, "input: `{input}`" ); } } #[test] fn test_tracking_channels() { static CASES: &[(&str, bool)] = &[ ("stable", true), ("beta", true), ("nightly", true), ("nightly-2020-10-04", false), ("1.48", true), ("1.47.0", false), ]; for case in CASES { let full_tcn = format!("{}-x86_64-unknown-linux-gnu", case.0); let tcd = ToolchainDesc::from_str(&full_tcn).unwrap(); eprintln!("Considering {}", case.0); assert_eq!(tcd.is_tracking(), case.1); } } #[test] fn compatible_host_triples() { static CASES: &[(&str, &[&str], &[&str])] = &[ ( // 64bit linux "x86_64-unknown-linux-gnu", // Not compatible beyond itself &[], // Even 32bit linux is considered not compatible by default &["i686-unknown-linux-gnu"], ), ( // On the other hand, 64 bit Windows "x86_64-pc-windows-msvc", // is compatible with 32 bit windows, and even gnu &[ "i686-pc-windows-msvc", "x86_64-pc-windows-gnu", "i686-pc-windows-gnu", ], // But is not compatible with Linux &["x86_64-unknown-linux-gnu"], ), ( // Indeed, 64bit windows with the gnu toolchain "x86_64-pc-windows-gnu", // is compatible with the other windows platforms &[ "i686-pc-windows-msvc", "x86_64-pc-windows-gnu", "i686-pc-windows-gnu", ], // But is not compatible with Linux despite also being gnu &["x86_64-unknown-linux-gnu"], ), ( // However, 32bit Windows is not expected to be able to run // 64bit windows "i686-pc-windows-msvc", &["i686-pc-windows-gnu"], &["x86_64-pc-windows-msvc", "x86_64-pc-windows-gnu"], ), ]; for (host, compatible, incompatible) in CASES { println!("host={host}"); let host = TargetTriple::new(host); assert!(host.can_run(&host).unwrap(), "host wasn't self-compatible"); for other in compatible.iter() { println!("compatible with {other}"); let other = TargetTriple::new(other); assert!( host.can_run(&other).unwrap(), "host and other were unexpectedly incompatible" ); } for other in incompatible.iter() { println!("incompatible with {other}"); let other = TargetTriple::new(other); assert!( !host.can_run(&other).unwrap(), "host and other were unexpectedly compatible" ); } } } } rustup-1.26.0/src/dist/download.rs000066400000000000000000000154061441327105200170560ustar00rootroot00000000000000use std::fs; use std::ops; use std::path::{Path, PathBuf}; use anyhow::{anyhow, Context, Result}; use sha2::{Digest, Sha256}; use url::Url; use crate::dist::notifications::*; use crate::dist::temp; use crate::errors::*; use crate::utils::utils; const UPDATE_HASH_LEN: usize = 20; #[derive(Copy, Clone)] pub struct DownloadCfg<'a> { pub dist_root: &'a str, pub temp_cfg: &'a temp::Cfg, pub download_dir: &'a PathBuf, pub notify_handler: &'a dyn Fn(Notification<'_>), } pub(crate) struct File { path: PathBuf, } impl ops::Deref for File { type Target = Path; fn deref(&self) -> &Path { self.path.as_path() } } impl<'a> DownloadCfg<'a> { /// Downloads a file and validates its hash. Resumes interrupted downloads. /// Partial downloads are stored in `self.download_dir`, keyed by hash. If the /// target file already exists, then the hash is checked and it is returned /// immediately without re-downloading. pub(crate) fn download(&self, url: &Url, hash: &str) -> Result { utils::ensure_dir_exists( "Download Directory", self.download_dir, &self.notify_handler, )?; let target_file = self.download_dir.join(Path::new(hash)); if target_file.exists() { let cached_result = file_hash(&target_file, self.notify_handler)?; if hash == cached_result { (self.notify_handler)(Notification::FileAlreadyDownloaded); (self.notify_handler)(Notification::ChecksumValid(url.as_ref())); return Ok(File { path: target_file }); } else { (self.notify_handler)(Notification::CachedFileChecksumFailed); fs::remove_file(&target_file).context("cleaning up previous download")?; } } let partial_file_path = target_file.with_file_name( target_file .file_name() .map(|s| s.to_str().unwrap_or("_")) .unwrap_or("_") .to_owned() + ".partial", ); let partial_file_existed = partial_file_path.exists(); let mut hasher = Sha256::new(); if let Err(e) = utils::download_file_with_resume( url, &partial_file_path, Some(&mut hasher), true, &|n| (self.notify_handler)(n.into()), ) { let err = Err(e); if partial_file_existed { return err.context(RustupError::BrokenPartialFile); } else { return err; } }; let actual_hash = format!("{:x}", hasher.finalize()); if hash != actual_hash { // Incorrect hash if partial_file_existed { self.clean(&[hash.to_string() + ".partial"])?; Err(anyhow!(RustupError::BrokenPartialFile)) } else { Err(RustupError::ChecksumFailed { url: url.to_string(), expected: hash.to_string(), calculated: actual_hash, } .into()) } } else { (self.notify_handler)(Notification::ChecksumValid(url.as_ref())); utils::rename_file( "downloaded", &partial_file_path, &target_file, self.notify_handler, )?; Ok(File { path: target_file }) } } pub(crate) fn clean(&self, hashes: &[String]) -> Result<()> { for hash in hashes.iter() { let used_file = self.download_dir.join(hash); if self.download_dir.join(&used_file).exists() { fs::remove_file(used_file).context("cleaning up cached downloads")?; } } Ok(()) } fn download_hash(&self, url: &str) -> Result { let hash_url = utils::parse_url(&(url.to_owned() + ".sha256"))?; let hash_file = self.temp_cfg.new_file()?; utils::download_file(&hash_url, &hash_file, None, &|n| { (self.notify_handler)(n.into()) })?; utils::read_file("hash", &hash_file).map(|s| s[0..64].to_owned()) } /// Downloads a file, sourcing its hash from the same url with a `.sha256` suffix. /// If `update_hash` is present, then that will be compared to the downloaded hash, /// and if they match, the download is skipped. /// Verifies the signature found at the same url with a `.asc` suffix, and prints a /// warning when the signature does not verify, or is not found. pub(crate) fn download_and_check( &self, url_str: &str, update_hash: Option<&Path>, ext: &str, ) -> Result, String)>> { let hash = self.download_hash(url_str)?; let partial_hash: String = hash.chars().take(UPDATE_HASH_LEN).collect(); if let Some(hash_file) = update_hash { if utils::is_file(hash_file) { if let Ok(contents) = utils::read_file("update hash", hash_file) { if contents == partial_hash { // Skip download, update hash matches return Ok(None); } } else { (self.notify_handler)(Notification::CantReadUpdateHash(hash_file)); } } else { (self.notify_handler)(Notification::NoUpdateHash(hash_file)); } } let url = utils::parse_url(url_str)?; let file = self.temp_cfg.new_file_with_ext("", ext)?; let mut hasher = Sha256::new(); utils::download_file(&url, &file, Some(&mut hasher), &|n| { (self.notify_handler)(n.into()) })?; let actual_hash = format!("{:x}", hasher.finalize()); if hash != actual_hash { // Incorrect hash return Err(RustupError::ChecksumFailed { url: url_str.to_owned(), expected: hash, calculated: actual_hash, } .into()); } else { (self.notify_handler)(Notification::ChecksumValid(url_str)); } Ok(Some((file, partial_hash))) } } fn file_hash(path: &Path, notify_handler: &dyn Fn(Notification<'_>)) -> Result { let mut hasher = Sha256::new(); let notification_converter = |notification: crate::utils::Notification<'_>| { notify_handler(notification.into()); }; let mut downloaded = utils::FileReaderWithProgress::new_file(path, ¬ification_converter)?; use std::io::Read; let mut buf = vec![0; 32768]; while let Ok(n) = downloaded.read(&mut buf) { if n == 0 { break; } hasher.update(&buf[..n]); } Ok(format!("{:x}", hasher.finalize())) } rustup-1.26.0/src/dist/manifest.rs000066400000000000000000000524141441327105200170550ustar00rootroot00000000000000//! Rust distribution v2 manifests. //! //! This manifest describes the distributable artifacts for a single //! release of Rust. They are toml files, typically downloaded from //! e.g. static.rust-lang.org/dist/channel-rust-nightly.toml. They //! describe where to download, for all platforms, each component of //! the release, and their relationships to each other. //! //! Installers use this info to customize Rust installations. //! //! See tests/channel-rust-nightly-example.toml for an example. //! //! Docs: use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::str::FromStr; use anyhow::{anyhow, bail, Context, Result}; use crate::dist::dist::{PartialTargetTriple, Profile, TargetTriple}; use crate::errors::*; use crate::utils::toml_utils::*; pub(crate) const SUPPORTED_MANIFEST_VERSIONS: [&str; 1] = ["2"]; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Manifest { manifest_version: String, pub date: String, pub packages: HashMap, pub renames: HashMap, pub reverse_renames: HashMap, profiles: HashMap>, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct Package { pub version: String, pub targets: PackageTargets, } #[derive(Clone, Debug, Eq, PartialEq)] pub enum PackageTargets { Wildcard(TargetedPackage), Targeted(HashMap), } #[derive(Clone, Debug, Eq, PartialEq)] pub struct TargetedPackage { pub bins: Vec<(CompressionKind, HashedBinary)>, pub components: Vec, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CompressionKind { GZip, XZ, ZStd, } /// Each compression kind, in order of preference for use, from most desirable /// to least desirable. static COMPRESSION_KIND_PREFERENCE_ORDER: &[CompressionKind] = &[ CompressionKind::ZStd, CompressionKind::XZ, CompressionKind::GZip, ]; impl CompressionKind { const fn key_prefix(self) -> &'static str { match self { Self::GZip => "", Self::XZ => "xz_", Self::ZStd => "zst_", } } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct HashedBinary { pub url: String, pub hash: String, } #[derive(Clone, Debug, Eq, Ord, PartialOrd)] pub struct Component { pkg: String, pub target: Option, // Older Rustup distinguished between components (which are essential) and // extensions (which are not). is_extension: bool, } impl PartialEq for Component { fn eq(&self, other: &Self) -> bool { self.pkg == other.pkg && self.target == other.target } } impl Hash for Component { fn hash(&self, hasher: &mut H) where H: Hasher, { self.pkg.hash(hasher); self.target.hash(hasher); } } impl Manifest { pub fn parse(data: &str) -> Result { let value = toml::from_str(data).context("error parsing manifest")?; let manifest = Self::from_toml(value, "")?; manifest.validate()?; Ok(manifest) } pub fn stringify(self) -> String { toml::Value::Table(self.into_toml()).to_string() } pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let version = get_string(&mut table, "manifest-version", path)?; if !SUPPORTED_MANIFEST_VERSIONS.contains(&&*version) { bail!(RustupError::UnsupportedVersion(version)); } let (renames, reverse_renames) = Self::table_to_renames(&mut table, path)?; Ok(Self { manifest_version: version, date: get_string(&mut table, "date", path)?, packages: Self::table_to_packages(&mut table, path)?, renames, reverse_renames, profiles: Self::table_to_profiles(&mut table, path)?, }) } pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); result.insert("date".to_owned(), toml::Value::String(self.date)); result.insert( "manifest-version".to_owned(), toml::Value::String(self.manifest_version), ); let renames = Self::renames_to_table(self.renames); result.insert("renames".to_owned(), toml::Value::Table(renames)); let packages = Self::packages_to_table(self.packages); result.insert("pkg".to_owned(), toml::Value::Table(packages)); let profiles = Self::profiles_to_table(self.profiles); result.insert("profiles".to_owned(), toml::Value::Table(profiles)); result } fn table_to_packages( table: &mut toml::value::Table, path: &str, ) -> Result> { let mut result = HashMap::new(); let pkg_table = get_table(table, "pkg", path)?; for (k, v) in pkg_table { if let toml::Value::Table(t) = v { result.insert(k, Package::from_toml(t, path)?); } } Ok(result) } fn packages_to_table(packages: HashMap) -> toml::value::Table { let mut result = toml::value::Table::new(); for (k, v) in packages { result.insert(k, toml::Value::Table(v.into_toml())); } result } fn table_to_renames( table: &mut toml::value::Table, path: &str, ) -> Result<(HashMap, HashMap)> { let mut renames = HashMap::new(); let mut reverse_renames = HashMap::new(); let renames_table = get_table(table, "renames", path)?; for (k, v) in renames_table { if let toml::Value::Table(mut t) = v { let to = get_string(&mut t, "to", path)?; renames.insert(k.to_owned(), to.clone()); reverse_renames.insert(to, k.to_owned()); } } Ok((renames, reverse_renames)) } fn renames_to_table(renames: HashMap) -> toml::value::Table { let mut result = toml::value::Table::new(); for (from, to) in renames { let mut table = toml::value::Table::new(); table.insert("to".to_owned(), toml::Value::String(to)); result.insert(from, toml::Value::Table(table)); } result } fn table_to_profiles( table: &mut toml::value::Table, path: &str, ) -> Result>> { let mut result = HashMap::new(); let profile_table = match get_table(table, "profiles", path) { Ok(t) => t, Err(_) => return Ok(result), }; for (k, v) in profile_table { if let toml::Value::Array(a) = v { let values = a .into_iter() .filter_map(|v| match v { toml::Value::String(s) => Some(s), _ => None, }) .collect(); result.insert(Profile::from_str(&k)?, values); } } Ok(result) } fn profiles_to_table(profiles: HashMap>) -> toml::value::Table { let mut result = toml::value::Table::new(); for (profile, values) in profiles { let array = values.into_iter().map(toml::Value::String).collect(); result.insert(profile.to_string(), toml::Value::Array(array)); } result } pub fn get_package(&self, name: &str) -> Result<&Package> { self.packages .get(name) .ok_or_else(|| anyhow!(format!("package not found: '{name}'"))) } pub(crate) fn get_rust_version(&self) -> Result<&str> { self.get_package("rust").map(|p| &*p.version) } pub(crate) fn get_legacy_components(&self, target: &TargetTriple) -> Result> { // Build a profile from the components/extensions. let result = self .get_package("rust")? .get_target(Some(target))? .components .iter() .filter(|c| !c.is_extension && c.target.as_ref().map(|t| t == target).unwrap_or(true)) .cloned() .collect(); Ok(result) } pub fn get_profile_components( &self, profile: Profile, target: &TargetTriple, ) -> Result> { // An older manifest with no profiles section. if self.profiles.is_empty() { return self.get_legacy_components(target); } let profile = self .profiles .get(&profile) .ok_or_else(|| anyhow!(format!("profile not found: '{profile}'")))?; let rust_pkg = self.get_package("rust")?.get_target(Some(target))?; let result = profile .iter() .map(|s| { ( s, rust_pkg.components.iter().find(|c| { &c.pkg == s && c.target.as_ref().map(|t| t == target).unwrap_or(true) }), ) }) .filter(|(_, c)| c.is_some()) .map(|(s, c)| Component::new(s.to_owned(), c.and_then(|c| c.target.clone()), false)) .collect(); Ok(result) } fn validate_targeted_package(&self, tpkg: &TargetedPackage) -> Result<()> { for c in tpkg.components.iter() { let cpkg = self .get_package(&c.pkg) .with_context(|| RustupError::MissingPackageForComponent(c.short_name(self)))?; let _ctpkg = cpkg .get_target(c.target.as_ref()) .with_context(|| RustupError::MissingPackageForComponent(c.short_name(self)))?; } Ok(()) } fn validate(&self) -> Result<()> { // Every component mentioned must have an actual package to download for pkg in self.packages.values() { match pkg.targets { PackageTargets::Wildcard(ref tpkg) => { self.validate_targeted_package(tpkg)?; } PackageTargets::Targeted(ref tpkgs) => { for tpkg in tpkgs.values() { self.validate_targeted_package(tpkg)?; } } } } // The target of any renames must be an actual package. The subject of // renames is unconstrained. for name in self.renames.values() { if !self.packages.contains_key(name) { bail!(format!( "server sent a broken manifest: missing package for the target of a rename {name}" )); } } Ok(()) } // If the component should be renamed by this manifest, then return a new // component with the new name. If not, return `None`. pub(crate) fn rename_component(&self, component: &Component) -> Option { self.renames.get(&component.pkg).map(|r| { let mut c = component.clone(); c.pkg = r.clone(); c }) } } impl Package { pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { Ok(Self { version: get_string(&mut table, "version", path)?, targets: Self::toml_to_targets(table, path)?, }) } pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); result.insert("version".to_owned(), toml::Value::String(self.version)); let targets = Self::targets_to_toml(self.targets); result.insert("target".to_owned(), toml::Value::Table(targets)); result } fn toml_to_targets(mut table: toml::value::Table, path: &str) -> Result { let mut target_table = get_table(&mut table, "target", path)?; if let Some(toml::Value::Table(t)) = target_table.remove("*") { Ok(PackageTargets::Wildcard(TargetedPackage::from_toml( t, path, )?)) } else { let mut result = HashMap::new(); for (k, v) in target_table { if let toml::Value::Table(t) = v { result.insert(TargetTriple::new(&k), TargetedPackage::from_toml(t, path)?); } } Ok(PackageTargets::Targeted(result)) } } fn targets_to_toml(targets: PackageTargets) -> toml::value::Table { let mut result = toml::value::Table::new(); match targets { PackageTargets::Wildcard(tpkg) => { result.insert("*".to_owned(), toml::Value::Table(tpkg.into_toml())); } PackageTargets::Targeted(tpkgs) => { for (k, v) in tpkgs { result.insert(k.to_string(), toml::Value::Table(v.into_toml())); } } } result } pub fn get_target(&self, target: Option<&TargetTriple>) -> Result<&TargetedPackage> { match self.targets { PackageTargets::Wildcard(ref tpkg) => Ok(tpkg), PackageTargets::Targeted(ref tpkgs) => { if let Some(t) = target { tpkgs .get(t) .ok_or_else(|| anyhow!(format!("target '{t}' not found in channel. \ Perhaps check https://doc.rust-lang.org/nightly/rustc/platform-support.html for available targets"))) } else { Err(anyhow!("no target specified")) } } } } } impl PackageTargets { pub(crate) fn get<'a>(&'a self, target: &TargetTriple) -> Option<&'a TargetedPackage> { match self { Self::Wildcard(tpkg) => Some(tpkg), Self::Targeted(tpkgs) => tpkgs.get(target), } } pub fn get_mut<'a>(&'a mut self, target: &TargetTriple) -> Option<&'a mut TargetedPackage> { match self { Self::Wildcard(tpkg) => Some(tpkg), Self::Targeted(tpkgs) => tpkgs.get_mut(target), } } } impl TargetedPackage { pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let components = get_array(&mut table, "components", path)?; let extensions = get_array(&mut table, "extensions", path)?; let mut components = Self::toml_to_components(components, &format!("{}{}.", path, "components"), false)?; components.append(&mut Self::toml_to_components( extensions, &format!("{}{}.", path, "extensions"), true, )?); if get_bool(&mut table, "available", path)? { let mut bins = Vec::new(); for kind in COMPRESSION_KIND_PREFERENCE_ORDER.iter().copied() { let url_key = format!("{}url", kind.key_prefix()); let hash_key = format!("{}hash", kind.key_prefix()); let url = get_string(&mut table, &url_key, path).ok(); let hash = get_string(&mut table, &hash_key, path).ok(); if let (Some(url), Some(hash)) = (url, hash) { bins.push((kind, HashedBinary { url, hash })); } } Ok(Self { bins, components }) } else { Ok(Self { bins: Vec::new(), components: Vec::new(), }) } } pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); let (components, extensions) = Self::components_to_toml(self.components); if !components.is_empty() { result.insert("components".to_owned(), toml::Value::Array(components)); } if !extensions.is_empty() { result.insert("extensions".to_owned(), toml::Value::Array(extensions)); } if self.bins.is_empty() { result.insert("available".to_owned(), toml::Value::Boolean(false)); } else { for (kind, bin) in self.bins { let url_key = format!("{}url", kind.key_prefix()); let hash_key = format!("{}hash", kind.key_prefix()); result.insert(url_key, toml::Value::String(bin.url)); result.insert(hash_key, toml::Value::String(bin.hash)); } result.insert("available".to_owned(), toml::Value::Boolean(true)); } result } pub fn available(&self) -> bool { !self.bins.is_empty() } fn toml_to_components( arr: toml::value::Array, path: &str, is_extension: bool, ) -> Result> { let mut result = Vec::new(); for (i, v) in arr.into_iter().enumerate() { if let toml::Value::Table(t) = v { let path = format!("{path}[{i}]"); result.push(Component::from_toml(t, &path, is_extension)?); } } Ok(result) } fn components_to_toml(data: Vec) -> (toml::value::Array, toml::value::Array) { let mut components = toml::value::Array::new(); let mut extensions = toml::value::Array::new(); for v in data { if v.is_extension { extensions.push(toml::Value::Table(v.into_toml())); } else { components.push(toml::Value::Table(v.into_toml())); } } (components, extensions) } } impl Component { pub fn new(pkg: String, target: Option, is_extension: bool) -> Self { Self { pkg, target, is_extension, } } pub(crate) fn new_with_target(pkg_with_target: &str, is_extension: bool) -> Option { for (pos, _) in pkg_with_target.match_indices('-') { let pkg = &pkg_with_target[0..pos]; let target = &pkg_with_target[pos + 1..]; if let Some(partial) = PartialTargetTriple::new(target) { if let Ok(triple) = TargetTriple::try_from(partial) { return Some(Self { pkg: pkg.to_string(), target: Some(triple), is_extension, }); } } } None } pub(crate) fn wildcard(&self) -> Self { Self { pkg: self.pkg.clone(), target: None, is_extension: false, } } pub(crate) fn from_toml( mut table: toml::value::Table, path: &str, is_extension: bool, ) -> Result { Ok(Self { pkg: get_string(&mut table, "pkg", path)?, target: get_string(&mut table, "target", path).map(|s| { if s == "*" { None } else { Some(TargetTriple::new(&s)) } })?, is_extension, }) } pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); result.insert( "target".to_owned(), toml::Value::String( self.target .map(|t| t.to_string()) .unwrap_or_else(|| "*".to_owned()), ), ); result.insert("pkg".to_owned(), toml::Value::String(self.pkg)); result } pub(crate) fn name(&self, manifest: &Manifest) -> String { let pkg = self.short_name(manifest); if let Some(ref t) = self.target { format!("{pkg}-{t}") } else { pkg } } pub(crate) fn short_name(&self, manifest: &Manifest) -> String { if let Some(from) = manifest.reverse_renames.get(&self.pkg) { from.to_owned() } else { self.pkg.clone() } } pub(crate) fn description(&self, manifest: &Manifest) -> String { let pkg = self.short_name(manifest); if let Some(ref t) = self.target { format!("'{pkg}' for target '{t}'") } else { format!("'{pkg}'") } } pub fn short_name_in_manifest(&self) -> &String { &self.pkg } pub(crate) fn name_in_manifest(&self) -> String { let pkg = self.short_name_in_manifest(); if let Some(ref t) = self.target { format!("{pkg}-{t}") } else { pkg.to_string() } } pub(crate) fn target(&self) -> String { if let Some(t) = self.target.as_ref() { t.to_string() } else { String::new() } } pub(crate) fn contained_within(&self, components: &[Component]) -> bool { if components.contains(self) { // Yes, we're within the component set, move on true } else if self.target.is_none() { // We weren't in the given component set, but we're a package // which targets "*" and as such older rustups might have // accidentally made us target specific due to a bug in profiles. components .iter() // As such, if our target is None, it's sufficient to check pkg .any(|other| other.pkg == self.pkg) } else { // As a last ditch effort, we're contained within the component // set if the name matches and the other component's target // is None components .iter() .any(|other| other.pkg == self.pkg && other.target.is_none()) } } } rustup-1.26.0/src/dist/manifestation.rs000066400000000000000000000654601441327105200201150ustar00rootroot00000000000000//! Maintains a Rust installation by installing individual Rust //! platform components from a distribution server. use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use retry::delay::NoDelay; use retry::{retry, OperationResult}; use crate::dist::component::{ Components, Package, TarGzPackage, TarXzPackage, TarZStdPackage, Transaction, }; use crate::dist::config::Config; use crate::dist::dist::{Profile, TargetTriple, DEFAULT_DIST_SERVER}; use crate::dist::download::{DownloadCfg, File}; use crate::dist::manifest::{Component, CompressionKind, Manifest, TargetedPackage}; use crate::dist::notifications::*; use crate::dist::prefix::InstallPrefix; use crate::dist::temp; use crate::errors::{OperationError, RustupError}; use crate::process; use crate::utils::utils; pub(crate) const DIST_MANIFEST: &str = "multirust-channel-manifest.toml"; pub(crate) const CONFIG_FILE: &str = "multirust-config.toml"; #[derive(Debug)] pub struct Manifestation { installation: Components, target_triple: TargetTriple, } #[derive(Debug)] pub struct Changes { pub explicit_add_components: Vec, pub remove_components: Vec, } impl Changes { fn iter_add_components(&self) -> impl Iterator { self.explicit_add_components.iter() } fn check_invariants(&self, config: &Option) -> Result<()> { for component_to_add in self.iter_add_components() { if self.remove_components.contains(component_to_add) { bail!("can't both add and remove components"); } } for component_to_remove in &self.remove_components { let config = config .as_ref() .expect("removing component on fresh install?"); assert!( config.components.contains(component_to_remove), "removing package that isn't installed" ); } Ok(()) } } #[derive(PartialEq, Debug, Eq)] pub enum UpdateStatus { Changed, Unchanged, } impl Manifestation { /// Open the install prefix for updates from a distribution /// channel. The install prefix directory does not need to exist; /// it will be created as needed. If there's an existing install /// then the rust-install installation format will be verified. A /// bad installer version is the only reason this will fail. pub fn open(prefix: InstallPrefix, triple: TargetTriple) -> Result { // TODO: validate the triple with the existing install as well // as the metadata format of the existing install Ok(Self { installation: Components::open(prefix)?, target_triple: triple, }) } /// Install or update from a given channel manifest, while /// selecting extension components to add or remove. /// /// `update` takes a manifest describing a release of Rust (which /// may be either a freshly-downloaded one, or the same one used /// for the previous install), as well as lists of extension /// components to add and remove. /// From that it schedules a list of components to install and /// to uninstall to bring the installation up to date. It /// downloads the components' packages. Then in a Transaction /// uninstalls old packages and installs new packages, writes the /// distribution manifest to "rustlib/rustup-dist.toml" and a /// configuration containing the component name-target pairs to /// "rustlib/rustup-config.toml". pub fn update( &self, new_manifest: &Manifest, changes: Changes, force_update: bool, download_cfg: &DownloadCfg<'_>, notify_handler: &dyn Fn(Notification<'_>), toolchain_str: &str, implicit_modify: bool, ) -> Result { // Some vars we're going to need a few times let temp_cfg = download_cfg.temp_cfg; let prefix = self.installation.prefix(); let rel_installed_manifest_path = prefix.rel_manifest_file(DIST_MANIFEST); let installed_manifest_path = prefix.path().join(&rel_installed_manifest_path); // Create the lists of components needed for installation let config = self.read_config()?; let mut update = Update::build_update(self, new_manifest, &changes, &config, notify_handler)?; if update.nothing_changes() { return Ok(UpdateStatus::Unchanged); } // Validate that the requested components are available match update.unavailable_components(new_manifest, toolchain_str) { Ok(_) => {} Err(e) => { if force_update { if let Ok(RustupError::RequestedComponentsUnavailable { components, .. }) = e.downcast::() { for component in &components { notify_handler(Notification::ForcingUnavailableComponent( component.name(new_manifest).as_str(), )); } update.drop_components_to_install(&components); } } else { return Err(e); } } } let altered = temp_cfg.dist_server != DEFAULT_DIST_SERVER; // Download component packages and validate hashes let mut things_to_install: Vec<(Component, CompressionKind, File)> = Vec::new(); let mut things_downloaded: Vec = Vec::new(); let components = update.components_urls_and_hashes(new_manifest)?; const DEFAULT_MAX_RETRIES: usize = 3; let max_retries: usize = process() .var("RUSTUP_MAX_RETRIES") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(DEFAULT_MAX_RETRIES); for (component, format, url, hash) in components { notify_handler(Notification::DownloadingComponent( &component.short_name(new_manifest), &self.target_triple, component.target.as_ref(), )); let url = if altered { url.replace(DEFAULT_DIST_SERVER, temp_cfg.dist_server.as_str()) } else { url }; let url_url = utils::parse_url(&url)?; let downloaded_file = retry(NoDelay.take(max_retries), || { match download_cfg.download(&url_url, &hash) { Ok(f) => OperationResult::Ok(f), Err(e) => { match e.downcast_ref::() { Some(RustupError::BrokenPartialFile) => { notify_handler(Notification::RetryingDownload(&url)); return OperationResult::Retry(OperationError(e)); } Some(RustupError::DownloadingFile { .. }) => { notify_handler(Notification::RetryingDownload(&url)); return OperationResult::Retry(OperationError(e)); } Some(_) => return OperationResult::Err(OperationError(e)), None => (), }; OperationResult::Err(OperationError(e)) } } }) .with_context(|| RustupError::ComponentDownloadFailed(component.name(new_manifest)))?; things_downloaded.push(hash); things_to_install.push((component, format, downloaded_file)); } // Begin transaction let mut tx = Transaction::new(prefix.clone(), temp_cfg, notify_handler); // If the previous installation was from a v1 manifest we need // to uninstall it first. tx = self.maybe_handle_v2_upgrade(&config, tx)?; // Uninstall components for component in &update.components_to_uninstall { let notification = if implicit_modify { Notification::RemovingOldComponent } else { Notification::RemovingComponent }; notify_handler(notification( &component.short_name(new_manifest), &self.target_triple, component.target.as_ref(), )); tx = self.uninstall_component(component, new_manifest, tx, ¬ify_handler)?; } // Install components for (component, format, installer_file) in things_to_install { // For historical reasons, the rust-installer component // names are not the same as the dist manifest component // names. Some are just the component name some are the // component name plus the target triple. let pkg_name = component.name_in_manifest(); let short_pkg_name = component.short_name_in_manifest(); let short_name = component.short_name(new_manifest); notify_handler(Notification::InstallingComponent( &short_name, &self.target_triple, component.target.as_ref(), )); let notification_converter = |notification: crate::utils::Notification<'_>| { notify_handler(notification.into()); }; let gz; let xz; let zst; let reader = utils::FileReaderWithProgress::new_file(&installer_file, ¬ification_converter)?; let package: &dyn Package = match format { CompressionKind::GZip => { gz = TarGzPackage::new(reader, temp_cfg, Some(¬ification_converter))?; &gz } CompressionKind::XZ => { xz = TarXzPackage::new(reader, temp_cfg, Some(¬ification_converter))?; &xz } CompressionKind::ZStd => { zst = TarZStdPackage::new(reader, temp_cfg, Some(¬ification_converter))?; &zst } }; // If the package doesn't contain the component that the // manifest says it does then somebody must be playing a joke on us. if !package.contains(&pkg_name, Some(short_pkg_name)) { return Err(RustupError::CorruptComponent(short_name).into()); } tx = package.install(&self.installation, &pkg_name, Some(short_pkg_name), tx)?; } // Install new distribution manifest let new_manifest_str = new_manifest.clone().stringify(); tx.modify_file(rel_installed_manifest_path)?; utils::write_file("manifest", &installed_manifest_path, &new_manifest_str)?; // Write configuration. // // NB: This configuration is mostly for keeping track of the name/target pairs // that identify installed components. The rust-installer metadata maintained by // `Components` *also* tracks what is installed, but it only tracks names, not // name/target. Needs to be fixed in rust-installer. let mut new_config = Config::new(); new_config.components = update.final_component_list; let config_str = new_config.stringify(); let rel_config_path = prefix.rel_manifest_file(CONFIG_FILE); let config_path = prefix.path().join(&rel_config_path); tx.modify_file(rel_config_path)?; utils::write_file("dist config", &config_path, &config_str)?; // End transaction tx.commit(); download_cfg.clean(&things_downloaded)?; Ok(UpdateStatus::Changed) } pub fn uninstall( &self, manifest: &Manifest, temp_cfg: &temp::Cfg, notify_handler: &dyn Fn(Notification<'_>), ) -> Result<()> { let prefix = self.installation.prefix(); let mut tx = Transaction::new(prefix.clone(), temp_cfg, notify_handler); // Read configuration and delete it let rel_config_path = prefix.rel_manifest_file(CONFIG_FILE); let config_str = utils::read_file("dist config", &prefix.path().join(&rel_config_path))?; let config = Config::parse(&config_str)?; tx.remove_file("dist config", rel_config_path)?; for component in config.components { tx = self.uninstall_component(&component, manifest, tx, notify_handler)?; } tx.commit(); Ok(()) } fn uninstall_component<'a>( &self, component: &Component, manifest: &Manifest, mut tx: Transaction<'a>, notify_handler: &dyn Fn(Notification<'_>), ) -> Result> { // For historical reasons, the rust-installer component // names are not the same as the dist manifest component // names. Some are just the component name some are the // component name plus the target triple. let name = component.name_in_manifest(); let short_name = component.short_name_in_manifest(); if let Some(c) = self.installation.find(&name)? { tx = c.uninstall(tx)?; } else if let Some(c) = self.installation.find(short_name)? { tx = c.uninstall(tx)?; } else { notify_handler(Notification::MissingInstalledComponent( &component.short_name(manifest), )); } Ok(tx) } // Read the config file. Config files are presently only created // for v2 installations. pub(crate) fn read_config(&self) -> Result> { let prefix = self.installation.prefix(); let rel_config_path = prefix.rel_manifest_file(CONFIG_FILE); let config_path = prefix.path().join(rel_config_path); if utils::path_exists(&config_path) { let config_str = utils::read_file("dist config", &config_path)?; Ok(Some(Config::parse(&config_str)?)) } else { Ok(None) } } 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) { let manifest_str = utils::read_file("installed manifest", &old_manifest_path)?; Ok(Some(Manifest::parse(&manifest_str)?)) } else { Ok(None) } } /// Installation using the legacy v1 manifest format pub(crate) fn update_v1( &self, new_manifest: &[String], update_hash: Option<&Path>, temp_cfg: &temp::Cfg, notify_handler: &dyn Fn(Notification<'_>), ) -> Result> { // If there's already a v2 installation then something has gone wrong if self.read_config()?.is_some() { return Err(anyhow!( "the server unexpectedly provided an obsolete version of the distribution manifest" )); } let url = new_manifest .iter() .find(|u| u.contains(&format!("{}{}", self.target_triple, ".tar.gz"))); if url.is_none() { return Err(anyhow!( "binary package was not provided for '{}'", self.target_triple.to_string() )); } // Only replace once. The cost is inexpensive. let url = url .unwrap() .replace(DEFAULT_DIST_SERVER, temp_cfg.dist_server.as_str()); notify_handler(Notification::DownloadingComponent( "rust", &self.target_triple, Some(&self.target_triple), )); use std::path::PathBuf; let dld_dir = PathBuf::from("bogus"); let dlcfg = DownloadCfg { dist_root: "bogus", download_dir: &dld_dir, temp_cfg, notify_handler, }; let dl = dlcfg.download_and_check(&url, update_hash, ".tar.gz")?; if dl.is_none() { return Ok(None); }; let (installer_file, installer_hash) = dl.unwrap(); let prefix = self.installation.prefix(); notify_handler(Notification::InstallingComponent( "rust", &self.target_triple, Some(&self.target_triple), )); // Begin transaction let mut tx = Transaction::new(prefix, temp_cfg, notify_handler); // Uninstall components let components = self.installation.list()?; for component in components { tx = component.uninstall(tx)?; } // Install all the components in the installer let notification_converter = |notification: crate::utils::Notification<'_>| { notify_handler(notification.into()); }; let reader = utils::FileReaderWithProgress::new_file(&installer_file, ¬ification_converter)?; let package: &dyn Package = &TarGzPackage::new(reader, temp_cfg, Some(¬ification_converter))?; for component in package.components() { tx = package.install(&self.installation, &component, None, tx)?; } // End transaction tx.commit(); Ok(Some(installer_hash)) } // If the previous installation was from a v1 manifest, then it // doesn't have a configuration or manifest-derived list of // component/target pairs. Uninstall it using the installer's // component list before upgrading. fn maybe_handle_v2_upgrade<'a>( &self, config: &Option, mut tx: Transaction<'a>, ) -> Result> { let installed_components = self.installation.list()?; let looks_like_v1 = config.is_none() && !installed_components.is_empty(); if !looks_like_v1 { return Ok(tx); } for component in installed_components { tx = component.uninstall(tx)?; } Ok(tx) } } #[derive(Debug)] struct Update { components_to_uninstall: Vec, components_to_install: Vec, final_component_list: Vec, missing_components: Vec, } impl Update { /// Returns components to uninstall, install, and the list of all /// components that will be up to date after the update. fn build_update( manifestation: &Manifestation, new_manifest: &Manifest, changes: &Changes, config: &Option, notify_handler: &dyn Fn(Notification<'_>), ) -> Result { // The package to install. let rust_package = new_manifest.get_package("rust")?; let rust_target_package = rust_package.get_target(Some(&manifestation.target_triple))?; changes.check_invariants(config)?; // The list of components already installed, empty if a new install let mut starting_list = config .as_ref() .map(|c| c.components.clone()) .unwrap_or_default(); let installed_components = manifestation.installation.list()?; let looks_like_v1 = config.is_none() && !installed_components.is_empty(); if looks_like_v1 { let mut profile_components = new_manifest .get_profile_components(Profile::Default, &manifestation.target_triple)?; starting_list.append(&mut profile_components); } let mut result = Self { components_to_uninstall: vec![], components_to_install: vec![], final_component_list: vec![], missing_components: vec![], }; // Find the final list of components we want to be left with when // we're done: required components, added components, and existing // installed components. result.build_final_component_list( &starting_list, rust_target_package, new_manifest, changes, ); // If this is a full upgrade then the list of components to // uninstall is all that are currently installed, and those // to install the final list. It's a complete reinstall. // // If it's a modification then the components to uninstall are // those that are currently installed but not in the final list. // To install are those on the final list but not already // installed. let old_manifest = manifestation.load_manifest()?; let just_modifying_existing_install = old_manifest.as_ref() == Some(new_manifest); if just_modifying_existing_install { for existing_component in &starting_list { if !result.final_component_list.contains(existing_component) { result .components_to_uninstall .push(existing_component.clone()) } } for component in &result.final_component_list { if !starting_list.contains(component) { result.components_to_install.push(component.clone()); } else if changes.explicit_add_components.contains(component) { notify_handler(Notification::ComponentAlreadyInstalled( &component.description(new_manifest), )); } } } else { result.components_to_uninstall = starting_list; result.components_to_install = result.final_component_list.clone(); } Ok(result) } /// Build the list of components we'll have installed at the end fn build_final_component_list( &mut self, starting_list: &[Component], rust_target_package: &TargetedPackage, new_manifest: &Manifest, changes: &Changes, ) { // Add requested components for component in &changes.explicit_add_components { self.final_component_list.push(component.clone()); } // Add components that are already installed for existing_component in starting_list { let removed = changes.remove_components.contains(existing_component); if !removed { // If there is a rename in the (new) manifest, then we uninstall the component with the // old name and install a component with the new name if let Some(renamed_component) = new_manifest.rename_component(existing_component) { let is_already_included = self.final_component_list.contains(&renamed_component); if !is_already_included { self.final_component_list.push(renamed_component); } } else { let is_already_included = self.final_component_list.contains(existing_component); if !is_already_included { let component_is_present = rust_target_package.components.contains(existing_component); if component_is_present { self.final_component_list.push(existing_component.clone()); } else { // Component not available, check if this is a case of // where rustup brokenly installed `rust-src` during // the 1.20.x series if existing_component.contained_within(&rust_target_package.components) { // It is the case, so we need to create a fresh wildcard // component using the package name and add it to the final // component list let wildcarded = existing_component.wildcard(); if !self.final_component_list.contains(&wildcarded) { self.final_component_list.push(wildcarded); } } else { self.missing_components.push(existing_component.clone()); } } } } } } } fn nothing_changes(&self) -> bool { self.components_to_uninstall.is_empty() && self.components_to_install.is_empty() } fn unavailable_components(&self, new_manifest: &Manifest, toolchain_str: &str) -> Result<()> { let mut unavailable_components: Vec = self .components_to_install .iter() .filter(|c| { use crate::dist::manifest::*; let pkg: Option<&Package> = new_manifest.get_package(c.short_name_in_manifest()).ok(); let target_pkg: Option<&TargetedPackage> = pkg.and_then(|p| p.get_target(c.target.as_ref()).ok()); target_pkg.map(TargetedPackage::available) != Some(true) }) .cloned() .collect(); unavailable_components.extend_from_slice(&self.missing_components); if !unavailable_components.is_empty() { bail!(RustupError::RequestedComponentsUnavailable { components: unavailable_components, manifest: new_manifest.clone(), toolchain: toolchain_str.to_owned(), }); } Ok(()) } fn drop_components_to_install(&mut self, to_drop: &[Component]) { let components: Vec<_> = self .components_to_install .drain(..) .filter(|c| !to_drop.contains(c)) .collect(); self.components_to_install.extend(components); let final_components: Vec<_> = self .final_component_list .drain(..) .filter(|c| !to_drop.contains(c)) .collect(); self.final_component_list = final_components; } /// Map components to urls and hashes fn components_urls_and_hashes( &self, new_manifest: &Manifest, ) -> Result> { let mut components_urls_and_hashes = Vec::new(); for component in &self.components_to_install { let package = new_manifest.get_package(component.short_name_in_manifest())?; let target_package = package.get_target(component.target.as_ref())?; if target_package.bins.is_empty() { // This package is not available, no files to download. continue; } // We prefer the first format in the list, since the parsing of the // manifest leaves us with the files/hash pairs in preference order. components_urls_and_hashes.push(( component.clone(), target_package.bins[0].0, target_package.bins[0].1.url.clone(), target_package.bins[0].1.hash.clone(), )); } Ok(components_urls_and_hashes) } } rustup-1.26.0/src/dist/mod.rs000066400000000000000000000005171441327105200160230ustar00rootroot00000000000000//! Installation from a Rust distribution server pub use crate::dist::notifications::Notification; pub mod temp; pub mod component; pub(crate) mod config; #[allow(clippy::module_inception)] pub mod dist; pub mod download; pub mod manifest; pub mod manifestation; pub(crate) mod notifications; pub mod prefix; pub(crate) mod triple; rustup-1.26.0/src/dist/notifications.rs000066400000000000000000000172311441327105200201160ustar00rootroot00000000000000use crate::dist::dist::{TargetTriple, ToolchainDesc}; use crate::dist::manifest::Component; use crate::dist::temp; use crate::utils::notify::NotificationLevel; use std::fmt::{self, Display}; use std::path::Path; use super::manifest::Manifest; #[derive(Debug)] pub enum Notification<'a> { Utils(crate::utils::Notification<'a>), Temp(temp::Notification<'a>), Extracting(&'a Path, &'a Path), ComponentAlreadyInstalled(&'a str), CantReadUpdateHash(&'a Path), NoUpdateHash(&'a Path), ChecksumValid(&'a str), FileAlreadyDownloaded, CachedFileChecksumFailed, RollingBack, ExtensionNotInstalled(&'a str), NonFatalError(&'a anyhow::Error), MissingInstalledComponent(&'a str), DownloadingComponent(&'a str, &'a TargetTriple, Option<&'a TargetTriple>), InstallingComponent(&'a str, &'a TargetTriple, Option<&'a TargetTriple>), RemovingComponent(&'a str, &'a TargetTriple, Option<&'a TargetTriple>), RemovingOldComponent(&'a str, &'a TargetTriple, Option<&'a TargetTriple>), DownloadingManifest(&'a str), DownloadedManifest(&'a str, Option<&'a str>), DownloadingLegacyManifest, SkippingNightlyMissingComponent(&'a ToolchainDesc, &'a Manifest, &'a [Component]), ForcingUnavailableComponent(&'a str), ManifestChecksumFailedHack, ComponentUnavailable(&'a str, Option<&'a TargetTriple>), StrayHash(&'a Path), SignatureInvalid(&'a str), RetryingDownload(&'a str), } impl<'a> From> for Notification<'a> { fn from(n: crate::utils::Notification<'a>) -> Self { Notification::Utils(n) } } impl<'a> From> for Notification<'a> { fn from(n: temp::Notification<'a>) -> Self { Notification::Temp(n) } } impl<'a> Notification<'a> { pub(crate) fn level(&self) -> NotificationLevel { use self::Notification::*; match self { Temp(n) => n.level(), Utils(n) => n.level(), ChecksumValid(_) | NoUpdateHash(_) | FileAlreadyDownloaded | DownloadingLegacyManifest => NotificationLevel::Verbose, Extracting(_, _) | DownloadingComponent(_, _, _) | InstallingComponent(_, _, _) | RemovingComponent(_, _, _) | RemovingOldComponent(_, _, _) | ComponentAlreadyInstalled(_) | ManifestChecksumFailedHack | RollingBack | DownloadingManifest(_) | SkippingNightlyMissingComponent(_, _, _) | RetryingDownload(_) | DownloadedManifest(_, _) => NotificationLevel::Info, CantReadUpdateHash(_) | ExtensionNotInstalled(_) | MissingInstalledComponent(_) | CachedFileChecksumFailed | ComponentUnavailable(_, _) | ForcingUnavailableComponent(_) | StrayHash(_) => NotificationLevel::Warn, NonFatalError(_) => NotificationLevel::Error, SignatureInvalid(_) => NotificationLevel::Warn, } } } impl<'a> Display for Notification<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { use self::Notification::*; match self { Temp(n) => n.fmt(f), Utils(n) => n.fmt(f), Extracting(_, _) => write!(f, "extracting..."), ComponentAlreadyInstalled(c) => write!(f, "component {c} is up to date"), CantReadUpdateHash(path) => write!( f, "can't read update hash file: '{}', can't skip update...", path.display() ), NoUpdateHash(path) => write!(f, "no update hash at: '{}'", path.display()), ChecksumValid(_) => write!(f, "checksum passed"), FileAlreadyDownloaded => write!(f, "reusing previously downloaded file"), CachedFileChecksumFailed => write!(f, "bad checksum for cached download"), RollingBack => write!(f, "rolling back changes"), ExtensionNotInstalled(c) => write!(f, "extension '{c}' was not installed"), NonFatalError(e) => write!(f, "{e}"), MissingInstalledComponent(c) => { write!(f, "during uninstall component {c} was not found") } DownloadingComponent(c, h, t) => { if Some(h) == t.as_ref() || t.is_none() { write!(f, "downloading component '{c}'") } else { write!(f, "downloading component '{}' for '{}'", c, t.unwrap()) } } InstallingComponent(c, h, t) => { if Some(h) == t.as_ref() || t.is_none() { write!(f, "installing component '{c}'") } else { write!(f, "installing component '{}' for '{}'", c, t.unwrap()) } } RemovingComponent(c, h, t) => { if Some(h) == t.as_ref() || t.is_none() { write!(f, "removing component '{c}'") } else { write!(f, "removing component '{}' for '{}'", c, t.unwrap()) } } RemovingOldComponent(c, h, t) => { if Some(h) == t.as_ref() || t.is_none() { write!(f, "removing previous version of component '{c}'") } else { write!( f, "removing previous version of component '{}' for '{}'", c, t.unwrap() ) } } DownloadingManifest(t) => write!(f, "syncing channel updates for '{t}'"), DownloadedManifest(date, Some(version)) => { write!(f, "latest update on {date}, rust version {version}") } DownloadedManifest(date, None) => { write!(f, "latest update on {date}, no rust version") } DownloadingLegacyManifest => write!(f, "manifest not found. trying legacy manifest"), ManifestChecksumFailedHack => { write!(f, "update not yet available, sorry! try again later") } ComponentUnavailable(pkg, toolchain) => { if let Some(tc) = toolchain { write!(f, "component '{pkg}' is not available on target '{tc}'") } else { write!(f, "component '{pkg}' is not available") } } StrayHash(path) => write!( f, "removing stray hash found at '{}' in order to continue", path.display() ), SkippingNightlyMissingComponent(toolchain, manifest, components) => write!( f, "skipping nightly which is missing installed component{} '{}'", if components.len() > 1 { "s" } else { "" }, components .iter() .map(|component| { if component.target.as_ref() != Some(&toolchain.target) { component.name(manifest) } else { component.short_name(manifest) } }) .collect::>() .join("', '") ), ForcingUnavailableComponent(component) => { write!(f, "Force-skipping unavailable component '{component}'") } SignatureInvalid(url) => write!(f, "Signature verification failed for '{url}'"), RetryingDownload(url) => write!(f, "retrying download for '{url}'"), } } } rustup-1.26.0/src/dist/prefix.rs000066400000000000000000000015501441327105200165370ustar00rootroot00000000000000use std::path::{Path, PathBuf}; const REL_MANIFEST_DIR: &str = "lib/rustlib"; #[derive(Clone, Debug)] pub struct InstallPrefix { path: PathBuf, } impl InstallPrefix { pub fn from(path: PathBuf) -> Self { Self { path } } pub fn path(&self) -> &Path { &self.path } pub(crate) fn abs_path>(&self, path: P) -> PathBuf { self.path.join(path) } pub(crate) fn manifest_dir(&self) -> PathBuf { let mut path = self.path.clone(); path.push(REL_MANIFEST_DIR); path } pub fn manifest_file(&self, name: &str) -> PathBuf { let mut path = self.manifest_dir(); path.push(name); path } pub(crate) fn rel_manifest_file(&self, name: &str) -> PathBuf { let mut path = PathBuf::from(REL_MANIFEST_DIR); path.push(name); path } } rustup-1.26.0/src/dist/temp.rs000066400000000000000000000140131441327105200162050ustar00rootroot00000000000000use std::fmt::{self, Display}; use std::fs; use std::io; use std::ops; use std::path::{Path, PathBuf}; pub(crate) use anyhow::{Context, Result}; use thiserror::Error as ThisError; use crate::utils::notify::NotificationLevel; use crate::utils::raw; use crate::utils::utils; #[derive(Debug, ThisError)] pub(crate) enum CreatingError { #[error("could not create temp root {}" ,.0.display())] Root(PathBuf), #[error("could not create temp file {}",.0.display())] File(PathBuf), #[error("could not create temp directory {}",.0.display())] Directory(PathBuf), } #[derive(Debug)] pub enum Notification<'a> { CreatingRoot(&'a Path), CreatingFile(&'a Path), CreatingDirectory(&'a Path), FileDeletion(&'a Path, io::Result<()>), DirectoryDeletion(&'a Path, io::Result<()>), } pub struct Cfg { root_directory: PathBuf, pub dist_server: String, notify_handler: Box)>, } #[derive(Debug)] pub(crate) struct Dir<'a> { cfg: &'a Cfg, path: PathBuf, } #[derive(Debug)] pub struct File<'a> { cfg: &'a Cfg, path: PathBuf, } impl<'a> Notification<'a> { pub(crate) fn level(&self) -> NotificationLevel { use self::Notification::*; match self { CreatingRoot(_) | CreatingFile(_) | CreatingDirectory(_) => NotificationLevel::Verbose, FileDeletion(_, result) | DirectoryDeletion(_, result) => { if result.is_ok() { NotificationLevel::Verbose } else { NotificationLevel::Warn } } } } } impl<'a> Display for Notification<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { use self::Notification::*; match self { CreatingRoot(path) => write!(f, "creating temp root: {}", path.display()), CreatingFile(path) => write!(f, "creating temp file: {}", path.display()), CreatingDirectory(path) => write!(f, "creating temp directory: {}", path.display()), FileDeletion(path, result) => { if result.is_ok() { write!(f, "deleted temp file: {}", path.display()) } else { write!(f, "could not delete temp file: {}", path.display()) } } DirectoryDeletion(path, result) => { if result.is_ok() { write!(f, "deleted temp directory: {}", path.display()) } else { write!(f, "could not delete temp directory: {}", path.display()) } } } } } impl Cfg { pub fn new( root_directory: PathBuf, dist_server: &str, notify_handler: Box)>, ) -> Self { Self { root_directory, dist_server: dist_server.to_owned(), notify_handler, } } pub(crate) fn create_root(&self) -> Result { raw::ensure_dir_exists(&self.root_directory, |p| { (self.notify_handler)(Notification::CreatingRoot(p)); }) .with_context(|| CreatingError::Root(PathBuf::from(&self.root_directory))) } pub(crate) fn new_directory(&self) -> Result> { self.create_root()?; loop { let temp_name = raw::random_string(16) + "_dir"; let temp_dir = self.root_directory.join(temp_name); // This is technically racey, but the probability of getting the same // random names at exactly the same time is... low. if !raw::path_exists(&temp_dir) { (self.notify_handler)(Notification::CreatingDirectory(&temp_dir)); fs::create_dir(&temp_dir) .with_context(|| CreatingError::Directory(PathBuf::from(&temp_dir)))?; return Ok(Dir { cfg: self, path: temp_dir, }); } } } pub fn new_file(&self) -> Result> { self.new_file_with_ext("", "") } pub(crate) fn new_file_with_ext(&self, prefix: &str, ext: &str) -> Result> { self.create_root()?; loop { let temp_name = prefix.to_owned() + &raw::random_string(16) + "_file" + ext; let temp_file = self.root_directory.join(temp_name); // This is technically racey, but the probability of getting the same // random names at exactly the same time is... low. if !raw::path_exists(&temp_file) { (self.notify_handler)(Notification::CreatingFile(&temp_file)); fs::File::create(&temp_file) .with_context(|| CreatingError::File(PathBuf::from(&temp_file)))?; return Ok(File { cfg: self, path: temp_file, }); } } } pub(crate) fn clean(&self) { utils::delete_dir_contents(&self.root_directory); } } impl fmt::Debug for Cfg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Cfg") .field("root_directory", &self.root_directory) .field("notify_handler", &"...") .finish() } } impl<'a> ops::Deref for Dir<'a> { type Target = Path; fn deref(&self) -> &Path { self.path.as_path() } } impl<'a> ops::Deref for File<'a> { type Target = Path; fn deref(&self) -> &Path { self.path.as_path() } } impl<'a> Drop for Dir<'a> { fn drop(&mut self) { if raw::is_directory(&self.path) { let n = Notification::DirectoryDeletion( &self.path, remove_dir_all::remove_dir_all(&self.path), ); (self.cfg.notify_handler)(n); } } } impl<'a> Drop for File<'a> { fn drop(&mut self) { if raw::is_file(&self.path) { let n = Notification::FileDeletion(&self.path, fs::remove_file(&self.path)); (self.cfg.notify_handler)(n); } } } rustup-1.26.0/src/dist/triple.rs000066400000000000000000000100701441327105200165360ustar00rootroot00000000000000use lazy_static::lazy_static; use regex::Regex; // These lists contain the targets known to rustup, and used to build // the PartialTargetTriple. static LIST_ARCHS: &[&str] = &[ "i386", "i586", "i686", "x86_64", "arm", "armv7", "armv7s", "aarch64", "mips", "mipsel", "mips64", "mips64el", "powerpc", "powerpc64", "powerpc64le", "riscv64gc", "s390x", "loongarch64", ]; static LIST_OSES: &[&str] = &[ "pc-windows", "unknown-linux", "apple-darwin", "unknown-netbsd", "apple-ios", "linux", "rumprun-netbsd", "unknown-freebsd", "unknown-illumos", ]; static LIST_ENVS: &[&str] = &[ "gnu", "gnux32", "msvc", "gnueabi", "gnueabihf", "gnuabi64", "androideabi", "android", "musl", ]; #[derive(Debug, Clone, PartialEq, Eq)] pub struct PartialTargetTriple { pub arch: Option, pub os: Option, pub env: Option, } impl PartialTargetTriple { pub(crate) fn new(name: &str) -> Option { if name.is_empty() { return Some(Self { arch: None, os: None, env: None, }); } // Prepending `-` makes this next regex easier since // we can count on all triple components being // delineated by it. let name = format!("-{name}"); lazy_static! { static ref PATTERN: String = format!( r"^(?:-({}))?(?:-({}))?(?:-({}))?$", LIST_ARCHS.join("|"), LIST_OSES.join("|"), LIST_ENVS.join("|") ); static ref RE: Regex = Regex::new(&PATTERN).unwrap(); } RE.captures(&name).map(|c| { fn fn_map(s: &str) -> Option { if s.is_empty() { None } else { Some(s.to_owned()) } } Self { arch: c.get(1).map(|s| s.as_str()).and_then(fn_map), os: c.get(2).map(|s| s.as_str()).and_then(fn_map), env: c.get(3).map(|s| s.as_str()).and_then(fn_map), } }) } } #[cfg(test)] mod test { use super::*; #[test] fn test_partial_target_triple_new() { let success_cases = vec![ ("", (None, None, None)), ("i386", (Some("i386"), None, None)), ("pc-windows", (None, Some("pc-windows"), None)), ("gnu", (None, None, Some("gnu"))), ("i386-gnu", (Some("i386"), None, Some("gnu"))), ("pc-windows-gnu", (None, Some("pc-windows"), Some("gnu"))), ("i386-pc-windows", (Some("i386"), Some("pc-windows"), None)), ( "i386-pc-windows-gnu", (Some("i386"), Some("pc-windows"), Some("gnu")), ), ]; for (input, (arch, os, env)) in success_cases { let partial_target_triple = PartialTargetTriple::new(input); assert!( partial_target_triple.is_some(), "expected `{input}` to create some partial target triple; got None" ); let expected = PartialTargetTriple { arch: arch.map(String::from), os: os.map(String::from), env: env.map(String::from), }; assert_eq!(partial_target_triple.unwrap(), expected, "input: `{input}`"); } let failure_cases = vec![ "anything", "any-other-thing", "-", "--", "i386-", "i386-pc-", "i386-pc-windows-", "-pc-windows", "i386-pc-windows-anything", "0000-00-00-", "00000-000-000", ]; for input in failure_cases { let partial_target_triple = PartialTargetTriple::new(input); assert!( partial_target_triple.is_none(), "expected `{input}` to be `None`, was: `{partial_target_triple:?}`" ); } } } rustup-1.26.0/src/env_var.rs000066400000000000000000000056011441327105200157400ustar00rootroot00000000000000use std::collections::VecDeque; use std::env; use std::path::PathBuf; use std::process::Command; use crate::process; pub const RUST_RECURSION_COUNT_MAX: u32 = 20; pub(crate) fn prepend_path(name: &str, prepend: Vec, cmd: &mut Command) { let old_value = process().var_os(name); let parts = if let Some(ref v) = old_value { let mut tail = env::split_paths(v).collect::>(); for path in prepend.into_iter().rev() { if !tail.contains(&path) { tail.push_front(path); } } tail } else { prepend.into() }; if let Ok(new_value) = env::join_paths(parts) { cmd.env(name, new_value); } } pub(crate) fn inc(name: &str, cmd: &mut Command) { let old_value = process() .var(name) .ok() .and_then(|v| v.parse().ok()) .unwrap_or(0); cmd.env(name, (old_value + 1).to_string()); } #[cfg(test)] mod tests { use super::*; use crate::currentprocess; use crate::test::{with_saved_path, Env}; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; #[test] fn prepend_unique_path() { let mut vars = HashMap::new(); vars.env( "PATH", env::join_paths(vec!["/home/a/.cargo/bin", "/home/b/.cargo/bin"].iter()).unwrap(), ); let tp = Box::new(currentprocess::TestProcess { vars, ..Default::default() }); with_saved_path(&mut || { currentprocess::with(tp.clone(), || { let mut path_entries = vec![]; let mut cmd = Command::new("test"); let a = OsString::from("/home/a/.cargo/bin"); let path_a = PathBuf::from(a); path_entries.push(path_a); let _a = OsString::from("/home/a/.cargo/bin"); let _path_a = PathBuf::from(_a); path_entries.push(_path_a); let z = OsString::from("/home/z/.cargo/bin"); let path_z = PathBuf::from(z); path_entries.push(path_z); prepend_path("PATH", path_entries, &mut cmd); let envs: Vec<_> = cmd.get_envs().collect(); assert_eq!( envs, &[( OsStr::new("PATH"), Some( env::join_paths( vec![ "/home/z/.cargo/bin", "/home/a/.cargo/bin", "/home/b/.cargo/bin" ] .iter() ) .unwrap() .as_os_str() ) ),] ); }); }); } } rustup-1.26.0/src/errors.rs000066400000000000000000000177001441327105200156170ustar00rootroot00000000000000#![allow(clippy::large_enum_variant)] use std::ffi::OsString; use std::fmt::Debug; use std::io::{self, Write}; use std::path::PathBuf; use thiserror::Error as ThisError; use url::Url; use crate::currentprocess::process; use crate::dist::manifest::{Component, Manifest}; const TOOLSTATE_MSG: &str = "If you require these components, please install and use the latest successful build version,\n\ which you can find at .\n\nAfter determining \ the correct date, install it with a command such as:\n\n \ rustup toolchain install nightly-2018-12-27\n\n\ Then you can use the toolchain with commands such as:\n\n \ cargo +nightly-2018-12-27 build"; /// A type erasing thunk for the retry crate to permit use with anyhow. See #[derive(Debug, ThisError)] #[error(transparent)] pub struct OperationError(pub anyhow::Error); #[derive(ThisError, Debug)] pub enum RustupError { #[error("partially downloaded file may have been damaged and was removed, please try again")] BrokenPartialFile, #[error("component download failed for {0}")] ComponentDownloadFailed(String), #[error("failure removing component '{name}', directory does not exist: '{}'", .path.display())] ComponentMissingDir { name: String, path: PathBuf }, #[error("failure removing component '{name}', directory does not exist: '{}'", .path.display())] ComponentMissingFile { name: String, path: PathBuf }, #[error("could not create {name} directory: '{}'", .path.display())] CreatingDirectory { name: &'static str, path: PathBuf }, #[error("unable to read the PGP key '{}'", .path.display())] InvalidPgpKey { path: PathBuf, source: anyhow::Error, }, #[error("invalid toolchain name: '{0}'")] InvalidToolchainName(String), #[error("could not create link from '{}' to '{}'", .src.display(), .dest.display())] LinkingFile { src: PathBuf, dest: PathBuf }, #[error("Unable to proceed. Could not locate working directory.")] LocatingWorkingDir, #[error("failed to set permissions for '{}'", .p.display())] SettingPermissions { p: PathBuf, source: io::Error }, #[error("checksum failed for '{url}', expected: '{expected}', calculated: '{calculated}'")] ChecksumFailed { url: String, expected: String, calculated: String, }, #[error("failed to install component: '{name}', detected conflict: '{}'", .path.display())] ComponentConflict { name: String, path: PathBuf }, #[error("toolchain '{0}' does not support components")] ComponentsUnsupported(String), #[error("component manifest for '{0}' is corrupt")] CorruptComponent(String), #[error("could not download file from '{url}' to '{}'", .path.display())] DownloadingFile { url: Url, path: PathBuf }, #[error("could not download file from '{url}' to '{}'", .path.display())] DownloadNotExists { url: Url, path: PathBuf }, #[error("Missing manifest in toolchain '{}'", .name)] MissingManifest { name: String }, #[error("server sent a broken manifest: missing package for component {0}")] MissingPackageForComponent(String), #[error("could not read {name} directory: '{}'", .path.display())] ReadingDirectory { name: &'static str, path: PathBuf }, #[error("could not read {name} file: '{}'", .path.display())] ReadingFile { name: &'static str, path: PathBuf }, #[error("could not remove '{}' directory: '{}'", .name, .path.display())] RemovingDirectory { name: &'static str, path: PathBuf }, #[error("could not remove '{name}' file: '{}'", .path.display())] RemovingFile { name: &'static str, path: PathBuf }, #[error("{}", component_unavailable_msg(.components, .manifest, .toolchain))] RequestedComponentsUnavailable { components: Vec, manifest: Manifest, toolchain: String, }, #[error("command failed: '{}'", PathBuf::from(.name).display())] RunningCommand { name: OsString }, #[error("toolchain '{0}' is not installable")] ToolchainNotInstallable(String), #[error("toolchain '{0}' is not installed")] ToolchainNotInstalled(String), #[error( "rustup could not choose a version of {} to run, because one wasn't specified explicitly, and no default is configured.\n{}", process().name().unwrap_or_else(|| "Rust".into()), "help: run 'rustup default stable' to download the latest stable release of Rust and set it as your default toolchain." )] ToolchainNotSelected, #[error("toolchain '{}' does not contain component {}{}{}", .name, .component, if let Some(suggestion) = .suggestion { format!("; did you mean '{suggestion}'?") } else { "".to_string() }, if .component.contains("rust-std") { format!("\nnote: not all platforms have the standard library pre-compiled: https://doc.rust-lang.org/nightly/rustc/platform-support.html{}", if name.contains("nightly") { "\nhelp: consider using `cargo build -Z build-std` instead" } else { "" } ) } else { "".to_string() })] UnknownComponent { name: String, component: String, suggestion: Option, }, #[error("unknown metadata version: '{0}'")] UnknownMetadataVersion(String), #[error("manifest version '{0}' is not supported")] UnsupportedVersion(String), #[error("could not write {name} file: '{}'", .path.display())] WritingFile { name: &'static str, path: PathBuf }, } fn remove_component_msg(cs: &Component, manifest: &Manifest, toolchain: &str) -> String { if cs.short_name_in_manifest() == "rust-std" { // We special-case rust-std as it's the stdlib so really you want to do // rustup target remove format!( " rustup target remove --toolchain {} {}", toolchain, cs.target.as_deref().unwrap_or(toolchain) ) } else { format!( " rustup component remove --toolchain {}{} {}", toolchain, if let Some(target) = cs.target.as_ref() { format!(" --target {target}") } else { String::default() }, cs.short_name(manifest) ) } } fn component_unavailable_msg(cs: &[Component], manifest: &Manifest, toolchain: &str) -> String { assert!(!cs.is_empty()); let mut buf = vec![]; if cs.len() == 1 { let _ = writeln!( buf, "component {} is unavailable for download for channel '{}'", &cs[0].description(manifest), toolchain, ); if toolchain.starts_with("nightly") { let _ = write!( buf, "Sometimes not all components are available in any given nightly. " ); } let _ = write!( buf, "If you don't need the component, you can remove it with:\n\n{}", remove_component_msg(&cs[0], manifest, toolchain) ); } else { // More than one component let same_target = cs .iter() .all(|c| c.target == cs[0].target || c.target.is_none()); let cs_str = if same_target { cs.iter() .map(|c| format!("'{}'", c.short_name(manifest))) .collect::>() .join(", ") } else { cs.iter() .map(|c| c.description(manifest)) .collect::>() .join(", ") }; let remove_msg = cs .iter() .map(|c| remove_component_msg(c, manifest, toolchain)) .collect::>() .join("\n"); let _ = write!( buf, "some components unavailable for download for channel '{toolchain}': {cs_str}\n\ If you don't need the components, you can remove them with:\n\n{remove_msg}\n\n{TOOLSTATE_MSG}", ); } String::from_utf8(buf).unwrap() } rustup-1.26.0/src/fallback_settings.rs000066400000000000000000000024261441327105200177610ustar00rootroot00000000000000use serde::Deserialize; use std::io; use std::path::Path; use anyhow::{Context, Result}; use crate::utils::utils; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Default)] pub struct FallbackSettings { pub default_toolchain: Option, } impl FallbackSettings { pub(crate) fn new>(path: P) -> Result> { // Users cannot fix issues with missing/unreadable/invalid centralised files, but logging isn't setup early so // we can't simply trap all errors and log diagnostics. Ideally we would, and then separate these into different // sorts of issues, logging messages about errors that should be fixed. Instead we trap some conservative errors // that hopefully won't lead to too many tickets. match utils::read_file("fallback settings", path.as_ref()) { Err(e) => match e.downcast_ref::() { Some(io_err) => match io_err.kind() { io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied => Ok(None), _ => Err(e), }, None => Err(e), }, Ok(file_contents) => Ok(Some( toml::from_str(&file_contents).context("error parsing settings")?, )), } } } rustup-1.26.0/src/install.rs000066400000000000000000000113471441327105200157520ustar00rootroot00000000000000//! Installation and upgrade of both distribution-managed and local //! toolchains use std::path::Path; use anyhow::Result; use crate::dist::dist; use crate::dist::download::DownloadCfg; use crate::dist::prefix::InstallPrefix; use crate::dist::Notification; use crate::errors::RustupError; use crate::notifications::Notification as RootNotification; use crate::toolchain::{CustomToolchain, Toolchain, UpdateStatus}; use crate::utils::utils; #[derive(Copy, Clone)] pub(crate) enum InstallMethod<'a> { Copy(&'a Path, &'a CustomToolchain<'a>), Link(&'a Path, &'a CustomToolchain<'a>), // bool is whether to force an update Dist { desc: &'a dist::ToolchainDesc, profile: dist::Profile, update_hash: Option<&'a Path>, dl_cfg: DownloadCfg<'a>, // --force force_update: bool, // --allow-downgrade allow_downgrade: bool, // toolchain already exists exists: bool, // currently installed date old_date: Option<&'a str>, // Extra components to install from dist components: &'a [&'a str], // Extra targets to install from dist targets: &'a [&'a str], }, } impl<'a> InstallMethod<'a> { // Install a toolchain pub(crate) fn install(&self, toolchain: &Toolchain<'a>) -> Result { let previous_version = if toolchain.exists() { Some(toolchain.rustc_version()) } else { None }; if previous_version.is_some() { (toolchain.cfg().notify_handler)(RootNotification::UpdatingToolchain(toolchain.name())); } else { (toolchain.cfg().notify_handler)(RootNotification::InstallingToolchain( toolchain.name(), )); } (toolchain.cfg().notify_handler)(RootNotification::ToolchainDirectory( toolchain.path(), toolchain.name(), )); let updated = self.run(toolchain.path(), &|n| { (toolchain.cfg().notify_handler)(n.into()) })?; if !updated { (toolchain.cfg().notify_handler)(RootNotification::UpdateHashMatches); } else { (toolchain.cfg().notify_handler)(RootNotification::InstalledToolchain( toolchain.name(), )); } let status = match (updated, previous_version) { (true, None) => UpdateStatus::Installed, (true, Some(v)) => UpdateStatus::Updated(v), (false, _) => UpdateStatus::Unchanged, }; // Final check, to ensure we're installed if !toolchain.exists() { Err(RustupError::ToolchainNotInstallable(toolchain.name().to_string()).into()) } else { Ok(status) } } pub(crate) fn run( self, path: &Path, notify_handler: &dyn Fn(Notification<'_>), ) -> Result { if path.exists() { // Don't uninstall first for Dist method match self { InstallMethod::Dist { .. } => {} _ => { uninstall(path, notify_handler)?; } } } match self { InstallMethod::Copy(src, ..) => { utils::copy_dir(src, path, notify_handler)?; Ok(true) } InstallMethod::Link(src, ..) => { utils::symlink_dir(src, path, notify_handler)?; Ok(true) } InstallMethod::Dist { desc, profile, update_hash, dl_cfg, force_update, allow_downgrade, exists, old_date, components, targets, .. } => { let prefix = &InstallPrefix::from(path.to_owned()); let maybe_new_hash = dist::update_from_dist( dl_cfg, update_hash, desc, if exists { None } else { Some(profile) }, prefix, force_update, allow_downgrade, old_date, components, targets, )?; if let Some(hash) = maybe_new_hash { if let Some(hash_file) = update_hash { utils::write_file("update hash", hash_file, &hash)?; } Ok(true) } else { Ok(false) } } } } } pub(crate) fn uninstall(path: &Path, notify_handler: &dyn Fn(Notification<'_>)) -> Result<()> { utils::remove_dir("install", path, notify_handler) } rustup-1.26.0/src/lib.rs000066400000000000000000000063261441327105200150530ustar00rootroot00000000000000#![deny(rust_2018_idioms)] #![allow( clippy::too_many_arguments, clippy::type_complexity, clippy::upper_case_acronyms, // see https://github.com/rust-lang/rust-clippy/issues/6974 clippy::vec_init_then_push, // uses two different styles of initialization )] #![recursion_limit = "1024"] pub use crate::config::*; use crate::currentprocess::*; pub use crate::errors::*; pub use crate::notifications::*; use crate::toolchain::*; pub(crate) use crate::utils::toml_utils; use anyhow::{anyhow, Result}; #[macro_use] extern crate rs_tracing; // A list of all binaries which Rustup will proxy. pub static TOOLS: &[&str] = &[ "rustc", "rustdoc", "cargo", "rust-lldb", "rust-gdb", "rust-gdbgui", "rls", "cargo-clippy", "clippy-driver", "cargo-miri", ]; // Tools which are commonly installed by Cargo as well as rustup. We take a bit // more care with these to ensure we don't overwrite the user's previous // installation. pub static DUP_TOOLS: &[&str] = &["rust-analyzer", "rustfmt", "cargo-fmt"]; // If the given name is one of the tools we proxy. pub fn is_proxyable_tools(tool: &str) -> Result<()> { if TOOLS .iter() .chain(DUP_TOOLS.iter()) .any(|&name| name == tool) { Ok(()) } else { Err(anyhow!(format!( "unknown proxy name: '{}'; valid proxy names are {}", tool, TOOLS .iter() .chain(DUP_TOOLS.iter()) .map(|s| format!("'{s}'")) .collect::>() .join(", ") ))) } } fn component_for_bin(binary: &str) -> Option<&'static str> { use std::env::consts::EXE_SUFFIX; let binary_prefix = match binary.find(EXE_SUFFIX) { _ if EXE_SUFFIX.is_empty() => binary, Some(i) => &binary[..i], None => binary, }; match binary_prefix { "rustc" | "rustdoc" => Some("rustc"), "cargo" => Some("cargo"), "rust-lldb" | "rust-gdb" | "rust-gdbgui" => Some("rustc"), // These are not always available "rls" => Some("rls"), "cargo-clippy" => Some("clippy"), "clippy-driver" => Some("clippy"), "cargo-miri" => Some("miri"), "rustfmt" | "cargo-fmt" => Some("rustfmt"), _ => None, } } #[macro_use] pub mod cli; mod command; mod config; pub mod currentprocess; mod diskio; pub mod dist; pub mod env_var; pub mod errors; mod fallback_settings; mod install; pub mod notifications; mod settings; pub mod test; mod toolchain; pub mod utils; #[cfg(test)] mod tests { use crate::{is_proxyable_tools, DUP_TOOLS, TOOLS}; #[test] fn test_is_proxyable_tools() { for tool in TOOLS { assert!(is_proxyable_tools(tool).is_ok()); } for tool in DUP_TOOLS { assert!(is_proxyable_tools(tool).is_ok()); } let message = "unknown proxy name: 'unknown-tool'; valid proxy names are 'rustc', \ 'rustdoc', 'cargo', 'rust-lldb', 'rust-gdb', 'rust-gdbgui', 'rls', \ 'cargo-clippy', 'clippy-driver', 'cargo-miri', 'rust-analyzer', 'rustfmt', 'cargo-fmt'"; assert_eq!( is_proxyable_tools("unknown-tool").unwrap_err().to_string(), message ); } } rustup-1.26.0/src/notifications.rs000066400000000000000000000141511441327105200171510ustar00rootroot00000000000000use std::fmt::{self, Display}; use std::path::{Path, PathBuf}; use crate::dist::temp; use crate::utils::notify::NotificationLevel; #[derive(Debug)] pub enum Notification<'a> { Install(crate::dist::Notification<'a>), Utils(crate::utils::Notification<'a>), Temp(temp::Notification<'a>), SetDefaultToolchain(&'a str), SetOverrideToolchain(&'a Path, &'a str), SetProfile(&'a str), SetSelfUpdate(&'a str), LookingForToolchain(&'a str), ToolchainDirectory(&'a Path, &'a str), UpdatingToolchain(&'a str), InstallingToolchain(&'a str), InstalledToolchain(&'a str), UsingExistingToolchain(&'a str), UninstallingToolchain(&'a str), UninstalledToolchain(&'a str), ToolchainNotInstalled(&'a str), UpdateHashMatches, UpgradingMetadata(&'a str, &'a str), MetadataUpgradeNotNeeded(&'a str), WritingMetadataVersion(&'a str), ReadMetadataVersion(&'a str), NonFatalError(&'a anyhow::Error), UpgradeRemovesToolchains, MissingFileDuringSelfUninstall(PathBuf), PlainVerboseMessage(&'a str), /// Both `rust-toolchain` and `rust-toolchain.toml` exist within a directory DuplicateToolchainFile { rust_toolchain: &'a Path, rust_toolchain_toml: &'a Path, }, } impl<'a> From> for Notification<'a> { fn from(n: crate::dist::Notification<'a>) -> Self { Notification::Install(n) } } impl<'a> From> for Notification<'a> { fn from(n: crate::utils::Notification<'a>) -> Self { Notification::Utils(n) } } impl<'a> From> for Notification<'a> { fn from(n: temp::Notification<'a>) -> Self { Notification::Temp(n) } } impl<'a> Notification<'a> { pub(crate) fn level(&self) -> NotificationLevel { use self::Notification::*; match self { Install(n) => n.level(), Utils(n) => n.level(), Temp(n) => n.level(), ToolchainDirectory(_, _) | LookingForToolchain(_) | WritingMetadataVersion(_) | InstallingToolchain(_) | UpdatingToolchain(_) | ReadMetadataVersion(_) | InstalledToolchain(_) | PlainVerboseMessage(_) | UpdateHashMatches => NotificationLevel::Verbose, SetDefaultToolchain(_) | SetOverrideToolchain(_, _) | SetProfile(_) | SetSelfUpdate(_) | UsingExistingToolchain(_) | UninstallingToolchain(_) | UninstalledToolchain(_) | ToolchainNotInstalled(_) | UpgradingMetadata(_, _) | MetadataUpgradeNotNeeded(_) => NotificationLevel::Info, NonFatalError(_) => NotificationLevel::Error, UpgradeRemovesToolchains | MissingFileDuringSelfUninstall(_) | DuplicateToolchainFile { .. } => NotificationLevel::Warn, } } } impl<'a> Display for Notification<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { use self::Notification::*; match self { Install(n) => n.fmt(f), Utils(n) => n.fmt(f), Temp(n) => n.fmt(f), SetDefaultToolchain("none") => write!(f, "default toolchain unset"), SetDefaultToolchain(name) => write!(f, "default toolchain set to '{name}'"), SetOverrideToolchain(path, name) => write!( f, "override toolchain for '{}' set to '{}'", path.display(), name ), SetProfile(name) => write!(f, "profile set to '{name}'"), SetSelfUpdate(mode) => write!(f, "auto-self-update mode set to '{mode}'"), LookingForToolchain(name) => write!(f, "looking for installed toolchain '{name}'"), ToolchainDirectory(path, _) => write!(f, "toolchain directory: '{}'", path.display()), UpdatingToolchain(name) => write!(f, "updating existing install for '{name}'"), InstallingToolchain(name) => write!(f, "installing toolchain '{name}'"), InstalledToolchain(name) => write!(f, "toolchain '{name}' installed"), UsingExistingToolchain(name) => write!(f, "using existing install for '{name}'"), UninstallingToolchain(name) => write!(f, "uninstalling toolchain '{name}'"), UninstalledToolchain(name) => write!(f, "toolchain '{name}' uninstalled"), ToolchainNotInstalled(name) => write!(f, "no toolchain installed for '{name}'"), UpdateHashMatches => write!(f, "toolchain is already up to date"), UpgradingMetadata(from_ver, to_ver) => write!( f, "upgrading metadata version from '{from_ver}' to '{to_ver}'" ), MetadataUpgradeNotNeeded(ver) => { write!(f, "nothing to upgrade: metadata version is already '{ver}'") } WritingMetadataVersion(ver) => write!(f, "writing metadata version: '{ver}'"), ReadMetadataVersion(ver) => write!(f, "read metadata version: '{ver}'"), NonFatalError(e) => write!(f, "{e}"), UpgradeRemovesToolchains => write!( f, "this upgrade will remove all existing toolchains. you will need to reinstall them" ), MissingFileDuringSelfUninstall(p) => write!( f, "expected file does not exist to uninstall: {}", p.display() ), PlainVerboseMessage(r) => write!(f, "{r}"), DuplicateToolchainFile { rust_toolchain, rust_toolchain_toml, } => write!( f, "both `{0}` and `{1}` exist. Using `{0}`", rust_toolchain .canonicalize() .unwrap_or_else(|_| PathBuf::from(rust_toolchain)) .display(), rust_toolchain_toml .canonicalize() .unwrap_or_else(|_| PathBuf::from(rust_toolchain_toml)) .display(), ), } } } rustup-1.26.0/src/settings.rs000066400000000000000000000156071441327105200161470ustar00rootroot00000000000000use std::cell::RefCell; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::str::FromStr; use anyhow::{Context, Result}; use crate::cli::self_update::SelfUpdateMode; use crate::dist::dist::Profile; use crate::errors::*; use crate::notifications::*; use crate::toml_utils::*; use crate::utils::utils; pub(crate) const SUPPORTED_METADATA_VERSIONS: [&str; 2] = ["2", "12"]; pub(crate) const DEFAULT_METADATA_VERSION: &str = "12"; #[derive(Clone, Debug, Eq, PartialEq)] pub struct SettingsFile { path: PathBuf, cache: RefCell>, } impl SettingsFile { pub(crate) fn new(path: PathBuf) -> Self { Self { path, cache: RefCell::new(None), } } fn write_settings(&self) -> Result<()> { let s = self.cache.borrow().as_ref().unwrap().clone(); utils::write_file("settings", &self.path, &s.stringify())?; Ok(()) } fn read_settings(&self) -> Result<()> { let mut needs_save = false; { let mut b = self.cache.borrow_mut(); if b.is_none() { *b = Some(if utils::is_file(&self.path) { let content = utils::read_file("settings", &self.path)?; Settings::parse(&content)? } else { needs_save = true; Default::default() }); } } if needs_save { self.write_settings()?; } Ok(()) } pub(crate) fn with Result>(&self, f: F) -> Result { self.read_settings()?; // Settings can no longer be None so it's OK to unwrap f(self.cache.borrow().as_ref().unwrap()) } pub(crate) fn with_mut Result>(&self, f: F) -> Result { self.read_settings()?; // Settings can no longer be None so it's OK to unwrap let result = { f(self.cache.borrow_mut().as_mut().unwrap())? }; self.write_settings()?; Ok(result) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct Settings { pub version: String, pub default_host_triple: Option, pub default_toolchain: Option, pub profile: Option, pub overrides: BTreeMap, pub pgp_keys: Option, pub auto_self_update: Option, } impl Default for Settings { fn default() -> Self { Self { version: DEFAULT_METADATA_VERSION.to_owned(), default_host_triple: None, default_toolchain: None, profile: Some(Profile::Default), overrides: BTreeMap::new(), pgp_keys: None, auto_self_update: None, } } } impl Settings { fn path_to_key(path: &Path, notify_handler: &dyn Fn(Notification<'_>)) -> String { if path.exists() { utils::canonicalize_path(path, notify_handler) .display() .to_string() } else { path.display().to_string() } } pub(crate) fn remove_override( &mut self, path: &Path, notify_handler: &dyn Fn(Notification<'_>), ) -> bool { let key = Self::path_to_key(path, notify_handler); self.overrides.remove(&key).is_some() } pub(crate) fn add_override( &mut self, path: &Path, toolchain: String, notify_handler: &dyn Fn(Notification<'_>), ) { let key = Self::path_to_key(path, notify_handler); notify_handler(Notification::SetOverrideToolchain(path, &toolchain)); self.overrides.insert(key, toolchain); } pub(crate) fn dir_override( &self, dir: &Path, notify_handler: &dyn Fn(Notification<'_>), ) -> Option { let key = Self::path_to_key(dir, notify_handler); self.overrides.get(&key).cloned() } pub(crate) fn parse(data: &str) -> Result { let value = toml::from_str(data).context("error parsing settings")?; Self::from_toml(value, "") } pub(crate) fn stringify(self) -> String { toml::Value::Table(self.into_toml()).to_string() } pub(crate) fn from_toml(mut table: toml::value::Table, path: &str) -> Result { let version = get_string(&mut table, "version", path)?; if !SUPPORTED_METADATA_VERSIONS.contains(&&*version) { return Err(RustupError::UnknownMetadataVersion(version).into()); } let auto_self_update = get_opt_string(&mut table, "auto_self_update", path)? .and_then(|mode| SelfUpdateMode::from_str(mode.as_str()).ok()); let profile = get_opt_string(&mut table, "profile", path)? .and_then(|p| Profile::from_str(p.as_str()).ok()); Ok(Self { version, default_host_triple: get_opt_string(&mut table, "default_host_triple", path)?, default_toolchain: get_opt_string(&mut table, "default_toolchain", path)?, profile, overrides: Self::table_to_overrides(&mut table, path)?, pgp_keys: get_opt_string(&mut table, "pgp_keys", path)?, auto_self_update, }) } pub(crate) fn into_toml(self) -> toml::value::Table { let mut result = toml::value::Table::new(); result.insert("version".to_owned(), toml::Value::String(self.version)); if let Some(v) = self.default_host_triple { result.insert("default_host_triple".to_owned(), toml::Value::String(v)); } if let Some(v) = self.default_toolchain { result.insert("default_toolchain".to_owned(), toml::Value::String(v)); } if let Some(v) = self.profile { result.insert("profile".to_owned(), toml::Value::String(v.to_string())); } if let Some(v) = self.pgp_keys { result.insert("pgp_keys".to_owned(), toml::Value::String(v)); } if let Some(v) = self.auto_self_update { result.insert( "auto_self_update".to_owned(), toml::Value::String(v.to_string()), ); } let overrides = Self::overrides_to_table(self.overrides); result.insert("overrides".to_owned(), toml::Value::Table(overrides)); result } fn table_to_overrides( table: &mut toml::value::Table, path: &str, ) -> Result> { let mut result = BTreeMap::new(); let pkg_table = get_table(table, "overrides", path)?; for (k, v) in pkg_table { if let toml::Value::String(t) = v { result.insert(k, t); } } Ok(result) } fn overrides_to_table(overrides: BTreeMap) -> toml::value::Table { let mut result = toml::value::Table::new(); for (k, v) in overrides { result.insert(k, toml::Value::String(v)); } result } } rustup-1.26.0/src/test.rs000066400000000000000000000151421441327105200152600ustar00rootroot00000000000000#![allow(clippy::box_default)] //! Test support module; public to permit use from integration tests. use std::collections::HashMap; use std::env; use std::ffi::OsStr; use std::fmt; use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::process::Command; #[cfg(test)] use anyhow::Result; pub use crate::cli::self_update::test::{get_path, with_saved_path}; use crate::currentprocess; use crate::dist::dist::TargetTriple; // Things that can have environment variables applied to them. pub trait Env { fn env(&mut self, key: K, val: V) where K: AsRef, V: AsRef; } impl Env for Command { fn env(&mut self, key: K, val: V) where K: AsRef, V: AsRef, { self.env(key, val); } } impl Env for HashMap { fn env(&mut self, key: K, val: V) where K: AsRef, V: AsRef, { let key = key.as_ref().to_os_string().into_string().unwrap(); let val = val.as_ref().to_os_string().into_string().unwrap(); self.insert(key, val); } } /// The path to a dir for this test binaries state fn exe_test_dir() -> io::Result { let current_exe_path = env::current_exe().unwrap(); let mut exe_dir = current_exe_path.parent().unwrap(); if exe_dir.ends_with("deps") { exe_dir = exe_dir.parent().unwrap(); } Ok(exe_dir.parent().unwrap().to_owned()) } /// Returns a tempdir for running tests in pub fn test_dir() -> io::Result { let exe_dir = exe_test_dir()?; let test_dir = exe_dir.join("tests"); fs::create_dir_all(&test_dir).unwrap(); tempfile::Builder::new() .prefix("running-test-") .tempdir_in(test_dir) } /// Returns a directory for storing immutable distributions in pub fn const_dist_dir() -> io::Result { // TODO: do something smart, like managing garbage collection or something. let exe_dir = exe_test_dir()?; let dists_dir = exe_dir.join("dists"); fs::create_dir_all(&dists_dir)?; let current_exe = env::current_exe().unwrap(); let current_exe_name = current_exe.file_name().unwrap(); tempfile::Builder::new() .prefix(&format!( "dist-for-{}-", Path::new(current_exe_name).display() )) .tempdir_in(dists_dir) } /// Returns a tempdir for storing test-scoped distributions in pub fn test_dist_dir() -> io::Result { let exe_dir = exe_test_dir()?; let test_dir = exe_dir.join("tests"); fs::create_dir_all(&test_dir).unwrap(); tempfile::Builder::new() .prefix("test-dist-dir-") .tempdir_in(test_dir) } /// Makes persistent unique directory inside path. /// /// Should only be used with path=a tempdir that will be cleaned up, as the /// directory tempdir_in_with_prefix creates won't be automatically cleaned up. fn tempdir_in_with_prefix>(path: P, prefix: &str) -> io::Result { Ok(tempfile::Builder::new() .prefix(prefix) .tempdir_in(path.as_ref())? .into_path()) } /// What is this host's triple - seems very redundant with from_host_or_build() /// ... perhaps this is so that the test data we have is only exercised on known /// triples? /// /// NOTE: This *cannot* be called within a currentprocess context as it creates /// its own context on Windows hosts. This is partly by chance but also partly /// deliberate: If you need the host triple, or to call for_host(), you can do /// so outside of calls to run() or unit test code that runs in a currentprocess /// context. /// /// IF it becomes very hard to workaround that, then we can either make a second /// this_host_triple that doesn't make its own currentprocess or use /// TargetTriple::from_host() from within the currentprocess context as needed. pub fn this_host_triple() -> String { if cfg!(target_os = "windows") { // For windows, this host may be different to the target: we may be // building with i686 toolchain, but on an x86_64 host, so run the // actual detection logic and trust it. let tp = Box::new(currentprocess::TestProcess::default()); return currentprocess::with(tp, || TargetTriple::from_host().unwrap().to_string()); } let arch = if cfg!(target_arch = "x86") { "i686" } else if cfg!(target_arch = "x86_64") { "x86_64" } else if cfg!(target_arch = "riscv64") { "riscv64gc" } else if cfg!(target_arch = "aarch64") { "aarch64" } else if cfg!(target_arch = "loongarch64") { "loongarch64" } else { unimplemented!() }; let os = if cfg!(target_os = "linux") { "unknown-linux" } else if cfg!(target_os = "macos") { "apple-darwin" } else if cfg!(target_os = "illumos") { "unknown-illumos" } else if cfg!(target_os = "freebsd") { "unknown-freebsd" } else { unimplemented!() }; let env = if cfg!(target_env = "gnu") { Some("gnu") } else { None }; if let Some(env) = env { format!("{arch}-{os}-{env}") } else { format!("{arch}-{os}") } } // Format a string with this host triple. #[macro_export] macro_rules! for_host { ($s: expr) => { &format!($s, $crate::test::this_host_triple()) }; } #[derive(Clone)] /// The smallest form of test isolation: an isolated RUSTUP_HOME, for codepaths /// that read and write config files but do not invoke processes, download data /// etc. pub struct RustupHome { pub rustupdir: PathBuf, } impl RustupHome { pub fn apply(&self, e: &mut E) { e.env("RUSTUP_HOME", self.rustupdir.to_string_lossy().to_string()) } pub fn has>(&self, path: P) -> bool { self.rustupdir.join(path).exists() } pub fn join>(&self, path: P) -> PathBuf { self.rustupdir.join(path) } pub fn new_in>(path: P) -> io::Result { let rustupdir = tempdir_in_with_prefix(path, "rustup")?; Ok(RustupHome { rustupdir }) } pub fn remove(&self) -> io::Result<()> { remove_dir_all::remove_dir_all(&self.rustupdir) } } impl fmt::Display for RustupHome { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.rustupdir.display()) } } /// Create an isolated rustup home with no content, then call f with it, and /// delete it afterwards. #[cfg(test)] pub(crate) fn with_rustup_home(f: F) -> Result<()> where F: FnOnce(&RustupHome) -> Result<()>, { let test_dir = test_dir()?; let rustup_home = RustupHome::new_in(test_dir)?; f(&rustup_home) } rustup-1.26.0/src/toolchain.rs000066400000000000000000001106241441327105200162620ustar00rootroot00000000000000use std::env; use std::env::consts::EXE_SUFFIX; use std::ffi::OsStr; use std::ffi::OsString; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::FromStr; use std::time::Duration; use anyhow::{anyhow, bail, Context, Result}; use thiserror::Error as ThisError; use wait_timeout::ChildExt; use crate::component_for_bin; use crate::config::Cfg; use crate::dist::dist::Profile; use crate::dist::dist::TargetTriple; use crate::dist::dist::ToolchainDesc; use crate::dist::download::DownloadCfg; use crate::dist::manifest::Component; use crate::dist::manifest::Manifest; use crate::dist::manifestation::{Changes, Manifestation}; use crate::dist::prefix::InstallPrefix; use crate::env_var; use crate::errors::*; use crate::install::{self, InstallMethod}; use crate::notifications::*; use crate::process; use crate::utils::utils; /// An installed toolchain trait InstalledToolchain<'a> { /// What (root) paths are associated with this installed toolchain. fn installed_paths(&self) -> Result>>; } /// Installed paths enum InstalledPath<'a> { File { name: &'static str, path: PathBuf }, Dir { path: &'a Path }, } /// A fully resolved reference to a toolchain which may or may not exist pub struct Toolchain<'a> { cfg: &'a Cfg, name: String, path: PathBuf, dist_handler: Box) + 'a>, } /// Used by the `list_component` function pub struct ComponentStatus { pub component: Component, pub name: String, pub installed: bool, pub available: bool, } #[derive(Clone, Debug)] pub enum UpdateStatus { Installed, Updated(String), // Stores the version of rustc *before* the update Unchanged, } static V1_COMMON_COMPONENT_LIST: &[&str] = &["cargo", "rustc", "rust-docs"]; impl<'a> Toolchain<'a> { pub(crate) fn from(cfg: &'a Cfg, name: &str) -> Result { let resolved_name = cfg.resolve_toolchain(name)?; let path = cfg.toolchains_dir.join(&resolved_name); Ok(Toolchain { cfg, name: resolved_name, path, dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())), }) } pub(crate) fn from_path( cfg: &'a Cfg, cfg_file: Option>, path: impl AsRef, ) -> Result { let path = if let Some(cfg_file) = cfg_file { cfg_file.as_ref().parent().unwrap().join(path) } else { path.as_ref().to_path_buf() }; #[derive(Debug, ThisError)] #[error("invalid toolchain path: '{}'", .0.to_string_lossy())] struct InvalidToolchainPath(PathBuf); // Perform minimal validation; there should at least be a `bin/` that might // contain things for us to run. if !path.join("bin").is_dir() { bail!(InvalidToolchainPath(path)); } Ok(Toolchain { cfg, name: utils::canonicalize_path(&path, cfg.notify_handler.as_ref()) .to_str() .ok_or_else(|| anyhow!(InvalidToolchainPath(path.clone())))? .to_owned(), path, dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())), }) } pub fn as_installed_common(&'a self) -> Result> { if !self.exists() { // Should be verify perhaps? return Err(RustupError::ToolchainNotInstalled(self.name.to_owned()).into()); } Ok(InstalledCommonToolchain(self)) } fn as_installed(&'a self) -> Result + 'a>> { if self.is_custom() { let toolchain = CustomToolchain::new(self)?; Ok(Box::new(toolchain) as Box>) } else { let toolchain = DistributableToolchain::new(self)?; Ok(Box::new(toolchain) as Box>) } } pub(crate) fn cfg(&self) -> &Cfg { self.cfg } pub fn name(&self) -> &str { &self.name } pub fn path(&self) -> &Path { &self.path } fn is_symlink(&self) -> bool { use std::fs; fs::symlink_metadata(&self.path) .map(|m| m.file_type().is_symlink()) .unwrap_or(false) } /// Is there a filesystem component with the name of the toolchain in the toolchains dir - valid install or not. /// Used to determine whether this toolchain should be uninstallable. /// Custom and Distributable. Installed and uninstalled. (perhaps onstalled only?) pub fn exists(&self) -> bool { // HACK: linked toolchains are symlinks, and, contrary to what std docs // lead me to believe `fs::metadata`, used by `is_directory` does not // seem to follow symlinks on windows. let is_symlink = if cfg!(windows) { self.is_symlink() } else { false }; utils::is_directory(&self.path) || is_symlink } /// Is there a valid usable toolchain with this name, either in the toolchains dir, or symlinked from it. // Could in future check for rustc perhaps. // Custom and Distributable. Installed only? pub fn verify(&self) -> Result<()> { utils::assert_is_directory(&self.path) } // Custom and Distributable. Installed only. pub fn remove(&self) -> Result<()> { if self.exists() || self.is_symlink() { (self.cfg.notify_handler)(Notification::UninstallingToolchain(&self.name)); } else { (self.cfg.notify_handler)(Notification::ToolchainNotInstalled(&self.name)); return Ok(()); } let installed = self.as_installed()?; for path in installed.installed_paths()? { match path { InstalledPath::File { name, path } => utils::ensure_file_removed(name, &path)?, InstalledPath::Dir { path } => { install::uninstall(path, &|n| (self.cfg.notify_handler)(n.into()))? } } } if !self.exists() { (self.cfg.notify_handler)(Notification::UninstalledToolchain(&self.name)); } Ok(()) } // Custom only pub fn is_custom(&self) -> bool { Toolchain::is_custom_name(&self.name) } pub(crate) fn is_custom_name(name: &str) -> bool { ToolchainDesc::from_str(name).is_err() } // Distributable only pub fn is_tracking(&self) -> bool { ToolchainDesc::from_str(&self.name) .ok() .map(|d| d.is_tracking()) == Some(true) } // Custom and Distributable. Installed only. pub fn doc_path(&self, relative: &str) -> Result { self.verify()?; let parts = vec!["share", "doc", "rust", "html"]; let mut doc_dir = self.path.clone(); for part in parts { doc_dir.push(part); } doc_dir.push(relative); Ok(doc_dir) } // Custom and Distributable. Installed only. pub fn open_docs(&self, relative: &str) -> Result<()> { self.verify()?; utils::open_browser(&self.doc_path(relative)?) } // Custom and Distributable. Installed only. pub fn make_default(&self) -> Result<()> { self.cfg.set_default(&self.name) } // Custom and Distributable. Installed only. pub fn make_override(&self, path: &Path) -> Result<()> { self.cfg.settings_file.with_mut(|s| { s.add_override(path, self.name.clone(), self.cfg.notify_handler.as_ref()); Ok(()) }) } // Distributable and Custom. Installed only. pub fn binary_file(&self, name: &str) -> PathBuf { let mut path = self.path.clone(); path.push("bin"); path.push(name.to_owned() + env::consts::EXE_SUFFIX); path } // Distributable and Custom. Installed only. pub fn rustc_version(&self) -> String { if let Ok(installed) = self.as_installed_common() { let rustc_path = self.binary_file("rustc"); if utils::is_file(&rustc_path) { let mut cmd = Command::new(&rustc_path); cmd.arg("--version"); cmd.stdin(Stdio::null()); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); installed.set_ldpath(&mut cmd); // some toolchains are faulty with some combinations of platforms and // may fail to launch but also to timely terminate. // (known cases include Rust 1.3.0 through 1.10.0 in recent macOS Sierra.) // we guard against such cases by enforcing a reasonable timeout to read. let mut line1 = None; if let Ok(mut child) = cmd.spawn() { let timeout = Duration::new(10, 0); match child.wait_timeout(timeout) { Ok(Some(status)) if status.success() => { let out = child .stdout .expect("Child::stdout requested but not present"); let mut line = String::new(); if BufReader::new(out).read_line(&mut line).is_ok() { let lineend = line.trim_end_matches(&['\r', '\n'][..]).len(); line.truncate(lineend); line1 = Some(line); } } Ok(None) => { let _ = child.kill(); return String::from("(timeout reading rustc version)"); } Ok(Some(_)) | Err(_) => {} } } if let Some(line1) = line1 { line1 } else { String::from("(error reading rustc version)") } } else { String::from("(rustc does not exist)") } } else { String::from("(toolchain not installed)") } } } impl<'a> std::fmt::Debug for Toolchain<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Toolchain") .field("name", &self.name) .field("path", &self.path) .finish() } } fn install_msg(bin: &str, toolchain: &str, is_default: bool) -> String { if Toolchain::is_custom_name(toolchain) { return "\nnote: this is a custom toolchain, which cannot use `rustup component add`\n\ help: if you built this toolchain from source, and used `rustup toolchain link`, then you may be able to build the component with `x.py`".to_string(); } match component_for_bin(bin) { Some(c) => format!("\nTo install, run `rustup component add {}{}`", c, { if is_default { String::new() } else { format!(" --toolchain {toolchain}") } }), None => String::new(), } } /// Newtype hosting functions that apply to both custom and distributable toolchains that are installed. pub struct InstalledCommonToolchain<'a>(&'a Toolchain<'a>); impl<'a> InstalledCommonToolchain<'a> { pub fn create_command>(&self, binary: T) -> Result { // Create the path to this binary within the current toolchain sysroot let binary = if let Some(binary_str) = binary.as_ref().to_str() { if binary_str.to_lowercase().ends_with(EXE_SUFFIX) { binary.as_ref().to_owned() } else { OsString::from(format!("{binary_str}{EXE_SUFFIX}")) } } else { // Very weird case. Non-unicode command. binary.as_ref().to_owned() }; let bin_path = self.0.path.join("bin").join(&binary); let path = if utils::is_file(&bin_path) { &bin_path } else { let recursion_count = process() .var("RUST_RECURSION_COUNT") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(0); if recursion_count > env_var::RUST_RECURSION_COUNT_MAX - 1 { let binary_lossy: String = binary.to_string_lossy().into(); if let Ok(distributable) = DistributableToolchain::new(self.0) { if let (Some(component_name), Ok(component_statuses), Ok(Some(manifest))) = ( component_for_bin(&binary_lossy), distributable.list_components(), distributable.get_manifest(), ) { let component_status = component_statuses .iter() .find(|cs| cs.component.short_name(&manifest) == component_name) .unwrap_or_else(|| { panic!("component {component_name} should be in the manifest") }); if !component_status.available { return Err(anyhow!(format!( "the '{}' component which provides the command '{}' is not available for the '{}' toolchain", component_status.component.short_name(&manifest), binary_lossy, self.0.name))); } if component_status.installed { return Err(anyhow!(format!( "the '{}' binary, normally provided by the '{}' component, is not applicable to the '{}' toolchain", binary_lossy, component_status.component.short_name(&manifest), self.0.name))); } } } let defaults = self.0.cfg.get_default()?; return Err(anyhow!(format!( "'{}' is not installed for the toolchain '{}'{}", binary.to_string_lossy(), self.0.name, install_msg( &binary.to_string_lossy(), &self.0.name, Some(&self.0.name) == defaults.as_ref() ) ))); } Path::new(&binary) }; let mut cmd = Command::new(path); self.set_env(&mut cmd); Ok(cmd) } fn set_env(&self, cmd: &mut Command) { self.set_ldpath(cmd); // Older versions of Cargo used a slightly different definition of // cargo home. Rustup does not read HOME on Windows whereas the older // versions of Cargo did. Rustup and Cargo should be in sync now (both // using the same `home` crate), but this is retained to ensure cargo // and rustup agree in older versions. if let Ok(cargo_home) = utils::cargo_home() { cmd.env("CARGO_HOME", &cargo_home); } env_var::inc("RUST_RECURSION_COUNT", cmd); cmd.env("RUSTUP_TOOLCHAIN", &self.0.name); cmd.env("RUSTUP_HOME", &self.0.cfg.rustup_dir); } fn set_ldpath(&self, cmd: &mut Command) { let mut new_path = vec![self.0.path.join("lib")]; #[cfg(not(target_os = "macos"))] mod sysenv { pub const LOADER_PATH: &str = "LD_LIBRARY_PATH"; } #[cfg(target_os = "macos")] mod sysenv { // When loading and linking a dynamic library or bundle, dlopen // searches in LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, PWD, and // DYLD_FALLBACK_LIBRARY_PATH. // In the Mach-O format, a dynamic library has an "install path." // Clients linking against the library record this path, and the // dynamic linker, dyld, uses it to locate the library. // dyld searches DYLD_LIBRARY_PATH *before* the install path. // dyld searches DYLD_FALLBACK_LIBRARY_PATH only if it cannot // find the library in the install path. // Setting DYLD_LIBRARY_PATH can easily have unintended // consequences. pub const LOADER_PATH: &str = "DYLD_FALLBACK_LIBRARY_PATH"; } if cfg!(target_os = "macos") && process() .var_os(sysenv::LOADER_PATH) .filter(|x| x.len() > 0) .is_none() { // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't // set or set to an empty string. Since we are explicitly setting // the value, make sure the defaults still work. if let Some(home) = process().var_os("HOME") { new_path.push(PathBuf::from(home).join("lib")); } new_path.push(PathBuf::from("/usr/local/lib")); new_path.push(PathBuf::from("/usr/lib")); } env_var::prepend_path(sysenv::LOADER_PATH, new_path, cmd); // Prepend CARGO_HOME/bin to the PATH variable so that we're sure to run // cargo/rustc via the proxy bins. There is no fallback case for if the // proxy bins don't exist. We'll just be running whatever happens to // be on the PATH. let mut path_entries = vec![]; if let Ok(cargo_home) = utils::cargo_home() { path_entries.push(cargo_home.join("bin")); } if cfg!(target_os = "windows") { // Historically rustup has included the bin directory in PATH to // work around some bugs (see // https://github.com/rust-lang/rustup/pull/3178 for more // information). This shouldn't be needed anymore, and it causes // problems because calling tools recursively (like `cargo // +nightly metadata` from within a cargo subcommand). The // recursive call won't work because it is not executing the // proxy, so the `+` toolchain override doesn't work. // // This is opt-in to allow us to get more real-world testing. if process() .var_os("RUSTUP_WINDOWS_PATH_ADD_BIN") .map_or(true, |s| s == "1") { path_entries.push(self.0.path.join("bin")); } } env_var::prepend_path("PATH", path_entries, cmd); } } /// Newtype to facilitate splitting out custom-toolchain specific code. pub struct CustomToolchain<'a>(&'a Toolchain<'a>); impl<'a> CustomToolchain<'a> { pub fn new(toolchain: &'a Toolchain<'a>) -> Result> { if toolchain.is_custom() { Ok(CustomToolchain(toolchain)) } else { Err(anyhow!(format!( "{} is not a custom toolchain", toolchain.name() ))) } } // Not installed only. pub fn install_from_dir(&self, src: &Path, link: bool) -> Result<()> { let mut pathbuf = PathBuf::from(src); pathbuf.push("lib"); utils::assert_is_directory(&pathbuf)?; pathbuf.pop(); pathbuf.push("bin"); utils::assert_is_directory(&pathbuf)?; pathbuf.push(format!("rustc{EXE_SUFFIX}")); utils::assert_is_file(&pathbuf)?; if link { InstallMethod::Link(&utils::to_absolute(src)?, self).install(self.0)?; } else { InstallMethod::Copy(src, self).install(self.0)?; } Ok(()) } } impl<'a> InstalledToolchain<'a> for CustomToolchain<'a> { fn installed_paths(&self) -> Result>> { let path = &self.0.path; Ok(vec![InstalledPath::Dir { path }]) } } /// Newtype to facilitate splitting out distributable-toolchain specific code. pub struct DistributableToolchain<'a>(&'a Toolchain<'a>); impl<'a> DistributableToolchain<'a> { pub fn new(toolchain: &'a Toolchain<'a>) -> Result> { if toolchain.is_custom() { Err(anyhow!(format!( "{} is a custom toolchain", toolchain.name() ))) } else { Ok(DistributableToolchain(toolchain)) } } /// Temporary helper until we further split this into a newtype for /// InstalledDistributableToolchain - one where the type can protect component operations. pub fn new_for_components(toolchain: &'a Toolchain<'a>) -> Result> { DistributableToolchain::new(toolchain).context(RustupError::ComponentsUnsupported( toolchain.name().to_string(), )) } // Installed only. pub(crate) fn add_component(&self, mut component: Component) -> Result<()> { if let Some(desc) = self.get_toolchain_desc_with_manifest()? { // Rename the component if necessary. if let Some(c) = desc.manifest.rename_component(&component) { component = c; } // Validate the component name let rust_pkg = desc .manifest .packages .get("rust") .expect("manifest should contain a rust package"); let targ_pkg = rust_pkg .targets .get(&desc.toolchain.target) .expect("installed manifest should have a known target"); if !targ_pkg.components.contains(&component) { let wildcard_component = component.wildcard(); if targ_pkg.components.contains(&wildcard_component) { component = wildcard_component; } else { return Err(RustupError::UnknownComponent { name: self.0.name.to_string(), component: component.description(&desc.manifest), suggestion: self.get_component_suggestion( &component, &desc.manifest, false, ), } .into()); } } let changes = Changes { explicit_add_components: vec![component], remove_components: vec![], }; desc.manifestation.update( &desc.manifest, changes, false, &self.download_cfg(), &self.download_cfg().notify_handler, &desc.toolchain.manifest_name(), false, )?; Ok(()) } else { Err(RustupError::MissingManifest { name: self.0.name.to_string(), } .into()) } } // Create a command as a fallback for another toolchain. This is used // to give custom toolchains access to cargo // Installed only. pub fn create_fallback_command>( &self, binary: T, primary_toolchain: &Toolchain<'_>, ) -> Result { // With the hacks below this only works for cargo atm assert!(binary.as_ref() == "cargo" || binary.as_ref() == "cargo.exe"); if !self.0.exists() { return Err(RustupError::ToolchainNotInstalled(self.0.name.to_owned()).into()); } let installed_primary = primary_toolchain.as_installed_common()?; let src_file = self.0.path.join("bin").join(format!("cargo{EXE_SUFFIX}")); // MAJOR HACKS: Copy cargo.exe to its own directory on windows before // running it. This is so that the fallback cargo, when it in turn runs // rustc.exe, will run the rustc.exe out of the PATH environment // variable, _not_ the rustc.exe sitting in the same directory as the // fallback. See the `fallback_cargo_calls_correct_rustc` test case and // PR 812. // // On Windows, spawning a process will search the running application's // directory for the exe to spawn before searching PATH, and we don't want // it to do that, because cargo's directory contains the _wrong_ rustc. See // the documentation for the lpCommandLine argument of CreateProcess. let exe_path = if cfg!(windows) { use std::fs; let fallback_dir = self.0.cfg.rustup_dir.join("fallback"); fs::create_dir_all(&fallback_dir) .context("unable to create dir to hold fallback exe")?; let fallback_file = fallback_dir.join("cargo.exe"); if fallback_file.exists() { fs::remove_file(&fallback_file).context("unable to unlink old fallback exe")?; } fs::hard_link(&src_file, &fallback_file).context("unable to hard link fallback exe")?; fallback_file } else { src_file }; let mut cmd = Command::new(exe_path); installed_primary.set_env(&mut cmd); // set up the environment to match rustc, not cargo cmd.env("RUSTUP_TOOLCHAIN", &primary_toolchain.name); Ok(cmd) } // Installed and not-installed? pub(crate) fn desc(&self) -> Result { ToolchainDesc::from_str(&self.0.name) } fn download_cfg(&self) -> DownloadCfg<'_> { self.0.cfg.download_cfg(&*self.0.dist_handler) } // Installed only? fn get_component_suggestion( &self, component: &Component, manifest: &Manifest, only_installed: bool, ) -> Option { use strsim::damerau_levenshtein; // Suggest only for very small differences // High number can result in inaccurate suggestions for short queries e.g. `rls` const MAX_DISTANCE: usize = 3; let components = self.list_components(); if let Ok(components) = components { let short_name_distance = components .iter() .filter(|c| !only_installed || c.installed) .map(|c| { ( damerau_levenshtein( &c.component.name(manifest)[..], &component.name(manifest)[..], ), c, ) }) .min_by_key(|t| t.0) .expect("There should be always at least one component"); let long_name_distance = components .iter() .filter(|c| !only_installed || c.installed) .map(|c| { ( damerau_levenshtein( &c.component.name_in_manifest()[..], &component.name(manifest)[..], ), c, ) }) .min_by_key(|t| t.0) .expect("There should be always at least one component"); let mut closest_distance = short_name_distance; let mut closest_match = short_name_distance.1.component.short_name(manifest); // Find closer suggestion if short_name_distance.0 > long_name_distance.0 { closest_distance = long_name_distance; // Check if only targets differ if closest_distance.1.component.short_name_in_manifest() == component.short_name_in_manifest() { closest_match = long_name_distance.1.component.target(); } else { closest_match = long_name_distance .1 .component .short_name_in_manifest() .to_string(); } } else { // Check if only targets differ if closest_distance.1.component.short_name(manifest) == component.short_name(manifest) { closest_match = short_name_distance.1.component.target(); } } // If suggestion is too different don't suggest anything if closest_distance.0 > MAX_DISTANCE { None } else { Some(closest_match) } } else { None } } // Installed only. pub(crate) fn get_manifest(&self) -> Result> { Ok(self.get_toolchain_desc_with_manifest()?.map(|d| d.manifest)) } // Not installed only? pub(crate) fn install_from_dist( &self, force_update: bool, allow_downgrade: bool, components: &[&str], targets: &[&str], profile: Option, ) -> Result { let update_hash = self.update_hash()?; let old_date = self.get_manifest().ok().and_then(|m| m.map(|m| m.date)); InstallMethod::Dist { desc: &self.desc()?, profile: profile .map(Ok) .unwrap_or_else(|| self.0.cfg.get_profile())?, update_hash: Some(&update_hash), dl_cfg: self.download_cfg(), force_update, allow_downgrade, exists: self.0.exists(), old_date: old_date.as_deref(), components, targets, } .install(self.0) } // Installed or not installed. pub fn install_from_dist_if_not_installed(&self) -> Result { let update_hash = self.update_hash()?; (self.0.cfg.notify_handler)(Notification::LookingForToolchain(&self.0.name)); if !self.0.exists() { Ok(InstallMethod::Dist { desc: &self.desc()?, profile: self.0.cfg.get_profile()?, update_hash: Some(&update_hash), dl_cfg: self.download_cfg(), force_update: false, allow_downgrade: false, exists: false, old_date: None, components: &[], targets: &[], } .install(self.0)?) } else { (self.0.cfg.notify_handler)(Notification::UsingExistingToolchain(&self.0.name)); Ok(UpdateStatus::Unchanged) } } pub(crate) fn get_toolchain_desc_with_manifest( &self, ) -> Result> { if !self.0.exists() { bail!(RustupError::ToolchainNotInstalled(self.0.name.to_owned())); } let toolchain = &self.0.name; let toolchain = ToolchainDesc::from_str(toolchain) .context(RustupError::ComponentsUnsupported(self.0.name.to_string()))?; let prefix = InstallPrefix::from(self.0.path.to_owned()); let manifestation = Manifestation::open(prefix, toolchain.target.clone())?; Ok(manifestation .load_manifest()? .map(|manifest| ToolchainDescWithManifest { toolchain, manifestation, manifest, })) } pub fn list_components(&self) -> Result> { if let Some(toolchain) = self.get_toolchain_desc_with_manifest()? { toolchain.list_components() } else { Err(RustupError::ComponentsUnsupported(self.0.name.to_string()).into()) } } // Installed only. pub(crate) fn remove_component(&self, mut component: Component) -> Result<()> { if let Some(desc) = self.get_toolchain_desc_with_manifest()? { // Rename the component if necessary. if let Some(c) = desc.manifest.rename_component(&component) { component = c; } let dist_config = desc.manifestation.read_config()?.unwrap(); if !dist_config.components.contains(&component) { let wildcard_component = component.wildcard(); if dist_config.components.contains(&wildcard_component) { component = wildcard_component; } else { return Err(RustupError::UnknownComponent { name: self.0.name.to_string(), component: component.description(&desc.manifest), suggestion: self.get_component_suggestion(&component, &desc.manifest, true), } .into()); } } let changes = Changes { explicit_add_components: vec![], remove_components: vec![component], }; desc.manifestation.update( &desc.manifest, changes, false, &self.download_cfg(), &self.download_cfg().notify_handler, &desc.toolchain.manifest_name(), false, )?; Ok(()) } else { Err(RustupError::MissingManifest { name: self.0.name.to_string(), } .into()) } } // Installed only. pub fn show_dist_version(&self) -> Result> { let update_hash = self.update_hash()?; match crate::dist::dist::dl_v2_manifest( self.download_cfg(), Some(&update_hash), &self.desc()?, )? { Some((manifest, _)) => Ok(Some(manifest.get_rust_version()?.to_string())), None => Ok(None), } } // Installed only. pub fn show_version(&self) -> Result> { match self.get_manifest()? { Some(manifest) => Ok(Some(manifest.get_rust_version()?.to_string())), None => Ok(None), } } // Installed only. fn update_hash(&self) -> Result { self.0.cfg.get_hash_file(&self.0.name, true) } // Installed only. pub fn guess_v1_manifest(&self) -> bool { let prefix = InstallPrefix::from(self.0.path().to_owned()); // If all the v1 common components are present this is likely to be // a v1 manifest install. The v1 components are not called the same // in a v2 install. for component in V1_COMMON_COMPONENT_LIST { let manifest = format!("manifest-{component}"); let manifest_path = prefix.manifest_file(&manifest); if !utils::path_exists(manifest_path) { return false; } } // It's reasonable to assume this is a v1 manifest installation true } } /// Helper type to avoid parsing a manifest more than once pub(crate) struct ToolchainDescWithManifest { toolchain: ToolchainDesc, manifestation: Manifestation, pub manifest: Manifest, } impl ToolchainDescWithManifest { pub(crate) fn list_components(&self) -> Result> { let config = self.manifestation.read_config()?; // Return all optional components of the "rust" package for the // toolchain's target triple. let mut res = Vec::new(); let rust_pkg = self .manifest .packages .get("rust") .expect("manifest should contain a rust package"); let targ_pkg = rust_pkg .targets .get(&self.toolchain.target) .expect("installed manifest should have a known target"); for component in &targ_pkg.components { let installed = config .as_ref() .map(|c| component.contained_within(&c.components)) .unwrap_or(false); let component_target = TargetTriple::new(&component.target()); // Get the component so we can check if it is available let component_pkg = self .manifest .get_package(component.short_name_in_manifest()) .unwrap_or_else(|_| { panic!( "manifest should contain component {}", &component.short_name(&self.manifest) ) }); let component_target_pkg = component_pkg .targets .get(&component_target) .expect("component should have target toolchain"); res.push(ComponentStatus { component: component.clone(), name: component.name(&self.manifest), installed, available: component_target_pkg.available(), }); } res.sort_by(|a, b| a.component.cmp(&b.component)); Ok(res) } } impl<'a> InstalledToolchain<'a> for DistributableToolchain<'a> { fn installed_paths(&self) -> Result>> { let path = &self.0.path; Ok(vec![ InstalledPath::File { name: "update hash", path: self.update_hash()?, }, InstalledPath::Dir { path }, ]) } } rustup-1.26.0/src/utils/000077500000000000000000000000001441327105200150705ustar00rootroot00000000000000rustup-1.26.0/src/utils/mod.rs000066400000000000000000000004261441327105200162170ustar00rootroot00000000000000//! Utility functions for Rustup pub(crate) mod notifications; pub mod raw; pub(crate) mod toml_utils; pub(crate) mod tty; pub(crate) mod units; #[allow(clippy::module_inception)] pub mod utils; pub(crate) use crate::utils::notifications::Notification; pub(crate) mod notify; rustup-1.26.0/src/utils/notifications.rs000066400000000000000000000103461441327105200203130ustar00rootroot00000000000000use std::fmt::{self, Display}; use std::path::Path; use url::Url; use crate::utils::notify::NotificationLevel; use crate::utils::units::{self, Unit}; #[derive(Debug)] pub enum Notification<'a> { CreatingDirectory(&'a str, &'a Path), LinkingDirectory(&'a Path, &'a Path), CopyingDirectory(&'a Path, &'a Path), RemovingDirectory(&'a str, &'a Path), DownloadingFile(&'a Url, &'a Path), /// Received the Content-Length of the to-be downloaded data. DownloadContentLengthReceived(u64), /// Received some data. DownloadDataReceived(&'a [u8]), /// Download has finished. DownloadFinished, /// The things we're tracking that are not counted in bytes. /// Must be paired with a pop-units; our other calls are not /// setup to guarantee this any better. DownloadPushUnit(Unit), /// finish using an unusual unit. DownloadPopUnit, NoCanonicalPath(&'a Path), ResumingPartialDownload, /// This would make more sense as a crate::notifications::Notification /// member, but the notification callback is already narrowed to /// utils::notifications by the time tar unpacking is called. SetDefaultBufferSize(usize), Error(String), UsingCurl, UsingReqwest, /// Renaming encountered a file in use error and is retrying. /// The InUse aspect is a heuristic - the OS specifies /// Permission denied, but as we work in users home dirs and /// running programs like virus scanner are known to cause this /// the heuristic is quite good. RenameInUse(&'a Path, &'a Path), } impl<'a> Notification<'a> { pub(crate) fn level(&self) -> NotificationLevel { use self::Notification::*; match self { SetDefaultBufferSize(_) => NotificationLevel::Debug, CreatingDirectory(_, _) | RemovingDirectory(_, _) | LinkingDirectory(_, _) | CopyingDirectory(_, _) | DownloadingFile(_, _) | DownloadContentLengthReceived(_) | DownloadDataReceived(_) | DownloadPushUnit(_) | DownloadPopUnit | DownloadFinished | ResumingPartialDownload | UsingCurl | UsingReqwest => NotificationLevel::Verbose, RenameInUse(_, _) => NotificationLevel::Info, NoCanonicalPath(_) => NotificationLevel::Warn, Error(_) => NotificationLevel::Error, } } } impl<'a> Display for Notification<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { use self::Notification::*; match self { CreatingDirectory(name, path) => { write!(f, "creating {} directory: '{}'", name, path.display()) } Error(e) => write!(f, "error: '{e}'"), LinkingDirectory(_, dest) => write!(f, "linking directory from: '{}'", dest.display()), CopyingDirectory(src, _) => write!(f, "copying directory from: '{}'", src.display()), RemovingDirectory(name, path) => { write!(f, "removing {} directory: '{}'", name, path.display()) } RenameInUse(src, dest) => write!( f, "retrying renaming '{}' to '{}'", src.display(), dest.display() ), SetDefaultBufferSize(size) => write!( f, "using up to {} of RAM to unpack components", units::Size::new(*size, units::Unit::B, units::UnitMode::Norm) ), DownloadingFile(url, _) => write!(f, "downloading file from: '{url}'"), DownloadContentLengthReceived(len) => write!(f, "download size is: '{len}'"), DownloadDataReceived(data) => write!(f, "received some data of size {}", data.len()), DownloadPushUnit(_) => Ok(()), DownloadPopUnit => Ok(()), DownloadFinished => write!(f, "download finished"), NoCanonicalPath(path) => write!(f, "could not canonicalize path: '{}'", path.display()), ResumingPartialDownload => write!(f, "resuming partial download"), UsingCurl => write!(f, "downloading with curl"), UsingReqwest => write!(f, "downloading with reqwest"), } } } rustup-1.26.0/src/utils/notify.rs000066400000000000000000000001561441327105200167500ustar00rootroot00000000000000#[derive(Debug)] pub(crate) enum NotificationLevel { Verbose, Info, Warn, Error, Debug, } rustup-1.26.0/src/utils/raw.rs000066400000000000000000000176751441327105200162470ustar00rootroot00000000000000#[cfg(not(windows))] use std::env; use std::fs; use std::io; use std::io::Write; use std::path::Path; use std::str; #[cfg(not(windows))] use crate::process; pub(crate) fn ensure_dir_exists, F: FnOnce(&Path)>( path: P, callback: F, ) -> io::Result { if !is_directory(path.as_ref()) { callback(path.as_ref()); fs::create_dir_all(path.as_ref()).map(|()| true) } else { Ok(false) } } pub(crate) fn is_directory>(path: P) -> bool { fs::metadata(path).ok().as_ref().map(fs::Metadata::is_dir) == Some(true) } pub fn is_file>(path: P) -> bool { fs::metadata(path).ok().as_ref().map(fs::Metadata::is_file) == Some(true) } pub fn path_exists>(path: P) -> bool { fs::metadata(path).is_ok() } pub(crate) fn random_string(length: usize) -> String { use rand::Rng; const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789_"; let mut rng = rand::thread_rng(); (0..length) .map(|_| char::from(CHARSET[rng.gen_range(0..CHARSET.len())])) .collect() } pub(crate) fn if_not_empty>(s: S) -> Option { if s == *"" { None } else { Some(s) } } pub fn write_file(path: &Path, contents: &str) -> io::Result<()> { let mut file = fs::OpenOptions::new() .write(true) .truncate(true) .create(true) .open(path)?; io::Write::write_all(&mut file, contents.as_bytes())?; file.sync_data()?; Ok(()) } pub(crate) fn filter_file bool>( src: &Path, dest: &Path, mut filter: F, ) -> io::Result { let src_file = fs::File::open(src)?; let dest_file = fs::File::create(dest)?; let mut reader = io::BufReader::new(src_file); let mut writer = io::BufWriter::new(dest_file); let mut removed = 0; for result in io::BufRead::lines(&mut reader) { let line = result?; if filter(&line) { writeln!(writer, "{}", &line)?; } else { removed += 1; } } writer.flush()?; Ok(removed) } pub fn append_file(dest: &Path, line: &str) -> io::Result<()> { let mut dest_file = fs::OpenOptions::new() .write(true) .append(true) .create(true) .open(dest)?; writeln!(dest_file, "{line}")?; dest_file.sync_data()?; Ok(()) } pub(crate) fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> { #[cfg(windows)] fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> { // std's symlink uses Windows's symlink function, which requires // admin. We can create directory junctions the hard way without // though. symlink_junction_inner(src, dest) } #[cfg(not(windows))] fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> { std::os::unix::fs::symlink(src, dest) } let _ = remove_dir(dest); symlink_dir_inner(src, dest) } // Creating a directory junction on windows involves dealing with reparse // points and the DeviceIoControl function, and this code is a skeleton of // what can be found here: // // http://www.flexhex.com/docs/articles/hard-links.phtml // // Copied from std #[cfg(windows)] #[allow(non_snake_case)] fn symlink_junction_inner(target: &Path, junction: &Path) -> io::Result<()> { use std::os::windows::ffi::OsStrExt; use std::ptr; use winapi::shared::minwindef::*; use winapi::um::fileapi::*; use winapi::um::ioapiset::*; use winapi::um::winbase::*; use winapi::um::winioctl::FSCTL_SET_REPARSE_POINT; use winapi::um::winnt::*; const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; #[repr(C)] #[allow(non_snake_case)] struct REPARSE_MOUNTPOINT_DATA_BUFFER { ReparseTag: DWORD, ReparseDataLength: DWORD, Reserved: WORD, ReparseTargetLength: WORD, ReparseTargetMaximumLength: WORD, Reserved1: WORD, ReparseTarget: WCHAR, } // We're using low-level APIs to create the junction, and these are more picky about paths. // For example, forward slashes cannot be used as a path separator, so we should try to // canonicalize the path first. let target = fs::canonicalize(target)?; fs::create_dir(junction)?; let path = windows::to_u16s(junction)?; unsafe { let h = CreateFileW( path.as_ptr(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, ptr::null_mut(), OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, ptr::null_mut(), ); let mut data = [0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; let db = data.as_mut_ptr().cast::(); let buf = &mut (*db).ReparseTarget as *mut WCHAR; let mut i = 0; // FIXME: this conversion is very hacky let v = br"\??\"; let v = v.iter().map(|x| *x as u16); for c in v.chain(target.as_os_str().encode_wide().skip(4)) { *buf.offset(i) = c; i += 1; } *buf.offset(i) = 0; i += 1; (*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; (*db).ReparseTargetMaximumLength = (i * 2) as WORD; (*db).ReparseTargetLength = ((i - 1) * 2) as WORD; (*db).ReparseDataLength = (*db).ReparseTargetLength as DWORD + 12; let mut ret = 0; let res = DeviceIoControl( h.cast(), FSCTL_SET_REPARSE_POINT, data.as_mut_ptr().cast(), (*db).ReparseDataLength + 8, ptr::null_mut(), 0, &mut ret, ptr::null_mut(), ); if res == 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } } pub(crate) fn hardlink(src: &Path, dest: &Path) -> io::Result<()> { let _ = fs::remove_file(dest); fs::hard_link(src, dest) } pub fn remove_dir(path: &Path) -> io::Result<()> { if fs::symlink_metadata(path)?.file_type().is_symlink() { if cfg!(windows) { fs::remove_dir(path) } else { fs::remove_file(path) } } else { // Again because remove_dir all doesn't delete write-only files on windows, // this is a custom implementation, more-or-less copied from cargo. // cc rust-lang/rust#31944 // cc https://github.com/rust-lang/cargo/blob/master/tests/support/paths.rs#L52 remove_dir_all::remove_dir_all(path) } } pub(crate) fn copy_dir(src: &Path, dest: &Path) -> io::Result<()> { fs::create_dir(dest)?; for entry in src.read_dir()? { let entry = entry?; let kind = entry.file_type()?; let src = entry.path(); let dest = dest.join(entry.file_name()); if kind.is_dir() { copy_dir(&src, &dest)?; } else { fs::copy(&src, &dest)?; } } Ok(()) } #[cfg(not(windows))] fn has_cmd(cmd: &str) -> bool { let cmd = format!("{}{}", cmd, env::consts::EXE_SUFFIX); let path = process().var_os("PATH").unwrap_or_default(); env::split_paths(&path) .map(|p| p.join(&cmd)) .any(|p| p.exists()) } #[cfg(not(windows))] pub(crate) fn find_cmd<'a>(cmds: &[&'a str]) -> Option<&'a str> { cmds.iter().cloned().find(|&s| has_cmd(s)) } #[cfg(windows)] pub(crate) mod windows { use std::ffi::OsStr; use std::io; use std::os::windows::ffi::OsStrExt; pub(crate) fn to_u16s>(s: S) -> io::Result> { fn inner(s: &OsStr) -> io::Result> { let mut maybe_result: Vec = s.encode_wide().collect(); if maybe_result.iter().any(|&u| u == 0) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "strings passed to WinAPI cannot contain NULs", )); } maybe_result.push(0); Ok(maybe_result) } inner(s.as_ref()) } } rustup-1.26.0/src/utils/toml_utils.rs000066400000000000000000000041101441327105200176250ustar00rootroot00000000000000use anyhow::{anyhow, Result}; use thiserror::Error as ThisError; fn get_value(table: &mut toml::value::Table, key: &str, path: &str) -> Result { table .remove(key) .ok_or_else(|| anyhow!(format!("missing key: '{}'", path.to_owned() + key))) } #[derive(Debug, ThisError)] #[error("expected type: '{0}' for '{1}'")] struct ExpectedType(&'static str, String); pub(crate) fn get_string(table: &mut toml::value::Table, key: &str, path: &str) -> Result { if let toml::Value::String(s) = get_value(table, key, path)? { Ok(s) } else { Err(ExpectedType("string", path.to_owned() + key).into()) } } pub(crate) fn get_opt_string( table: &mut toml::value::Table, key: &str, path: &str, ) -> Result> { if let Ok(v) = get_value(table, key, path) { if let toml::Value::String(s) = v { Ok(Some(s)) } else { Err(ExpectedType("string", path.to_owned() + key).into()) } } else { Ok(None) } } pub(crate) fn get_bool(table: &mut toml::value::Table, key: &str, path: &str) -> Result { get_value(table, key, path).and_then(|v| { if let toml::Value::Boolean(b) = v { Ok(b) } else { Err(ExpectedType("bool", path.to_owned() + key).into()) } }) } pub(crate) fn get_table( table: &mut toml::value::Table, key: &str, path: &str, ) -> Result { if let Some(v) = table.remove(key) { if let toml::Value::Table(t) = v { Ok(t) } else { Err(ExpectedType("table", path.to_owned() + key).into()) } } else { Ok(toml::value::Table::new()) } } pub(crate) fn get_array( table: &mut toml::value::Table, key: &str, path: &str, ) -> Result { if let Some(v) = table.remove(key) { if let toml::Value::Array(s) = v { Ok(s) } else { Err(ExpectedType("array", path.to_owned() + key).into()) } } else { Ok(toml::value::Array::new()) } } rustup-1.26.0/src/utils/tty.rs000066400000000000000000000020421441327105200162540ustar00rootroot00000000000000// Copied from rustc. isatty crate did not work as expected #[cfg(unix)] pub(crate) fn stderr_isatty() -> bool { isatty(libc::STDERR_FILENO) } #[cfg(windows)] pub(crate) fn stderr_isatty() -> bool { isatty(winapi::um::winbase::STD_ERROR_HANDLE) } #[cfg(unix)] pub(crate) fn stdout_isatty() -> bool { isatty(libc::STDOUT_FILENO) } #[cfg(windows)] pub(crate) fn stdout_isatty() -> bool { isatty(winapi::um::winbase::STD_OUTPUT_HANDLE) } #[inline] #[cfg(unix)] fn isatty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) == 1 } } #[inline] #[cfg(windows)] fn isatty(fd: winapi::shared::minwindef::DWORD) -> bool { if std::env::var("MSYSTEM").is_ok() { // FIXME: No color is better than broken color codes in MSYS shells // https://github.com/rust-lang/rustup/issues/2292 return false; } use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle}; unsafe { let handle = GetStdHandle(fd); let mut out = 0; GetConsoleMode(handle, &mut out) != 0 } } rustup-1.26.0/src/utils/units.rs000066400000000000000000000107671441327105200166130ustar00rootroot00000000000000use std::fmt::{self, Display}; #[derive(Copy, Clone, Debug)] pub enum Unit { B, IO, } pub(crate) enum UnitMode { Norm, Rate, } /// Human readable size (some units) pub(crate) enum Size { B(usize, UnitMode), IO(usize, UnitMode), } impl Size { pub(crate) fn new(size: usize, unit: Unit, unitmode: UnitMode) -> Self { match unit { Unit::B => Self::B(size, unitmode), Unit::IO => Self::IO(size, unitmode), } } } impl Display for Size { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Size::B(size, unitmode) => { const KI: f64 = 1024.0; const MI: f64 = KI * KI; const GI: f64 = KI * KI * KI; let size = *size as f64; let suffix: String = match unitmode { UnitMode::Norm => "".into(), UnitMode::Rate => "/s".into(), }; if size >= GI { write!(f, "{:5.1} GiB{}", size / GI, suffix) } else if size >= MI { write!(f, "{:5.1} MiB{}", size / MI, suffix) } else if size >= KI { write!(f, "{:5.1} KiB{}", size / KI, suffix) } else { write!(f, "{size:3.0} B{suffix}") } } Size::IO(size, unitmode) => { const K: f64 = 1000.0; const M: f64 = K * K; const G: f64 = K * K * K; let size = *size as f64; let suffix: String = match unitmode { UnitMode::Norm => "IO-ops".into(), UnitMode::Rate => "IOPS".into(), }; if size >= G { write!(f, "{:5.1} giga-{}", size / G, suffix) } else if size >= M { write!(f, "{:5.1} mega-{}", size / M, suffix) } else if size >= K { write!(f, "{:5.1} kilo-{}", size / K, suffix) } else { write!(f, "{size:3.0} {suffix}") } } } } } #[cfg(test)] mod tests { #[test] fn unit_formatter_test() { use crate::utils::units::{Size, Unit, UnitMode}; // Test Bytes assert_eq!( format!("{}", Size::new(1, Unit::B, UnitMode::Norm)), " 1 B" ); assert_eq!( format!("{}", Size::new(1024, Unit::B, UnitMode::Norm)), " 1.0 KiB" ); assert_eq!( format!("{}", Size::new(1024usize.pow(2), Unit::B, UnitMode::Norm)), " 1.0 MiB" ); assert_eq!( format!("{}", Size::new(1024usize.pow(3), Unit::B, UnitMode::Norm)), " 1.0 GiB" ); // Test Bytes at given rate assert_eq!( format!("{}", Size::new(1, Unit::B, UnitMode::Rate)), " 1 B/s" ); assert_eq!( format!("{}", Size::new(1024, Unit::B, UnitMode::Rate)), " 1.0 KiB/s" ); assert_eq!( format!("{}", Size::new(1024usize.pow(2), Unit::B, UnitMode::Rate)), " 1.0 MiB/s" ); assert_eq!( format!("{}", Size::new(1024usize.pow(3), Unit::B, UnitMode::Rate)), " 1.0 GiB/s" ); //Test I/O Operations assert_eq!( format!("{}", Size::new(1, Unit::IO, UnitMode::Norm)), " 1 IO-ops" ); assert_eq!( format!("{}", Size::new(1000, Unit::IO, UnitMode::Norm)), " 1.0 kilo-IO-ops" ); assert_eq!( format!("{}", Size::new(1000usize.pow(2), Unit::IO, UnitMode::Norm)), " 1.0 mega-IO-ops" ); assert_eq!( format!("{}", Size::new(1000usize.pow(3), Unit::IO, UnitMode::Norm)), " 1.0 giga-IO-ops" ); //Test I/O Operations at given rate assert_eq!( format!("{}", Size::new(1, Unit::IO, UnitMode::Rate)), " 1 IOPS" ); assert_eq!( format!("{}", Size::new(1000, Unit::IO, UnitMode::Rate)), " 1.0 kilo-IOPS" ); assert_eq!( format!("{}", Size::new(1000usize.pow(2), Unit::IO, UnitMode::Rate)), " 1.0 mega-IOPS" ); assert_eq!( format!("{}", Size::new(1000usize.pow(3), Unit::IO, UnitMode::Rate)), " 1.0 giga-IOPS" ); } } rustup-1.26.0/src/utils/utils.rs000066400000000000000000000560451441327105200166100ustar00rootroot00000000000000use std::cmp::Ord; use std::env; use std::fs::{self, File}; use std::io::{self, BufReader, Write}; use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context, Result}; use home::env as home; use retry::delay::{jitter, Fibonacci}; use retry::{retry, OperationResult}; use sha2::Sha256; use url::Url; // use crate::currentprocess::cwdsource::CurrentDirSource; use crate::errors::*; use crate::utils::notifications::Notification; use crate::utils::raw; use crate::{home_process, process}; #[cfg(not(windows))] pub(crate) use crate::utils::utils::raw::find_cmd; pub(crate) use crate::utils::utils::raw::{if_not_empty, is_directory}; pub use crate::utils::utils::raw::{is_file, path_exists}; pub struct ExitCode(pub i32); pub fn ensure_dir_exists<'a, N>( name: &'static str, path: &'a Path, notify_handler: &'a dyn Fn(N), ) -> Result where N: From>, { raw::ensure_dir_exists(path, |_| { notify_handler(Notification::CreatingDirectory(name, path).into()) }) .with_context(|| RustupError::CreatingDirectory { name, path: PathBuf::from(path), }) } pub fn read_file(name: &'static str, path: &Path) -> Result { fs::read_to_string(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, 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, path: PathBuf::from(path), }) } pub(crate) fn write_line( name: &'static str, file: &mut File, path: &Path, line: &str, ) -> Result<()> { writeln!(file, "{line}").with_context(|| RustupError::WritingFile { name, path: path.to_path_buf(), }) } pub(crate) fn write_str(name: &'static str, file: &mut File, path: &Path, s: &str) -> Result<()> { write!(file, "{s}").with_context(|| RustupError::WritingFile { name, path: path.to_path_buf(), }) } pub fn rename_file<'a, N>( name: &'static str, src: &'a Path, dest: &'a Path, notify: &'a dyn Fn(N), ) -> Result<()> where N: From>, { rename(name, src, dest, notify) } pub(crate) fn rename_dir<'a, N>( name: &'static str, src: &'a Path, dest: &'a Path, notify: &'a dyn Fn(N), ) -> Result<()> where N: From>, { rename(name, src, dest, notify) } pub(crate) fn filter_file bool>( name: &'static str, src: &Path, dest: &Path, filter: F, ) -> Result { raw::filter_file(src, dest, filter).with_context(|| { format!( "could not copy {} file from '{}' to '{}'", name, src.display(), dest.display() ) }) } pub(crate) fn canonicalize_path<'a, N>(path: &'a Path, notify_handler: &dyn Fn(N)) -> PathBuf where N: From>, { fs::canonicalize(path).unwrap_or_else(|_| { notify_handler(Notification::NoCanonicalPath(path).into()); PathBuf::from(path) }) } pub fn download_file( url: &Url, path: &Path, hasher: Option<&mut Sha256>, notify_handler: &dyn Fn(Notification<'_>), ) -> Result<()> { download_file_with_resume(url, path, hasher, false, ¬ify_handler) } pub(crate) fn download_file_with_resume( url: &Url, path: &Path, hasher: Option<&mut Sha256>, resume_from_partial: bool, notify_handler: &dyn Fn(Notification<'_>), ) -> Result<()> { use download::DownloadError as DEK; match download_file_(url, path, hasher, resume_from_partial, notify_handler) { Ok(_) => Ok(()), Err(e) => { let is_client_error = match e.downcast_ref::() { // Specifically treat the bad partial range error as not our // fault in case it was something odd which happened. Some(DEK::HttpStatus(416)) => false, Some(DEK::HttpStatus(400..=499)) | Some(DEK::FileNotFound) => true, _ => false, }; Err(e).with_context(|| { if is_client_error { RustupError::DownloadNotExists { url: url.clone(), path: path.to_path_buf(), } } else { RustupError::DownloadingFile { url: url.clone(), path: path.to_path_buf(), } } }) } } } fn download_file_( url: &Url, path: &Path, hasher: Option<&mut Sha256>, resume_from_partial: bool, notify_handler: &dyn Fn(Notification<'_>), ) -> Result<()> { use download::download_to_path_with_backend; use download::{Backend, Event, TlsBackend}; use sha2::Digest; use std::cell::RefCell; notify_handler(Notification::DownloadingFile(url, path)); let hasher = RefCell::new(hasher); // This callback will write the download to disk and optionally // hash the contents, then forward the notification up the stack let callback: &dyn Fn(Event<'_>) -> download::Result<()> = &|msg| { if let Event::DownloadDataReceived(data) = msg { if let Some(h) = hasher.borrow_mut().as_mut() { h.update(data); } } match msg { Event::DownloadContentLengthReceived(len) => { notify_handler(Notification::DownloadContentLengthReceived(len)); } Event::DownloadDataReceived(data) => { notify_handler(Notification::DownloadDataReceived(data)); } Event::ResumingPartialDownload => { notify_handler(Notification::ResumingPartialDownload); } } Ok(()) }; // Download the file // Keep the curl env var around for a bit let use_curl_backend = process().var_os("RUSTUP_USE_CURL").is_some(); let use_rustls = process().var_os("RUSTUP_USE_RUSTLS").is_some(); let (backend, notification) = if use_curl_backend { (Backend::Curl, Notification::UsingCurl) } else { let tls_backend = if use_rustls { TlsBackend::Rustls } else { #[cfg(feature = "reqwest-default-tls")] { TlsBackend::Default } #[cfg(not(feature = "reqwest-default-tls"))] { TlsBackend::Rustls } }; (Backend::Reqwest(tls_backend), Notification::UsingReqwest) }; notify_handler(notification); let res = download_to_path_with_backend(backend, url, path, resume_from_partial, Some(callback)); notify_handler(Notification::DownloadFinished); res } pub(crate) fn parse_url(url: &str) -> Result { Url::parse(url).with_context(|| format!("failed to parse url: {url}")) } pub(crate) fn assert_is_file(path: &Path) -> Result<()> { if !is_file(path) { Err(anyhow!(format!("not a file: '{}'", path.display()))) } else { Ok(()) } } pub(crate) fn assert_is_directory(path: &Path) -> Result<()> { if !is_directory(path) { Err(anyhow!(format!("not a directory: '{}'", path.display()))) } else { Ok(()) } } pub(crate) fn symlink_dir<'a, N>( src: &'a Path, dest: &'a Path, notify_handler: &dyn Fn(N), ) -> Result<()> where N: From>, { notify_handler(Notification::LinkingDirectory(src, dest).into()); raw::symlink_dir(src, dest).with_context(|| { format!( "could not create link from '{}' to '{}'", src.display(), dest.display() ) }) } pub(crate) fn hard_or_symlink_file(src: &Path, dest: &Path) -> Result<()> { // Some mac filesystems can do hardlinks to symlinks, some can't. // See rust-lang/rustup#3136 for why it's better never to use them. #[cfg(target_os = "macos")] let force_symlink = fs::symlink_metadata(src) .map(|m| m.file_type().is_symlink()) .unwrap_or(false); #[cfg(not(target_os = "macos"))] let force_symlink = false; if force_symlink || hardlink_file(src, dest).is_err() { symlink_file(src, dest)?; } Ok(()) } pub fn hardlink_file(src: &Path, dest: &Path) -> Result<()> { raw::hardlink(src, dest).with_context(|| RustupError::LinkingFile { src: PathBuf::from(src), dest: PathBuf::from(dest), }) } #[cfg(unix)] fn symlink_file(src: &Path, dest: &Path) -> Result<()> { std::os::unix::fs::symlink(src, dest).with_context(|| RustupError::LinkingFile { src: PathBuf::from(src), dest: PathBuf::from(dest), }) } #[cfg(windows)] fn symlink_file(src: &Path, dest: &Path) -> Result<()> { // we are supposed to not use symlink on windows Err(anyhow!(RustupError::LinkingFile { src: PathBuf::from(src), dest: PathBuf::from(dest), })) } pub(crate) fn copy_dir<'a, N>( src: &'a Path, dest: &'a Path, notify_handler: &dyn Fn(N), ) -> Result<()> where N: From>, { notify_handler(Notification::CopyingDirectory(src, dest).into()); raw::copy_dir(src, dest).with_context(|| { format!( "could not copy directory from '{}' to '{}'", src.display(), dest.display() ) }) } pub(crate) fn copy_file(src: &Path, dest: &Path) -> Result<()> { let metadata = fs::symlink_metadata(src).with_context(|| RustupError::ReadingFile { name: "metadata for", path: PathBuf::from(src), })?; if metadata.file_type().is_symlink() { symlink_file(src, dest).map(|_| ()) } else { fs::copy(src, dest) .with_context(|| { format!( "could not copy file from '{}' to '{}'", src.display(), dest.display() ) }) .map(|_| ()) } } pub(crate) fn remove_dir<'a, N>( name: &'static str, path: &'a Path, notify_handler: &dyn Fn(N), ) -> Result<()> where N: From>, { notify_handler(Notification::RemovingDirectory(name, path).into()); raw::remove_dir(path).with_context(|| RustupError::RemovingDirectory { name, path: PathBuf::from(path), }) } pub fn remove_file(name: &'static str, path: &Path) -> Result<()> { // Most files we go to remove won't ever be in use. Some, like proxies, may // be for indefinite periods, and this will mean we are slower to error and // have the user fix the issue. Others, like the setup binary, are // transiently in use, and this wait loop will fix the issue transparently // for a rare performance hit. retry( Fibonacci::from_millis(1).map(jitter).take(10), || match fs::remove_file(path) { Ok(()) => OperationResult::Ok(()), Err(e) => match e.kind() { io::ErrorKind::PermissionDenied => OperationResult::Retry(e), _ => OperationResult::Err(e), }, }, ) .with_context(|| RustupError::RemovingFile { name, path: PathBuf::from(path), }) } pub(crate) fn ensure_file_removed(name: &'static str, path: &Path) -> Result<()> { let result = remove_file(name, path); if let Err(err) = &result { if let Some(retry::Error::Operation { error: e, .. }) = err.downcast_ref::>() { if e.kind() == io::ErrorKind::NotFound { return Ok(()); } } } result.with_context(|| RustupError::RemovingFile { name, path: PathBuf::from(path), }) } pub(crate) fn read_dir(name: &'static str, path: &Path) -> Result { fs::read_dir(path).with_context(|| RustupError::ReadingDirectory { name, path: PathBuf::from(path), }) } pub(crate) fn open_browser(path: &Path) -> Result<()> { opener::open_browser(path).context("couldn't open browser") } #[cfg(not(windows))] fn set_permissions(path: &Path, perms: fs::Permissions) -> Result<()> { fs::set_permissions(path, perms).map_err(|e| { RustupError::SettingPermissions { p: PathBuf::from(path), source: e, } .into() }) } pub fn file_size(path: &Path) -> Result { Ok(fs::metadata(path) .with_context(|| RustupError::ReadingFile { name: "metadata for", path: PathBuf::from(path), })? .len()) } pub(crate) fn make_executable(path: &Path) -> Result<()> { #[allow(clippy::unnecessary_wraps)] #[cfg(windows)] fn inner(_: &Path) -> Result<()> { Ok(()) } #[cfg(not(windows))] fn inner(path: &Path) -> Result<()> { use std::os::unix::fs::PermissionsExt; let metadata = fs::metadata(path).map_err(|e| RustupError::SettingPermissions { p: PathBuf::from(path), source: e, })?; let mut perms = metadata.permissions(); let mode = perms.mode(); let new_mode = (mode & !0o777) | 0o755; // Check if permissions are ok already - #1638 if mode == new_mode { return Ok(()); } perms.set_mode(new_mode); set_permissions(path, perms) } inner(path) } pub fn current_dir() -> Result { process() .current_dir() .context(RustupError::LocatingWorkingDir) } pub fn current_exe() -> Result { env::current_exe().context(RustupError::LocatingWorkingDir) } pub(crate) fn to_absolute>(path: P) -> Result { current_dir().map(|mut v| { v.push(path); v }) } pub(crate) fn home_dir() -> Option { home::home_dir_with_env(&home_process()) } pub(crate) fn cargo_home() -> Result { home::cargo_home_with_env(&home_process()).context("failed to determine cargo home") } // Creates a ~/.rustup folder pub(crate) fn create_rustup_home() -> Result<()> { // If RUSTUP_HOME is set then don't make any assumptions about where it's // ok to put ~/.rustup if process().var_os("RUSTUP_HOME").is_some() { return Ok(()); } let home = rustup_home_in_user_dir()?; fs::create_dir_all(home).context("unable to create ~/.rustup")?; Ok(()) } fn dot_dir(name: &str) -> Option { home_dir().map(|p| p.join(name)) } fn rustup_home_in_user_dir() -> Result { // XXX: This error message seems wrong/bogus. dot_dir(".rustup").ok_or_else(|| anyhow::anyhow!("couldn't find value of RUSTUP_HOME")) } pub(crate) fn rustup_home() -> Result { home::rustup_home_with_env(&home_process()).context("failed to determine rustup home dir") } pub(crate) fn format_path_for_display(path: &str) -> String { let unc_present = path.find(r"\\?\"); match unc_present { None => path.to_owned(), Some(_) => path[4..].to_owned(), } } pub(crate) fn toolchain_sort>(v: &mut [T]) { use semver::{BuildMetadata, Prerelease, Version}; fn special_version(ord: u64, s: &str) -> Version { Version { major: 0, minor: 0, patch: 0, pre: Prerelease::new(&format!("pre.{}.{}", ord, s.replace('_', "-"))).unwrap(), build: BuildMetadata::EMPTY, } } fn toolchain_sort_key(s: &str) -> Version { if s.starts_with("stable") { special_version(0, s) } else if s.starts_with("beta") { special_version(1, s) } else if s.starts_with("nightly") { special_version(2, s) } else { Version::parse(&s.replace('_', "-")).unwrap_or_else(|_| special_version(3, s)) } } v.sort_by(|a, b| { let a_str: &str = a.as_ref(); let b_str: &str = b.as_ref(); let a_key = toolchain_sort_key(a_str); let b_key = toolchain_sort_key(b_str); a_key.cmp(&b_key) }); } #[cfg(target_os = "linux")] fn copy_and_delete<'a, N>( name: &'static str, src: &'a Path, dest: &'a Path, notify_handler: &'a dyn Fn(N), ) -> Result<()> where N: From>, { // https://github.com/rust-lang/rustup/issues/1239 // This uses std::fs::copy() instead of the faster std::fs::rename() to // avoid cross-device link errors. if src.is_dir() { copy_dir(src, dest, notify_handler).and(remove_dir_all::remove_dir_all(src).with_context( || RustupError::RemovingDirectory { name, path: PathBuf::from(src), }, )) } else { copy_file(src, dest).and(remove_file(name, src)) } } fn rename<'a, N>( name: &'static str, src: &'a Path, dest: &'a Path, notify_handler: &'a dyn Fn(N), ) -> Result<()> where N: From>, { // https://github.com/rust-lang/rustup/issues/1870 // 21 fib steps from 1 sums to ~28 seconds, hopefully more than enough // for our previous poor performance that avoided the race condition with // McAfee and Norton. #[cfg(target_os = "linux")] use libc::EXDEV; retry( Fibonacci::from_millis(1).map(jitter).take(26), || match fs::rename(src, dest) { Ok(()) => OperationResult::Ok(()), Err(e) => match e.kind() { io::ErrorKind::PermissionDenied => { notify_handler(Notification::RenameInUse(src, dest).into()); OperationResult::Retry(e) } #[cfg(target_os = "linux")] _ if process().var_os("RUSTUP_PERMIT_COPY_RENAME").is_some() && Some(EXDEV) == e.raw_os_error() => { match copy_and_delete(name, src, dest, notify_handler) { Ok(()) => OperationResult::Ok(()), Err(_) => OperationResult::Err(e), } } _ => OperationResult::Err(e), }, }, ) .with_context(|| { format!( "could not rename {} file from '{}' to '{}'", name, src.display(), dest.display() ) }) } pub(crate) fn delete_dir_contents(dir_path: &Path) { match remove_dir_all::remove_dir_contents(dir_path) { Err(e) if e.kind() != io::ErrorKind::NotFound => { panic!("Unable to clean up {}: {:?}", dir_path.display(), e); } _ => {} } } pub(crate) struct FileReaderWithProgress<'a> { fh: io::BufReader, notify_handler: &'a dyn Fn(Notification<'_>), nbytes: u64, flen: u64, } impl<'a> FileReaderWithProgress<'a> { pub(crate) fn new_file( path: &Path, notify_handler: &'a dyn Fn(Notification<'_>), ) -> Result { let fh = match File::open(path) { Ok(fh) => fh, Err(_) => { bail!(RustupError::ReadingFile { name: "downloaded", path: path.to_path_buf(), }) } }; // Inform the tracker of the file size let flen = fh.metadata()?.len(); (notify_handler)(Notification::DownloadContentLengthReceived(flen)); let fh = BufReader::with_capacity(8 * 1024 * 1024, fh); Ok(FileReaderWithProgress { fh, notify_handler, nbytes: 0, flen, }) } } impl<'a> io::Read for FileReaderWithProgress<'a> { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self.fh.read(buf) { Ok(nbytes) => { self.nbytes += nbytes as u64; if nbytes != 0 { (self.notify_handler)(Notification::DownloadDataReceived(&buf[0..nbytes])); } if (nbytes == 0) || (self.flen == self.nbytes) { (self.notify_handler)(Notification::DownloadFinished); } Ok(nbytes) } Err(e) => Err(e), } } } // search user database to get home dir of euid user #[cfg(unix)] pub(crate) fn home_dir_from_passwd() -> Option { use std::ffi::{CStr, OsString}; use std::mem::MaybeUninit; use std::os::unix::ffi::OsStringExt; use std::ptr; unsafe { let init_size = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) { -1 => 1024, n => n as usize, }; let mut buf = Vec::with_capacity(init_size); let mut pwd: MaybeUninit = MaybeUninit::uninit(); let mut pwdp = ptr::null_mut(); match libc::getpwuid_r( libc::geteuid(), pwd.as_mut_ptr(), buf.as_mut_ptr(), buf.capacity(), &mut pwdp, ) { 0 if !pwdp.is_null() => { let pwd = pwd.assume_init(); let bytes = CStr::from_ptr(pwd.pw_dir).to_bytes().to_vec(); let pw_dir = OsString::from_vec(bytes); Some(PathBuf::from(pw_dir)) } _ => None, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_toolchain_sort() { let expected = vec![ "stable-x86_64-unknown-linux-gnu", "beta-x86_64-unknown-linux-gnu", "nightly-x86_64-unknown-linux-gnu", "1.0.0-x86_64-unknown-linux-gnu", "1.2.0-x86_64-unknown-linux-gnu", "1.8.0-x86_64-unknown-linux-gnu", "1.10.0-x86_64-unknown-linux-gnu", ]; let mut v = vec![ "1.8.0-x86_64-unknown-linux-gnu", "1.0.0-x86_64-unknown-linux-gnu", "nightly-x86_64-unknown-linux-gnu", "stable-x86_64-unknown-linux-gnu", "1.10.0-x86_64-unknown-linux-gnu", "beta-x86_64-unknown-linux-gnu", "1.2.0-x86_64-unknown-linux-gnu", ]; toolchain_sort(&mut v); assert_eq!(expected, v); } #[test] fn test_remove_file() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let f_path = tempdir.path().join("f"); File::create(&f_path).unwrap(); assert!(f_path.exists()); assert!(remove_file("f", &f_path).is_ok()); assert!(!f_path.exists()); let result = remove_file("f", &f_path); let err = result.unwrap_err(); match err.downcast_ref::() { Some(RustupError::RemovingFile { name, path }) => { assert_eq!(*name, "f"); assert_eq!(path.clone(), f_path); } _ => panic!("Expected an error removing file"), } } #[test] fn test_ensure_file_removed() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let f_path = tempdir.path().join("f"); File::create(&f_path).unwrap(); assert!(f_path.exists()); assert!(ensure_file_removed("f", &f_path).is_ok()); assert!(!f_path.exists()); assert!(ensure_file_removed("f", &f_path).is_ok()); } } rustup-1.26.0/tests/000077500000000000000000000000001441327105200143035ustar00rootroot00000000000000rustup-1.26.0/tests/mock/000077500000000000000000000000001441327105200152345ustar00rootroot00000000000000rustup-1.26.0/tests/mock/clitools.rs000066400000000000000000001516411441327105200174420ustar00rootroot00000000000000//! A mock distribution server used by tests/cli-v1.rs and //! tests/cli-v2.rs use std::cell::RefCell; use std::collections::HashMap; use std::env; use std::env::consts::EXE_SUFFIX; use std::ffi::OsStr; use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::{Arc, RwLock, RwLockWriteGuard}; use std::time::Instant; use enum_map::{enum_map, Enum, EnumMap}; use lazy_static::lazy_static; use once_cell::sync::Lazy; use rustup::test::const_dist_dir; use url::Url; use rustup::cli::rustup_mode; use rustup::currentprocess; use rustup::test as rustup_test; use rustup::test::this_host_triple; use rustup::utils::{raw, utils}; use crate::mock::dist::{ change_channel_date, ManifestVersion, MockChannel, MockComponent, MockDistServer, MockPackage, MockTargetedPackage, }; use crate::mock::topical_doc_data; use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder}; /// The configuration used by the tests in this module pub struct Config { /// Where we put the rustup / rustc / cargo bins pub exedir: PathBuf, /// The tempfile for the mutable distribution server pub test_dist_dir: tempfile::TempDir, /// The mutable distribution server; None if none is set. pub distdir: Option, /// The const distribution server; None if none is set const_dist_dir: Option, /// RUSTUP_HOME pub rustupdir: rustup_test::RustupHome, /// Custom toolchains pub customdir: PathBuf, /// CARGO_HOME pub cargodir: PathBuf, /// ~ pub homedir: PathBuf, /// Root for updates to rustup itself aka RUSTUP_UPDATE_ROOT pub rustup_update_root: Option, /// This is cwd for the test pub workdir: RefCell, /// This is the test root for keeping stuff together pub test_root_dir: PathBuf, } // Describes all the features of the mock dist server. // Building the mock server is slow, so use simple scenario when possible. #[derive(Copy, Clone, Debug, Eq, PartialEq, Enum)] pub enum Scenario { /// No mutable dist server at all None, /// No dist server content Empty, /// Two dates, two manifests Full, /// Two dates, v2 manifests /// See SimpleV2 for the 2015_01_02 date of ArchivesV2 ArchivesV2, /// The 2015-01-01 date of ArchivesV2 ArchivesV2_2015_01_01, /// Two versions of 'stable' ArchivesV2TwoVersions, /// Two dates, v1 manifests ArchivesV1, /// One date, v2 manifests SimpleV2, /// One date, v1 manifests SimpleV1, /// One date, v2 manifests, MULTI_ARCH1 host MultiHost, /// Two dates, v2 manifests, everything unavailable in second date. Unavailable, /// Two dates, v2 manifests, RLS unavailable in first date, restored on second. UnavailableRls, /// Three dates, v2 manifests, RLS available in first and last, not middle MissingComponent, /// Three dates, v2 manifests, RLS available in first, middle missing nightly MissingNightly, /// 1 date, v2 manifests, host and MULTI_ARCH1 in first HostGoesMissingBefore, /// 1 later date, v2 manifests, MULTI_ARCH1 only HostGoesMissingAfter, /// Three dates, v2 manifests, host and MULTI_ARCH1 in first, host only in second, /// host and MULTI_ARCH1 but no RLS in last MissingComponentMulti, } pub static CROSS_ARCH1: &str = "x86_64-unknown-linux-musl"; pub static CROSS_ARCH2: &str = "arm-linux-androideabi"; // Architecture for testing 'multi-host' installation. #[cfg(target_pointer_width = "64")] pub static MULTI_ARCH1: &str = "i686-unknown-linux-gnu"; #[cfg(not(target_pointer_width = "64"))] pub static MULTI_ARCH1: &str = "x86_64-unknown-linux-gnu"; static CONST_TEST_STATE: Lazy = Lazy::new(|| ConstState::new(const_dist_dir().unwrap())); /// Const test state - test dirs that can be reused across tests. struct ConstState { scenarios: EnumMap>>, const_dist_dir: tempfile::TempDir, } /// The lock to be used when creating test environments. /// /// Essentially we use this in `.read()` mode to gate access to `fork()` /// new subprocesses, and in `.write()` mode to gate creation of new test /// environments. In doing this we can ensure that new test environment creation /// does not result in ETXTBSY because the FDs in question happen to be in /// newly `fork()`d but not yet `exec()`d subprocesses of other tests. pub static CMD_LOCK: Lazy> = Lazy::new(|| RwLock::new(0)); impl ConstState { fn new(const_dist_dir: tempfile::TempDir) -> Self { Self { const_dist_dir, scenarios: enum_map! { Scenario::ArchivesV1 => RwLock::new(None), Scenario::ArchivesV2 => RwLock::new(None), Scenario::ArchivesV2_2015_01_01 => RwLock::new(None), Scenario::ArchivesV2TwoVersions => RwLock::new(None), Scenario::Empty => RwLock::new(None), Scenario::Full => RwLock::new(None), Scenario::HostGoesMissingBefore => RwLock::new(None), Scenario::HostGoesMissingAfter => RwLock::new(None), Scenario::MissingComponent => RwLock::new(None), Scenario::MissingComponentMulti => RwLock::new(None), Scenario::MissingNightly => RwLock::new(None), Scenario::MultiHost => RwLock::new(None), Scenario::None => RwLock::new(None), Scenario::SimpleV1 => RwLock::new(None), Scenario::SimpleV2 => RwLock::new(None), Scenario::Unavailable => RwLock::new(None), Scenario::UnavailableRls => RwLock::new(None), }, } } /// Get a dist server for a scenario fn dist_server_for(&self, s: Scenario) -> io::Result { { // fast path: the dist already exists let lock = self.scenarios[s].read().unwrap(); if let Some(ref path) = *lock { return Ok(path.clone()); } } { let mut lock = self.scenarios[s].write().unwrap(); // another writer may have initialized it match *lock { Some(ref path) => Ok(path.clone()), None => { let dist_path = self.const_dist_dir.path().join(format!("{:?}", s)); create_mock_dist_server(&dist_path, s); *lock = Some(dist_path.clone()); Ok(dist_path) } } } } } /// State a test can interact and mutate pub fn setup_test_state(test_dist_dir: tempfile::TempDir) -> (tempfile::TempDir, Config) { // Unset env variables that will break our testing env::remove_var("RUSTUP_UPDATE_ROOT"); env::remove_var("RUSTUP_TOOLCHAIN"); env::remove_var("SHELL"); env::remove_var("ZDOTDIR"); // clap does its own terminal colour probing, and that isn't // trait-controllable, but it does honour the terminal. To avoid testing // claps code, lie about whatever terminal this process was started under. env::set_var("TERM", "dumb"); match env::var("RUSTUP_BACKTRACE") { Ok(val) => env::set_var("RUST_BACKTRACE", val), _ => env::remove_var("RUST_BACKTRACE"), } let current_exe_path = env::current_exe().unwrap(); let mut built_exe_dir = current_exe_path.parent().unwrap(); if built_exe_dir.ends_with("deps") { built_exe_dir = built_exe_dir.parent().unwrap(); } let test_dir = rustup_test::test_dir().unwrap(); fn tempdir_in_with_prefix>(path: P, prefix: &str) -> PathBuf { tempfile::Builder::new() .prefix(prefix) .tempdir_in(path.as_ref()) .unwrap() .into_path() } let exedir = tempdir_in_with_prefix(&test_dir, "rustup-exe"); let customdir = tempdir_in_with_prefix(&test_dir, "rustup-custom"); let cargodir = tempdir_in_with_prefix(&test_dir, "rustup-cargo"); let homedir = tempdir_in_with_prefix(&test_dir, "rustup-home"); let workdir = tempdir_in_with_prefix(&test_dir, "rustup-workdir"); // The uninstall process on windows involves using the directory above // CARGO_HOME, so make sure it's a subdir of our tempdir let cargodir = cargodir.join("ch"); fs::create_dir(&cargodir).unwrap(); let config = Config { exedir, distdir: None, const_dist_dir: None, test_dist_dir, rustupdir: rustup_test::RustupHome::new_in(&test_dir).unwrap(), customdir, cargodir, homedir, rustup_update_root: None, workdir: RefCell::new(workdir), test_root_dir: test_dir.path().to_path_buf(), }; let build_path = built_exe_dir.join(format!("rustup-init{EXE_SUFFIX}")); let rustup_path = config.exedir.join(format!("rustup{EXE_SUFFIX}")); // Used to create dist servers. Perhaps should only link when needed? let init_path = config.exedir.join(format!("rustup-init{EXE_SUFFIX}")); let rustc_path = config.exedir.join(format!("rustc{EXE_SUFFIX}")); let cargo_path = config.exedir.join(format!("cargo{EXE_SUFFIX}")); let rls_path = config.exedir.join(format!("rls{EXE_SUFFIX}")); let rust_lldb_path = config.exedir.join(format!("rust-lldb{EXE_SUFFIX}")); const ESTIMATED_LINKS_PER_TEST: usize = 6 * 2; // NTFS has a limit of 1023 links per file; test setup creates 6 links, and // then some tests will link the cached installer to rustup/cargo etc, // adding more links const MAX_TESTS_PER_RUSTUP_EXE: usize = 1023 / ESTIMATED_LINKS_PER_TEST; // This returning-result inner structure allows failures without poisoning // cmd_lock. { fn link_or_copy( original: &Path, link: &Path, lock: &mut RwLockWriteGuard, ) -> io::Result<()> { **lock += 1; if **lock < MAX_TESTS_PER_RUSTUP_EXE { hard_link(original, link) } else { // break the *original* so new tests form a new distinct set of // links. Do this by copying to link, breaking the source, // linking back. **lock = 0; fs::copy(original, link)?; fs::remove_file(original)?; hard_link(link, original) } } let mut lock = CMD_LOCK.write().unwrap(); link_or_copy(&build_path, &rustup_path, &mut lock) } .unwrap(); hard_link(&rustup_path, init_path).unwrap(); hard_link(&rustup_path, rustc_path).unwrap(); hard_link(&rustup_path, cargo_path).unwrap(); hard_link(&rustup_path, rls_path).unwrap(); hard_link(&rustup_path, rust_lldb_path).unwrap(); // Make sure the host triple matches the build triple. Otherwise testing a 32-bit build of // rustup on a 64-bit machine will fail, because the tests do not have the host detection // functionality built in. config.run("rustup", &["set", "default-host", &this_host_triple()], &[]); // Set the auto update mode to disable, as most tests do not want to update rustup itself during the test. config.run("rustup", &["set", "auto-self-update", "disable"], &[]); // Create some custom toolchains create_custom_toolchains(&config.customdir); (test_dir, config) } /// Run this to create the test environment containing rustup, and /// a mock dist server. pub fn test(s: Scenario, f: &dyn Fn(&mut Config)) { // Things we might cache or what not // Mutable dist server - working toward elimination let test_dist_dir = rustup::test::test_dist_dir().unwrap(); create_mock_dist_server(test_dist_dir.path(), s); // Things that are just about the test itself let (_test_dir, mut config) = setup_test_state(test_dist_dir); // Pulled out of setup_test_state for clarity: the long term intent is to // not have this at all. if s != Scenario::None { config.distdir = Some(config.test_dist_dir.path().to_path_buf()); } // Run the test f(&mut config); } fn create_local_update_server(self_dist: &Path, exedir: &Path, version: &str) -> String { let trip = this_host_triple(); let dist_dir = self_dist.join(format!("archive/{version}/{trip}")); let dist_exe = dist_dir.join(format!("rustup-init{EXE_SUFFIX}")); let rustup_bin = exedir.join(format!("rustup-init{EXE_SUFFIX}")); fs::create_dir_all(dist_dir).unwrap(); output_release_file(self_dist, "1", version); // TODO: should this hardlink since the modify-codepath presumes it has to // link break? fs::copy(rustup_bin, dist_exe).unwrap(); let root_url = format!("file://{}", self_dist.display()); root_url } pub fn self_update_setup(f: &dyn Fn(&mut Config, &Path), version: &str) { test(Scenario::SimpleV2, &|config| { // Create a mock self-update server let self_dist_tmp = tempfile::Builder::new() .prefix("self_dist") .tempdir_in(&config.test_root_dir) .unwrap(); let self_dist = self_dist_tmp.path(); let root_url = create_local_update_server(self_dist, &config.exedir, version); config.rustup_update_root = Some(root_url); let trip = this_host_triple(); let dist_dir = self_dist.join(format!("archive/{version}/{trip}")); let dist_exe = dist_dir.join(format!("rustup-init{EXE_SUFFIX}")); let dist_tmp = dist_dir.join("rustup-init-tmp"); // Modify the exe so it hashes different // 1) move out of the way the file fs::rename(&dist_exe, &dist_tmp).unwrap(); // 2) copy it fs::copy(dist_tmp, &dist_exe).unwrap(); // modify it let mut dest_file = fs::OpenOptions::new() .write(true) .append(true) .create(true) .open(dist_exe) .unwrap(); writeln!(dest_file).unwrap(); f(config, self_dist); }); } pub fn with_update_server(config: &mut Config, version: &str, f: &dyn Fn(&mut Config)) { let self_dist_tmp = tempfile::Builder::new() .prefix("self_dist") .tempdir() .unwrap(); let self_dist = self_dist_tmp.path(); let root_url = create_local_update_server(self_dist, &config.exedir, version); let trip = this_host_triple(); let dist_dir = self_dist.join(format!("archive/{version}/{trip}")); let dist_exe = dist_dir.join(format!("rustup-init{EXE_SUFFIX}")); let dist_tmp = dist_dir.join("rustup-init-tmp"); // Modify the exe so it hashes different // 1) move out of the way the file fs::rename(&dist_exe, &dist_tmp).unwrap(); // 2) copy it fs::copy(dist_tmp, &dist_exe).unwrap(); // modify it let mut dest_file = fs::OpenOptions::new() .write(true) .append(true) .create(true) .open(dist_exe) .unwrap(); writeln!(dest_file).unwrap(); config.rustup_update_root = Some(root_url); f(config); config.rustup_update_root = None; } pub fn output_release_file(dist_dir: &Path, schema: &str, version: &str) { let contents = format!( r#" schema-version = "{schema}" version = "{version}" "# ); let file = dist_dir.join("release-stable.toml"); utils::write_file("release", &file, &contents).unwrap(); } impl Config { pub fn current_dir(&self) -> PathBuf { self.workdir.borrow().clone() } pub fn change_dir(&mut self, path: &Path, f: &dyn Fn(&mut Config)) { let prev = self.workdir.replace(path.to_owned()); f(self); *self.workdir.borrow_mut() = prev; } pub fn create_rustup_sh_metadata(&self) { let rustup_dir = self.homedir.join(".rustup"); fs::create_dir_all(&rustup_dir).unwrap(); let version_file = rustup_dir.join("rustup-version"); raw::write_file(&version_file, "").unwrap(); } /// Move the dist server to the specified scenario and restore it /// afterwards. pub fn with_scenario(&mut self, s: Scenario, f: &dyn Fn(&mut Config)) { let dist_path = CONST_TEST_STATE.dist_server_for(s).unwrap(); self.const_dist_dir = Some(dist_path); f(self); self.const_dist_dir = None; } pub fn cmd(&self, name: &str, args: I) -> Command where I: IntoIterator, A: AsRef, { let exe_path = self.exedir.join(format!("{name}{EXE_SUFFIX}")); let mut cmd = Command::new(exe_path); cmd.args(args); cmd.current_dir(&*self.workdir.borrow()); self.env(&mut cmd); cmd } pub fn env(&self, cmd: &mut E) { // Ensure PATH is prefixed with the rustup-exe directory let prev_path = env::var_os("PATH"); let mut new_path = self.exedir.clone().into_os_string(); if let Some(ref p) = prev_path { new_path.push(if cfg!(windows) { ";" } else { ":" }); new_path.push(p); } cmd.env("PATH", new_path); self.rustupdir.apply(cmd); let distdir = match (&self.distdir, &self.const_dist_dir) { (None, None) => Path::new("no-such-distdir"), // mutable takes precedence (Some(distdir), _) => distdir, (_, Some(distdir)) => distdir, }; cmd.env( "RUSTUP_DIST_SERVER", format!("file://{}", distdir.to_string_lossy()), ); cmd.env("CARGO_HOME", self.cargodir.to_string_lossy().to_string()); cmd.env("RUSTUP_OVERRIDE_HOST_TRIPLE", this_host_triple()); // These are used in some installation tests that unset RUSTUP_HOME/CARGO_HOME cmd.env("HOME", self.homedir.to_string_lossy().to_string()); cmd.env("USERPROFILE", self.homedir.to_string_lossy().to_string()); // Setting HOME will confuse the sudo check for rustup-init. Override it cmd.env("RUSTUP_INIT_SKIP_SUDO_CHECK", "yes"); // Skip the MSVC warning check since it's environment dependent cmd.env("RUSTUP_INIT_SKIP_MSVC_CHECK", "yes"); // The test environment may interfere with checking the PATH for the existence of rustc or // cargo, so we disable that check globally cmd.env("RUSTUP_INIT_SKIP_PATH_CHECK", "yes"); // Setup pgp test key cmd.env( "RUSTUP_PGP_KEY", std::env::current_dir() .unwrap() .join("tests/mock/signing-key.pub.asc"), ); // The unix fallback settings file may be present in the test environment, so override // the path to the settings file with a non-existing path to avoid interference cmd.env( "RUSTUP_OVERRIDE_UNIX_FALLBACK_SETTINGS", "/bogus-config-file.toml", ); if let Some(root) = self.rustup_update_root.as_ref() { cmd.env("RUSTUP_UPDATE_ROOT", root); } } pub fn expect_ok(&mut self, args: &[&str]) { let out = self.run(args[0], &args[1..], &[]); if !out.ok { print_command(args, &out); println!("expected.ok: true"); panic!(); } } pub fn expect_err(&self, args: &[&str], expected: &str) { let out = self.run(args[0], &args[1..], &[]); if out.ok || !out.stderr.contains(expected) { print_command(args, &out); println!("expected.ok: false"); print_indented("expected.stderr.contains", expected); panic!(); } } pub fn expect_stdout_ok(&self, args: &[&str], expected: &str) { let out = self.run(args[0], &args[1..], &[]); if !out.ok || !out.stdout.contains(expected) { print_command(args, &out); println!("expected.ok: true"); print_indented("expected.stdout.contains", expected); panic!(); } } pub fn expect_not_stdout_ok(&self, args: &[&str], expected: &str) { let out = self.run(args[0], &args[1..], &[]); if !out.ok || out.stdout.contains(expected) { print_command(args, &out); println!("expected.ok: true"); print_indented("expected.stdout.does_not_contain", expected); panic!(); } } pub fn expect_not_stderr_ok(&self, args: &[&str], expected: &str) { let out = self.run(args[0], &args[1..], &[]); if !out.ok || out.stderr.contains(expected) { print_command(args, &out); println!("expected.ok: false"); print_indented("expected.stderr.does_not_contain", expected); panic!(); } } pub fn expect_not_stderr_err(&self, args: &[&str], expected: &str) { let out = self.run(args[0], &args[1..], &[]); if out.ok || out.stderr.contains(expected) { print_command(args, &out); println!("expected.ok: false"); print_indented("expected.stderr.does_not_contain", expected); panic!(); } } pub fn expect_stderr_ok(&self, args: &[&str], expected: &str) { let out = self.run(args[0], &args[1..], &[]); if !out.ok || !out.stderr.contains(expected) { print_command(args, &out); println!("expected.ok: true"); print_indented("expected.stderr.contains", expected); panic!(); } } pub fn expect_ok_ex(&mut self, args: &[&str], stdout: &str, stderr: &str) { let out = self.run(args[0], &args[1..], &[]); if !out.ok || out.stdout != stdout || out.stderr != stderr { print_command(args, &out); println!("expected.ok: true"); print_indented("expected.stdout", stdout); print_indented("expected.stderr", stderr); dbg!(out.stdout == stdout); dbg!(out.stderr == stderr); panic!(); } } pub fn expect_err_ex(&self, args: &[&str], stdout: &str, stderr: &str) { let out = self.run(args[0], &args[1..], &[]); if out.ok || out.stdout != stdout || out.stderr != stderr { print_command(args, &out); println!("expected.ok: false"); print_indented("expected.stdout", stdout); print_indented("expected.stderr", stderr); if out.ok { panic!("expected command to fail"); } else if out.stdout != stdout { panic!("expected stdout to match"); } else if out.stderr != stderr { panic!("expected stderr to match"); } else { unreachable!() } } } pub fn expect_ok_contains(&self, args: &[&str], stdout: &str, stderr: &str) { let out = self.run(args[0], &args[1..], &[]); if !out.ok || !out.stdout.contains(stdout) || !out.stderr.contains(stderr) { print_command(args, &out); println!("expected.ok: true"); print_indented("expected.stdout.contains", stdout); print_indented("expected.stderr.contains", stderr); panic!(); } } pub fn expect_ok_eq(&self, args1: &[&str], args2: &[&str]) { let out1 = self.run(args1[0], &args1[1..], &[]); let out2 = self.run(args2[0], &args2[1..], &[]); if !out1.ok || !out2.ok || out1.stdout != out2.stdout || out1.stderr != out2.stderr { print_command(args1, &out1); println!("expected.ok: true"); print_command(args2, &out2); println!("expected.ok: true"); panic!(); } } pub fn expect_component_executable(&self, cmd: &str) { let out1 = self.run(cmd, &["--version"], &[]); if !out1.ok { print_command(&[cmd, "--version"], &out1); println!("expected.ok: true"); panic!() } } pub fn expect_component_not_executable(&self, cmd: &str) { let out1 = self.run(cmd, &["--version"], &[]); if out1.ok { print_command(&[cmd, "--version"], &out1); println!("expected.ok: false"); panic!() } } pub fn run(&self, name: &str, args: I, env: &[(&str, &str)]) -> SanitizedOutput where I: IntoIterator + Clone, A: AsRef, { let inprocess = allow_inprocess(name, args.clone()); let start = Instant::now(); let out = if inprocess { self.run_inprocess(name, args, env) } else { self.run_subprocess(name, args, env) }; let duration = Instant::now() - start; let output = SanitizedOutput { ok: matches!(out.status, Some(0)), stdout: String::from_utf8(out.stdout).unwrap(), stderr: String::from_utf8(out.stderr).unwrap(), }; println!("inprocess: {inprocess}"); println!("status: {:?}", out.status); println!("duration: {:.3}s", duration.as_secs_f32()); println!("stdout:\n====\n{}\n====\n", output.stdout); println!("stderr:\n====\n{}\n====\n", output.stderr); output } pub(crate) fn run_inprocess(&self, name: &str, args: I, env: &[(&str, &str)]) -> Output where I: IntoIterator, A: AsRef, { // should we use vars_os, or skip over non-stringable vars? This is test // code after all... let mut vars: HashMap = HashMap::default(); self::env(self, &mut vars); vars.extend(env.iter().map(|(k, v)| (k.to_string(), v.to_string()))); let mut arg_strings: Vec> = Vec::new(); arg_strings.push(name.to_owned().into_boxed_str()); for arg in args { arg_strings.push( arg.as_ref() .to_os_string() .into_string() .unwrap() .into_boxed_str(), ); } let tp = Box::new(currentprocess::TestProcess::new( &*self.workdir.borrow(), &arg_strings, vars, "", )); let process_res = currentprocess::with(tp.clone(), rustup_mode::main); // convert Err's into an ec let ec = match process_res { Ok(process_res) => process_res, Err(e) => { currentprocess::with(tp.clone(), || rustup::cli::common::report_error(&e)); utils::ExitCode(1) } }; Output { status: Some(ec.0), stderr: (*tp).get_stderr(), stdout: (*tp).get_stdout(), } } pub fn run_subprocess(&self, name: &str, args: I, env: &[(&str, &str)]) -> Output where I: IntoIterator, A: AsRef, { let mut cmd = self.cmd(name, args); for env in env { cmd.env(env.0, env.1); } let mut retries = 8; let out = loop { let lock = CMD_LOCK.read().unwrap(); let out = cmd.output(); drop(lock); match out { Ok(out) => break out, Err(e) => { retries -= 1; if retries > 0 && e.kind() == std::io::ErrorKind::Other && e.raw_os_error() == Some(26) { // This is an ETXTBSY situation std::thread::sleep(std::time::Duration::from_millis(250)); } else { panic!("Unable to run test command: {e:?}"); } } } }; Output { status: out.status.code(), stdout: out.stdout, stderr: out.stderr, } } } /// Change the current distribution manifest to a particular date pub fn set_current_dist_date(config: &Config, date: &str) { let url = Url::from_file_path(config.distdir.as_ref().unwrap()).unwrap(); for channel in &["nightly", "beta", "stable"] { change_channel_date(&url, channel, date); } } pub(crate) fn print_command(args: &[&str], out: &SanitizedOutput) { print!("\n>"); for arg in args { if arg.contains(' ') { print!(" {arg:?}"); } else { print!(" {arg}"); } } println!(); println!("out.ok: {}", out.ok); print_indented("out.stdout", &out.stdout); print_indented("out.stderr", &out.stderr); } pub(crate) fn print_indented(heading: &str, text: &str) { let mut lines = text.lines().count(); // The standard library treats `a\n` and `a` as both being one line. // This is confusing when the test fails because of a missing newline. if !text.is_empty() && !text.ends_with('\n') { lines -= 1; } println!( "{} ({} lines):\n {}", heading, lines, text.replace('\n', "\n ") ); } pub struct Output { pub status: Option, pub stdout: Vec, pub stderr: Vec, } #[derive(Debug)] pub struct SanitizedOutput { pub ok: bool, pub stdout: String, pub stderr: String, } pub fn cmd(config: &Config, name: &str, args: I) -> Command where I: IntoIterator, A: AsRef, { config.cmd(name, args) } pub fn env(config: &Config, cmd: &mut E) { config.env(cmd) } fn allow_inprocess(name: &str, args: I) -> bool where I: IntoIterator, A: AsRef, { // Only the rustup alias is currently ready for in-process testing: // - -init performs self-update which monkey with global external state. // - proxies themselves behave appropriately the proxied output needs to be // collected for assertions to be made on it as our tests traverse layers. // - self update executions cannot run in-process because on windows the // process replacement dance would replace the test process. // - any command with --version in it is testing to see something was // installed properly, so we have to shell out to it to be sure if name != "rustup" { return false; } let mut is_update = false; let mut no_self_update = false; let mut self_cmd = false; let mut run = false; let mut version = false; for arg in args { if arg.as_ref() == "update" { is_update = true; } else if arg.as_ref() == "--no-self-update" { no_self_update = true; } else if arg.as_ref() == "self" { self_cmd = true; } else if arg.as_ref() == "run" { run = true; } else if arg.as_ref() == "--version" { version = true; } } !(run || self_cmd || version || (is_update && !no_self_update)) } #[derive(Copy, Clone, Eq, PartialEq)] enum RlsStatus { Available, Renamed, Unavailable, } impl RlsStatus { fn pkg_name(self) -> &'static str { match self { Self::Renamed => "rls-preview", _ => "rls", } } } struct Release { // Either "nightly", "stable", "beta", or an explicit version number channel: String, date: String, version: String, hash: String, rls: RlsStatus, available: bool, multi_arch: bool, } impl Release { fn stable(version: &str, date: &str) -> Self { Release::new("stable", version, date, version) } fn beta(version: &str, date: &str) -> Self { Release::new("beta", version, date, version) } fn with_rls(mut self, status: RlsStatus) -> Self { self.rls = status; self } fn unavailable(mut self) -> Self { self.available = false; self } fn multi_arch(mut self) -> Self { self.multi_arch = true; self } fn only_multi_arch(mut self) -> Self { self.multi_arch = true; self.available = false; self } fn new(channel: &str, version: &str, date: &str, suffix: &str) -> Self { Release { channel: channel.to_string(), date: date.to_string(), version: version.to_string(), hash: format!("hash-{channel}-{suffix}"), available: true, multi_arch: false, rls: RlsStatus::Available, } } fn mock(&self) -> MockChannel { if self.available { build_mock_channel( &self.channel, &self.date, &self.version, &self.hash, self.rls, self.multi_arch, false, ) } else if self.multi_arch { // unavailable but multiarch means to build only with host==MULTI_ARCH1 // instead of true multiarch build_mock_channel( &self.channel, &self.date, &self.version, &self.hash, self.rls, false, true, ) } else { build_mock_unavailable_channel(&self.channel, &self.date, &self.version, &self.hash) } } fn link(&self, path: &Path) { // Also create the manifests for releases by version let _ = hard_link( path.join(format!( "dist/{}/channel-rust-{}.toml", self.date, self.channel )), path.join(format!("dist/channel-rust-{}.toml", self.version)), ); let _ = hard_link( path.join(format!( "dist/{}/channel-rust-{}.toml.asc", self.date, self.channel )), path.join(format!("dist/channel-rust-{}.toml.asc", self.version)), ); let _ = hard_link( path.join(format!( "dist/{}/channel-rust-{}.toml.sha256", self.date, self.channel )), path.join(format!("dist/channel-rust-{}.toml.sha256", self.version)), ); if self.channel == "stable" { // Same for v1 manifests. These are just the installers. let host_triple = this_host_triple(); hard_link( path.join(format!( "dist/{}/rust-stable-{}.tar.gz", self.date, host_triple )), path.join(format!("dist/rust-{}-{}.tar.gz", self.version, host_triple)), ) .unwrap(); hard_link( path.join(format!( "dist/{}/rust-stable-{}.tar.gz.sha256", self.date, host_triple )), path.join(format!( "dist/rust-{}-{}.tar.gz.sha256", self.version, host_triple )), ) .unwrap(); } } } // Creates a mock dist server populated with some test data fn create_mock_dist_server(path: &Path, s: Scenario) { let chans = match s { Scenario::None => return, Scenario::Empty => vec![], Scenario::MissingComponent => vec![ Release::new("nightly", "1.37.0", "2019-09-12", "1"), Release::new("nightly", "1.37.0", "2019-09-13", "2"), Release::new("nightly", "1.37.0", "2019-09-14", "3").with_rls(RlsStatus::Unavailable), ], Scenario::MissingNightly => vec![ Release::new("nightly", "1.37.0", "2019-09-16", "1"), Release::stable("1.37.0", "2019-09-17"), Release::new("nightly", "1.37.0", "2019-09-18", "2").with_rls(RlsStatus::Unavailable), ], Scenario::Unavailable => vec![ Release::new("nightly", "1.2.0", "2015-01-01", "1"), Release::beta("1.1.0", "2015-01-01"), Release::stable("1.0.0", "2015-01-01"), Release::new("nightly", "1.3.0", "2015-01-02", "2").unavailable(), ], Scenario::ArchivesV2_2015_01_01 => vec![ Release::new("nightly", "1.2.0", "2015-01-01", "1").with_rls(RlsStatus::Available), Release::beta("1.1.0", "2015-01-01"), Release::stable("1.0.0", "2015-01-01"), ], Scenario::ArchivesV2TwoVersions => vec![ Release::stable("0.100.99", "2014-12-31"), Release::stable("1.0.0", "2015-01-01"), ], Scenario::Full | Scenario::ArchivesV1 | Scenario::ArchivesV2 | Scenario::UnavailableRls => { vec![ Release::new("nightly", "1.2.0", "2015-01-01", "1").with_rls( if s == Scenario::UnavailableRls { RlsStatus::Unavailable } else { RlsStatus::Available }, ), Release::beta("1.1.0", "2015-01-01"), // Pre-release "stable" ? Release::stable("0.100.99", "2014-12-31"), Release::stable("1.0.0", "2015-01-01"), Release::new("nightly", "1.3.0", "2015-01-02", "2").with_rls(RlsStatus::Renamed), Release::beta("1.2.0", "2015-01-02"), Release::stable("1.1.0", "2015-01-02"), ] } Scenario::SimpleV1 | Scenario::SimpleV2 => vec![ Release::new("nightly", "1.3.0", "2015-01-02", "2").with_rls(RlsStatus::Renamed), Release::beta("1.2.0", "2015-01-02"), Release::stable("1.1.0", "2015-01-02"), ], Scenario::MultiHost => vec![ Release::new("nightly", "1.3.0", "2015-01-02", "2").multi_arch(), Release::beta("1.2.0", "2015-01-02").multi_arch(), Release::stable("1.1.0", "2015-01-02").multi_arch(), ], Scenario::HostGoesMissingBefore => { vec![Release::new("nightly", "1.3.0", "2019-12-09", "1")] } Scenario::HostGoesMissingAfter => { vec![Release::new("nightly", "1.3.0", "2019-12-10", "2").only_multi_arch()] } Scenario::MissingComponentMulti => vec![ Release::new("nightly", "1.37.0", "2019-09-12", "1") .multi_arch() .with_rls(RlsStatus::Renamed), Release::new("nightly", "1.37.0", "2019-09-13", "2").with_rls(RlsStatus::Renamed), Release::new("nightly", "1.37.0", "2019-09-14", "3") .multi_arch() .with_rls(RlsStatus::Unavailable), ], }; let vs = match s { Scenario::None => unreachable!("None exits above"), Scenario::Empty => vec![], Scenario::Full => vec![ManifestVersion::V1, ManifestVersion::V2], Scenario::SimpleV1 | Scenario::ArchivesV1 => vec![ManifestVersion::V1], Scenario::SimpleV2 | Scenario::ArchivesV2 | Scenario::ArchivesV2_2015_01_01 | Scenario::ArchivesV2TwoVersions | Scenario::MultiHost | Scenario::Unavailable | Scenario::UnavailableRls | Scenario::MissingNightly | Scenario::HostGoesMissingBefore | Scenario::HostGoesMissingAfter | Scenario::MissingComponent | Scenario::MissingComponentMulti => vec![ManifestVersion::V2], }; MockDistServer { path: path.to_owned(), channels: chans.iter().map(|c| c.mock()).collect(), } .write(&vs, true, true); for chan in &chans { chan.link(path) } } #[derive(Default)] struct MockChannelContent { std: Vec<(MockInstallerBuilder, String)>, rustc: Vec<(MockInstallerBuilder, String)>, cargo: Vec<(MockInstallerBuilder, String)>, rls: Vec<(MockInstallerBuilder, String)>, docs: Vec<(MockInstallerBuilder, String)>, src: Vec<(MockInstallerBuilder, String)>, analysis: Vec<(MockInstallerBuilder, String)>, combined: Vec<(MockInstallerBuilder, String)>, } impl MockChannelContent { fn into_packages( self, rls_name: &'static str, ) -> Vec<(&'static str, Vec<(MockInstallerBuilder, String)>)> { vec![ ("rust-std", self.std), ("rustc", self.rustc), ("cargo", self.cargo), (rls_name, self.rls), ("rust-docs", self.docs), ("rust-src", self.src), ("rust-analysis", self.analysis), ("rust", self.combined), ] } } fn build_mock_channel( channel: &str, date: &str, version: &str, version_hash: &str, rls: RlsStatus, multi_arch: bool, swap_triples: bool, ) -> MockChannel { // Build the mock installers let host_triple = if swap_triples { MULTI_ARCH1.to_owned() } else { this_host_triple() }; let std = build_mock_std_installer(&host_triple); let rustc = build_mock_rustc_installer(&host_triple, version, version_hash); let cargo = build_mock_cargo_installer(version, version_hash); let rust_docs = build_mock_rust_doc_installer(); let rust = build_combined_installer(&[&std, &rustc, &cargo, &rust_docs]); let cross_std1 = build_mock_cross_std_installer(CROSS_ARCH1, date); let cross_std2 = build_mock_cross_std_installer(CROSS_ARCH2, date); let rust_src = build_mock_rust_src_installer(); let rust_analysis = build_mock_rust_analysis_installer(&host_triple); // Convert the mock installers to mock package definitions for the // mock dist server let mut all = MockChannelContent::default(); all.std.extend( vec![ (std, host_triple.clone()), (cross_std1, CROSS_ARCH1.to_string()), (cross_std2, CROSS_ARCH2.to_string()), ] .into_iter(), ); all.rustc.push((rustc, host_triple.clone())); all.cargo.push((cargo, host_triple.clone())); if rls != RlsStatus::Unavailable { let rls = build_mock_rls_installer(version, version_hash, rls.pkg_name()); all.rls.push((rls, host_triple.clone())); } else { all.rls.push(( MockInstallerBuilder { components: vec![] }, host_triple.clone(), )); } all.docs.push((rust_docs, host_triple.clone())); all.src.push((rust_src, "*".to_string())); all.analysis.push((rust_analysis, "*".to_string())); all.combined.push((rust, host_triple)); if multi_arch { let std = build_mock_std_installer(MULTI_ARCH1); let rustc = build_mock_rustc_installer(MULTI_ARCH1, version, version_hash); let cargo = build_mock_cargo_installer(version, version_hash); let rust_docs = build_mock_rust_doc_installer(); let rust = build_combined_installer(&[&std, &rustc, &cargo, &rust_docs]); let triple = MULTI_ARCH1.to_string(); all.std.push((std, triple.clone())); all.rustc.push((rustc, triple.clone())); all.cargo.push((cargo, triple.clone())); if rls != RlsStatus::Unavailable { let rls = build_mock_rls_installer(version, version_hash, rls.pkg_name()); all.rls.push((rls, triple.clone())); } else { all.rls .push((MockInstallerBuilder { components: vec![] }, triple.clone())); } all.docs.push((rust_docs, triple.to_string())); all.combined.push((rust, triple)); } let all_std_archs: Vec = all.std.iter().map(|(_, arch)| arch).cloned().collect(); let all = all.into_packages(rls.pkg_name()); let packages = all.into_iter().map(|(name, target_pkgs)| { let target_pkgs = target_pkgs .into_iter() .map(|(installer, triple)| MockTargetedPackage { target: triple, available: !installer.components.is_empty(), components: vec![], installer, }); MockPackage { name, version: format!("{version} ({version_hash})"), targets: target_pkgs.collect(), } }); let mut packages: Vec<_> = packages.collect(); // Add subcomponents of the rust package { let rust_pkg = packages.last_mut().unwrap(); for target_pkg in rust_pkg.targets.iter_mut() { let target = &target_pkg.target; target_pkg.components.push(MockComponent { name: "rust-std".to_string(), target: target.to_string(), is_extension: false, }); target_pkg.components.push(MockComponent { name: "rustc".to_string(), target: target.to_string(), is_extension: false, }); target_pkg.components.push(MockComponent { name: "cargo".to_string(), target: target.to_string(), is_extension: false, }); target_pkg.components.push(MockComponent { name: "rust-docs".to_string(), target: target.to_string(), is_extension: false, }); if rls == RlsStatus::Renamed { target_pkg.components.push(MockComponent { name: "rls-preview".to_string(), target: target.to_string(), is_extension: true, }); } else if rls == RlsStatus::Available { target_pkg.components.push(MockComponent { name: "rls".to_string(), target: target.to_string(), is_extension: true, }); } else { target_pkg.components.push(MockComponent { name: "rls".to_string(), target: target.to_string(), is_extension: true, }) } for other_target in &all_std_archs { if other_target != target { target_pkg.components.push(MockComponent { name: "rust-std".to_string(), target: other_target.to_string(), is_extension: false, }); } } target_pkg.components.push(MockComponent { name: "rust-src".to_string(), target: "*".to_string(), is_extension: true, }); target_pkg.components.push(MockComponent { name: "rust-analysis".to_string(), target: target.to_string(), is_extension: true, }); } } let mut renames = HashMap::new(); if rls == RlsStatus::Renamed { renames.insert("rls".to_owned(), "rls-preview".to_owned()); } MockChannel { name: channel.to_string(), date: date.to_string(), packages, renames, } } fn build_mock_unavailable_channel( channel: &str, date: &str, version: &str, version_hash: &str, ) -> MockChannel { let host_triple = this_host_triple(); let packages = [ "cargo", "rust", "rust-docs", "rust-std", "rustc", "rls", "rust-analysis", ]; let packages = packages .iter() .map(|name| MockPackage { name, version: format!("{version} ({version_hash})"), targets: vec![MockTargetedPackage { target: host_triple.clone(), available: false, components: vec![], installer: MockInstallerBuilder { components: vec![] }, }], }) .collect(); MockChannel { name: channel.to_string(), date: date.to_string(), packages, renames: HashMap::new(), } } fn build_mock_std_installer(trip: &str) -> MockInstallerBuilder { MockInstallerBuilder { components: vec![MockComponentBuilder { name: format!("rust-std-{trip}"), files: vec![MockFile::new( format!("lib/rustlib/{trip}/libstd.rlib"), b"", )], }], } } fn build_mock_cross_std_installer(target: &str, date: &str) -> MockInstallerBuilder { MockInstallerBuilder { components: vec![MockComponentBuilder { name: format!("rust-std-{target}"), files: vec![ MockFile::new(format!("lib/rustlib/{target}/lib/libstd.rlib"), b""), MockFile::new(format!("lib/rustlib/{target}/lib/{date}"), b""), ], }], } } fn build_mock_rustc_installer( target: &str, version: &str, version_hash_: &str, ) -> MockInstallerBuilder { // For cross-host rustc's modify the version_hash so they can be identified from // test cases. let this_host = this_host_triple(); let version_hash = if this_host != target { format!("xxxx-{}", &version_hash_[5..]) } else { version_hash_.to_string() }; MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rustc".to_string(), files: mock_bin("rustc", version, &version_hash), }], } } fn build_mock_cargo_installer(version: &str, version_hash: &str) -> MockInstallerBuilder { MockInstallerBuilder { components: vec![MockComponentBuilder { name: "cargo".to_string(), files: mock_bin("cargo", version, version_hash), }], } } fn build_mock_rls_installer( version: &str, version_hash: &str, pkg_name: &str, ) -> MockInstallerBuilder { MockInstallerBuilder { components: vec![MockComponentBuilder { name: pkg_name.to_string(), files: mock_bin("rls", version, version_hash), }], } } fn build_mock_rust_doc_installer() -> MockInstallerBuilder { let mut files: Vec = topical_doc_data::unique_paths() .map(|x| MockFile::new(x, b"")) .collect(); files.insert(0, MockFile::new("share/doc/rust/html/index.html", b"")); MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-docs".to_string(), files, }], } } fn build_mock_rust_analysis_installer(trip: &str) -> MockInstallerBuilder { MockInstallerBuilder { components: vec![MockComponentBuilder { name: format!("rust-analysis-{trip}"), files: vec![MockFile::new( format!("lib/rustlib/{trip}/analysis/libfoo.json"), b"", )], }], } } fn build_mock_rust_src_installer() -> MockInstallerBuilder { MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-src".to_string(), files: vec![MockFile::new("lib/rustlib/src/rust-src/foo.rs", b"")], }], } } fn build_combined_installer(components: &[&MockInstallerBuilder]) -> MockInstallerBuilder { MockInstallerBuilder { components: components .iter() .flat_map(|m| m.components.clone()) .collect(), } } /// This is going to run the compiler to create an executable that /// prints some version information. These binaries are stuffed into /// the mock installers so we have executables for rustup to run. /// /// To avoid compiling tons of files we globally cache one compiled executable /// and then we store some associated files next to it which indicate /// the version/version hash information. fn mock_bin(name: &str, version: &str, version_hash: &str) -> Vec { lazy_static! { static ref MOCK_BIN: Arc> = { // Create a temp directory to hold the source and the output let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let source_path = tempdir.path().join("in.rs"); let dest_path = tempdir.path().join(format!("out{EXE_SUFFIX}")); // Write the source let source = include_bytes!("mock_bin_src.rs"); fs::write(&source_path, &source[..]).unwrap(); // Create the executable let status = Command::new("rustc") .arg(&source_path) .arg("-C").arg("panic=abort") .arg("-O") .arg("-o").arg(&dest_path) .status() .unwrap(); assert!(status.success()); assert!(dest_path.exists()); // Remove debug info from std/core which included in every programs, // otherwise we just ignore the return result here if cfg!(unix) { drop(Command::new("strip").arg(&dest_path).status()); } // Now load it into memory let buf = fs::read(dest_path).unwrap(); Arc::new(buf) }; } let name = format!("bin/{name}{EXE_SUFFIX}"); vec![ MockFile::new(format!("{name}.version"), version.as_bytes()), MockFile::new(format!("{name}.version-hash"), version_hash.as_bytes()), MockFile::new_arc(name, MOCK_BIN.clone()).executable(true), ] } // These are toolchains for installation with --link-local and --copy-local fn create_custom_toolchains(customdir: &Path) { let libdir = customdir.join("custom-1/lib"); fs::create_dir_all(libdir).unwrap(); for file in mock_bin("rustc", "1.0.0", "hash-c-1") { file.build(&customdir.join("custom-1")); } let libdir = customdir.join("custom-2/lib"); fs::create_dir_all(libdir).unwrap(); for file in mock_bin("rustc", "1.0.0", "hash-c-2") { file.build(&customdir.join("custom-2")); } } pub fn hard_link(original: A, link: B) -> io::Result<()> where A: AsRef, B: AsRef, { fn inner(a: &Path, b: &Path) -> io::Result<()> { match fs::remove_file(b) { Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e), _ => {} } fs::hard_link(a, b).map(drop) } inner(original.as_ref(), link.as_ref()) } rustup-1.26.0/tests/mock/dist.rs000066400000000000000000000462501441327105200165540ustar00rootroot00000000000000//! Tools for building and working with the filesystem of a mock Rust //! distribution server, with v1 and v2 manifests. use crate::mock::MockInstallerBuilder; use lazy_static::lazy_static; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use std::sync::Mutex; use url::Url; use crate::mock::clitools::hard_link; // This function changes the mock manifest for a given channel to that // of a particular date. For advancing the build from e.g. 2016-02-1 // to 2016-02-02 pub fn change_channel_date(dist_server: &Url, channel: &str, date: &str) { let path = dist_server.to_file_path().unwrap(); // V2 let manifest_name = format!("dist/channel-rust-{channel}"); let manifest_path = path.join(format!("{manifest_name}.toml")); let hash_path = path.join(format!("{manifest_name}.toml.sha256")); let sig_path = path.join(format!("{manifest_name}.toml.asc")); let archive_manifest_name = format!("dist/{date}/channel-rust-{channel}"); let archive_manifest_path = path.join(format!("{archive_manifest_name}.toml")); let archive_hash_path = path.join(format!("{archive_manifest_name}.toml.sha256")); let archive_sig_path = path.join(format!("{archive_manifest_name}.toml.asc")); let _ = hard_link(archive_manifest_path, manifest_path); let _ = hard_link(archive_hash_path, hash_path); let _ = hard_link(archive_sig_path, sig_path); // V1 let manifest_name = format!("dist/channel-rust-{channel}"); let manifest_path = path.join(&manifest_name); let hash_path = path.join(format!("{manifest_name}.sha256")); let sig_path = path.join(format!("{manifest_name}.asc")); let archive_manifest_name = format!("dist/{date}/channel-rust-{channel}"); let archive_manifest_path = path.join(&archive_manifest_name); let archive_hash_path = path.join(format!("{archive_manifest_name}.sha256")); let archive_sig_path = path.join(format!("{archive_manifest_name}.asc")); let _ = hard_link(archive_manifest_path, manifest_path); let _ = hard_link(archive_hash_path, hash_path); let _ = hard_link(archive_sig_path, sig_path); // Copy all files that look like rust-* for the v1 installers let archive_path = path.join(format!("dist/{date}")); for dir in fs::read_dir(archive_path).unwrap() { let dir = dir.unwrap(); if dir.file_name().to_str().unwrap().contains("rust-") { let path = path.join(format!("dist/{}", dir.file_name().to_str().unwrap())); hard_link(&dir.path(), path).unwrap(); } } } // The manifest version created by this mock pub const MOCK_MANIFEST_VERSION: &str = "2"; // A mock Rust v2 distribution server. Create it and and run `write` // to write its structure to a directory. pub struct MockDistServer { // The local path to the dist server root pub path: PathBuf, pub channels: Vec, } // A Rust distribution channel pub struct MockChannel { // e.g. "nightly" pub name: String, // YYYY-MM-DD pub date: String, pub packages: Vec, pub renames: HashMap, } // A single rust-installer package #[derive(Debug, Hash, Eq, PartialEq)] pub struct MockPackage { // rust, rustc, rust-std-$triple, rust-doc, etc. pub name: &'static str, pub version: String, pub targets: Vec, } #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct MockTargetedPackage { // Target triple pub target: String, // Whether the file actually exists (could be due to build failure) pub available: bool, pub components: Vec, // The mock rust-installer pub installer: MockInstallerBuilder, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct MockComponent { pub name: String, pub target: String, pub is_extension: bool, } #[derive(Clone)] pub struct MockHashes { pub gz: String, pub xz: Option, pub zst: Option, } pub enum ManifestVersion { V1, V2, } impl MockDistServer { pub fn write(&self, vs: &[ManifestVersion], enable_xz: bool, enable_zst: bool) { fs::create_dir_all(&self.path).unwrap(); for channel in self.channels.iter() { let mut hashes = HashMap::new(); for package in &channel.packages { let new_hashes = self.build_package(channel, package, enable_xz, enable_zst); hashes.extend(new_hashes.into_iter()); } for v in vs { match *v { ManifestVersion::V1 => self.write_manifest_v1(channel), ManifestVersion::V2 => self.write_manifest_v2(channel, &hashes), } } } } fn build_package( &self, channel: &MockChannel, package: &MockPackage, enable_xz: bool, enable_zst: bool, ) -> HashMap { let mut hashes = HashMap::new(); for target_package in &package.targets { let gz_hash = self.build_target_package(channel, package, target_package, ".tar.gz"); let xz_hash = if enable_xz { Some(self.build_target_package(channel, package, target_package, ".tar.xz")) } else { None }; let zst_hash = if enable_zst { Some(self.build_target_package(channel, package, target_package, ".tar.zst")) } else { None }; let component = MockComponent { name: package.name.to_string(), target: target_package.target.to_string(), is_extension: false, }; hashes.insert( component, MockHashes { gz: gz_hash, xz: xz_hash, zst: zst_hash, }, ); } hashes } // Returns the hash of the tarball fn build_target_package( &self, channel: &MockChannel, package: &MockPackage, target_package: &MockTargetedPackage, format: &str, ) -> String { // This is where the tarball, sums and sigs will go let dist_dir = self.path.join("dist"); let archive_dir = dist_dir.join(&channel.date); fs::create_dir_all(&archive_dir).unwrap(); let tmpdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let workdir = tmpdir.path().join("work"); let installer_name = if target_package.target != "*" { format!( "{}-{}-{}", package.name, channel.name, target_package.target ) } else { format!("{}-{}", package.name, channel.name) }; let installer_dir = workdir.join(&installer_name); let installer_tarball = archive_dir.join(format!("{installer_name}{format}")); let installer_hash = archive_dir.join(format!("{installer_name}{format}.sha256")); fs::create_dir_all(&installer_dir).unwrap(); type Tarball = HashMap<(String, MockTargetedPackage, String), (Vec, String)>; // Tarball creation can be super slow, so cache created tarballs // globally to avoid recreating and recompressing tons of tarballs. lazy_static! { static ref TARBALLS: Mutex = Mutex::new(HashMap::new()); } let key = ( installer_name.to_string(), target_package.clone(), format.to_string(), ); let tarballs = TARBALLS.lock().unwrap(); let hash = if tarballs.contains_key(&key) { let (ref contents, ref hash) = tarballs[&key]; File::create(&installer_tarball) .unwrap() .write_all(contents) .unwrap(); File::create(&installer_hash) .unwrap() .write_all(hash.as_bytes()) .unwrap(); hash.clone() } else { drop(tarballs); target_package.installer.build(&installer_dir); create_tarball( &PathBuf::from(&installer_name), &installer_dir, &installer_tarball, ) .unwrap(); let mut contents = Vec::new(); File::open(&installer_tarball) .unwrap() .read_to_end(&mut contents) .unwrap(); let hash = create_hash(&installer_tarball, &installer_hash); TARBALLS .lock() .unwrap() .insert(key, (contents, hash.clone())); hash }; // Copy from the archive to the main dist directory if package.name == "rust" { let main_installer_tarball = dist_dir.join(format!("{installer_name}{format}")); let main_installer_hash = dist_dir.join(format!("{installer_name}{format}.sha256")); hard_link(installer_tarball, main_installer_tarball).unwrap(); hard_link(installer_hash, main_installer_hash).unwrap(); } hash } // The v1 manifest is just the directory listing of the rust tarballs fn write_manifest_v1(&self, channel: &MockChannel) { let mut buf = String::new(); let package = channel.packages.iter().find(|p| p.name == "rust").unwrap(); for target in &package.targets { let package_file_name = if target.target != "*" { format!("{}-{}-{}.tar.gz", package.name, channel.name, target.target) } else { format!("{}-{}.tar.gz", package.name, channel.name) }; buf = buf + &package_file_name + "\n"; } let manifest_name = format!("dist/channel-rust-{}", channel.name); let manifest_path = self.path.join(&manifest_name); write_file(&manifest_path, &buf); let hash_path = self.path.join(format!("{manifest_name}.sha256")); create_hash(&manifest_path, &hash_path); // Also copy the manifest and hash into the archive folder let archive_manifest_name = format!("dist/{}/channel-rust-{}", channel.date, channel.name); let archive_manifest_path = self.path.join(&archive_manifest_name); hard_link(manifest_path, archive_manifest_path).unwrap(); let archive_hash_path = self.path.join(format!("{archive_manifest_name}.sha256")); hard_link(&hash_path, archive_hash_path).unwrap(); } fn write_manifest_v2( &self, channel: &MockChannel, hashes: &HashMap, ) { let mut toml_manifest = toml::value::Table::new(); toml_manifest.insert( String::from("manifest-version"), toml::Value::String(MOCK_MANIFEST_VERSION.to_owned()), ); toml_manifest.insert( String::from("date"), toml::Value::String(channel.date.to_owned()), ); // [pkg.*] let mut toml_packages = toml::value::Table::new(); for package in &channel.packages { let mut toml_package = toml::value::Table::new(); toml_package.insert( String::from("version"), toml::Value::String(package.version.to_owned()), ); // [pkg.*.target.*] let mut toml_targets = toml::value::Table::new(); for target in &package.targets { let mut toml_target = toml::value::Table::new(); toml_target.insert( String::from("available"), toml::Value::Boolean(target.available), ); let package_file_name = if target.target != "*" { format!("{}-{}-{}.tar.gz", package.name, channel.name, target.target) } else { format!("{}-{}.tar.gz", package.name, channel.name) }; let path = self .path .join("dist") .join(&channel.date) .join(package_file_name); let url = format!("file://{}", path.to_string_lossy()); toml_target.insert(String::from("url"), toml::Value::String(url.clone())); let component = MockComponent { name: package.name.to_owned(), target: target.target.to_owned(), is_extension: false, }; let hash = hashes[&component].clone(); toml_target.insert(String::from("hash"), toml::Value::String(hash.gz)); if let Some(xz_hash) = hash.xz { toml_target.insert( String::from("xz_url"), toml::Value::String(url.replace(".tar.gz", ".tar.xz")), ); toml_target.insert(String::from("xz_hash"), toml::Value::String(xz_hash)); } if let Some(zst_hash) = hash.zst { toml_target.insert( String::from("zst_url"), toml::Value::String(url.replace(".tar.gz", ".tar.zst")), ); toml_target.insert(String::from("zst_hash"), toml::Value::String(zst_hash)); } // [pkg.*.target.*.components.*] and [pkg.*.target.*.extensions.*] let mut toml_components = toml::value::Array::new(); let mut toml_extensions = toml::value::Array::new(); for component in &target.components { let mut toml_component = toml::value::Table::new(); toml_component.insert( String::from("pkg"), toml::Value::String(component.name.to_owned()), ); toml_component.insert( String::from("target"), toml::Value::String(component.target.to_owned()), ); if component.is_extension { toml_extensions.push(toml::Value::Table(toml_component)); } else { toml_components.push(toml::Value::Table(toml_component)); } } toml_target.insert( String::from("components"), toml::Value::Array(toml_components), ); toml_target.insert( String::from("extensions"), toml::Value::Array(toml_extensions), ); toml_targets.insert(target.target.clone(), toml::Value::Table(toml_target)); } toml_package.insert(String::from("target"), toml::Value::Table(toml_targets)); toml_packages.insert(String::from(package.name), toml::Value::Table(toml_package)); } toml_manifest.insert(String::from("pkg"), toml::Value::Table(toml_packages)); let mut toml_renames = toml::value::Table::new(); for (from, to) in &channel.renames { let mut toml_rename = toml::value::Table::new(); toml_rename.insert(String::from("to"), toml::Value::String(to.to_owned())); toml_renames.insert(from.to_owned(), toml::Value::Table(toml_rename)); } toml_manifest.insert(String::from("renames"), toml::Value::Table(toml_renames)); let mut toml_profiles = toml::value::Table::new(); let profiles = &[ ("minimal", vec!["rustc"]), ("default", vec!["rustc", "cargo", "rust-std", "rust-docs"]), ( "complete", vec!["rustc", "cargo", "rust-std", "rust-docs", "rls"], ), ]; for (profile, values) in profiles { let array = values .iter() .map(|v| toml::Value::String((**v).to_owned())) .collect(); toml_profiles.insert((*profile).to_string(), toml::Value::Array(array)); } toml_manifest.insert(String::from("profiles"), toml::Value::Table(toml_profiles)); let manifest_name = format!("dist/channel-rust-{}", channel.name); let manifest_path = self.path.join(format!("{manifest_name}.toml")); let manifest_content = toml::to_string(&toml_manifest).unwrap(); write_file(&manifest_path, &manifest_content); let hash_path = self.path.join(format!("{manifest_name}.toml.sha256")); create_hash(&manifest_path, &hash_path); // Also copy the manifest and hash into the archive folder let archive_manifest_name = format!("dist/{}/channel-rust-{}", channel.date, channel.name); let archive_manifest_path = self.path.join(format!("{archive_manifest_name}.toml")); hard_link(&manifest_path, archive_manifest_path).unwrap(); let archive_hash_path = self .path .join(format!("{archive_manifest_name}.toml.sha256")); hard_link(hash_path, archive_hash_path).unwrap(); } } fn create_tarball(relpath: &Path, src: &Path, dst: &Path) -> io::Result<()> { match fs::remove_file(dst) { Ok(_) => {} Err(e) if e.kind() == io::ErrorKind::NotFound => {} Err(e) => return Err(e), } let outfile = File::create(dst)?; let mut gzwriter; let mut xzwriter; let mut zstwriter; let writer: &mut dyn Write = match &dst.to_string_lossy() { s if s.ends_with(".tar.gz") => { gzwriter = flate2::write::GzEncoder::new(outfile, flate2::Compression::none()); &mut gzwriter } s if s.ends_with(".tar.xz") => { xzwriter = xz2::write::XzEncoder::new(outfile, 0); &mut xzwriter } s if s.ends_with(".tar.zst") => { zstwriter = zstd::stream::write::Encoder::new(outfile, 0)?.auto_finish(); &mut zstwriter } _ => panic!("Unsupported archive format"), }; let mut tar = tar::Builder::new(writer); for entry in walkdir::WalkDir::new(src) { let entry = entry?; let parts: Vec<_> = entry.path().iter().map(ToOwned::to_owned).collect(); let parts_len = parts.len(); let parts = parts.into_iter().skip(parts_len - entry.depth()); let mut relpath = relpath.to_owned(); relpath.extend(parts); if entry.file_type().is_file() { let mut srcfile = File::open(entry.path())?; tar.append_file(relpath, &mut srcfile)?; } else if entry.file_type().is_dir() { tar.append_dir(relpath, entry.path())?; } } tar.finish() } pub fn calc_hash(src: &Path) -> String { let mut buf = Vec::new(); File::open(src).unwrap().read_to_end(&mut buf).unwrap(); let mut hasher = Sha256::new(); hasher.update(buf); format!("{:x}", hasher.finalize()) } pub fn create_hash(src: &Path, dst: &Path) -> String { let hex = calc_hash(src); let src_file = src.file_name().unwrap(); let file_contents = format!("{} *{}\n", hex, src_file.to_string_lossy()); write_file(dst, &file_contents); hex } pub fn write_file(dst: &Path, contents: &str) { drop(fs::remove_file(dst)); File::create(dst) .and_then(|mut f| f.write_all(contents.as_bytes())) .unwrap(); } rustup-1.26.0/tests/mock/mock_bin_src.rs000066400000000000000000000122161441327105200202340ustar00rootroot00000000000000use std::env; use std::env::consts::EXE_SUFFIX; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::Command; fn main() { let mut args = env::args_os().skip(1); match args.next().as_ref().and_then(|s| s.to_str()) { Some("--version") => { let me = env::current_exe().unwrap(); let mut version_file = PathBuf::from(format!("{}.version", me.display())); let mut hash_file = PathBuf::from(format!("{}.version-hash", me.display())); if !version_file.exists() { // There's a "MAJOR HACKS" statement in `toolchain.rs` right // now where custom toolchains use a `cargo.exe` that's // temporarily located elsewhere so they can execute the correct // `rustc.exe`. This means that our dummy version files may not // be just next to use. // // Detect this here and work around it. assert!(cfg!(windows)); assert!(env::var_os("RUSTUP_TOOLCHAIN").is_some()); let mut alt = me.clone(); alt.pop(); // remove our filename assert!(alt.ends_with("fallback")); alt.pop(); // pop 'fallback' alt.push("toolchains"); let mut part = PathBuf::from("bin"); part.push(me.file_name().unwrap()); let path = alt .read_dir() .unwrap() .map(|e| e.unwrap().path().join(&part)) .filter(|p| p.exists()) .find(|p| equivalent(&p, &me)) .unwrap(); version_file = format!("{}.version", path.display()).into(); hash_file = format!("{}.version-hash", path.display()).into(); } let version = std::fs::read_to_string(&version_file).unwrap(); let hash = std::fs::read_to_string(&hash_file).unwrap(); println!("{} ({})", version, hash); } Some("--empty-arg-test") => { assert_eq!(args.next().unwrap(), ""); } Some("--huge-output") => { let mut out = io::stderr(); for _ in 0..10000 { out.write_all(b"error: a value named `fail` has already been defined in this module [E0428]\n").unwrap(); } } Some("--call-rustc") => { // Used by the fallback_cargo_calls_correct_rustc test. Tests that // the environment has been set up right such that invoking rustc // will actually invoke the wrapper let rustc = &format!("rustc{}", EXE_SUFFIX); Command::new(rustc).arg("--version").status().unwrap(); } Some("--recursive-cargo-subcommand") => { let status = Command::new("cargo-foo") .arg("--recursive-cargo") .status() .unwrap(); assert!(status.success()); } Some("--recursive-cargo") => { let status = Command::new("cargo") .args(&["+nightly", "--version"]) .status() .unwrap(); assert!(status.success()); } Some("--echo-args") => { let mut out = io::stderr(); for arg in args { writeln!(out, "{}", arg.to_string_lossy()).unwrap(); } } Some("--echo-path") => { let mut out = io::stderr(); writeln!(out, "{}", std::env::var("PATH").unwrap()).unwrap(); } arg => panic!("bad mock proxy commandline: {:?}", arg), } } #[cfg(unix)] fn equivalent(_: &Path, _: &Path) -> bool { false } #[cfg(windows)] #[allow(non_snake_case)] fn equivalent(a: &Path, b: &Path) -> bool { use std::fs::File; use std::mem::MaybeUninit; use std::os::windows::io::AsRawHandle; use std::os::windows::raw::HANDLE; #[repr(C)] struct FILETIME { dwLowDateTime: u32, dwHighDateTime: u32, } #[repr(C)] struct BY_HANDLE_FILE_INFORMATION { dwFileAttributes: u32, ftCreationTime: FILETIME, ftLastAccessTime: FILETIME, ftLastWriteTime: FILETIME, dwVolumeSerialNumber: u32, nFileSizeHigh: u32, nFileSizeLow: u32, nNumberOfLinks: u32, nFileIndexHigh: u32, nFileIndexLow: u32, } extern "system" { fn GetFileInformationByHandle(a: HANDLE, b: *mut BY_HANDLE_FILE_INFORMATION) -> i32; } let a = File::open(a).unwrap(); let b = File::open(b).unwrap(); let (ainfo, binfo) = unsafe { let mut ainfo = MaybeUninit::uninit(); let mut binfo = MaybeUninit::uninit(); if GetFileInformationByHandle(a.as_raw_handle(), ainfo.as_mut_ptr()) == 0 { return false; } if GetFileInformationByHandle(b.as_raw_handle(), binfo.as_mut_ptr()) == 0 { return false; } (ainfo.assume_init(), binfo.assume_init()) }; ainfo.dwVolumeSerialNumber == binfo.dwVolumeSerialNumber && ainfo.nFileIndexHigh == binfo.nFileIndexHigh && ainfo.nFileIndexLow == binfo.nFileIndexLow } rustup-1.26.0/tests/mock/mod.rs000066400000000000000000000115061441327105200163640ustar00rootroot00000000000000//! Mocks for testing pub mod clitools; pub mod dist; pub mod topical_doc_data; use std::fs::{self, File, OpenOptions}; use std::io::Write; use std::path::Path; use std::sync::Arc; // Mock of the on-disk structure of rust-installer installers #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct MockInstallerBuilder { pub components: Vec, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct MockComponentBuilder { pub name: String, pub files: Vec, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct MockFile { path: String, contents: Contents, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] enum Contents { File(MockContents), Dir(Vec<(&'static str, MockContents)>), } #[derive(PartialEq, Eq, Hash, Clone)] struct MockContents { contents: Arc>, executable: bool, } impl std::fmt::Debug for MockContents { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("MockContents") .field("content_len", &self.contents.len()) .field("executable", &self.executable) .finish() } } impl MockInstallerBuilder { pub fn build(&self, path: &Path) { for component in &self.components { // Update the components file let comp_file = path.join("components"); let mut comp_file = OpenOptions::new() .write(true) .append(true) .create(true) .open(comp_file.clone()) .unwrap(); writeln!(comp_file, "{}", component.name).unwrap(); // Create the component directory let component_dir = path.join(&component.name); if !component_dir.exists() { fs::create_dir(&component_dir).unwrap(); } // Create the component files and manifest let mut manifest = File::create(component_dir.join("manifest.in")).unwrap(); for file in component.files.iter() { match file.contents { Contents::Dir(_) => { writeln!(manifest, "dir:{}", file.path).unwrap(); } Contents::File(_) => { writeln!(manifest, "file:{}", file.path).unwrap(); } } file.build(&component_dir); } } let mut ver = File::create(path.join("rust-installer-version")).unwrap(); writeln!(ver, "3").unwrap(); } } impl MockFile { pub fn new>(path: S, contents: &[u8]) -> MockFile { MockFile::_new(path.into(), Arc::new(contents.to_vec())) } pub fn new_arc>(path: S, contents: Arc>) -> MockFile { MockFile::_new(path.into(), contents) } fn _new(path: String, contents: Arc>) -> MockFile { MockFile { path, contents: Contents::File(MockContents { contents, executable: false, }), } } pub fn new_dir(path: &str, files: &[(&'static str, &'static [u8], bool)]) -> MockFile { MockFile { path: path.to_string(), contents: Contents::Dir( files .iter() .map(|&(name, data, exe)| { ( name, MockContents { contents: Arc::new(data.to_vec()), executable: exe, }, ) }) .collect(), ), } } pub fn executable(mut self, exe: bool) -> Self { if let Contents::File(c) = &mut self.contents { c.executable = exe; } self } pub fn build(&self, path: &Path) { let path = path.join(&self.path); match self.contents { Contents::Dir(ref files) => { for (name, contents) in files { let fname = path.join(name); contents.build(&fname); } } Contents::File(ref contents) => contents.build(&path), } } } impl MockContents { fn build(&self, path: &Path) { let dir_path = path.parent().unwrap().to_owned(); fs::create_dir_all(dir_path).unwrap(); File::create(path) .unwrap() .write_all(&self.contents) .unwrap(); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if self.executable { let mut perm = fs::metadata(path).unwrap().permissions(); perm.set_mode(0o755); fs::set_permissions(path, perm).unwrap(); } } } } rustup-1.26.0/tests/mock/topical_doc_data.rs000066400000000000000000000031461441327105200210570ustar00rootroot00000000000000use std::collections::HashSet; use std::path::PathBuf; // Paths are written as a string in the UNIX format to make it easy // to maintain. static TEST_CASES: &[&[&str]] = &[ &["core", "core/index.html"], &["core::arch", "core/arch/index.html"], &["fn", "std/keyword.fn.html"], &["keyword:fn", "std/keyword.fn.html"], &["primitive:fn", "std/primitive.fn.html"], &["macro:file!", "std/macro.file!.html"], &["macro:file", "std/macro.file.html"], &["std::fs", "std/fs/index.html"], &["std::fs::read_dir", "std/fs/fn.read_dir.html"], &["std::io::Bytes", "std/io/struct.Bytes.html"], &["std::iter::Sum", "std/iter/trait.Sum.html"], &["std::io::error::Result", "std/io/error/type.Result.html"], &["usize", "std/primitive.usize.html"], &["eprintln", "std/macro.eprintln.html"], &["alloc::format", "alloc/macro.format.html"], ]; fn repath(origin: &str) -> String { // Add doc prefix and rewrite string paths for the current platform let with_prefix = "share/doc/rust/html/".to_owned() + origin; let splitted = with_prefix.split('/'); let repathed = splitted.fold(PathBuf::new(), |acc, e| acc.join(e)); repathed.into_os_string().into_string().unwrap() } pub fn test_cases<'a>() -> impl Iterator { TEST_CASES.iter().map(|x| (x[0], repath(x[1]))) } pub fn unique_paths() -> impl Iterator { // Hashset used to test uniqueness of values through insert method. let mut unique_paths = HashSet::new(); TEST_CASES .iter() .filter(move |e| unique_paths.insert(e[1])) .map(|e| repath(e[1])) } rustup-1.26.0/tests/suite/000077500000000000000000000000001441327105200154345ustar00rootroot00000000000000rustup-1.26.0/tests/suite/channel-rust-nightly-example.toml000066400000000000000000000036541441327105200240510ustar00rootroot00000000000000manifest-version = "2" date = "2015-10-10" [pkg.rust] version = "rustc 1.3.0 (9a92aaf19 2015-09-15)" [pkg.rust.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rustc" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rust-docs" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "cargo" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" # extensions are rust-std or rust-docs that aren't in the rust tarball's component list [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [pkg.rustc] version = "rustc 1.3.0 (9a92aaf19 2015-09-15)" [pkg.rustc.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." [pkg.cargo] version = "cargo 0.4.0-nightly (553b363 2015-08-03)" [pkg.cargo.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." [pkg.rust-std] version = "rustc 1.3.0 (9a92aaf19 2015-09-15)" [pkg.rust-std.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." [pkg.rust-std.target.x86_64-unknown-linux-musl] available = true url = "example.com" hash = "..." [pkg.rust-std.target.i686-unknown-linux-gnu] available = true url = "example.com" hash = "..." [pkg.rust-docs] version = "rustc 1.3.0 (9a92aaf19 2015-09-15)" [pkg.rust-docs.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." rustup-1.26.0/tests/suite/channel-rust-nightly-example2.toml000066400000000000000000000626401441327105200241330ustar00rootroot00000000000000manifest-version = "2" date = "2016-03-04" [renames.cargo-old] to = "cargo" [pkg.cargo] version = "0.9.0-nightly (34269d0 2016-02-18)" [pkg.cargo.target.i686-apple-darwin] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-i686-apple-darwin.tar.gz" hash = "2a27507b46cb235de9baa49111fcb72a4e37c7ba6b3a00ee07c50be76f6b40de" [pkg.cargo.target.i686-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-i686-pc-windows-gnu.tar.gz" hash = "da9c3bac5a0a04d7c967efba4203a536240a1741dd7a87ad0fd1e62b11210be6" [pkg.cargo.target.i686-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-i686-pc-windows-msvc.tar.gz" hash = "890fca92cd4dbcc2b969fd7bc70db8c63fd999e38d31488f2ad4cf49aeb0e660" [pkg.cargo.target.i686-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-i686-unknown-linux-gnu.tar.gz" hash = "972f01ec69b7601719f72e1a6a9d25a3e7c233cc896e6fa0111d618320316bb4" [pkg.cargo.target.x86_64-apple-darwin] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-x86_64-apple-darwin.tar.gz" hash = "bc9f16958a2fb529956c1b7fbf5b29c81981e50aa609717725d389d32a8b7293" [pkg.cargo.target.x86_64-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-x86_64-pc-windows-gnu.tar.gz" hash = "6161c10b04c617bd6dd61f260ea1a0dbf1e43809f2431199ab58336380e66bd1" [pkg.cargo.target.x86_64-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-x86_64-pc-windows-msvc.tar.gz" hash = "6a68687e68397581494ecc453f89e1a03eabf71be84dd90ecd4daadfbe1dbe46" [pkg.cargo.target.x86_64-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/cargo-dist/2016-02-19/cargo-nightly-x86_64-unknown-linux-gnu.tar.gz" hash = "ba812247cab6ab6b5387228d916aef38a0f86f4d779d427bda507cac5d4a8178" [pkg.rust] version = "1.9.0-nightly (d31d8a9a9 2016-03-04)" [pkg.rust.target.i686-apple-darwin] available = false url = "" hash = "" [[pkg.rust.target.i686-apple-darwin.components]] pkg = "rustc" target = "i686-apple-darwin" [[pkg.rust.target.i686-apple-darwin.components]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.i686-apple-darwin.components]] pkg = "rust-docs" target = "i686-apple-darwin" [[pkg.rust.target.i686-apple-darwin.components]] pkg = "cargo" target = "i686-apple-darwin" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.i686-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.i686-pc-windows-gnu] available = false url = "" hash = "" [[pkg.rust.target.i686-pc-windows-gnu.components]] pkg = "rustc" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.components]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.components]] pkg = "rust-docs" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.components]] pkg = "cargo" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.components]] pkg = "rust-mingw" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.i686-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.i686-pc-windows-msvc] available = false url = "" hash = "" [[pkg.rust.target.i686-pc-windows-msvc.components]] pkg = "rustc" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-msvc.components]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-msvc.components]] pkg = "rust-docs" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-msvc.components]] pkg = "cargo" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.i686-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.i686-unknown-linux-gnu] available = false url = "" hash = "" [[pkg.rust.target.i686-unknown-linux-gnu.components]] pkg = "rustc" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.components]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.components]] pkg = "rust-docs" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.components]] pkg = "cargo" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.i686-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.x86_64-apple-darwin] available = false url = "" hash = "" [[pkg.rust.target.x86_64-apple-darwin.components]] pkg = "rustc" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-apple-darwin.components]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-apple-darwin.components]] pkg = "rust-docs" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-apple-darwin.components]] pkg = "cargo" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-apple-darwin.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.x86_64-pc-windows-gnu] available = false url = "" hash = "" [[pkg.rust.target.x86_64-pc-windows-gnu.components]] pkg = "rustc" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.components]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.components]] pkg = "rust-docs" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.components]] pkg = "cargo" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.components]] pkg = "rust-mingw" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-pc-windows-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.x86_64-pc-windows-msvc] available = false url = "" hash = "" [[pkg.rust.target.x86_64-pc-windows-msvc.components]] pkg = "rustc" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-msvc.components]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-msvc.components]] pkg = "rust-docs" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-msvc.components]] pkg = "cargo" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-pc-windows-msvc.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust.target.x86_64-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-04/rust-nightly-x86_64-unknown-linux-gnu.tar.gz" hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rustc" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rust-docs" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "cargo" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "aarch64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "arm-linux-androideabi" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabi" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "arm-unknown-linux-gnueabihf" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-apple-darwin" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-pc-windows-msvc" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "i686-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "mips-unknown-linux" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "mipsel-unknown-linux" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-apple-darwin" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-pc-windows-msvc" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rust-docs] version = "1.9.0-nightly (d31d8a9a9 2016-03-04)" [pkg.rust-docs.target.i686-apple-darwin] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-i686-apple-darwin.tar.gz" hash = "1e5df676b576def78293913ecad7d0df50fe7a11bd4b4e79a5fd6c2ee46acfb1" [pkg.rust-docs.target.i686-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-i686-pc-windows-gnu.tar.gz" hash = "626d0abc1f8df848ad5765b8a0de38a656ad81957bab91942fb28487149ff41a" [pkg.rust-docs.target.i686-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-i686-pc-windows-msvc.tar.gz" hash = "74ec0a09590606f8436a71c8bd815148a2a0bf06b12a8c2e99a099990555dce7" [pkg.rust-docs.target.i686-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-i686-unknown-linux-gnu.tar.gz" hash = "db36ce902d932bcb4625675e35db36f21112ca4e7084e1d594b6f57137bbf018" [pkg.rust-docs.target.x86_64-apple-darwin] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-x86_64-apple-darwin.tar.gz" hash = "2ea3021e34c4e023f6d47fddc5a6cd5a80d649b898984a730730e0bb192d9cd9" [pkg.rust-docs.target.x86_64-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-x86_64-pc-windows-gnu.tar.gz" hash = "7a217384e1b5f018f7bd768fdc2bfed9530ca5f261c8a3f68648e8aace70a617" [pkg.rust-docs.target.x86_64-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-x86_64-pc-windows-msvc.tar.gz" hash = "67c45fe1619b5e3b2553b5ffee3f2a39465ca0c8efcc8f46eb2f06e11a09df76" [pkg.rust-docs.target.x86_64-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-docs-nightly-x86_64-unknown-linux-gnu.tar.gz" hash = "32d0b1266b995d0c8e29db85b8788b33c573fafcc36a7aef580c887a02951eb4" [pkg.rust-mingw] version = "1.9.0-nightly (d31d8a9a9 2016-03-04)" [pkg.rust-mingw.target.i686-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-mingw-nightly-i686-pc-windows-gnu.tar.gz" hash = "33ee3f66dfe7012abeb4cf8f88548283d60ad4bbd3cd10082d90202301e079cd" [pkg.rust-mingw.target.x86_64-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-mingw-nightly-x86_64-pc-windows-gnu.tar.gz" hash = "2a091f4b2159283468e43e4bb45e4b5506a3d1bf5779299bd17897499c68adce" [pkg.rust-std] version = "1.9.0-nightly (d31d8a9a9 2016-03-04)" [pkg.rust-std.target.aarch64-unknown-linux-gnu] available = false url = "" hash = "" [pkg.rust-std.target.arm-linux-androideabi] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-arm-linux-androideabi.tar.gz" hash = "be7d3d475296ac18d5e52e1cc042d8b9063b9366c5363850479b863bb786c05b" [pkg.rust-std.target.arm-unknown-linux-gnueabi] available = false url = "" hash = "" [pkg.rust-std.target.arm-unknown-linux-gnueabihf] available = false url = "" hash = "" [pkg.rust-std.target.i686-apple-darwin] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-i686-apple-darwin.tar.gz" hash = "17095a3f448ca7cc3ae85ea5b8d0181a34e6c0784967e9bfc2ad1ed8352a7591" [pkg.rust-std.target.i686-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-i686-pc-windows-gnu.tar.gz" hash = "0661c920ffc4e9cff76aabb546371f4be53b8d92563bc41d257b0e00e9d05ef1" [pkg.rust-std.target.i686-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-i686-pc-windows-msvc.tar.gz" hash = "3d57245a7cc7f527e0069fc775134d980cb046b8c14ffff69fbd18790906c6ea" [pkg.rust-std.target.i686-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-i686-unknown-linux-gnu.tar.gz" hash = "e5b2e67f777069c5f2e11e341d23d2e727c86e8a9a0017cba77d58d8673f8207" [pkg.rust-std.target.mips-unknown-linux] available = false url = "" hash = "" [pkg.rust-std.target.mipsel-unknown-linux] available = false url = "" hash = "" [pkg.rust-std.target.x86_64-apple-darwin] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-x86_64-apple-darwin.tar.gz" hash = "07e060958d3cb325021a653f36ba8175bfc192794890647f975f05e3abcc24dd" [pkg.rust-std.target.x86_64-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-x86_64-pc-windows-gnu.tar.gz" hash = "756ba9c65883e42213a50fe0e8667842d3a64518294ee8b3b4d3b7eaca3c4a10" [pkg.rust-std.target.x86_64-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-x86_64-pc-windows-msvc.tar.gz" hash = "e649b56460ff226aa12db0858d7c0ac3bdb90fcad612c210dc2f5430faf25ba8" [pkg.rust-std.target.x86_64-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-x86_64-unknown-linux-gnu.tar.gz" hash = "557e942caea2397eb331620847ce79547ce6242f36ad7e30e9fb8290ebe03d24" [pkg.rust-std.target.x86_64-unknown-linux-musl] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rust-std-nightly-x86_64-unknown-linux-musl.tar.gz" hash = "c9bc2c18ec67d4d7eb97e7ef2669bc422883b50a61867f87998ba45694b7082f" [pkg.rustc] version = "1.9.0-nightly (d31d8a9a9 2016-03-04)" [pkg.rustc.target.i686-apple-darwin] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-i686-apple-darwin.tar.gz" hash = "ee9a368744ef36d66e3405754b76cfb4ea4c32c5ea49bb1946dd349249aa0871" [pkg.rustc.target.i686-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-i686-pc-windows-gnu.tar.gz" hash = "14dcf873331b4cd5332dd323ec60cd7f2147f739a38027b1494a538f1fac1fa4" [pkg.rustc.target.i686-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-i686-pc-windows-msvc.tar.gz" hash = "d6360a18d8123a4309cb05dbfa62c15371313439733d03894f505d4aa4f6a910" [pkg.rustc.target.i686-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-i686-unknown-linux-gnu.tar.gz" hash = "c3affbe7663e8ede07bd5bed7d53ff34aef0578ec4457176dca2d764322e1b41" [pkg.rustc.target.x86_64-apple-darwin] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-x86_64-apple-darwin.tar.gz" hash = "6ea9a4fa0f74016417e9e8187364243787347a9ddd1272dad8e8ebc58c1fa147" [pkg.rustc.target.x86_64-pc-windows-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-x86_64-pc-windows-gnu.tar.gz" hash = "16c2490e2698fa28a47ff814bd128b491a43c044f61d3e329f709db38423edd7" [pkg.rustc.target.x86_64-pc-windows-msvc] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-x86_64-pc-windows-msvc.tar.gz" hash = "a7fe5a58707282186e673863d9518211d61cd29651b49b21d864008c12aeefa5" [pkg.rustc.target.x86_64-unknown-linux-gnu] available = true url = "https://dev-static.rust-lang.org/dist/2016-03-05/rustc-nightly-x86_64-unknown-linux-gnu.tar.gz" hash = "1b0736d7e49652989269017fc8ea224ecd5e2549da7abf7b92109b4e00256b34" rustup-1.26.0/tests/suite/cli-ui/000077500000000000000000000000001441327105200166165ustar00rootroot00000000000000rustup-1.26.0/tests/suite/cli-ui/rustup-init/000077500000000000000000000000001441327105200211215ustar00rootroot00000000000000rustup-1.26.0/tests/suite/cli-ui/rustup-init/rustup-init_help_flag_stdout.toml000066400000000000000000000021371441327105200277270ustar00rootroot00000000000000bin.name = "rustup-init" args = ["--help"] status.code = 0 stdout = """ rustup-init [..] The installer for rustup USAGE: rustup-init[EXE] [OPTIONS] OPTIONS: -v, --verbose Enable verbose output -q, --quiet Disable progress output -y Disable confirmation prompt. --default-host Choose a default host triple --default-toolchain Choose a default toolchain to install. Use 'none' to not install any toolchains at all --profile [default: default] [possible values: minimal, default, complete] -c, --component ... Component name to also install -t, --target ... Target name to also install --no-update-default-toolchain Don't update any existing default toolchain after install --no-modify-path Don't configure the PATH environment variable -h, --help Print help information -V, --version Print version information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup-init/rustup-init_sh_help_flag_stdout.toml000066400000000000000000000021421441327105200304150ustar00rootroot00000000000000bin.name = "rustup-init.sh" args = ["--help"] status.code = 0 stdout = """ rustup-init [..] The installer for rustup USAGE: rustup-init[EXE] [OPTIONS] OPTIONS: -v, --verbose Enable verbose output -q, --quiet Disable progress output -y Disable confirmation prompt. --default-host Choose a default host triple --default-toolchain Choose a default toolchain to install. Use 'none' to not install any toolchains at all --profile [default: default] [possible values: minimal, default, complete] -c, --component ... Component name to also install -t, --target ... Target name to also install --no-update-default-toolchain Don't update any existing default toolchain after install --no-modify-path Don't configure the PATH environment variable -h, --help Print help information -V, --version Print version information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/000077500000000000000000000000001441327105200201605ustar00rootroot00000000000000rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_check_cmd_help_flag_stdout.toml000066400000000000000000000003201441327105200277750ustar00rootroot00000000000000bin.name = "rustup" args = ["check","--help"] stdout = """ ... Check for updates to Rust toolchains and rustup USAGE: rustup[EXE] check OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_completions_cmd_help_flag_stdout.toml000066400000000000000000000110071441327105200312600ustar00rootroot00000000000000bin.name = "rustup" args = ["completions","--help"] stdout = """ ... Generate tab-completion scripts for your shell USAGE: rustup[EXE] completions [ARGS] ARGS: [possible values: bash, elvish, fish, powershell, zsh] [possible values: rustup, cargo] OPTIONS: -h, --help Print help information DISCUSSION: Enable tab completion for Bash, Fish, Zsh, or PowerShell The script is output on `stdout`, allowing one to re-direct the output to the file of their choosing. Where you place the file will depend on which shell, and which operating system you are using. Your particular configuration may also determine where these scripts need to be placed. Here are some common set ups for the three supported shells under Unix and similar operating systems (such as GNU/Linux). BASH: Completion files are commonly stored in `/etc/bash_completion.d/` for system-wide commands, but can be stored in `~/.local/share/bash-completion/completions` for user-specific commands. Run the command: $ mkdir -p ~/.local/share/bash-completion/completions $ rustup completions bash >> ~/.local/share/bash-completion/completions/rustup This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect. BASH (macOS/Homebrew): Homebrew stores bash completion files within the Homebrew directory. With the `bash-completion` brew formula installed, run the command: $ mkdir -p $(brew --prefix)/etc/bash_completion.d $ rustup completions bash > $(brew --prefix)/etc/bash_completion.d/rustup.bash-completion FISH: Fish completion files are commonly stored in `$HOME/.config/fish/completions`. Run the command: $ mkdir -p ~/.config/fish/completions $ rustup completions fish > ~/.config/fish/completions/rustup.fish This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect. ZSH: ZSH completions are commonly stored in any directory listed in your `$fpath` variable. To use these completions, you must either add the generated script to one of those directories, or add your own to this list. Adding a custom directory is often the safest bet if you are unsure of which directory to use. First create the directory; for this example we'll create a hidden directory inside our `$HOME` directory: $ mkdir ~/.zfunc Then add the following lines to your `.zshrc` just before `compinit`: fpath+=~/.zfunc Now you can install the completions script using the following command: $ rustup completions zsh > ~/.zfunc/_rustup You must then either log out and log back in, or simply run $ exec zsh for the new completions to take effect. CUSTOM LOCATIONS: Alternatively, you could save these files to the place of your choosing, such as a custom directory inside your $HOME. Doing so will require you to add the proper directives, such as `source`ing inside your login script. Consult your shells documentation for how to add such directives. POWERSHELL: The powershell completion scripts require PowerShell v5.0+ (which comes with Windows 10, but can be downloaded separately for windows 7 or 8.1). First, check if a profile has already been set PS C:/> Test-Path $profile If the above command returns `False` run the following PS C:/> New-Item -path $profile -type file -force Now open the file provided by `$profile` (if you used the `New-Item` command it will be `${env:USERPROFILE}/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` Next, we either save the completions file into our profile, or into a separate file and source it inside our profile. To save the completions into our profile simply use PS C:/> rustup completions powershell >> ${env:USERPROFILE}/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1 CARGO: Rustup can also generate a completion script for `cargo`. The script output by `rustup` will source the completion script distributed with your default toolchain. Not all shells are currently supported. Here are examples for the currently supported shells. BASH: $ rustup completions bash cargo >> ~/.local/share/bash-completion/completions/cargo ZSH: $ rustup completions zsh cargo > ~/.zfunc/_cargo """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_component_cmd_add_cmd_help_flag_stdout.toml000066400000000000000000000007621441327105200323670ustar00rootroot00000000000000bin.name = "rustup" args = ["component","add","--help"] stdout = """ ... Add a component to a Rust toolchain USAGE: rustup[EXE] component add [OPTIONS] ... ARGS: ... OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` --target -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_component_cmd_list_cmd_help_flag_stdout.toml000066400000000000000000000007511441327105200326100ustar00rootroot00000000000000bin.name = "rustup" args = ["component","list","--help"] stdout = """ ... List installed and available components USAGE: rustup[EXE] component list [OPTIONS] OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` --installed List only installed components -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_component_cmd_remove_cmd_help_flag_stdout.toml000066400000000000000000000007751441327105200331400ustar00rootroot00000000000000bin.name = "rustup" args = ["component","remove","--help"] stdout = """ ... Remove a component from a Rust toolchain USAGE: rustup[EXE] component remove [OPTIONS] ... ARGS: ... OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` --target -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_default_cmd_help_flag_stdout.toml000066400000000000000000000007551441327105200303600ustar00rootroot00000000000000bin.name = "rustup" args = ["default","--help"] stdout = """ ... Set the default toolchain USAGE: rustup[EXE] default [toolchain] ARGS: Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: -h, --help Print help information DISCUSSION: Sets the default toolchain to the one specified. If the toolchain is not already installed then it is installed first. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_doc_cmd_help_flag_stdout.toml000066400000000000000000000041611441327105200274740ustar00rootroot00000000000000bin.name = "rustup" args = ["doc","--help"] stdout = """ ... Open the documentation for the current toolchain USAGE: rustup[EXE] doc [OPTIONS] [topic] ARGS: Topic such as 'core', 'fn', 'usize', 'eprintln!', 'core::arch', 'alloc::format!', 'std::fs', 'std::fs::read_dir', 'std::io::Bytes', 'std::iter::Sum', 'std::io::error::Result' etc... OPTIONS: --path Only print the path to the documentation --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` --alloc The Rust core allocation and collections library --book The Rust Programming Language book --cargo The Cargo Book --core The Rust Core Library --edition-guide The Rust Edition Guide --nomicon The Dark Arts of Advanced and Unsafe Rust Programming --proc_macro A support library for macro authors when defining new macros --reference The Rust Reference --rust-by-example A collection of runnable examples that illustrate various Rust concepts and standard libraries --rustc The compiler for the Rust programming language --rustdoc Documentation generator for Rust projects --std Standard library API documentation --test Support code for rustc's built in unit-test and micro-benchmarking framework --unstable-book The Unstable Book --embedded-book The Embedded Rust Book -h, --help Print help information DISCUSSION: Opens the documentation for the currently active toolchain with the default browser. By default, it opens the documentation index. Use the various flags to open specific pieces of documentation. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_help_cmd_stdout.toml000066400000000000000000000034211441327105200256540ustar00rootroot00000000000000bin.name = "rustup" args = ["help"] status.code = 0 stdout = """ rustup [..] The Rust toolchain installer USAGE: rustup[EXE] [OPTIONS] [+toolchain] ARGS: <+toolchain> release channel (e.g. +stable) or custom toolchain to set override OPTIONS: -v, --verbose Enable verbose output -q, --quiet Disable progress output -h, --help Print help information -V, --version Print version information SUBCOMMANDS: show Show the active and installed toolchains or profiles update Update Rust toolchains and rustup check Check for updates to Rust toolchains and rustup default Set the default toolchain toolchain Modify or query the installed toolchains target Modify a toolchain's supported targets component Modify a toolchain's installed components override Modify directory toolchain overrides run Run a command with an environment configured for a given toolchain which Display which binary will be run for a given command doc Open the documentation for the current toolchain ... self Modify the rustup installation set Alter rustup settings completions Generate tab-completion scripts for your shell help Print this message or the help of the given subcommand(s) DISCUSSION: Rustup installs The Rust Programming Language from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling simpler with binary builds of the standard library for common platforms. If you are new to Rust consider running `rustup doc --book` to learn Rust. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_help_flag_stdout.toml000066400000000000000000000034231441327105200260240ustar00rootroot00000000000000bin.name = "rustup" args = ["--help"] status.code = 0 stdout = """ rustup [..] The Rust toolchain installer USAGE: rustup[EXE] [OPTIONS] [+toolchain] ARGS: <+toolchain> release channel (e.g. +stable) or custom toolchain to set override OPTIONS: -v, --verbose Enable verbose output -q, --quiet Disable progress output -h, --help Print help information -V, --version Print version information SUBCOMMANDS: show Show the active and installed toolchains or profiles update Update Rust toolchains and rustup check Check for updates to Rust toolchains and rustup default Set the default toolchain toolchain Modify or query the installed toolchains target Modify a toolchain's supported targets component Modify a toolchain's installed components override Modify directory toolchain overrides run Run a command with an environment configured for a given toolchain which Display which binary will be run for a given command doc Open the documentation for the current toolchain ... self Modify the rustup installation set Alter rustup settings completions Generate tab-completion scripts for your shell help Print this message or the help of the given subcommand(s) DISCUSSION: Rustup installs The Rust Programming Language from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling simpler with binary builds of the standard library for common platforms. If you are new to Rust consider running `rustup doc --book` to learn Rust. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_man_cmd_help_flag_stdout.toml000066400000000000000000000006601441327105200275020ustar00rootroot00000000000000bin.name = "rustup" args = ["man","--help"] stdout = """ ... View the man page for a given command USAGE: rustup[EXE] man [OPTIONS] ARGS: OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_only_options_stdout.toml000066400000000000000000000034171441327105200266420ustar00rootroot00000000000000bin.name = "rustup" args = ["-v"] status.code = 1 stdout = """ rustup [..] The Rust toolchain installer USAGE: rustup[EXE] [OPTIONS] [+toolchain] ARGS: <+toolchain> release channel (e.g. +stable) or custom toolchain to set override OPTIONS: -v, --verbose Enable verbose output -q, --quiet Disable progress output -h, --help Print help information -V, --version Print version information SUBCOMMANDS: show Show the active and installed toolchains or profiles update Update Rust toolchains and rustup check Check for updates to Rust toolchains and rustup default Set the default toolchain toolchain Modify or query the installed toolchains target Modify a toolchain's supported targets component Modify a toolchain's installed components override Modify directory toolchain overrides run Run a command with an environment configured for a given toolchain which Display which binary will be run for a given command doc Open the documentation for the current toolchain ... self Modify the rustup installation set Alter rustup settings completions Generate tab-completion scripts for your shell help Print this message or the help of the given subcommand(s) DISCUSSION: Rustup installs The Rust Programming Language from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling simpler with binary builds of the standard library for common platforms. If you are new to Rust consider running `rustup doc --book` to learn Rust. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_override_cmd_add_cmd_help_flag_stdout.toml000066400000000000000000000006761441327105200322100ustar00rootroot00000000000000bin.name = "rustup" args = ["override","add","--help"] stdout = """ ... Set the override toolchain for a directory USAGE: rustup[EXE] override set [OPTIONS] ARGS: Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: --path Path to the directory -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_override_cmd_help_flag_stdout.toml000066400000000000000000000021671441327105200305520ustar00rootroot00000000000000bin.name = "rustup" args = ["override","--help"] stdout = """ ... Modify directory toolchain overrides USAGE: rustup[EXE] override OPTIONS: -h, --help Print help information SUBCOMMANDS: list List directory toolchain overrides set Set the override toolchain for a directory unset Remove the override toolchain for a directory help Print this message or the help of the given subcommand(s) DISCUSSION: Overrides configure Rustup to use a specific toolchain when running in a specific directory. Directories can be assigned their own Rust toolchain with `rustup override`. When a directory has an override then any time `rustc` or `cargo` is run inside that directory, or one of its child directories, the override toolchain will be invoked. To pin to a specific nightly: $ rustup override set nightly-2014-12-18 Or a specific stable release: $ rustup override set 1.0.0 To see the active toolchain use `rustup show`. To remove the override and use the default toolchain again, `rustup override unset`. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_override_cmd_list_cmd_help_flag_stdout.toml000066400000000000000000000003251441327105200324220ustar00rootroot00000000000000bin.name = "rustup" args = ["override","list","--help"] stdout = """ ... List directory toolchain overrides USAGE: rustup[EXE] override list OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_override_cmd_remove_cmd_help_flag_stdout.toml000066400000000000000000000012401441327105200327410ustar00rootroot00000000000000bin.name = "rustup" args = ["override","unset","--help"] stdout = """ ... Remove the override toolchain for a directory USAGE: rustup[EXE] override unset [OPTIONS] OPTIONS: --path Path to the directory --nonexistent Remove override toolchain for all nonexistent directories -h, --help Print help information DISCUSSION: If `--path` argument is present, removes the override toolchain for the specified directory. If `--nonexistent` argument is present, removes the override toolchain for all nonexistent directories. Otherwise, removes the override toolchain for the current directory. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_override_cmd_set_cmd_help_flag_stdout.toml000066400000000000000000000006761441327105200322530ustar00rootroot00000000000000bin.name = "rustup" args = ["override","set","--help"] stdout = """ ... Set the override toolchain for a directory USAGE: rustup[EXE] override set [OPTIONS] ARGS: Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: --path Path to the directory -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_override_cmd_unset_cmd_help_flag_stdout.toml000066400000000000000000000012401441327105200326020ustar00rootroot00000000000000bin.name = "rustup" args = ["override","unset","--help"] stdout = """ ... Remove the override toolchain for a directory USAGE: rustup[EXE] override unset [OPTIONS] OPTIONS: --path Path to the directory --nonexistent Remove override toolchain for all nonexistent directories -h, --help Print help information DISCUSSION: If `--path` argument is present, removes the override toolchain for the specified directory. If `--nonexistent` argument is present, removes the override toolchain for all nonexistent directories. Otherwise, removes the override toolchain for the current directory. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_run_cmd_help_flag_stdout.toml000066400000000000000000000020301441327105200275240ustar00rootroot00000000000000bin.name = "rustup" args = ["run","--help"] stdout = """ ... Run a command with an environment configured for a given toolchain USAGE: rustup[EXE] run [OPTIONS] ... ARGS: Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` ... OPTIONS: --install Install the requested toolchain if needed -h, --help Print help information DISCUSSION: Configures an environment to use the given toolchain and then runs the specified program. The command may be any program, not just rustc or cargo. This can be used for testing arbitrary toolchains without setting an override. Commands explicitly proxied by `rustup` (such as `rustc` and `cargo`) also have a shorthand for this available. The toolchain can be set by using `+toolchain` as the first argument. These are equivalent: $ cargo +nightly build $ rustup run nightly cargo build """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_self_cmd_help_flag_stdout.toml000066400000000000000000000006751441327105200276660ustar00rootroot00000000000000bin.name = "rustup" args = ["self","--help"] stdout = """ ... Modify the rustup installation USAGE: rustup[EXE] self OPTIONS: -h, --help Print help information SUBCOMMANDS: update Download and install updates to rustup uninstall Uninstall rustup. upgrade-data Upgrade the internal data format. help Print this message or the help of the given subcommand(s) """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_self_cmd_uninstall_cmd_help_flag_stdout.toml000066400000000000000000000003431441327105200325720ustar00rootroot00000000000000bin.name = "rustup" args = ["self","uninstall","--help"] stdout = """ ... Uninstall rustup. USAGE: rustup[EXE] self uninstall [OPTIONS] OPTIONS: -y -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_self_cmd_update_cmd_help_flag_stdout.toml000066400000000000000000000003251441327105200320430ustar00rootroot00000000000000bin.name = "rustup" args = ["self","update","--help"] stdout = """ ... Download and install updates to rustup USAGE: rustup[EXE] self update OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_self_cmd_upgrade-data _cmd_help_flag_stdout.toml000066400000000000000000000003341441327105200331570ustar00rootroot00000000000000bin.name = "rustup" args = ["self","upgrade-data","--help"] stdout = """ ... Upgrade the internal data format. USAGE: rustup[EXE] self upgrade-data OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_set_cmd_auto-self-update_cmd_help_flag_stdout.toml000066400000000000000000000005401441327105200336010ustar00rootroot00000000000000bin.name = "rustup" args = ["set","auto-self-update","--help"] stdout = """ ... The rustup auto self update mode USAGE: rustup[EXE] set auto-self-update ARGS: [default: enable] [possible values: enable, disable, check-only] OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_set_cmd_default-host_cmd_help_flag_stdout.toml000066400000000000000000000004351441327105200330240ustar00rootroot00000000000000bin.name = "rustup" args = ["set","default-host","--help"] stdout = """ ... The triple used to identify toolchains when not specified USAGE: rustup[EXE] set default-host ARGS: OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_set_cmd_help_flag_stdout.toml000066400000000000000000000007431441327105200275240ustar00rootroot00000000000000bin.name = "rustup" args = ["set","--help"] stdout = """ ... Alter rustup settings USAGE: rustup[EXE] set OPTIONS: -h, --help Print help information SUBCOMMANDS: default-host The triple used to identify toolchains when not specified profile The default components installed auto-self-update The rustup auto self update mode help Print this message or the help of the given subcommand(s) """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_set_cmd_profile_cmd_help_flag_stdout.toml000066400000000000000000000004741441327105200320700ustar00rootroot00000000000000bin.name = "rustup" args = ["set","profile","--help"] stdout = """ ... The default components installed USAGE: rustup[EXE] set profile ARGS: [default: default] [possible values: minimal, default, complete] OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_show_cmd_active-toolchain_cmd_help_flag_stdout.toml000066400000000000000000000010501441327105200340350ustar00rootroot00000000000000bin.name = "rustup" args = ["show","active-toolchain","--help"] stdout = """ ... Show the active toolchain USAGE: rustup[EXE] show active-toolchain [OPTIONS] OPTIONS: -v, --verbose Enable verbose output with rustc information -h, --help Print help information DISCUSSION: Shows the name of the active toolchain. This is useful for figuring out the active tool chain from scripts. You should use `rustc --print sysroot` to get the sysroot, or `rustc --version` to get the toolchain version. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_show_cmd_help_flag_stdout.toml000066400000000000000000000016041441327105200277060ustar00rootroot00000000000000bin.name = "rustup" args = ["show","--help"] stdout = """ ... Show the active and installed toolchains or profiles USAGE: rustup[EXE] show [OPTIONS] [SUBCOMMAND] OPTIONS: -v, --verbose Enable verbose output with rustc information for all installed toolchains -h, --help Print help information SUBCOMMANDS: active-toolchain Show the active toolchain home Display the computed value of RUSTUP_HOME profile Show the current profile help Print this message or the help of the given subcommand(s) DISCUSSION: Shows the name of the active toolchain and the version of `rustc`. If the active toolchain has installed support for additional compilation targets, then they are listed as well. If there are multiple toolchains installed then all installed toolchains are listed as well. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_show_cmd_home_cmd_help_flag_stdout.toml000066400000000000000000000003241441327105200315370ustar00rootroot00000000000000bin.name = "rustup" args = ["show","home","--help"] stdout = """ ... Display the computed value of RUSTUP_HOME USAGE: rustup[EXE] show home OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_show_cmd_profile_cmd_help_flag_stdout.toml000066400000000000000000000003111441327105200322430ustar00rootroot00000000000000bin.name = "rustup" args = ["show","profile","--help"] stdout = """ ... Show the current profile USAGE: rustup[EXE] show profile OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_target_cmd_add_cmd_help_flag_stdout.toml000066400000000000000000000010011441327105200316360ustar00rootroot00000000000000bin.name = "rustup" args = ["target","add","--help"] stdout = """ ... Add a target to a Rust toolchain USAGE: rustup[EXE] target add [OPTIONS] ... ARGS: ... List of targets to install; \"all\" installs all available targets OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_target_cmd_help_flag_stdout.toml000066400000000000000000000007021441327105200302120ustar00rootroot00000000000000bin.name = "rustup" args = ["target","--help"] stdout = """ ... Modify a toolchain's supported targets USAGE: rustup[EXE] target OPTIONS: -h, --help Print help information SUBCOMMANDS: list List installed and available targets add Add a target to a Rust toolchain remove Remove a target from a Rust toolchain help Print this message or the help of the given subcommand(s) """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_target_cmd_list_cmd_help_flag_stdout.toml000066400000000000000000000007351441327105200320760ustar00rootroot00000000000000bin.name = "rustup" args = ["target","list","--help"] stdout = """ ... List installed and available targets USAGE: rustup[EXE] target list [OPTIONS] OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` --installed List only installed targets -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_target_cmd_remove_cmd_help_flag_stdout.toml000066400000000000000000000007461441327105200324220ustar00rootroot00000000000000bin.name = "rustup" args = ["target","remove","--help"] stdout = """ ... Remove a target from a Rust toolchain USAGE: rustup[EXE] target remove [OPTIONS] ... ARGS: ... List of targets to uninstall OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_toolchain_cmd_help_flag_stdout.toml000066400000000000000000000043331441327105200307100ustar00rootroot00000000000000bin.name = "rustup" args = ["toolchain","--help"] stdout = """ ... Modify or query the installed toolchains USAGE: rustup[EXE] toolchain OPTIONS: -h, --help Print help information SUBCOMMANDS: list List installed toolchains install Install or update a given toolchain uninstall Uninstall a toolchain link Create a custom toolchain by symlinking to a directory help Print this message or the help of the given subcommand(s) DISCUSSION: Many `rustup` commands deal with *toolchains*, a single installation of the Rust compiler. `rustup` supports multiple types of toolchains. The most basic track the official release channels: 'stable', 'beta' and 'nightly'; but `rustup` can also install toolchains from the official archives, for alternate host platforms, and from local builds. Standard release channel toolchain names have the following form: [-][-] = stable|beta|nightly|| = YYYY-MM-DD = 'channel' is a named release channel, a major and minor version number such as `1.42`, or a fully specified version number, such as `1.42.0`. Channel names can be optionally appended with an archive date, as in `nightly-2014-12-18`, in which case the toolchain is downloaded from the archive for that date. The host may be specified as a target triple. This is most useful for installing a 32-bit compiler on a 64-bit platform, or for installing the [MSVC-based toolchain] on Windows. For example: $ rustup toolchain install stable-x86_64-pc-windows-msvc For convenience, omitted elements of the target triple will be inferred, so the above could be written: $ rustup toolchain install stable-msvc The `rustup default` command may be used to both install and set the desired toolchain as default in a single command: $ rustup default stable-msvc rustup can also manage symlinked local toolchain builds, which are often used for developing Rust itself. For more information see `rustup toolchain help link`. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_toolchain_cmd_install_cmd_help_flag_stdout.toml000066400000000000000000000023431441327105200332600ustar00rootroot00000000000000bin.name = "rustup" args = ["toolchain","install","--help"] stdout = """ ... Install or update a given toolchain USAGE: rustup[EXE] toolchain install [OPTIONS] ... ARGS: ... Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: --profile [possible values: minimal, default, complete] -c, --component ... Add specific components on installation -t, --target ... Add specific targets on installation --no-self-update Don't perform self update when running the`rustup toolchain install` command --force Force an update, even if some components are missing --allow-downgrade Allow rustup to downgrade the toolchain to satisfy your component choice --force-non-host Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_toolchain_cmd_link_cmd_help_flag_stdout.toml000066400000000000000000000023431441327105200325470ustar00rootroot00000000000000bin.name = "rustup" args = ["toolchain","link","--help"] stdout = """ ... Create a custom toolchain by symlinking to a directory USAGE: rustup[EXE] toolchain link ARGS: Custom toolchain name Path to the directory OPTIONS: -h, --help Print help information DISCUSSION: 'toolchain' is the custom name to be assigned to the new toolchain. Any name is permitted as long as it does not fully match an initial substring of a standard release channel. For example, you can use the names 'latest' or '2017-04-01' but you cannot use 'stable' or 'beta-i686' or 'nightly-x86_64-unknown-linux-gnu'. 'path' specifies the directory where the binaries and libraries for the custom toolchain can be found. For example, when used for development of Rust itself, toolchains can be linked directly out of the build directory. After building, you can test out different compiler versions as follows: $ rustup toolchain link latest-stage1 build/x86_64-unknown-linux-gnu/stage1 $ rustup override set latest-stage1 If you now compile a crate in the current directory, the custom toolchain 'latest-stage1' will be used. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_toolchain_cmd_list_cmd_help_flag_stdout.toml000066400000000000000000000004411441327105200325620ustar00rootroot00000000000000bin.name = "rustup" args = ["toolchain","list","--help"] stdout = """ ... List installed toolchains USAGE: rustup[EXE] toolchain list [OPTIONS] OPTIONS: -v, --verbose Enable verbose output with toolchain information -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_toolchain_cmd_uninstall_cmd_help_flag_stdout.toml000066400000000000000000000006001441327105200336150ustar00rootroot00000000000000bin.name = "rustup" args = ["toolchain","uninstall","--help"] stdout = """ ... Uninstall a toolchain USAGE: rustup[EXE] toolchain uninstall ... ARGS: ... Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_unknown_arg_stdout.toml000066400000000000000000000006071441327105200264340ustar00rootroot00000000000000bin.name = "rustup" args = ["random"] status.code = 1 stdout = "" stderr = """ error: Invalid value \"random\" for '<+toolchain>': \"random\" is not a valid subcommand, so it was interpreted as a toolchain name, but it is also invalid. To override the toolchain using the 'rustup +toolchain' syntax, make sure to prefix the toolchain override with a '+' For more information try --help """ rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_up_cmd_help_flag_stdout.toml000066400000000000000000000017771441327105200273650ustar00rootroot00000000000000bin.name = "rustup" args = ["up","--help"] stdout = """ ... Update Rust toolchains and rustup USAGE: rustup[EXE] update [OPTIONS] [toolchain]... ARGS: ... Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: --no-self-update Don't perform self update when running the `rustup update` command --force Force an update, even if some components are missing --force-non-host Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains -h, --help Print help information DISCUSSION: With no toolchain specified, the `update` command updates each of the installed toolchains from the official release channels, then updates rustup itself. If given a toolchain argument then `update` updates that toolchain, the same as `rustup toolchain install`. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_update_cmd_help_flag_stdout.toml000066400000000000000000000020031441327105200302020ustar00rootroot00000000000000bin.name = "rustup" args = ["update","--help"] stdout = """ ... Update Rust toolchains and rustup USAGE: rustup[EXE] update [OPTIONS] [toolchain]... ARGS: ... Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: --no-self-update Don't perform self update when running the `rustup update` command --force Force an update, even if some components are missing --force-non-host Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains -h, --help Print help information DISCUSSION: With no toolchain specified, the `update` command updates each of the installed toolchains from the official release channels, then updates rustup itself. If given a toolchain argument then `update` updates that toolchain, the same as `rustup toolchain install`. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_upgrade_cmd_help_flag_stdout.toml000066400000000000000000000020041441327105200303500ustar00rootroot00000000000000bin.name = "rustup" args = ["upgrade","--help"] stdout = """ ... Update Rust toolchains and rustup USAGE: rustup[EXE] update [OPTIONS] [toolchain]... ARGS: ... Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` OPTIONS: --no-self-update Don't perform self update when running the `rustup update` command --force Force an update, even if some components are missing --force-non-host Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains -h, --help Print help information DISCUSSION: With no toolchain specified, the `update` command updates each of the installed toolchains from the official release channels, then updates rustup itself. If given a toolchain argument then `update` updates that toolchain, the same as `rustup toolchain install`. """ stderr = "" rustup-1.26.0/tests/suite/cli-ui/rustup/rustup_which_cmd_help_flag_stdout.toml000066400000000000000000000007031441327105200300270ustar00rootroot00000000000000bin.name = "rustup" args = ["which","--help"] stdout = """ ... Display which binary will be run for a given command USAGE: rustup[EXE] which [OPTIONS] ARGS: OPTIONS: --toolchain Toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain` -h, --help Print help information """ stderr = "" rustup-1.26.0/tests/suite/cli_exact.rs000066400000000000000000000566311441327105200177500ustar00rootroot00000000000000//! Yet more cli test cases. These are testing that the output //! is exactly as expected. use crate::mock::clitools::{self, set_current_dist_date, with_update_server, Config, Scenario}; use rustup::for_host; use rustup::test::this_host_triple; fn test(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::None, f); } #[test] fn update_once() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "update", "nightly"], for_host!( r" nightly-{0} installed - 1.3.0 (hash-nightly-2) " ), for_host!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: default toolchain set to 'nightly-{0}' " ), ); }) }); } #[test] fn update_once_and_check_self_update() { let test_version = "2.0.0"; test(&|config| { with_update_server(config, test_version, &|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok(&["rustup", "set", "auto-self-update", "check-only"]); let current_version = env!("CARGO_PKG_VERSION"); config.expect_ok_ex( &["rustup", "update", "nightly"], &format!( r" nightly-{} installed - 1.3.0 (hash-nightly-2) rustup - Update available : {} -> {} ", &this_host_triple(), current_version, test_version ), for_host!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' " ), ); }) }) }) } #[test] fn update_once_and_self_update() { let test_version = "2.0.0"; test(&|config| { with_update_server(config, test_version, &|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok(&["rustup", "set", "auto-self-update", "enable"]); config.expect_ok_ex( &["rustup", "update", "nightly"], for_host!( r" nightly-{0} installed - 1.3.0 (hash-nightly-2) " ), for_host!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: checking for self-update info: downloading self-update " ), ); }) }) }) } #[test] fn update_again() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "upgrade", "nightly"]); config.expect_ok_ex( &["rustup", "update", "nightly"], for_host!( r" nightly-{0} unchanged - 1.3.0 (hash-nightly-2) " ), for_host!( r"info: syncing channel updates for 'nightly-{0}' " ), ); config.expect_ok_ex( &["rustup", "upgrade", "nightly"], for_host!( r" nightly-{0} unchanged - 1.3.0 (hash-nightly-2) " ), for_host!( r"info: syncing channel updates for 'nightly-{0}' " ), ); }) }); } #[test] fn check_updates_none() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]); config.expect_stdout_ok( &["rustup", "check"], for_host!( r"stable-{0} - Up to date : 1.1.0 (hash-stable-1.1.0) beta-{0} - Up to date : 1.2.0 (hash-beta-1.2.0) nightly-{0} - Up to date : 1.3.0 (hash-nightly-2) " ), ); }) }) } #[test] fn check_updates_some() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]); }); config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_stdout_ok( &["rustup", "check"], for_host!( r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0) beta-{0} - Update available : 1.1.0 (hash-beta-1.1.0) -> 1.2.0 (hash-beta-1.2.0) nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2) " ), ); }) }) } #[test] fn check_updates_self() { let test_version = "2.0.0"; test(&|config| { with_update_server(config, test_version, &|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let current_version = env!("CARGO_PKG_VERSION"); config.expect_stdout_ok( &["rustup", "check"], &format!( r"rustup - Update available : {current_version} -> {test_version} " ), ); }) }) }) } #[test] fn check_updates_self_no_change() { let current_version = env!("CARGO_PKG_VERSION"); test(&|config| { with_update_server(config, current_version, &|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_stdout_ok( &["rustup", "check"], &format!( r"rustup - Up to date : {current_version} " ), ); }) }) }) } #[test] fn check_updates_with_update() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]); config.expect_stdout_ok( &["rustup", "check"], for_host!( r"stable-{0} - Up to date : 1.0.0 (hash-stable-1.0.0) beta-{0} - Up to date : 1.1.0 (hash-beta-1.1.0) nightly-{0} - Up to date : 1.2.0 (hash-nightly-1) " ), ); }); config.with_scenario(Scenario::SimpleV2, &|config | { config.expect_stdout_ok( &["rustup", "check"], for_host!( r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0) beta-{0} - Update available : 1.1.0 (hash-beta-1.1.0) -> 1.2.0 (hash-beta-1.2.0) nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2) " ), ); config.expect_ok(&["rustup", "update", "beta"]); config.expect_stdout_ok( &["rustup", "check"], for_host!( r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0) beta-{0} - Up to date : 1.2.0 (hash-beta-1.2.0) nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2) " ), ); }) }); } #[test] fn default() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "default", "nightly"], for_host!( r" nightly-{0} installed - 1.3.0 (hash-nightly-2) " ), for_host!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: default toolchain set to 'nightly-{0}' " ), ); }) }); } #[test] fn override_again() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok_ex( &["rustup", "override", "add", "nightly"], for_host!( r" nightly-{} unchanged - 1.3.0 (hash-nightly-2) " ), &format!( r"info: using existing install for 'nightly-{1}' info: override toolchain for '{}' set to 'nightly-{1}' ", cwd.display(), &this_host_triple() ), ); }) }); } #[test] fn remove_override() { for keyword in &["remove", "unset"] { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok_ex( &["rustup", "override", keyword], r"", &format!("info: override toolchain for '{}' removed\n", cwd.display()), ); }) }); } } #[test] fn remove_override_none() { for keyword in &["remove", "unset"] { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); config.expect_ok_ex( &["rustup", "override", keyword], r"", &format!( "info: no override toolchain for '{}' info: you may use `--path ` option to remove override toolchain for a specific path\n", cwd.display() ), ); }) }); } } #[test] fn remove_override_with_path() { for keyword in &["remove", "unset"] { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let dir = tempfile::Builder::new() .prefix("rustup-test") .tempdir() .unwrap(); config.change_dir(dir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); }); config.expect_ok_ex( &[ "rustup", "override", keyword, "--path", dir.path().to_str().unwrap(), ], r"", &format!( "info: override toolchain for '{}' removed\n", dir.path().display() ), ); }) }); } } #[test] fn remove_override_with_path_deleted() { for keyword in &["remove", "unset"] { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = { let dir = tempfile::Builder::new() .prefix("rustup-test") .tempdir() .unwrap(); let path = std::fs::canonicalize(dir.path()).unwrap(); config.change_dir(&path, &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); }); path }; config.expect_ok_ex( &[ "rustup", "override", keyword, "--path", path.to_str().unwrap(), ], r"", &format!( "info: override toolchain for '{}' removed\n", path.display() ), ); }) }); } } #[test] #[cfg_attr(target_os = "windows", ignore)] // FIXME #1103 fn remove_override_nonexistent() { for keyword in &["remove", "unset"] { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = { let dir = tempfile::Builder::new() .prefix("rustup-test") .tempdir() .unwrap(); let path = std::fs::canonicalize(dir.path()).unwrap(); config.change_dir(&path, &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); }); path }; // FIXME TempDir seems to succumb to difficulties removing dirs on windows let _ = rustup::utils::raw::remove_dir(&path); assert!(!path.exists()); config.expect_ok_ex( &["rustup", "override", keyword, "--nonexistent"], r"", &format!( "info: override toolchain for '{}' removed\n", path.display() ), ); }) }); } } #[test] fn list_overrides() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = std::fs::canonicalize(config.current_dir()).unwrap(); let mut cwd_formatted = format!("{}", cwd.display()); if cfg!(windows) { cwd_formatted = cwd_formatted[4..].to_owned(); } let trip = this_host_triple(); config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok_ex( &["rustup", "override", "list"], &format!( "{:<40}\t{:<20}\n", cwd_formatted, &format!("nightly-{trip}") ), r"", ); }) }); } #[test] fn list_overrides_with_nonexistent() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let trip = this_host_triple(); let nonexistent_path = { let dir = tempfile::Builder::new() .prefix("rustup-test") .tempdir() .unwrap(); config.change_dir(dir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); }); std::fs::canonicalize(dir.path()).unwrap() }; // FIXME TempDir seems to succumb to difficulties removing dirs on windows let _ = rustup::utils::raw::remove_dir(&nonexistent_path); assert!(!nonexistent_path.exists()); let mut path_formatted = format!("{}", nonexistent_path.display()); if cfg!(windows) { path_formatted = path_formatted[4..].to_owned(); } config.expect_ok_ex( &["rustup", "override", "list"], &format!( "{:<40}\t{:<20}\n\n", path_formatted + " (not a directory)", &format!("nightly-{trip}") ), "info: you may remove overrides for non-existent directories with `rustup override unset --nonexistent`\n", ); }) }); } #[test] fn update_no_manifest() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_err_ex( &["rustup", "update", "nightly-2016-01-01"], r"", for_host!( r"info: syncing channel updates for 'nightly-2016-01-01-{0}' error: no release found for 'nightly-2016-01-01' " ), ); }) }); } // Issue #111 #[test] fn update_invalid_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_err_ex( &["rustup", "update", "nightly-2016-03-1"], r"", r"info: syncing channel updates for 'nightly-2016-03-1' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) error: target '2016-03-1' not found in channel. Perhaps check https://doc.rust-lang.org/nightly/rustc/platform-support.html for available targets ", ); }) }); } #[test] fn default_invalid_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_err_ex( &["rustup", "default", "nightly-2016-03-1"], r"", r"info: syncing channel updates for 'nightly-2016-03-1' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) error: target '2016-03-1' not found in channel. Perhaps check https://doc.rust-lang.org/nightly/rustc/platform-support.html for available targets ", ); }) }); } #[test] fn default_none() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_stderr_ok( &["rustup", "default", "none"], "info: default toolchain unset", ); config.expect_err_ex( &["rustc", "--version"], "", "error: rustup could not choose a version of rustc to run, because one wasn't specified explicitly, and no default is configured. help: run 'rustup default stable' to download the latest stable release of Rust and set it as your default toolchain. ", ); }) }) } #[test] fn list_targets() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let trip = this_host_triple(); let mut sorted = vec![ format!("{} (installed)", &*trip), format!("{} (installed)", clitools::CROSS_ARCH1), clitools::CROSS_ARCH2.to_string(), ]; sorted.sort(); let expected = format!("{}\n{}\n{}\n", sorted[0], sorted[1], sorted[2]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_ok_ex(&["rustup", "target", "list"], &expected, r""); }) }); } #[test] fn list_installed_targets() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let trip = this_host_triple(); let mut sorted = vec![ trip, clitools::CROSS_ARCH1.to_string(), clitools::CROSS_ARCH2.to_string(), ]; sorted.sort(); let expected = format!("{}\n{}\n{}\n", sorted[0], sorted[1], sorted[2]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH2]); config.expect_ok_ex(&["rustup", "target", "list", "--installed"], &expected, r""); }) }); } #[test] fn cross_install_indicates_target() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); // TODO error 'nightly-x86_64-apple-darwin' is not installed config.expect_ok_ex( &["rustup", "target", "add", clitools::CROSS_ARCH1], r"", &format!( r"info: downloading component 'rust-std' for '{0}' info: installing component 'rust-std' for '{0}' ", clitools::CROSS_ARCH1 ), ); }) }); } // issue #927 #[test] fn undefined_linked_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_err_ex( &["cargo", "+bogus", "test"], r"", "error: toolchain 'bogus' is not installed\n", ); }) }); } #[test] fn install_by_version_number() { test(&|config| { config.with_scenario(Scenario::ArchivesV2TwoVersions, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "0.100.99"]); }) }) } // issue #2191 #[test] fn install_unreleased_component() { clitools::test(Scenario::MissingComponentMulti, &|config| { // Initial channel content is host + rls + multiarch-std set_current_dist_date(config, "2019-09-12"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_ok(&["rustup", "target", "add", clitools::MULTI_ARCH1]); // Next channel variant should have host + rls but not multiarch-std set_current_dist_date(config, "2019-09-13"); config.expect_ok_ex( &["rustup", "update", "nightly"], for_host!( r" nightly-{} unchanged - 1.37.0 (hash-nightly-1) " ), &format!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2019-09-13, rust version 1.37.0 (hash-nightly-2) info: skipping nightly which is missing installed component 'rust-std-{1}' info: syncing channel updates for 'nightly-2019-09-12-{0}' ", this_host_triple(), clitools::MULTI_ARCH1 ), ); // Next channel variant should have host + multiarch-std but have rls missing set_current_dist_date(config, "2019-09-14"); config.expect_ok_ex( &["rustup", "update", "nightly"], for_host!( r" nightly-{} unchanged - 1.37.0 (hash-nightly-1) " ), &format!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2019-09-14, rust version 1.37.0 (hash-nightly-3) info: skipping nightly which is missing installed component 'rls' info: syncing channel updates for 'nightly-2019-09-13-{0}' info: latest update on 2019-09-13, rust version 1.37.0 (hash-nightly-2) info: skipping nightly which is missing installed component 'rust-std-{1}' info: syncing channel updates for 'nightly-2019-09-12-{0}' ", this_host_triple(), clitools::MULTI_ARCH1, ), ); }) } rustup-1.26.0/tests/suite/cli_inst_interactive.rs000066400000000000000000000420451441327105200222100ustar00rootroot00000000000000//! Tests of the interactive console installer use std::env::consts::EXE_SUFFIX; use std::io::Write; use std::process::Stdio; use rustup::for_host; use rustup::test::this_host_triple; use rustup::test::with_saved_path; use rustup::utils::raw; use crate::mock::clitools::{self, set_current_dist_date, Config, SanitizedOutput, Scenario}; fn run_input(config: &Config, args: &[&str], input: &str) -> SanitizedOutput { run_input_with_env(config, args, input, &[]) } fn run_input_with_env( config: &Config, args: &[&str], input: &str, env: &[(&str, &str)], ) -> SanitizedOutput { let mut cmd = clitools::cmd(config, args[0], &args[1..]); clitools::env(config, &mut cmd); for (key, value) in env.iter() { cmd.env(key, value); } cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); let mut child = cmd.spawn().unwrap(); child .stdin .as_mut() .unwrap() .write_all(input.as_bytes()) .unwrap(); let out = child.wait_with_output().unwrap(); SanitizedOutput { ok: out.status.success(), stdout: String::from_utf8(out.stdout).unwrap(), stderr: String::from_utf8(out.stderr).unwrap(), } } #[test] fn update() { clitools::test(Scenario::SimpleV2, &|config| { with_saved_path(&mut || { run_input(config, &["rustup-init"], "\n\n"); let out = run_input(config, &["rustup-init"], "\n\n"); assert!(out.ok, "stdout:\n{}\nstderr:\n{}", out.stdout, out.stderr); }) }); } // Testing that the right number of blank lines are printed after the // 'pre-install' message and before the 'post-install' message - overall smoke // test for the install case. #[test] fn smoke_case_install_no_modify_path() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init", "--no-modify-path"], "\n\n"); assert!(out.ok); // During an interactive session, after "Press the Enter // key..." the UI emits a blank line, then there is a blank // line that comes from the user pressing enter, then log // output on stderr, then an explicit blank line on stdout // before printing $toolchain installed assert!( out.stdout.contains(for_host!( 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 triple: {0} default toolchain: stable (default) profile: default modify PATH variable: no 1) Proceed with installation (default) 2) Customize installation 3) Cancel installation > stable-{0} installed - 1.1.0 (hash-stable-1.1.0) Rust is installed now. Great! " )), "pattern not found in \"\"\"{}\"\"\"", out.stdout ); if cfg!(unix) { assert!(!config.homedir.join(".profile").exists()); assert!(config.cargodir.join("env").exists()); } }); } #[test] fn smoke_case_install_with_path_install() { clitools::test(Scenario::SimpleV2, &|config| { with_saved_path(&mut || { let out = run_input(config, &["rustup-init"], "\n\n"); assert!(out.ok); assert!(!out .stdout .contains("This path needs to be in your PATH environment variable")); }) }); } #[test] fn blank_lines_around_stderr_log_output_update() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let out = run_input( config, &["rustup-init", "--no-update-default-toolchain"], "\n\n", ); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" 3) Cancel installation > Rust is installed now. Great! " )); }); } #[test] fn installer_shows_default_host_triple() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init", "--no-modify-path"], "2\n"); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains(for_host!( r" Default host triple? [{0}] " ))); }); } #[test] fn installer_shows_default_toolchain_as_stable() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init", "--no-modify-path"], "2\n\n"); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" Default toolchain? (stable/beta/nightly/none) [stable] " )); }); } #[test] fn installer_shows_default_toolchain_when_set_in_args() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &[ "rustup-init", "--no-modify-path", "--default-toolchain=nightly", ], "2\n\n", ); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" Default toolchain? (stable/beta/nightly/none) [nightly] " )); }); } #[test] fn installer_shows_default_profile() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init", "--no-modify-path"], "2\n\n\n"); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" Profile (which tools and data to install)? (minimal/default/complete) [default] " )); }); } #[test] fn installer_shows_default_profile_when_set_in_args() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &["rustup-init", "--no-modify-path", "--profile=minimal"], "2\n\n\n", ); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" Profile (which tools and data to install)? (minimal/default/complete) [minimal] " )); }); } #[test] fn installer_shows_default_for_modify_path() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init"], "2\n\n\n\n"); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" Modify PATH variable? (Y/n) " )); }); } #[test] fn installer_shows_default_for_modify_path_when_set_with_args() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init", "--no-modify-path"], "2\n\n\n\n"); println!("-- stdout --\n {}", out.stdout); println!("-- stderr --\n {}", out.stderr); assert!(out.stdout.contains( r" Modify PATH variable? (y/N) " )); }); } #[test] fn user_says_nope() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input(config, &["rustup-init", "--no-modify-path"], "n\n\n"); assert!(out.ok); assert!(!config.cargodir.join("bin").exists()); }); } #[test] fn with_no_toolchain() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &[ "rustup-init", "--no-modify-path", "--default-toolchain=none", ], "\n\n", ); assert!(out.ok); config.expect_stdout_ok(&["rustup", "show"], "no active toolchain"); }); } #[test] fn with_non_default_toolchain_still_prompts() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &[ "rustup-init", "--no-modify-path", "--default-toolchain=nightly", ], "\n\n", ); assert!(out.ok); config.expect_stdout_ok(&["rustup", "show"], "nightly"); }); } #[test] fn with_non_release_channel_non_default_toolchain() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &[ "rustup-init", "--no-modify-path", "--default-toolchain=nightly-2015-01-02", ], "\n\n", ); assert!(out.ok); config.expect_stdout_ok(&["rustup", "show"], "nightly"); config.expect_stdout_ok(&["rustup", "show"], "2015-01-02"); }); } #[test] fn set_nightly_toolchain() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &["rustup-init", "--no-modify-path"], "2\n\nnightly\n\n\n\n\n", ); assert!(out.ok); config.expect_stdout_ok(&["rustup", "show"], "nightly"); }); } #[test] fn set_no_modify_path() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &["rustup-init", "--no-modify-path"], "2\n\n\n\nno\n\n\n", ); assert!(out.ok); if cfg!(unix) { assert!(!config.homedir.join(".profile").exists()); } }); } #[test] fn set_nightly_toolchain_and_unset() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &["rustup-init", "--no-modify-path"], "2\n\nnightly\n\n\n2\n\nbeta\n\n\n\n\n", ); println!("{:?}", out.stderr); println!("{:?}", out.stdout); assert!(out.ok); config.expect_stdout_ok(&["rustup", "show"], "beta"); }); } #[test] fn user_says_nope_after_advanced_install() { clitools::test(Scenario::SimpleV2, &|config| { let out = run_input( config, &["rustup-init", "--no-modify-path"], "2\n\n\n\n\nn\n\n\n", ); assert!(out.ok); assert!(!config.cargodir.join("bin").exists()); }); } #[test] fn install_with_components() { fn go(comp_args: &[&str]) { let mut args = vec!["rustup-init", "-y", "--no-modify-path"]; args.extend_from_slice(comp_args); clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&args); config.expect_stdout_ok(&["rustup", "component", "list"], "rust-src (installed)"); config.expect_stdout_ok( &["rustup", "component", "list"], &format!("rust-analysis-{} (installed)", this_host_triple()), ); }) } go(&["-c", "rust-src", "-c", "rust-analysis"]); go(&["-c", "rust-src,rust-analysis"]); } #[test] fn install_forces_and_skips_rls() { clitools::test(Scenario::UnavailableRls, &|config| { set_current_dist_date(config, "2015-01-01"); let out = run_input( config, &[ "rustup-init", "--profile", "complete", "--default-toolchain", "nightly", "--no-modify-path", ], "\n\n", ); assert!(out.ok); assert!(out .stderr .contains("warning: Force-skipping unavailable component")); }); } #[test] fn test_warn_if_complete_profile_is_used() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_stderr_ok( &[ "rustup-init", "-y", "--profile", "complete", "--no-modify-path", ], "warning: downloading with complete profile", ); }); } #[test] fn test_prompt_fail_if_rustup_sh_already_installed_reply_nothing() { clitools::test(Scenario::SimpleV2, &|config| { config.create_rustup_sh_metadata(); let out = run_input(config, &["rustup-init", "--no-modify-path"], "\n"); assert!(!out.ok); assert!(out .stderr .contains("warning: it looks like you have existing rustup.sh metadata")); assert!(out .stderr .contains("error: cannot install while rustup.sh is installed")); assert!(out.stdout.contains("Continue? (y/N)")); }) } #[test] fn test_prompt_fail_if_rustup_sh_already_installed_reply_no() { clitools::test(Scenario::SimpleV2, &|config| { config.create_rustup_sh_metadata(); let out = run_input(config, &["rustup-init", "--no-modify-path"], "no\n"); assert!(!out.ok); assert!(out .stderr .contains("warning: it looks like you have existing rustup.sh metadata")); assert!(out .stderr .contains("error: cannot install while rustup.sh is installed")); assert!(out.stdout.contains("Continue? (y/N)")); }) } #[test] fn test_prompt_succeed_if_rustup_sh_already_installed_reply_yes() { clitools::test(Scenario::SimpleV2, &|config| { config.create_rustup_sh_metadata(); let out = run_input(config, &["rustup-init", "--no-modify-path"], "yes\n\n\n"); assert!(out .stderr .contains("warning: it looks like you have existing rustup.sh metadata")); assert!(out .stderr .contains("error: cannot install while rustup.sh is installed")); assert!(out.stdout.contains("Continue? (y/N)")); assert!(!out.stdout.contains( "warning: continuing (because the -y flag is set and the error is ignorable)" )); assert!(out.ok); }) } #[test] fn installing_when_already_installed_updates_toolchain() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let out = run_input(config, &["rustup-init", "--no-modify-path"], "\n\n"); println!("stdout:\n{}\n...\n", out.stdout); assert!(out .stdout .contains(for_host!("stable-{} unchanged - 1.1.0 (hash-stable-1.1.0)"))); }) } #[test] fn install_stops_if_rustc_exists() { let temp_dir = tempfile::Builder::new() .prefix("fakebin") .tempdir() .unwrap(); // Create fake executable let fake_exe = temp_dir.path().join(format!("{}{}", "rustc", EXE_SUFFIX)); raw::append_file(&fake_exe, "").unwrap(); let temp_dir_path = temp_dir.path().to_str().unwrap(); clitools::test(Scenario::SimpleV2, &|config| { let out = config.run( "rustup-init", &["--no-modify-path"], &[ ("RUSTUP_INIT_SKIP_PATH_CHECK", "no"), ("PATH", temp_dir_path), ], ); assert!(!out.ok); assert!(out .stderr .contains("it looks like you have an existing installation of Rust at:")); assert!(out .stderr .contains("If you are sure that you want both rustup and your already installed Rust")); }); } #[test] fn install_stops_if_cargo_exists() { let temp_dir = tempfile::Builder::new() .prefix("fakebin") .tempdir() .unwrap(); // Create fake executable let fake_exe = temp_dir.path().join(format!("{}{}", "cargo", EXE_SUFFIX)); raw::append_file(&fake_exe, "").unwrap(); let temp_dir_path = temp_dir.path().to_str().unwrap(); clitools::test(Scenario::SimpleV2, &|config| { let out = config.run( "rustup-init", &["--no-modify-path"], &[ ("RUSTUP_INIT_SKIP_PATH_CHECK", "no"), ("PATH", temp_dir_path), ], ); assert!(!out.ok); assert!(out .stderr .contains("it looks like you have an existing installation of Rust at:")); assert!(out .stderr .contains("If you are sure that you want both rustup and your already installed Rust")); }); } #[test] fn with_no_prompt_install_succeeds_if_rustc_exists() { let temp_dir = tempfile::Builder::new() .prefix("fakebin") .tempdir() .unwrap(); // Create fake executable let fake_exe = temp_dir.path().join(format!("{}{}", "rustc", EXE_SUFFIX)); raw::append_file(&fake_exe, "").unwrap(); let temp_dir_path = temp_dir.path().to_str().unwrap(); clitools::test(Scenario::SimpleV2, &|config| { let out = config.run( "rustup-init", &["-y", "--no-modify-path"], &[ ("RUSTUP_INIT_SKIP_PATH_CHECK", "no"), ("PATH", temp_dir_path), ], ); assert!(out.ok); }); } // Issue 2547 #[test] fn install_non_installable_toolchain() { clitools::test(Scenario::Unavailable, &|config| { config.expect_err( &[ "rustup-init", "-y", "--no-modify-path", "--default-toolchain", "nightly", ], "is not installable", ); }) } rustup-1.26.0/tests/suite/cli_misc.rs000066400000000000000000000765011441327105200175750ustar00rootroot00000000000000//! Test cases of the rustup command that do not depend on the //! dist server, mostly derived from multirust/test-v2.sh use std::str; use std::{env::consts::EXE_SUFFIX, path::Path}; use rustup::for_host; use rustup::test::this_host_triple; use rustup::utils::utils; use crate::mock::clitools::{self, set_current_dist_date, Config, Scenario}; pub fn setup(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::SimpleV2, f); } #[test] fn smoke_test() { setup(&|config| { config.expect_ok(&["rustup", "--version"]); }); } #[test] fn version_mentions_rustc_version_confusion() { setup(&|config| { let out = config.run("rustup", &vec!["--version"], &[]); assert!(out.ok); assert!(out .stderr .contains("This is the version for the rustup toolchain manager")); let out = config.run("rustup", &vec!["+nightly", "--version"], &[]); assert!(out.ok); assert!(out .stderr .contains("The currently active `rustc` version is `1.3.0")); }); } #[test] fn no_colors_in_piped_error_output() { setup(&|config| { let args: Vec<&str> = vec![]; let out = config.run("rustc", &args, &[]); assert!(!out.ok); assert!(!out.stderr.contains('\x1b')); }); } #[test] fn rustc_with_bad_rustup_toolchain_env_var() { setup(&|config| { let args: Vec<&str> = vec![]; let out = config.run("rustc", &args, &[("RUSTUP_TOOLCHAIN", "bogus")]); assert!(!out.ok); assert!(out.stderr.contains("toolchain 'bogus' is not installed")); }); } #[test] fn custom_invalid_names() { setup(&|config| { config.expect_err( &["rustup", "toolchain", "link", "nightly", "foo"], for_host!("invalid custom toolchain name: 'nightly-{0}'"), ); config.expect_err( &["rustup", "toolchain", "link", "beta", "foo"], for_host!("invalid custom toolchain name: 'beta-{0}'"), ); config.expect_err( &["rustup", "toolchain", "link", "stable", "foo"], for_host!("invalid custom toolchain name: 'stable-{0}'"), ); }); } #[test] fn custom_invalid_names_with_archive_dates() { setup(&|config| { config.expect_err( &["rustup", "toolchain", "link", "nightly-2015-01-01", "foo"], for_host!("invalid custom toolchain name: 'nightly-2015-01-01-{0}'"), ); config.expect_err( &["rustup", "toolchain", "link", "beta-2015-01-01", "foo"], for_host!("invalid custom toolchain name: 'beta-2015-01-01-{0}'"), ); config.expect_err( &["rustup", "toolchain", "link", "stable-2015-01-01", "foo"], for_host!("invalid custom toolchain name: 'stable-2015-01-01-{0}'"), ); }); } // Regression test for newline placement #[test] fn update_all_no_update_whitespace() { setup(&|config| { config.expect_stdout_ok( &["rustup", "update", "nightly"], for_host!( r" nightly-{} installed - 1.3.0 (hash-nightly-2) " ), ); }); } // Issue #145 #[test] fn update_works_without_term() { setup(&|config| { let mut cmd = clitools::cmd(config, "rustup", ["update", "nightly"]); clitools::env(config, &mut cmd); cmd.env_remove("TERM"); let out = cmd.output().unwrap(); assert!(out.status.success()); }); } // Issue #1738 #[test] fn show_works_with_dumb_term() { setup(&|config| { let mut cmd = clitools::cmd(config, "rustup", ["show"]); clitools::env(config, &mut cmd); cmd.env("TERM", "dumb"); assert!(cmd.spawn().unwrap().wait().unwrap().success()); }); } // Issue #2425 // Exit with error and help output when called without subcommand. #[test] fn subcommand_required_for_target() { setup(&|config| { let mut cmd = clitools::cmd(config, "rustup", ["target"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(!out.status.success()); assert_eq!(out.status.code().unwrap(), 1); assert!(str::from_utf8(&out.stdout).unwrap().contains("USAGE")); }); } // Issue #2425 // Exit with error and help output when called without subcommand. #[test] fn subcommand_required_for_toolchain() { setup(&|config| { let mut cmd = clitools::cmd(config, "rustup", ["toolchain"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(!out.status.success()); assert_eq!(out.status.code().unwrap(), 1); assert!(str::from_utf8(&out.stdout).unwrap().contains("USAGE")); }); } // Issue #2425 // Exit with error and help output when called without subcommand. #[test] fn subcommand_required_for_override() { setup(&|config| { let mut cmd = clitools::cmd(config, "rustup", ["override"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(!out.status.success()); assert_eq!(out.status.code().unwrap(), 1); assert!(str::from_utf8(&out.stdout).unwrap().contains("USAGE")); }); } // Issue #2425 // Exit with error and help output when called without subcommand. #[test] fn subcommand_required_for_self() { setup(&|config| { let mut cmd = clitools::cmd(config, "rustup", ["self"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(!out.status.success()); assert_eq!(out.status.code().unwrap(), 1); assert!(str::from_utf8(&out.stdout).unwrap().contains("USAGE")); }); } #[test] fn multi_host_smoke_test() { // We cannot run this test if the current host triple is equal to the // multi-arch triple, but this should never be the case. Check that just // to be sure. assert_ne!(this_host_triple(), clitools::MULTI_ARCH1); clitools::test(Scenario::MultiHost, &|config| { let toolchain = format!("nightly-{}", clitools::MULTI_ARCH1); config.expect_ok(&["rustup", "default", &toolchain]); config.expect_stdout_ok(&["rustc", "--version"], "xxxx-nightly-2"); // cross-host mocks have their own versions }); } #[test] fn custom_toolchain_cargo_fallback_proxy() { setup(&|config| { let path = config.customdir.join("custom-1"); config.expect_ok(&[ "rustup", "toolchain", "link", "mytoolchain", &path.to_string_lossy(), ]); config.expect_ok(&["rustup", "default", "mytoolchain"]); config.expect_ok(&["rustup", "update", "stable"]); config.expect_stdout_ok(&["cargo", "--version"], "hash-stable-1.1.0"); config.expect_ok(&["rustup", "update", "beta"]); config.expect_stdout_ok(&["cargo", "--version"], "hash-beta-1.2.0"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["cargo", "--version"], "hash-nightly-2"); }); } #[test] fn custom_toolchain_cargo_fallback_run() { setup(&|config| { let path = config.customdir.join("custom-1"); config.expect_ok(&[ "rustup", "toolchain", "link", "mytoolchain", &path.to_string_lossy(), ]); config.expect_ok(&["rustup", "default", "mytoolchain"]); config.expect_ok(&["rustup", "update", "stable"]); config.expect_stdout_ok( &["rustup", "run", "mytoolchain", "cargo", "--version"], "hash-stable-1.1.0", ); config.expect_ok(&["rustup", "update", "beta"]); config.expect_stdout_ok( &["rustup", "run", "mytoolchain", "cargo", "--version"], "hash-beta-1.2.0", ); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok( &["rustup", "run", "mytoolchain", "cargo", "--version"], "hash-nightly-2", ); }); } #[test] fn rustup_run_searches_path() { setup(&|config| { #[cfg(windows)] let hello_cmd = &["rustup", "run", "nightly", "cmd", "/C", "echo hello"]; #[cfg(not(windows))] let hello_cmd = &["rustup", "run", "nightly", "sh", "-c", "echo hello"]; config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(hello_cmd, "hello"); }); } #[test] fn rustup_doesnt_prepend_path_unnecessarily() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); let expect_stderr_ok_env_first_then = |config: &Config, args: &[&str], env: &[(&str, &str)], first: &Path, second: Option<&Path>| { let out = config.run(args[0], &args[1..], env); let first_then_second = |list: &str| -> bool { let mut saw_first = false; let mut saw_second = false; for path in std::env::split_paths(list) { if path == first { if saw_second { return false; } saw_first = true; } if Some(&*path) == second { if !saw_first { return false; } saw_second = true; } } true }; if !out.ok || !first_then_second(&out.stderr) { clitools::print_command(args, &out); println!("expected.ok: true"); clitools::print_indented( "expected.stderr.first_then", &format!("{} comes before {:?}", first.display(), second), ); panic!(); } }; // For all of these, CARGO_HOME/bin will be auto-prepended. let cargo_home_bin = config.cargodir.join("bin"); expect_stderr_ok_env_first_then( config, &["cargo", "--echo-path"], &[], &cargo_home_bin, None, ); expect_stderr_ok_env_first_then( config, &["cargo", "--echo-path"], &[("PATH", "")], &cargo_home_bin, None, ); // Check that CARGO_HOME/bin is prepended to path. expect_stderr_ok_env_first_then( config, &["cargo", "--echo-path"], &[("PATH", &format!("{}", config.exedir.display()))], &cargo_home_bin, Some(&config.exedir), ); // But if CARGO_HOME/bin is already on PATH, it will not be prepended again, // so exedir will take precedence. expect_stderr_ok_env_first_then( config, &["cargo", "--echo-path"], &[( "PATH", std::env::join_paths([&config.exedir, &cargo_home_bin]) .unwrap() .to_str() .unwrap(), )], &config.exedir, Some(&cargo_home_bin), ); }); } #[test] fn rustup_failed_path_search() { setup(&|config| { use std::env::consts::EXE_SUFFIX; let rustup_path = config.exedir.join(format!("rustup{EXE_SUFFIX}")); let tool_path = config.exedir.join(format!("fake_proxy{EXE_SUFFIX}")); utils::hardlink_file(&rustup_path, &tool_path) .expect("Failed to create fake proxy for test"); config.expect_ok(&[ "rustup", "toolchain", "link", "custom", &config.customdir.join("custom-1").to_string_lossy(), ]); config.expect_ok(&["rustup", "default", "custom"]); let broken = &["rustup", "run", "custom", "fake_proxy"]; config.expect_err( broken, "unknown proxy name: 'fake_proxy'; valid proxy names are \ 'rustc', 'rustdoc', 'cargo', 'rust-lldb', 'rust-gdb', 'rust-gdbgui', \ 'rls', 'cargo-clippy', 'clippy-driver', 'cargo-miri', \ 'rust-analyzer', 'rustfmt', 'cargo-fmt'", ); // Hardlink will be automatically cleaned up by test setup code }); } #[test] fn rustup_failed_path_search_toolchain() { setup(&|config| { use std::env::consts::EXE_SUFFIX; let rustup_path = config.exedir.join(format!("rustup{EXE_SUFFIX}")); let tool_path = config.exedir.join(format!("cargo-miri{EXE_SUFFIX}")); utils::hardlink_file(&rustup_path, &tool_path) .expect("Failed to create fake cargo-miri for test"); config.expect_ok(&[ "rustup", "toolchain", "link", "custom-1", &config.customdir.join("custom-1").to_string_lossy(), ]); config.expect_ok(&[ "rustup", "toolchain", "link", "custom-2", &config.customdir.join("custom-2").to_string_lossy(), ]); config.expect_ok(&["rustup", "default", "custom-2"]); let broken = &["rustup", "run", "custom-1", "cargo-miri"]; config.expect_err(broken, "cannot use `rustup component add`"); let broken = &["rustup", "run", "custom-2", "cargo-miri"]; config.expect_err(broken, "cannot use `rustup component add`"); // Hardlink will be automatically cleaned up by test setup code }); } #[test] fn rustup_run_not_installed() { setup(&|config| { config.expect_ok(&["rustup", "install", "stable"]); config.expect_err( &["rustup", "run", "nightly", "rustc", "--version"], for_host!("toolchain 'nightly-{0}' is not installed"), ); }); } #[test] fn rustup_run_install() { setup(&|config| { config.expect_ok(&["rustup", "install", "stable"]); config.expect_stderr_ok( &[ "rustup", "run", "--install", "nightly", "cargo", "--version", ], "info: installing component 'rustc'", ); }); } #[test] fn toolchains_are_resolved_early() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); let full_toolchain = format!("nightly-{}", this_host_triple()); config.expect_stderr_ok( &["rustup", "default", &full_toolchain], &format!("info: using existing install for '{full_toolchain}'"), ); }); } #[test] fn no_panic_on_default_toolchain_missing() { setup(&|config| { config.expect_err(&["rustup", "default"], "no default toolchain configured"); }); } // #190 #[test] fn proxies_pass_empty_args() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "run", "nightly", "rustc", "--empty-arg-test", ""]); }); } #[test] fn rls_exists_in_toolchain() { setup(&|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "component", "add", "rls"]); assert!(config.exedir.join(format!("rls{EXE_SUFFIX}")).exists()); config.expect_ok(&["rls", "--version"]); }); } #[test] fn run_rls_when_not_available_in_toolchain() { clitools::test(Scenario::UnavailableRls, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rls", "--version"], &format!( "the 'rls' component which provides the command 'rls{}' is not available for the 'nightly-{}' toolchain", EXE_SUFFIX, this_host_triple(), ), ); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_ok(&["rls", "--version"]); }); } #[test] fn run_rls_when_not_installed() { setup(&|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_err( &["rls", "--version"], &format!( "'rls{}' is not installed for the toolchain 'stable-{}'\nTo install, run `rustup component add rls`", EXE_SUFFIX, this_host_triple(), ), ); }); } #[test] fn run_rust_lldb_when_not_in_toolchain() { clitools::test(Scenario::UnavailableRls, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rust-lldb", "--version"], &format!( "the 'rust-lldb{}' binary, normally provided by the 'rustc' component, is not applicable to the 'nightly-{}' toolchain", EXE_SUFFIX, this_host_triple(), ), ); }); } #[test] fn rename_rls_before() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "component", "add", "rls"]); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); assert!(config.exedir.join(format!("rls{EXE_SUFFIX}")).exists()); config.expect_ok(&["rls", "--version"]); }); } #[test] fn rename_rls_after() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); config.expect_ok(&["rustup", "component", "add", "rls-preview"]); assert!(config.exedir.join(format!("rls{EXE_SUFFIX}")).exists()); config.expect_ok(&["rls", "--version"]); }); } #[test] fn rename_rls_add_old_name() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); config.expect_ok(&["rustup", "component", "add", "rls"]); assert!(config.exedir.join(format!("rls{EXE_SUFFIX}")).exists()); config.expect_ok(&["rls", "--version"]); }); } #[test] fn rename_rls_list() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); config.expect_ok(&["rustup", "component", "add", "rls"]); let out = config.run("rustup", &["component", "list"], &[]); assert!(out.ok); assert!(out.stdout.contains(&format!("rls-{}", this_host_triple()))); }); } #[test] fn rename_rls_preview_list() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); config.expect_ok(&["rustup", "component", "add", "rls-preview"]); let out = config.run("rustup", &["component", "list"], &[]); assert!(out.ok); assert!(out.stdout.contains(&format!("rls-{}", this_host_triple()))); }); } #[test] fn rename_rls_remove() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update"]); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_ok(&["rls", "--version"]); config.expect_ok(&["rustup", "component", "remove", "rls"]); config.expect_err( &["rls", "--version"], &format!("'rls{EXE_SUFFIX}' is not installed"), ); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_ok(&["rls", "--version"]); config.expect_ok(&["rustup", "component", "remove", "rls-preview"]); config.expect_err( &["rls", "--version"], &format!("'rls{EXE_SUFFIX}' is not installed"), ); }); } // issue #1169 #[test] #[cfg(any(unix, windows))] fn toolchain_broken_symlink() { use std::fs; use std::path::Path; #[cfg(unix)] fn create_symlink_dir, Q: AsRef>(src: P, dst: Q) { use std::os::unix::fs; fs::symlink(src, dst).unwrap(); } #[cfg(windows)] fn create_symlink_dir, Q: AsRef>(src: P, dst: Q) { use std::os::windows::fs; fs::symlink_dir(src, dst).unwrap(); } setup(&|config| { // We artificially create a broken symlink toolchain -- but this can also happen "legitimately" // by having a proper toolchain there, using "toolchain link", and later removing the directory. fs::create_dir(config.rustupdir.join("toolchains")).unwrap(); create_symlink_dir( config.rustupdir.join("this-directory-does-not-exist"), config.rustupdir.join("toolchains").join("test"), ); // Make sure this "fake install" actually worked config.expect_ok_ex(&["rustup", "toolchain", "list"], "test\n", ""); // Now try to uninstall it. That should work only once. config.expect_ok_ex( &["rustup", "toolchain", "uninstall", "test"], "", r"info: uninstalling toolchain 'test' info: toolchain 'test' uninstalled ", ); config.expect_ok_ex( &["rustup", "toolchain", "uninstall", "test"], "", r"info: no toolchain installed for 'test' ", ); }); } // issue #1297 #[test] fn update_unavailable_rustc() { clitools::test(Scenario::Unavailable, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); // latest nightly is unavailable set_current_dist_date(config, "2015-01-02"); // update should do nothing config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); }); } // issue 2562 #[test] fn install_unavailable_platform() { clitools::test(Scenario::Unavailable, &|config| { set_current_dist_date(config, "2015-01-02"); // explicit attempt to install should fail config.expect_err( &["rustup", "toolchain", "install", "nightly"], "is not installable", ); // implicit attempt to install should fail config.expect_err(&["rustup", "default", "nightly"], "is not installable"); }); } #[test] fn update_nightly_even_with_incompat() { clitools::test(Scenario::MissingComponent, &|config| { set_current_dist_date(config, "2019-09-12"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_component_executable("rls"); // latest nightly is now one that does not have RLS set_current_dist_date(config, "2019-09-14"); config.expect_component_executable("rls"); // update should bring us to latest nightly that does config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_component_executable("rls"); }); } #[test] fn nightly_backtrack_skips_missing() { clitools::test(Scenario::MissingNightly, &|config| { set_current_dist_date(config, "2019-09-16"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_component_executable("rls"); // rls is missing on latest, nightly is missing on second-to-latest set_current_dist_date(config, "2019-09-18"); // update should not change nightly, and should not error config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); }); } #[test] fn completion_rustup() { setup(&|config| { config.expect_ok(&["rustup", "completions", "bash", "rustup"]); }); } #[test] fn completion_cargo() { setup(&|config| { config.expect_ok(&["rustup", "completions", "bash", "cargo"]); }); } #[test] fn completion_default() { setup(&|config| { config.expect_ok_eq( &["rustup", "completions", "bash"], &["rustup", "completions", "bash", "rustup"], ); }); } #[test] fn completion_bad_shell() { setup(&|config| { config.expect_err( &["rustup", "completions", "fake"], r#"error: "fake" isn't a valid value for ''"#, ); config.expect_err( &["rustup", "completions", "fake", "cargo"], r#"error: "fake" isn't a valid value for ''"#, ); }); } #[test] fn completion_bad_tool() { setup(&|config| { config.expect_err( &["rustup", "completions", "bash", "fake"], r#"error: "fake" isn't a valid value for ''"#, ); }); } #[test] fn completion_cargo_unsupported_shell() { setup(&|config| { config.expect_err( &["rustup", "completions", "fish", "cargo"], "error: cargo does not currently support completions for ", ); }); } #[test] fn add_remove_component() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_component_executable("rustc"); config.expect_ok(&["rustup", "component", "remove", "rustc"]); config.expect_component_not_executable("rustc"); config.expect_ok(&["rustup", "component", "add", "rustc"]); config.expect_component_executable("rustc"); }); } #[test] fn which() { setup(&|config| { let path_1 = config.customdir.join("custom-1"); let path_1 = path_1.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom-1", &path_1]); config.expect_ok(&["rustup", "default", "custom-1"]); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "which", "rustc"], "\\toolchains\\custom-1\\bin\\rustc", ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "which", "rustc"], "/toolchains/custom-1/bin/rustc", ); let path_2 = config.customdir.join("custom-2"); let path_2 = path_2.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom-2", &path_2]); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "which", "--toolchain=custom-2", "rustc"], "\\toolchains\\custom-2\\bin\\rustc", ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "which", "--toolchain=custom-2", "rustc"], "/toolchains/custom-2/bin/rustc", ); }); } #[test] fn which_asking_uninstalled_toolchain() { setup(&|config| { let path_1 = config.customdir.join("custom-1"); let path_1 = path_1.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom-1", &path_1]); config.expect_ok(&["rustup", "default", "custom-1"]); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "which", "rustc"], "\\toolchains\\custom-1\\bin\\rustc", ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "which", "rustc"], "/toolchains/custom-1/bin/rustc", ); config.expect_err( &["rustup", "which", "--toolchain=nightly", "rustc"], "toolchain 'nightly' is not installed", ); }); } #[test] fn override_by_toolchain_on_the_command_line() { setup(&|config| { #[cfg(windows)] config.expect_stdout_ok( &["rustup", "+stable", "which", "rustc"], for_host!("\\toolchains\\stable-{}"), ); #[cfg(windows)] config.expect_stdout_ok(&["rustup", "+stable", "which", "rustc"], "\\bin\\rustc"); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "+stable", "which", "rustc"], for_host!("/toolchains/stable-{}"), ); #[cfg(not(windows))] config.expect_stdout_ok(&["rustup", "+stable", "which", "rustc"], "/bin/rustc"); config.expect_ok(&["rustup", "default", "nightly"]); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "+nightly", "which", "rustc"], for_host!("\\toolchains\\nightly-{}"), ); #[cfg(windows)] config.expect_stdout_ok(&["rustup", "+nightly", "which", "rustc"], "\\bin\\rustc"); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "+nightly", "which", "rustc"], for_host!("/toolchains/nightly-{}"), ); #[cfg(not(windows))] config.expect_stdout_ok(&["rustup", "+nightly", "which", "rustc"], "/bin/rustc"); config.expect_stdout_ok( &["rustup", "+nightly", "show"], "(overridden by +toolchain on the command line)", ); config.expect_err( &["rustup", "+foo", "which", "rustc"], "toolchain 'foo' is not installed", ); config.expect_stderr_ok( &["rustup", "+stable", "set", "profile", "minimal"], "profile set to 'minimal'", ); config.expect_stdout_ok(&["rustup", "default"], for_host!("nightly-{}")); }); } #[test] fn toolchain_link_then_list_verbose() { setup(&|config| { let path_1 = config.customdir.join("custom-1"); let path_1 = path_1.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom-1", &path_1]); #[cfg(windows)] config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "\\custom-1"); #[cfg(not(windows))] config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "/custom-1"); }); } #[test] fn deprecated_interfaces() { setup(&|config| { // In verbose mode we want the deprecated interfaces to complain config.expect_ok_contains( &["rustup", "--verbose", "install", "nightly"], "", "Please use `rustup toolchain install` instead", ); config.expect_ok_contains( &["rustup", "--verbose", "uninstall", "nightly"], "", "Please use `rustup toolchain uninstall` instead", ); // But if not verbose then they should *NOT* complain config.expect_not_stderr_ok( &["rustup", "install", "nightly"], "Please use `rustup toolchain install` instead", ); config.expect_not_stderr_ok( &["rustup", "uninstall", "nightly"], "Please use `rustup toolchain uninstall` instead", ); }) } rustup-1.26.0/tests/suite/cli_paths.rs000066400000000000000000000373231441327105200177600ustar00rootroot00000000000000//! This file contains tests relevant to Rustup's handling of updating PATHs. //! It depends on self-update working, so if absolutely everything here breaks, //! check those tests as well. // Prefer omitting actually unpacking content while just testing paths. const INIT_NONE: [&str; 4] = ["rustup-init", "-y", "--default-toolchain", "none"]; #[cfg(unix)] mod unix { use std::fmt::Display; use std::fs; use std::path::PathBuf; use rustup::utils::raw; use super::INIT_NONE; use crate::mock::clitools::{self, Scenario}; // Let's write a fake .rc which looks vaguely like a real script. const FAKE_RC: &str = r#" # Sources fruity punch. . ~/fruit/punch # Adds apples to PATH. export PATH="$HOME/apple/bin" "#; const DEFAULT_EXPORT: &str = "export PATH=\"$HOME/.cargo/bin:$PATH\"\n"; const POSIX_SH: &str = "env"; fn source(dir: impl Display, sh: impl Display) -> String { format!(". \"{dir}/{sh}\"\n") } // In 1.23 we used `source` instead of `.` by accident. This is not POSIX // so we want to ensure that if we put this into someone's dot files, then // with newer rustups we will revert that. fn non_posix_source(dir: impl Display, sh: impl Display) -> String { format!("source \"{dir}/{sh}\"\n") } #[test] fn install_creates_necessary_scripts() { clitools::test(Scenario::Empty, &|config| { // Override the test harness so that cargo home looks like // $HOME/.cargo by removing CARGO_HOME from the environment, // otherwise the literal path will be written to the file. let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]); let files: Vec = [".cargo/env", ".profile", ".zshenv"] .iter() .map(|file| config.homedir.join(file)) .collect(); for file in &files { assert!(!file.exists()); } cmd.env_remove("CARGO_HOME"); cmd.env("SHELL", "zsh"); assert!(cmd.output().unwrap().status.success()); let mut rcs = files.iter(); let env = rcs.next().unwrap(); let envfile = fs::read_to_string(env).unwrap(); let (_, envfile_export) = envfile.split_at(envfile.find("export PATH").unwrap_or(0)); assert_eq!(&envfile_export[..DEFAULT_EXPORT.len()], DEFAULT_EXPORT); for rc in rcs { let expected = source("$HOME/.cargo", POSIX_SH); let new_profile = fs::read_to_string(rc).unwrap(); assert_eq!(new_profile, expected); } }); } #[test] fn install_updates_bash_rcs() { clitools::test(Scenario::Empty, &|config| { let rcs: Vec = [".bashrc", ".bash_profile", ".bash_login", ".profile"] .iter() .map(|rc| config.homedir.join(rc)) .collect(); for rc in &rcs { raw::write_file(rc, FAKE_RC).unwrap(); } config.expect_ok(&INIT_NONE); let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH); for rc in &rcs { let new_rc = fs::read_to_string(rc).unwrap(); assert_eq!(new_rc, expected); } }) } #[test] fn install_does_not_create_bash_rcs() { clitools::test(Scenario::Empty, &|config| { let rcs: Vec = [".bashrc", ".bash_profile", ".bash_login"] .iter() .map(|rc| config.homedir.join(rc)) .collect(); let rcs_before = rcs.iter().map(|rc| rc.exists()); config.expect_ok(&INIT_NONE); for (before, after) in rcs_before.zip(rcs.iter().map(|rc| rc.exists())) { assert!(!before); assert_eq!(before, after); } }); } // This test should NOT be run as root! #[test] fn install_errors_when_rc_cannot_be_updated() { clitools::test(Scenario::Empty, &|config| { let rc = config.homedir.join(".profile"); fs::File::create(&rc).unwrap(); let mut perms = fs::metadata(&rc).unwrap().permissions(); perms.set_readonly(true); fs::set_permissions(&rc, perms).unwrap(); config.expect_err(&INIT_NONE, "amend shell"); }); } #[test] fn install_with_zdotdir() { clitools::test(Scenario::Empty, &|config| { let zdotdir = tempfile::Builder::new() .prefix("zdotdir") .tempdir() .unwrap(); let rc = zdotdir.path().join(".zshenv"); raw::write_file(&rc, FAKE_RC).unwrap(); let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]); cmd.env("SHELL", "zsh"); cmd.env("ZDOTDIR", zdotdir.path()); assert!(cmd.output().unwrap().status.success()); let new_rc = fs::read_to_string(&rc).unwrap(); let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH); assert_eq!(new_rc, expected); }); } #[test] fn install_adds_path_to_rc_just_once() { clitools::test(Scenario::Empty, &|config| { let profile = config.homedir.join(".profile"); raw::write_file(&profile, FAKE_RC).unwrap(); config.expect_ok(&INIT_NONE); config.expect_ok(&INIT_NONE); let new_profile = fs::read_to_string(&profile).unwrap(); let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH); assert_eq!(new_profile, expected); }); } #[test] fn install_adds_path_to_rc_handling_no_newline() { clitools::test(Scenario::Empty, &|config| { let profile = config.homedir.join(".profile"); let fake_rc_modified = FAKE_RC.strip_suffix('\n').expect("Should end in a newline"); raw::write_file(&profile, fake_rc_modified).unwrap(); // Run once to to add the configuration config.expect_ok(&INIT_NONE); // Run twice to test that the process is idempotent config.expect_ok(&INIT_NONE); let new_profile = fs::read_to_string(&profile).unwrap(); let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH); assert_eq!(new_profile, expected); }); } #[test] fn install_adds_path_to_multiple_rc_files() { clitools::test(Scenario::Empty, &|config| { // Two RC files that are both from the same shell let bash_profile = config.homedir.join(".bash_profile"); let bashrc = config.homedir.join(".bashrc"); let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH); // The order that the two files are processed isn't known, so test both orders for [path1, path2] in &[[&bash_profile, &bashrc], [&bashrc, &bash_profile]] { raw::write_file(path1, &expected).unwrap(); raw::write_file(path2, FAKE_RC).unwrap(); config.expect_ok(&INIT_NONE); let new1 = fs::read_to_string(path1).unwrap(); assert_eq!(new1, expected); let new2 = fs::read_to_string(path2).unwrap(); assert_eq!(new2, expected); } }); } #[test] fn uninstall_removes_source_from_rcs() { clitools::test(Scenario::Empty, &|config| { let rcs: Vec = [ ".bashrc", ".bash_profile", ".bash_login", ".profile", ".zshenv", ] .iter() .map(|rc| config.homedir.join(rc)) .collect(); for rc in &rcs { raw::write_file(rc, FAKE_RC).unwrap(); } config.expect_ok(&INIT_NONE); config.expect_ok(&["rustup", "self", "uninstall", "-y"]); for rc in &rcs { let new_rc = fs::read_to_string(rc).unwrap(); assert_eq!(new_rc, FAKE_RC); } }) } #[test] fn install_adds_sources_while_removing_legacy_paths() { clitools::test(Scenario::Empty, &|config| { let zdotdir = tempfile::Builder::new() .prefix("zdotdir") .tempdir() .unwrap(); let rcs: Vec = [".bash_profile", ".profile"] .iter() .map(|rc| config.homedir.join(rc)) .collect(); let zprofiles = vec![ config.homedir.join(".zprofile"), zdotdir.path().join(".zprofile"), ]; let old_rc = FAKE_RC.to_owned() + DEFAULT_EXPORT + &non_posix_source("$HOME/.cargo", POSIX_SH); for rc in rcs.iter().chain(zprofiles.iter()) { raw::write_file(rc, &old_rc).unwrap(); } let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]); cmd.env("SHELL", "zsh"); cmd.env("ZDOTDIR", zdotdir.path()); cmd.env_remove("CARGO_HOME"); assert!(cmd.output().unwrap().status.success()); let fixed_rc = FAKE_RC.to_owned() + &source("$HOME/.cargo", POSIX_SH); for rc in &rcs { let new_rc = fs::read_to_string(rc).unwrap(); assert_eq!(new_rc, fixed_rc); } for rc in &zprofiles { let new_rc = fs::read_to_string(rc).unwrap(); assert_eq!(new_rc, FAKE_RC); } }) } #[test] fn uninstall_cleans_up_legacy_paths() { clitools::test(Scenario::Empty, &|config| { // Install first, then overwrite. config.expect_ok(&INIT_NONE); let zdotdir = tempfile::Builder::new() .prefix("zdotdir") .tempdir() .unwrap(); let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]); cmd.env("SHELL", "zsh"); cmd.env("ZDOTDIR", zdotdir.path()); cmd.env_remove("CARGO_HOME"); assert!(cmd.output().unwrap().status.success()); let mut rcs: Vec = [".bash_profile", ".profile", ".zprofile"] .iter() .map(|rc| config.homedir.join(rc)) .collect(); rcs.push(zdotdir.path().join(".zprofile")); let old_rc = FAKE_RC.to_owned() + DEFAULT_EXPORT + &non_posix_source("$HOME/.cargo", POSIX_SH); for rc in &rcs { raw::write_file(rc, &old_rc).unwrap(); } let mut cmd = clitools::cmd(config, "rustup", ["self", "uninstall", "-y"]); cmd.env("SHELL", "zsh"); cmd.env("ZDOTDIR", zdotdir.path()); cmd.env_remove("CARGO_HOME"); assert!(cmd.output().unwrap().status.success()); for rc in &rcs { let new_rc = fs::read_to_string(rc).unwrap(); // It's not ideal, but it's OK, if we leave whitespace. assert_eq!(new_rc, FAKE_RC); } }) } // In the default case we want to write $HOME/.cargo/bin as the path, // not the full path. #[test] fn when_cargo_home_is_the_default_write_path_specially() { clitools::test(Scenario::Empty, &|config| { // Override the test harness so that cargo home looks like // $HOME/.cargo by removing CARGO_HOME from the environment, // otherwise the literal path will be written to the file. let profile = config.homedir.join(".profile"); raw::write_file(&profile, FAKE_RC).unwrap(); let mut cmd = clitools::cmd(config, "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 = format!("{FAKE_RC}. \"$HOME/.cargo/env\"\n"); assert_eq!(new_profile, expected); let mut cmd = clitools::cmd(config, "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, FAKE_RC); }); } #[test] fn install_doesnt_modify_path_if_passed_no_modify_path() { clitools::test(Scenario::Empty, &|config| { let profile = config.homedir.join(".profile"); config.expect_ok(&[ "rustup-init", "-y", "--no-modify-path", "--default-toolchain", "none", ]); assert!(!profile.exists()); }); } } #[cfg(windows)] mod windows { use rustup::test::{get_path, with_saved_path}; use super::INIT_NONE; use crate::mock::clitools::{self, Scenario}; #[test] /// Smoke test for end-to-end code connectivity of the installer path mgmt on windows. fn install_uninstall_affect_path() { clitools::test(Scenario::Empty, &|config| { with_saved_path(&mut || { let path = format!("{:?}", config.cargodir.join("bin").to_string_lossy()); config.expect_ok(&INIT_NONE); assert!( get_path() .unwrap() .unwrap() .to_string() .contains(path.trim_matches('"')), "`{}` not in `{}`", path, get_path().unwrap().unwrap() ); config.expect_ok(&["rustup", "self", "uninstall", "-y"]); assert!(!get_path().unwrap().unwrap().to_string().contains(&path)); }) }); } #[test] /// Smoke test for end-to-end code connectivity of the installer path mgmt on windows. fn install_uninstall_affect_path_with_non_unicode() { use std::ffi::OsString; use std::os::windows::ffi::OsStrExt; use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; use winreg::{RegKey, RegValue}; clitools::test(Scenario::Empty, &|config| { with_saved_path(&mut || { // Set up a non unicode PATH let reg_value = RegValue { bytes: vec![ 0x00, 0xD8, // leading surrogate 0x01, 0x01, // bogus trailing surrogate 0x00, 0x00, // null ], vtype: RegType::REG_EXPAND_SZ, }; RegKey::predef(HKEY_CURRENT_USER) .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .unwrap() .set_raw_value("PATH", ®_value) .unwrap(); // compute expected path after installation let expected = RegValue { bytes: OsString::from(config.cargodir.join("bin")) .encode_wide() .flat_map(|v| vec![v as u8, (v >> 8) as u8]) .chain(vec![b';', 0]) .chain(reg_value.bytes.iter().copied()) .collect(), vtype: RegType::REG_EXPAND_SZ, }; config.expect_ok(&INIT_NONE); assert_eq!(get_path().unwrap().unwrap(), expected); config.expect_ok(&["rustup", "self", "uninstall", "-y"]); assert_eq!(get_path().unwrap().unwrap(), reg_value); }) }); } } rustup-1.26.0/tests/suite/cli_rustup.rs000066400000000000000000002237231441327105200202040ustar00rootroot00000000000000//! Test cases for new rustup UI use std::env::consts::EXE_SUFFIX; use std::fs; use std::path::{PathBuf, MAIN_SEPARATOR}; use rustup::for_host; use rustup::test::this_host_triple; use rustup::utils::raw; use crate::mock::{ self, clitools::{self, Config, Scenario}, }; macro_rules! for_host_and_home { ($config:ident, $s: expr) => { &format!($s, this_host_triple(), $config.rustupdir) }; } fn test(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::None, f); } #[test] fn rustup_stable() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable"]); }); config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "update"], for_host!( r" stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: latest update on 2015-01-02, rust version 1.1.0 (hash-stable-1.1.0) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: cleaning up downloads & tmp directories " ), ); }) }); } #[test] fn rustup_stable_quiet() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "--quiet", "update", "stable"]); }); config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "--quiet", "update"], for_host!( r" stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: latest update on 2015-01-02, rust version 1.1.0 (hash-stable-1.1.0) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: cleaning up downloads & tmp directories " ), ); }) }); } #[test] fn rustup_stable_no_change() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "update", "stable"]); config.expect_ok_ex( &["rustup", "update"], for_host!( r" stable-{0} unchanged - 1.0.0 (hash-stable-1.0.0) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: cleaning up downloads & tmp directories " ), ); }) }); } #[test] fn rustup_all_channels() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]); }); config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "update"], for_host!( r" stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) beta-{0} updated - 1.2.0 (hash-beta-1.2.0) (from 1.1.0 (hash-beta-1.1.0)) nightly-{0} updated - 1.3.0 (hash-nightly-2) (from 1.2.0 (hash-nightly-1)) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: latest update on 2015-01-02, rust version 1.1.0 (hash-stable-1.1.0) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: syncing channel updates for 'beta-{0}' info: latest update on 2015-01-02, rust version 1.2.0 (hash-beta-1.2.0) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: cleaning up downloads & tmp directories " ), ); }) }) } #[test] fn rustup_some_channels_up_to_date() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]); }); config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "update", "beta"]); config.expect_ok_ex( &["rustup", "update"], for_host!( r" stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) beta-{0} unchanged - 1.2.0 (hash-beta-1.2.0) nightly-{0} updated - 1.3.0 (hash-nightly-2) (from 1.2.0 (hash-nightly-1)) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: latest update on 2015-01-02, rust version 1.1.0 (hash-stable-1.1.0) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: syncing channel updates for 'beta-{0}' info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: removing previous version of component 'cargo' info: removing previous version of component 'rust-docs' info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: cleaning up downloads & tmp directories " ), ); }) }) } #[test] fn rustup_no_channels() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "update"], r"", r"info: no updatable toolchains installed info: cleaning up downloads & tmp directories ", ); }) }) } #[test] fn default() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok_ex( &["rustup", "default", "nightly"], for_host!( r" nightly-{0} installed - 1.3.0 (hash-nightly-2) " ), for_host!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: default toolchain set to 'nightly-{0}' " ), ); }) }); } #[test] fn default_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "nightly"]); config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "override", "set", "nightly"]); config.expect_stderr_ok( &["rustup", "default", "stable"], for_host!( r"info: using existing install for 'stable-{0}' info: default toolchain set to 'stable-{0}' info: note that the toolchain 'nightly-{0}' is currently in use (directory override for" ), ); }) }); } #[test] fn rustup_zstd() { test(&|config| { config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_stderr_ok( &["rustup", "--verbose", "toolchain", "add", "nightly"], for_host!(r"dist/2015-01-01/rust-std-nightly-{0}.tar.zst"), ); }) }); } #[test] fn add_target() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH1 ); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); assert!(config.rustupdir.has(path)); }) }); } #[test] fn remove_target() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH1 ); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); assert!(config.rustupdir.has(&path)); config.expect_ok(&["rustup", "target", "remove", clitools::CROSS_ARCH1]); assert!(!config.rustupdir.has(&path)); }) }); } #[test] fn add_remove_multiple_targets() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&[ "rustup", "target", "add", clitools::CROSS_ARCH1, clitools::CROSS_ARCH2, ]); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH1 ); assert!(config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH2 ); assert!(config.rustupdir.has(path)); config.expect_ok(&[ "rustup", "target", "remove", clitools::CROSS_ARCH1, clitools::CROSS_ARCH2, ]); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH1 ); assert!(!config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH2 ); assert!(!config.rustupdir.has(path)); }) }); } #[test] fn list_targets() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "target", "list"], clitools::CROSS_ARCH1); }) }); } #[test] fn list_installed_targets() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let trip = this_host_triple(); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "target", "list", "--installed"], &trip); }) }); } #[test] fn add_target_explicit() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH1 ); config.expect_ok(&["rustup", "toolchain", "add", "nightly"]); config.expect_ok(&[ "rustup", "target", "add", "--toolchain", "nightly", clitools::CROSS_ARCH1, ]); assert!(config.rustupdir.has(path)); }) }); } #[test] fn remove_target_explicit() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", &this_host_triple(), clitools::CROSS_ARCH1 ); config.expect_ok(&["rustup", "toolchain", "add", "nightly"]); config.expect_ok(&[ "rustup", "target", "add", "--toolchain", "nightly", clitools::CROSS_ARCH1, ]); assert!(config.rustupdir.has(&path)); config.expect_ok(&[ "rustup", "target", "remove", "--toolchain", "nightly", clitools::CROSS_ARCH1, ]); assert!(!config.rustupdir.has(&path)); }) }); } #[test] fn list_targets_explicit() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "nightly"]); config.expect_stdout_ok( &["rustup", "target", "list", "--toolchain", "nightly"], clitools::CROSS_ARCH1, ); }) }); } #[test] fn link() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom", &path]); config.expect_ok(&["rustup", "default", "custom"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-c-1"); config.expect_stdout_ok(&["rustup", "show"], "custom (default)"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "show"], "custom"); }) }); } // Issue #809. When we call the fallback cargo, when it in turn invokes // "rustc", that rustc should actually be the rustup proxy, not the toolchain rustc. // That way the proxy can pick the correct toolchain. #[test] fn fallback_cargo_calls_correct_rustc() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { // Hm, this is the _only_ test that assumes that toolchain proxies // exist in CARGO_HOME. Adding that proxy here. let rustup_path = config.exedir.join(format!("rustup{EXE_SUFFIX}")); let cargo_bin_path = config.cargodir.join("bin"); fs::create_dir_all(&cargo_bin_path).unwrap(); let rustc_path = cargo_bin_path.join(format!("rustc{EXE_SUFFIX}")); fs::hard_link(rustup_path, &rustc_path).unwrap(); // Install a custom toolchain and a nightly toolchain for the cargo fallback let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom", &path]); config.expect_ok(&["rustup", "default", "custom"]); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-c-1"); config.expect_stdout_ok(&["cargo", "--version"], "hash-nightly-2"); assert!(rustc_path.exists()); // Here --call-rustc tells the mock cargo bin to exec `rustc --version`. // We should be ultimately calling the custom rustc, according to the // RUSTUP_TOOLCHAIN variable set by the original "cargo" proxy, and // interpreted by the nested "rustc" proxy. config.expect_stdout_ok(&["cargo", "--call-rustc"], "hash-c-1"); }) }); } // Checks that cargo can recursively invoke itself with rustup shorthand (via // the proxy). // // This involves a series of chained commands: // // 1. Calls `cargo --recursive-cargo-subcommand` // 2. The rustup `cargo` proxy launches, and launches the "mock" nightly cargo exe. // 3. The nightly "mock" cargo sees --recursive-cargo-subcommand, and launches // `cargo-foo --recursive-cargo` // 4. `cargo-foo` sees `--recursive-cargo` and launches `cargo +nightly --version` // 5. The rustup `cargo` proxy launches, and launches the "mock" nightly cargo exe. // 6. The nightly "mock" cargo sees `--version` and prints the version. // // Previously, rustup would place the toolchain's `bin` directory in PATH for // Windows due to some DLL issues. However, those aren't necessary anymore. // If the toolchain `bin` directory is in PATH, then this test would fail in // step 5 because the `cargo` executable would be the "mock" nightly cargo, // and the first argument would be `+nightly` which would be an error. #[test] fn recursive_cargo() { test(&|config| { config.with_scenario(Scenario::ArchivesV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); // We need an intermediary to run cargo itself. // The "mock" cargo can't do that because on Windows it will check // for a `cargo.exe` in the current directory before checking PATH. // // The solution here is to copy from the "mock" `cargo.exe` into // `~/.cargo/bin/cargo-foo`. This is just for convenience to avoid // needing to build another executable just for this test. let output = config.run("rustup", &["which", "cargo"], &[]); let real_mock_cargo = output.stdout.trim(); let cargo_bin_path = config.cargodir.join("bin"); let cargo_subcommand = cargo_bin_path.join(format!("cargo-foo{}", EXE_SUFFIX)); fs::create_dir_all(&cargo_bin_path).unwrap(); fs::copy(&real_mock_cargo, &cargo_subcommand).unwrap(); // Verify the default behavior, which is currently broken on Windows. let args = &["cargo", "--recursive-cargo-subcommand"]; if cfg!(windows) { config.expect_err( &["cargo", "--recursive-cargo-subcommand"], "bad mock proxy commandline", ); } // Try the opt-in, which should fix it. let out = config.run(args[0], &args[1..], &[("RUSTUP_WINDOWS_PATH_ADD_BIN", "0")]); if !out.ok || !out.stdout.contains("hash-nightly-2") { clitools::print_command(args, &out); panic!("expected hash-nightly-2 in output"); } }); }); } #[test] fn show_home() { test(&|config| { config.expect_ok_ex( &["rustup", "show", "home"], &format!( r"{} ", config.rustupdir ), r"", ); }); } #[test] fn show_toolchain_none() { test(&|config| { config.expect_ok_ex( &["rustup", "show"], for_host_and_home!( config, r"Default host: {0} rustup home: {1} no active toolchain " ), r"", ); }); } #[test] fn show_toolchain_default() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok_ex( &["rustup", "show"], for_host_and_home!( config, r"Default host: {0} rustup home: {1} nightly-{0} (default) 1.3.0 (hash-nightly-2) " ), r"", ); }) }); } #[test] fn show_multiple_toolchains() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "update", "stable"]); config.expect_ok_ex( &["rustup", "show"], for_host_and_home!( config, r"Default host: {0} rustup home: {1} installed toolchains -------------------- stable-{0} nightly-{0} (default) active toolchain ---------------- nightly-{0} (default) 1.3.0 (hash-nightly-2) " ), r"", ); }) }); } #[test] fn show_multiple_targets() { test(&|config| { config.with_scenario(Scenario::MultiHost, &|config| { config.expect_ok(&[ "rustup", "default", &format!("nightly-{}", clitools::MULTI_ARCH1), ]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH2]); config.expect_ok_ex( &["rustup", "show"], &format!( r"Default host: {2} rustup home: {3} installed targets for active toolchain -------------------------------------- {1} {0} active toolchain ---------------- nightly-{0} (default) 1.3.0 (xxxx-nightly-2) ", clitools::MULTI_ARCH1, clitools::CROSS_ARCH2, this_host_triple(), config.rustupdir ), r"", ); }) }); } #[test] fn show_multiple_toolchains_and_targets() { if cfg!(target_os = "linux") && cfg!(target_arch = "x86") { return; } test(&|config| { config.with_scenario(Scenario::MultiHost, &|config| { config.expect_ok(&[ "rustup", "default", &format!("nightly-{}", clitools::MULTI_ARCH1), ]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH2]); config.expect_ok(&[ "rustup", "update", &format!("stable-{}", clitools::MULTI_ARCH1), ]); config.expect_ok_ex( &["rustup", "show"], &format!( r"Default host: {2} rustup home: {3} installed toolchains -------------------- stable-{0} nightly-{0} (default) installed targets for active toolchain -------------------------------------- {1} {0} active toolchain ---------------- nightly-{0} (default) 1.3.0 (xxxx-nightly-2) ", clitools::MULTI_ARCH1, clitools::CROSS_ARCH2, this_host_triple(), config.rustupdir ), r"", ); }) }); } #[test] fn list_default_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok_ex( &["rustup", "toolchain", "list"], for_host!( r"nightly-{0} (default) " ), r"", ); }) }); } #[test] fn list_override_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "override", "set", "nightly"]); config.expect_ok_ex( &["rustup", "toolchain", "list"], for_host!( r"nightly-{0} (override) " ), r"", ); }) }); } #[test] fn list_default_and_override_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "override", "set", "nightly"]); config.expect_ok_ex( &["rustup", "toolchain", "list"], for_host!( r"nightly-{0} (default) (override) " ), r"", ); }) }); } #[test] fn heal_damaged_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_not_stderr_ok( &["rustup", "show", "active-toolchain"], "syncing channel updates", ); let path = format!( "toolchains/nightly-{}/lib/rustlib/multirust-channel-manifest.toml", this_host_triple() ); fs::remove_file(config.rustupdir.join(path)).unwrap(); config.expect_ok_ex( &["rustup", "show", "active-toolchain"], &format!( r"nightly-{0} (default) ", this_host_triple() ), for_host!( r"info: syncing channel updates for 'nightly-{0}' " ), ); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stderr_ok( &["rustup", "show", "active-toolchain"], "syncing channel updates", ); }) }); } #[test] #[ignore = "FIXME: Windows shows UNC paths"] fn show_toolchain_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok_ex( &["rustup", "show"], &format!( r"Default host: {0} rustup home: {1} nightly-{0} (directory override for '{2}') 1.3.0 (hash-nightly-2) ", this_host_triple(), config.rustupdir, cwd.display(), ), r"", ); }) }); } #[test] #[ignore = "FIXME: Windows shows UNC paths"] fn show_toolchain_toolchain_file_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_ok_ex( &["rustup", "show"], &format!( r"Default host: {0} rustup home: {1} installed toolchains -------------------- stable-{0} (default) nightly-{0} active toolchain ---------------- nightly-{0} (overridden by '{2}') 1.3.0 (hash-nightly-2) ", this_host_triple(), config.rustupdir, toolchain_file.display() ), r"", ); }) }); } #[test] #[ignore = "FIXME: Windows shows UNC paths"] fn show_toolchain_version_nested_file_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]); config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); let subdir = cwd.join("foo"); fs::create_dir_all(&subdir).unwrap(); config.change_dir(&subdir, &|config| { config.expect_ok_ex( &["rustup", "show"], &format!( r"Default host: {0} installed toolchains -------------------- stable-{0} (default) nightly-{0} active toolchain ---------------- nightly-{0} (overridden by '{1}') 1.3.0 (hash-nightly-2) ", this_host_triple(), toolchain_file.display() ), r"", ); }); }) }); } #[test] #[ignore = "FIXME: Windows shows UNC paths"] fn show_toolchain_toolchain_file_override_not_installed() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); // I'm not sure this should really be erroring when the toolchain // is not installed; just capturing the behavior. let mut cmd = clitools::cmd(config, "rustup", ["show"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(!out.status.success()); let stderr = String::from_utf8(out.stderr).unwrap(); assert!(stderr.starts_with("error: override toolchain 'nightly' is not installed")); assert!(stderr.contains(&format!( "the toolchain file at '{}' specifies an uninstalled toolchain", toolchain_file.display() ))); }) }); } #[test] fn show_toolchain_override_not_installed() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); let mut cmd = clitools::cmd(config, "rustup", ["show"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(out.status.success()); let stdout = String::from_utf8(out.stdout).unwrap(); let stderr = String::from_utf8(out.stderr).unwrap(); assert!(!stdout.contains("not a directory")); assert!(!stdout.contains("is not installed")); assert!(stderr.contains("info: installing component 'rustc'")); }) }); } #[test] fn override_set_unset_with_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = fs::canonicalize(config.current_dir()).unwrap(); let mut cwd_str = cwd.to_str().unwrap(); if cfg!(windows) { cwd_str = &cwd_str[4..]; } let emptydir = tempfile::tempdir().unwrap(); config.change_dir(emptydir.path(), &|config| { config.expect_ok(&["rustup", "override", "set", "nightly", "--path", cwd_str]); }); config.expect_ok_ex( &["rustup", "override", "list"], &format!("{}\tnightly-{}\n", cwd_str, this_host_triple()), r"", ); config.change_dir(emptydir.path(), &|config| { config.expect_ok(&["rustup", "override", "unset", "--path", cwd_str]); }); config.expect_ok_ex(&["rustup", "override", "list"], "no overrides\n", r""); }) }); } #[test] fn show_toolchain_env() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); let mut cmd = clitools::cmd(config, "rustup", ["show"]); clitools::env(config, &mut cmd); cmd.env("RUSTUP_TOOLCHAIN", "nightly"); let out = cmd.output().unwrap(); assert!(out.status.success()); let stdout = String::from_utf8(out.stdout).unwrap(); assert_eq!( &stdout, for_host_and_home!( config, r"Default host: {0} rustup home: {1} nightly-{0} (environment override by RUSTUP_TOOLCHAIN) 1.3.0 (hash-nightly-2) " ) ); }) }); } #[test] fn show_toolchain_env_not_installed() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let mut cmd = clitools::cmd(config, "rustup", ["show"]); clitools::env(config, &mut cmd); cmd.env("RUSTUP_TOOLCHAIN", "nightly"); let out = cmd.output().unwrap(); assert!(out.status.success()); let stdout = String::from_utf8(out.stdout).unwrap(); let stderr = String::from_utf8(out.stderr).unwrap(); assert!(!stdout.contains("is not installed")); assert!(stderr.contains("info: installing component 'rustc'")); }) }); } #[test] fn show_active_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok_ex( &["rustup", "show", "active-toolchain"], for_host!( r"nightly-{0} (default) " ), r"", ); }) }); } #[test] fn show_with_verbose() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); }); config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "update", "nightly-2015-01-01"]); config.expect_ok_ex( &["rustup", "show", "--verbose"], for_host_and_home!( config, r"Default host: {0} rustup home: {1} installed toolchains -------------------- nightly-2015-01-01-{0} 1.2.0 (hash-nightly-1) nightly-{0} (default) 1.3.0 (hash-nightly-2) active toolchain ---------------- nightly-{0} (default) 1.3.0 (hash-nightly-2) " ), r"", ); }) }); } #[test] fn show_active_toolchain_with_verbose() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok_ex( &["rustup", "show", "active-toolchain", "--verbose"], for_host!( r"nightly-{0} (default) 1.3.0 (hash-nightly-2) " ), r"", ); }) }); } #[test] fn show_active_toolchain_with_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "override", "set", "stable"]); config.expect_stdout_ok( &["rustup", "show", "active-toolchain"], for_host!("stable-{0} (directory override for"), ); }) }); } #[test] fn show_active_toolchain_none() { test(&|config| { config.expect_ok_ex(&["rustup", "show", "active-toolchain"], r"", r""); }); } #[test] fn show_profile() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "show", "profile"], "default"); // Check we get the same thing after we add or remove a component. config.expect_ok(&["rustup", "component", "add", "rust-src"]); config.expect_stdout_ok(&["rustup", "show", "profile"], "default"); config.expect_ok(&["rustup", "component", "remove", "rustc"]); config.expect_stdout_ok(&["rustup", "show", "profile"], "default"); }) }); } // #846 #[test] fn set_default_host() { test(&|config| { config.expect_ok(&["rustup", "set", "default-host", &this_host_triple()]); config.expect_stdout_ok(&["rustup", "show"], for_host!("Default host: {0}")); }); } // #846 #[test] fn set_default_host_invalid_triple() { test(&|config| { config.expect_err( &["rustup", "set", "default-host", "foo"], "error: Provided host 'foo' couldn't be converted to partial triple", ); }); } // #745 #[test] fn set_default_host_invalid_triple_valid_partial() { test(&|config| { config.expect_err( &["rustup", "set", "default-host", "x86_64-msvc"], "error: Provided host 'x86_64-msvc' did not specify an operating system", ); }); } // #422 #[test] fn update_doesnt_update_non_tracking_channels() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); }); config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "update", "nightly-2015-01-01"]); let mut cmd = clitools::cmd(config, "rustup", ["update"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); let stderr = String::from_utf8(out.stderr).unwrap(); assert!(!stderr.contains(for_host!( "syncing channel updates for 'nightly-2015-01-01-{}'" ))); }) }); } #[test] fn toolchain_install_is_like_update() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); config.expect_stdout_ok( &["rustup", "run", "nightly", "rustc", "--version"], "hash-nightly-2", ); }) }); } #[test] fn toolchain_install_is_like_update_quiet() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "--quiet", "toolchain", "install", "nightly"]); config.expect_stdout_ok( &["rustup", "run", "nightly", "rustc", "--version"], "hash-nightly-2", ); }) }); } #[test] fn toolchain_install_is_like_update_except_that_bare_install_is_an_error() { test(&|config| { config.expect_err( &["rustup", "toolchain", "install"], "arguments were not provided", ); }); } #[test] fn toolchain_update_is_like_update() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "toolchain", "update", "nightly"]); config.expect_stdout_ok( &["rustup", "run", "nightly", "rustc", "--version"], "hash-nightly-2", ); }) }); } #[test] fn toolchain_uninstall_is_like_uninstall() { test(&|config| { config.expect_ok(&["rustup", "uninstall", "nightly"]); let mut cmd = clitools::cmd(config, "rustup", ["show"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(out.status.success()); let stdout = String::from_utf8(out.stdout).unwrap(); assert!(!stdout.contains(for_host!("'nightly-2015-01-01-{}'"))); }); } #[test] fn toolchain_update_is_like_update_except_that_bare_install_is_an_error() { test(&|config| { config.expect_err( &["rustup", "toolchain", "update"], "arguments were not provided", ); }); } #[test] fn proxy_toolchain_shorthand() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); config.expect_stdout_ok(&["rustc", "+stable", "--version"], "hash-stable-1.1.0"); config.expect_stdout_ok(&["rustc", "+nightly", "--version"], "hash-nightly-2"); }) }); } #[test] fn add_component() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "component", "add", "rust-src"]); let path = format!( "toolchains/stable-{}/lib/rustlib/src/rust-src/foo.rs", this_host_triple() ); assert!(config.rustupdir.has(path)); }) }); } #[test] fn remove_component() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "component", "add", "rust-src"]); let path = PathBuf::from(format!( "toolchains/stable-{}/lib/rustlib/src/rust-src/foo.rs", this_host_triple() )); assert!(config.rustupdir.has(&path)); config.expect_ok(&["rustup", "component", "remove", "rust-src"]); assert!(!config.rustupdir.has(path.parent().unwrap())); }) }); } #[test] fn add_remove_multiple_components() { let files = [ "lib/rustlib/src/rust-src/foo.rs".to_owned(), format!("lib/rustlib/{}/analysis/libfoo.json", this_host_triple()), ]; test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "component", "add", "rust-src", "rust-analysis"]); for file in &files { let path = format!("toolchains/nightly-{}/{}", this_host_triple(), file); assert!(config.rustupdir.has(&path)); } config.expect_ok(&["rustup", "component", "remove", "rust-src", "rust-analysis"]); for file in &files { let path = PathBuf::from(format!( "toolchains/nightly-{}/{}", this_host_triple(), file )); assert!(!config.rustupdir.has(path.parent().unwrap())); } }) }); } #[test] fn file_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }) }); } #[test] fn env_override_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let toolchain_path = config .rustupdir .join("toolchains") .join(format!("nightly-{}", this_host_triple())); let mut cmd = clitools::cmd(config, "rustc", ["--version"]); clitools::env(config, &mut cmd); cmd.env("RUSTUP_TOOLCHAIN", toolchain_path.to_str().unwrap()); let out = cmd.output().unwrap(); assert!(String::from_utf8(out.stdout) .unwrap() .contains("hash-nightly-2")); }) }); } #[test] fn plus_override_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let toolchain_path = config .rustupdir .join("toolchains") .join(format!("nightly-{}", this_host_triple())); config.expect_stdout_ok( &[ "rustup", "run", toolchain_path.to_str().unwrap(), "rustc", "--version", ], "hash-nightly-2", ); }) }); } #[test] fn file_override_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let toolchain_path = config .rustupdir .join("toolchains") .join(format!("nightly-{}", this_host_triple())); let toolchain_file = config.current_dir().join("rust-toolchain.toml"); raw::write_file( &toolchain_file, &format!("[toolchain]\npath='{}'", toolchain_path.to_str().unwrap()), ) .unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); // Check that the toolchain has the right name config.expect_stdout_ok( &["rustup", "show", "active-toolchain"], &format!("nightly-{}", this_host_triple()), ); }) }); } #[test] fn proxy_override_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let toolchain_path = config .rustupdir .join("toolchains") .join(format!("nightly-{}", this_host_triple())); let toolchain_file = config.current_dir().join("rust-toolchain.toml"); raw::write_file( &toolchain_file, &format!("[toolchain]\npath='{}'", toolchain_path.to_str().unwrap()), ) .unwrap(); config.expect_stdout_ok(&["cargo", "--call-rustc"], "hash-nightly-2"); }) }); } #[test] fn file_override_path_relative() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let toolchain_path = config .rustupdir .join("toolchains") .join(format!("nightly-{}", this_host_triple())); let toolchain_file = config.current_dir().join("rust-toolchain.toml"); // Find shared prefix so we can determine a relative path let mut p1 = toolchain_path.components().peekable(); let mut p2 = toolchain_file.components().peekable(); while let (Some(p1p), Some(p2p)) = (p1.peek(), p2.peek()) { if p1p == p2p { let _ = p1.next(); let _ = p2.next(); } else { // The two paths diverge here break; } } let mut relative_path = PathBuf::new(); // NOTE: We skip 1 since we don't need to .. across the .toml file at the end of the path for _ in p2.skip(1) { relative_path.push(".."); } for p in p1 { relative_path.push(p); } assert!(relative_path.is_relative()); raw::write_file( &toolchain_file, &format!("[toolchain]\npath='{}'", relative_path.to_str().unwrap()), ) .unwrap(); // Change into an ephemeral dir so that we test that the path is relative to the override let ephemeral = config.current_dir().join("ephemeral"); fs::create_dir_all(&ephemeral).unwrap(); config.change_dir(&ephemeral, &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); }) }); } #[test] fn file_override_path_no_options() { test(&|config| { // Make a plausible-looking toolchain let cwd = config.current_dir(); let toolchain_path = cwd.join("ephemeral"); let toolchain_bin = toolchain_path.join("bin"); fs::create_dir_all(toolchain_bin).unwrap(); let toolchain_file = cwd.join("rust-toolchain.toml"); raw::write_file( &toolchain_file, "[toolchain]\npath=\"ephemeral\"\ntargets=[\"dummy\"]", ) .unwrap(); config.expect_err( &["rustc", "--version"], "toolchain options are ignored for path toolchain (ephemeral)", ); raw::write_file( &toolchain_file, "[toolchain]\npath=\"ephemeral\"\ncomponents=[\"dummy\"]", ) .unwrap(); config.expect_err( &["rustc", "--version"], "toolchain options are ignored for path toolchain (ephemeral)", ); raw::write_file( &toolchain_file, "[toolchain]\npath=\"ephemeral\"\nprofile=\"minimal\"", ) .unwrap(); config.expect_err( &["rustc", "--version"], "toolchain options are ignored for path toolchain (ephemeral)", ); }); } #[test] fn file_override_path_xor_channel() { test(&|config| { // Make a plausible-looking toolchain let cwd = config.current_dir(); let toolchain_path = cwd.join("ephemeral"); let toolchain_bin = toolchain_path.join("bin"); fs::create_dir_all(toolchain_bin).unwrap(); let toolchain_file = cwd.join("rust-toolchain.toml"); raw::write_file( &toolchain_file, "[toolchain]\npath=\"ephemeral\"\nchannel=\"nightly\"", ) .unwrap(); config.expect_err( &["rustc", "--version"], "cannot specify both channel (nightly) and path (ephemeral) simultaneously", ); }); } #[test] fn file_override_subdir() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); let subdir = cwd.join("subdir"); fs::create_dir_all(&subdir).unwrap(); config.change_dir(&subdir, &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); }) }); } #[test] fn file_override_with_archive() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); }); config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "install", "nightly-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly-2015-01-01").unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); }) }); } #[test] fn file_override_toml_format_select_installed_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); }); config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_ok(&["rustup", "toolchain", "install", "nightly-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file( &toolchain_file, r#" [toolchain] channel = "nightly-2015-01-01" "#, ) .unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); }) }); } #[test] fn file_override_toml_format_install_both_toolchain_and_components() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); }); config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); config.expect_not_stdout_ok(&["rustup", "component", "list"], "rust-src (installed)"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file( &toolchain_file, r#" [toolchain] channel = "nightly-2015-01-01" components = [ "rust-src" ] "#, ) .unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_stdout_ok(&["rustup", "component", "list"], "rust-src (installed)"); }) }); } #[test] fn file_override_toml_format_add_missing_components() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_not_stdout_ok(&["rustup", "component", "list"], "rust-src (installed)"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file( &toolchain_file, r#" [toolchain] components = [ "rust-src" ] "#, ) .unwrap(); config.expect_stdout_ok(&["rustup", "component", "list"], "rust-src (installed)"); }) }); } #[test] fn file_override_toml_format_add_missing_targets() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_not_stdout_ok( &["rustup", "component", "list"], "arm-linux-androideabi (installed)", ); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file( &toolchain_file, r#" [toolchain] targets = [ "arm-linux-androideabi" ] "#, ) .unwrap(); config.expect_stdout_ok( &["rustup", "component", "list"], "arm-linux-androideabi (installed)", ); }) }); } #[test] fn file_override_toml_format_skip_invalid_component() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file( &toolchain_file, r#" [toolchain] components = [ "rust-bongo" ] "#, ) .unwrap(); config.expect_stderr_ok( &["rustc", "--version"], "warning: Force-skipping unavailable component 'rust-bongo", ); }) }); } #[test] fn file_override_toml_format_specify_profile() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "set", "profile", "default"]); config.expect_stderr_ok( &["rustup", "default", "stable"], "downloading component 'rust-docs'", ); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file( &toolchain_file, r#" [toolchain] profile = "minimal" channel = "nightly" "#, ) .unwrap(); config.expect_not_stdout_ok( &["rustup", "component", "list"], for_host!("rust-docs-{} (installed)"), ); }) }); } #[test] fn directory_override_beats_file_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "beta"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); config.expect_ok(&["rustup", "override", "set", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }) }); } #[test] fn close_file_override_beats_far_directory_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "beta"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); config.expect_ok(&["rustup", "override", "set", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); let cwd = config.current_dir(); let subdir = cwd.join("subdir"); fs::create_dir_all(&subdir).unwrap(); let toolchain_file = subdir.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.change_dir(&subdir, &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); }) }); } #[test] fn directory_override_doesnt_need_to_exist_unless_it_is_selected() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "beta"]); // not installing nightly config.expect_ok(&["rustup", "override", "set", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }) }); } #[test] fn env_override_beats_file_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "beta"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); let mut cmd = clitools::cmd(config, "rustc", ["--version"]); clitools::env(config, &mut cmd); cmd.env("RUSTUP_TOOLCHAIN", "beta"); let out = cmd.output().unwrap(); assert!(String::from_utf8(out.stdout) .unwrap() .contains("hash-beta-1.2.0")); }) }); } #[test] fn plus_override_beats_file_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "beta"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_stdout_ok(&["rustc", "+beta", "--version"], "hash-beta-1.2.0"); }) }); } #[test] fn bad_file_override() { test(&|config| { let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "gumbo").unwrap(); config.expect_err(&["rustc", "--version"], "invalid toolchain name: 'gumbo'"); }); } #[test] fn valid_override_settings() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); config.expect_ok(&["rustup", "default", "nightly"]); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_ok(&["rustc", "--version"]); raw::write_file(&toolchain_file, for_host!("nightly-{}")).unwrap(); config.expect_ok(&["rustc", "--version"]); let fullpath = config .rustupdir .clone() .join("toolchains") .join(for_host!("nightly-{}")); config.expect_ok(&[ "rustup", "toolchain", "link", "system", &format!("{}", fullpath.display()), ]); raw::write_file(&toolchain_file, "system").unwrap(); config.expect_ok(&["rustc", "--version"]); }) }) } #[test] fn file_override_with_target_info() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly-x86_64-unknown-linux-gnu").unwrap(); config.expect_err( &["rustc", "--version"], "target triple in channel name 'nightly-x86_64-unknown-linux-gnu'", ); }) }); } #[test] fn docs_with_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); let mut cmd = clitools::cmd(config, "rustup", ["doc", "--path"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); let path = format!("share{MAIN_SEPARATOR}doc{MAIN_SEPARATOR}rust{MAIN_SEPARATOR}html"); assert!(String::from_utf8(out.stdout).unwrap().contains(&path)); let mut cmd = clitools::cmd( config, "rustup", ["doc", "--path", "--toolchain", "nightly"], ); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(String::from_utf8(out.stdout).unwrap().contains("nightly")); }) }); } #[test] fn docs_topical_with_path() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); for (topic, path) in mock::topical_doc_data::test_cases() { let mut cmd = clitools::cmd(config, "rustup", ["doc", "--path", topic]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); eprintln!("{:?}", String::from_utf8(out.stderr).unwrap()); let out_str = String::from_utf8(out.stdout).unwrap(); assert!( out_str.contains(&path), "comparing path\ntopic: '{topic}'\nexpected path: '{path}'\noutput: {out_str}\n\n\n", ); } }) }); } #[test] fn docs_missing() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "set", "profile", "minimal"]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rustup", "doc"], "error: unable to view documentation which is not installed", ); }) }); } #[test] fn docs_custom() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "custom", &path]); config.expect_ok(&["rustup", "default", "custom"]); config.expect_stdout_ok(&["rustup", "doc", "--path"], "custom"); }) }); } #[cfg(unix)] #[test] fn non_utf8_arg() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); let out = config.run( "rustc", &[ OsStr::new("--echo-args"), OsStr::new("echoed non-utf8 arg:"), OsStr::from_bytes(b"\xc3\x28"), ], &[("RUST_BACKTRACE", "1")], ); assert!(out.stderr.contains("echoed non-utf8 arg")); }) }); } #[cfg(windows)] #[test] fn non_utf8_arg() { use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); let out = config.run( "rustc", &[ OsString::from("--echo-args".to_string()), OsString::from("echoed non-utf8 arg:".to_string()), OsString::from_wide(&[0xd801, 0xd801]), ], &[("RUST_BACKTRACE", "1")], ); assert!(out.stderr.contains("echoed non-utf8 arg")); }) }); } #[cfg(unix)] #[test] fn non_utf8_toolchain() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); let out = config.run( "rustc", &[OsStr::from_bytes(b"+\xc3\x28")], &[("RUST_BACKTRACE", "1")], ); assert!(out.stderr.contains("toolchain '�(' is not installed")); }) }); } #[cfg(windows)] #[test] fn non_utf8_toolchain() { use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); let out = config.run( "rustc", &[OsString::from_wide(&[u16::from(b'+'), 0xd801, 0xd801])], &[("RUST_BACKTRACE", "1")], ); assert!(out.stderr.contains("toolchain '��' is not installed")); }) }); } #[test] fn check_host_goes_away() { test(&|config| { config.with_scenario(Scenario::HostGoesMissingBefore, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); }); config.with_scenario(Scenario::HostGoesMissingAfter, &|config| { config.expect_err( &["rustup", "update", "nightly"], for_host!("target '{}' not found in channel"), ); }) }) } #[cfg(unix)] #[test] fn check_unix_settings_fallback() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { // No default toolchain specified yet config.expect_err(&["rustup", "default"], r"no default toolchain configured"); // Default toolchain specified in fallback settings file let mock_settings_file = config.current_dir().join("mock_fallback_settings.toml"); raw::write_file( &mock_settings_file, for_host!(r"default_toolchain = 'nightly-{0}'"), ) .unwrap(); let mut cmd = clitools::cmd(config, "rustup", ["default"]); clitools::env(config, &mut cmd); // Override the path to the fallback settings file to be the mock file cmd.env("RUSTUP_OVERRIDE_UNIX_FALLBACK_SETTINGS", mock_settings_file); let out = cmd.output().unwrap(); assert!(out.status.success()); let stdout = String::from_utf8(out.stdout).unwrap(); assert_eq!( &stdout, for_host!( r"nightly-{0} (default) " ) ); }) }); } #[test] fn warn_on_unmatch_build() { test(&|config| { config.with_scenario(Scenario::MultiHost, &|config| { let arch = clitools::MULTI_ARCH1; config.expect_stderr_ok( &["rustup", "toolchain", "install", &format!("nightly-{arch}")], &format!( r"warning: toolchain 'nightly-{arch}' may not be able to run on this system. warning: If you meant to build software to target that platform, perhaps try `rustup target add {arch}` instead?", ), ); }) }); } #[test] fn dont_warn_on_partial_build() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let triple = this_host_triple(); let arch = triple.split('-').next().unwrap(); let mut cmd = clitools::cmd( config, "rustup", ["toolchain", "install", &format!("nightly-{arch}")], ); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); assert!(out.status.success()); let stderr = String::from_utf8(out.stderr).unwrap(); assert!(stderr.contains(&format!( r"info: syncing channel updates for 'nightly-{triple}'" ))); assert!(!stderr.contains(&format!( r"warning: toolchain 'nightly-{arch}' may not be able to run on this system." ))); }) }) } /// Checks that `rust-toolchain.toml` files are considered #[test] fn rust_toolchain_toml() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_err( &["rustc", "--version"], "rustup could not choose a version of rustc to run", ); let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain.toml"); raw::write_file(&toolchain_file, "[toolchain]\nchannel = \"nightly\"").unwrap(); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }) }); } /// Ensures that `rust-toolchain.toml` files (with `.toml` extension) only allow TOML contents #[test] fn only_toml_in_rust_toolchain_toml() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain.toml"); raw::write_file(&toolchain_file, "nightly").unwrap(); config.expect_err(&["rustc", "--version"], "error parsing override file"); }) }); } /// Checks that a warning occurs if both `rust-toolchain` and `rust-toolchain.toml` files exist #[test] fn warn_on_duplicate_rust_toolchain_file() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { let cwd = config.current_dir(); let toolchain_file_1 = cwd.join("rust-toolchain"); raw::write_file(&toolchain_file_1, "stable").unwrap(); let toolchain_file_2 = cwd.join("rust-toolchain.toml"); raw::write_file(&toolchain_file_2, "[toolchain]").unwrap(); config.expect_stderr_ok( &["rustc", "--version"], &format!( "warning: both `{0}` and `{1}` exist. Using `{0}`", toolchain_file_1.canonicalize().unwrap().display(), toolchain_file_2.canonicalize().unwrap().display(), ), ); }) }); } rustup-1.26.0/tests/suite/cli_self_upd.rs000066400000000000000000000657711441327105200204520ustar00rootroot00000000000000//! Testing self install, uninstall and update use std::env; use std::env::consts::EXE_SUFFIX; use std::fs; use std::path::Path; use std::process::Command; use remove_dir_all::remove_dir_all; use rustup::test::{this_host_triple, with_saved_path}; use rustup::utils::{raw, utils}; use rustup::{for_host, Notification, DUP_TOOLS, TOOLS}; use crate::mock::{ clitools::{self, output_release_file, self_update_setup, Config, Scenario}, dist::calc_hash, }; const TEST_VERSION: &str = "1.1.1"; pub fn update_setup(f: &dyn Fn(&mut Config, &Path)) { self_update_setup(f, TEST_VERSION) } /// Empty dist server, rustup installed with no toolchain fn setup_empty_installed(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::Empty, &|config| { config.expect_ok(&[ "rustup-init", "-y", "--no-modify-path", "--default-toolchain", "none", ]); f(config); }) } /// SimpleV3 dist server, rustup installed with default toolchain fn setup_installed(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); f(config); }) } #[test] /// This is the primary smoke test testing the full end to end behavior of the /// installation code path: everything that is output, the proxy installation, /// status of the proxies. fn install_bins_to_cargo_home() { clitools::test(Scenario::SimpleV2, &|config| { with_saved_path(&mut || { config.expect_ok_contains( &["rustup-init", "-y"], for_host!( r" stable-{0} installed - 1.1.0 (hash-stable-1.1.0) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: latest update on 2015-01-02, rust version 1.1.0 (hash-stable-1.1.0) info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rust-std' info: installing component 'rustc' info: default toolchain set to 'stable-{0}' " ), ); #[cfg(windows)] fn check(path: &Path) { assert!(path.exists()); } #[cfg(not(windows))] fn check(path: &Path) { fn is_exe(path: &Path) -> bool { use std::os::unix::fs::MetadataExt; let mode = path.metadata().unwrap().mode(); mode & 0o777 == 0o755 } assert!(is_exe(path)); } for tool in TOOLS.iter().chain(DUP_TOOLS.iter()) { let path = &config.cargodir.join(&format!("bin/{tool}{EXE_SUFFIX}")); check(path); } }) }); } #[test] fn install_twice() { clitools::test(Scenario::SimpleV2, &|config| { with_saved_path(&mut || { config.expect_ok(&["rustup-init", "-y"]); config.expect_ok(&["rustup-init", "-y"]); let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); assert!(rustup.exists()); }) }); } #[test] /// Smoke test for the entire install process when dirs need to be made : /// depending just on unit tests here could miss subtle dependencies being added /// earlier in the code, so a black-box test is needed. fn install_creates_cargo_home() { clitools::test(Scenario::Empty, &|config| { remove_dir_all(&config.cargodir).unwrap(); config.rustupdir.remove().unwrap(); config.expect_ok(&[ "rustup-init", "-y", "--no-modify-path", "--default-toolchain", "none", ]); assert!(config.cargodir.exists()); }); } #[test] /// Functional test needed here - we need to do the full dance where we start /// with rustup.exe and end up deleting that exe itself. fn uninstall_deletes_bins() { setup_empty_installed(&|config| { // no-modify-path isn't needed here, as the test-dir-path isn't present // in the registry, so the no-change code path will be triggered. config.expect_ok(&["rustup", "self", "uninstall", "-y"]); let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let rustc = config.cargodir.join(format!("bin/rustc{EXE_SUFFIX}")); let rustdoc = config.cargodir.join(format!("bin/rustdoc{EXE_SUFFIX}")); let cargo = config.cargodir.join(format!("bin/cargo{EXE_SUFFIX}")); let rust_lldb = config.cargodir.join(format!("bin/rust-lldb{EXE_SUFFIX}")); let rust_gdb = config.cargodir.join(format!("bin/rust-gdb{EXE_SUFFIX}")); let rust_gdbgui = config.cargodir.join(format!("bin/rust-gdbgui{EXE_SUFFIX}")); assert!(!rustup.exists()); assert!(!rustc.exists()); assert!(!rustdoc.exists()); assert!(!cargo.exists()); assert!(!rust_lldb.exists()); assert!(!rust_gdb.exists()); assert!(!rust_gdbgui.exists()); }); } #[test] fn uninstall_works_if_some_bins_dont_exist() { setup_empty_installed(&|config| { let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let rustc = config.cargodir.join(format!("bin/rustc{EXE_SUFFIX}")); let rustdoc = config.cargodir.join(format!("bin/rustdoc{EXE_SUFFIX}")); let cargo = config.cargodir.join(format!("bin/cargo{EXE_SUFFIX}")); let rust_lldb = config.cargodir.join(format!("bin/rust-lldb{EXE_SUFFIX}")); let rust_gdb = config.cargodir.join(format!("bin/rust-gdb{EXE_SUFFIX}")); let rust_gdbgui = config.cargodir.join(format!("bin/rust-gdbgui{EXE_SUFFIX}")); fs::remove_file(&rustc).unwrap(); fs::remove_file(&cargo).unwrap(); config.expect_ok(&["rustup", "self", "uninstall", "-y"]); assert!(!rustup.exists()); assert!(!rustc.exists()); assert!(!rustdoc.exists()); assert!(!cargo.exists()); assert!(!rust_lldb.exists()); assert!(!rust_gdb.exists()); assert!(!rust_gdbgui.exists()); }); } #[test] fn uninstall_deletes_rustup_home() { setup_empty_installed(&|config| { config.expect_ok(&["rustup", "self", "uninstall", "-y"]); assert!(!config.rustupdir.has(".")); }); } #[test] fn uninstall_works_if_rustup_home_doesnt_exist() { setup_empty_installed(&|config| { config.rustupdir.remove().unwrap(); config.expect_ok(&["rustup", "self", "uninstall", "-y"]); }); } #[test] fn uninstall_deletes_cargo_home() { setup_empty_installed(&|config| { config.expect_ok(&["rustup", "self", "uninstall", "-y"]); assert!(!config.cargodir.exists()); }); } #[test] fn uninstall_fails_if_not_installed() { setup_empty_installed(&|config| { let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); fs::remove_file(rustup).unwrap(); config.expect_err( &["rustup", "self", "uninstall", "-y"], "rustup is not installed", ); }); } // The other tests here just run rustup from a temp directory. This // does the uninstall by actually invoking the installed binary in // order to test that it can successfully delete itself. #[test] #[cfg_attr(target_os = "macos", ignore)] // FIXME #1515 fn uninstall_self_delete_works() { setup_empty_installed(&|config| { let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let mut cmd = Command::new(rustup.clone()); cmd.args(["self", "uninstall", "-y"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); println!("out: {}", String::from_utf8(out.stdout).unwrap()); println!("err: {}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success()); assert!(!rustup.exists()); assert!(!config.cargodir.exists()); let rustc = config.cargodir.join(format!("bin/rustc{EXE_SUFFIX}")); let rustdoc = config.cargodir.join(format!("bin/rustdoc{EXE_SUFFIX}")); let cargo = config.cargodir.join(format!("bin/cargo{EXE_SUFFIX}")); let rust_lldb = config.cargodir.join(format!("bin/rust-lldb{EXE_SUFFIX}")); let rust_gdb = config.cargodir.join(format!("bin/rust-gdb{EXE_SUFFIX}")); let rust_gdbgui = config.cargodir.join(format!("bin/rust-gdbgui{EXE_SUFFIX}")); assert!(!rustc.exists()); assert!(!rustdoc.exists()); assert!(!cargo.exists()); assert!(!rust_lldb.exists()); assert!(!rust_gdb.exists()); assert!(!rust_gdbgui.exists()); }); } // On windows rustup self uninstall temporarily puts a rustup-gc-$randomnumber.exe // file in CONFIG.CARGODIR/.. ; check that it doesn't exist. #[test] fn uninstall_doesnt_leave_gc_file() { use std::thread; use std::time::Duration; setup_empty_installed(&|config| { config.expect_ok(&["rustup", "self", "uninstall", "-y"]); // The gc removal happens after rustup terminates. Give it a moment. thread::sleep(Duration::from_millis(100)); let parent = config.cargodir.parent().unwrap(); // Actually, there just shouldn't be any files here for dirent in fs::read_dir(parent).unwrap() { let dirent = dirent.unwrap(); println!("{}", dirent.path().display()); panic!(); } }) } #[test] fn update_exact() { let version = env!("CARGO_PKG_VERSION"); let expected_output = "info: checking for self-update info: downloading self-update " .to_string(); update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok_ex( &["rustup", "self", "update"], &format!(" rustup updated - {version} (from {version})\n\n",), &expected_output, ) }); } #[test] fn update_but_not_installed() { update_setup(&|config, _| { config.expect_err_ex( &["rustup", "self", "update"], r"", &format!( r"error: rustup is not installed at '{}' ", config.cargodir.display() ), ); }); } #[test] fn update_but_delete_existing_updater_first() { update_setup(&|config, _| { // The updater is stored in a known location let setup = config.cargodir.join(format!("bin/rustup-init{EXE_SUFFIX}")); config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); // If it happens to already exist for some reason it // should just be deleted. raw::write_file(&setup, "").unwrap(); config.expect_ok(&["rustup", "self", "update"]); let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); assert!(rustup.exists()); }); } #[test] fn update_download_404() { update_setup(&|config, self_dist| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let trip = this_host_triple(); let dist_dir = self_dist.join(format!("archive/{TEST_VERSION}/{trip}")); let dist_exe = dist_dir.join(format!("rustup-init{EXE_SUFFIX}")); fs::remove_file(dist_exe).unwrap(); config.expect_err(&["rustup", "self", "update"], "could not download file"); }); } #[test] fn update_bogus_version() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_err( &["rustup", "update", "1.0.0-alpha"], "could not download nonexistent rust version `1.0.0-alpha`", ); }); } // Check that rustup.exe has changed after the update. This // is hard for windows because the running process needs to exit // before the new updater can delete it. #[test] fn update_updates_rustup_bin() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let bin = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let before_hash = calc_hash(&bin); // Running the self update command on the installed binary, // so that the running binary must be replaced. let mut cmd = Command::new(&bin); cmd.args(["self", "update"]); clitools::env(config, &mut cmd); let out = cmd.output().unwrap(); println!("out: {}", String::from_utf8(out.stdout).unwrap()); println!("err: {}", String::from_utf8(out.stderr).unwrap()); assert!(out.status.success()); let after_hash = calc_hash(&bin); assert_ne!(before_hash, after_hash); }); } #[test] fn update_bad_schema() { update_setup(&|config, self_dist| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); output_release_file(self_dist, "17", "1.1.1"); config.expect_err(&["rustup", "self", "update"], "unknown schema version"); }); } #[test] fn update_no_change() { let version = env!("CARGO_PKG_VERSION"); update_setup(&|config, self_dist| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); output_release_file(self_dist, "1", version); config.expect_ok_ex( &["rustup", "self", "update"], &format!( r" rustup unchanged - {version} " ), r"info: checking for self-update ", ); }); } #[test] fn rustup_self_updates_trivial() { update_setup(&|config, _| { config.expect_ok(&["rustup", "set", "auto-self-update", "enable"]); config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let bin = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let before_hash = calc_hash(&bin); config.expect_ok(&["rustup", "update"]); let after_hash = calc_hash(&bin); assert_ne!(before_hash, after_hash); }) } #[test] fn rustup_self_updates_with_specified_toolchain() { update_setup(&|config, _| { config.expect_ok(&["rustup", "set", "auto-self-update", "enable"]); config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let bin = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let before_hash = calc_hash(&bin); config.expect_ok(&["rustup", "update", "stable"]); let after_hash = calc_hash(&bin); assert_ne!(before_hash, after_hash); }) } #[test] fn rustup_no_self_update_with_specified_toolchain() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let bin = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); let before_hash = calc_hash(&bin); config.expect_ok(&["rustup", "update", "stable"]); let after_hash = calc_hash(&bin); assert_eq!(before_hash, after_hash); }) } #[test] fn rustup_self_update_exact() { update_setup(&|config, _| { config.expect_ok(&["rustup", "set", "auto-self-update", "enable"]); config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok_ex( &["rustup", "update"], for_host!( r" stable-{0} unchanged - 1.1.0 (hash-stable-1.1.0) " ), for_host!( r"info: syncing channel updates for 'stable-{0}' info: checking for self-update info: downloading self-update info: cleaning up downloads & tmp directories " ), ); }) } // Because self-delete on windows is hard, rustup-init doesn't // do it. It instead leaves itself installed for cleanup by later // invocations of rustup. #[test] fn updater_leaves_itself_for_later_deletion() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "self", "update"]); let setup = config.cargodir.join(format!("bin/rustup-init{EXE_SUFFIX}")); assert!(setup.exists()); }); } #[test] fn updater_is_deleted_after_running_rustup() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "self", "update"]); config.expect_ok(&["rustup", "update", "nightly"]); let setup = config.cargodir.join(format!("bin/rustup-init{EXE_SUFFIX}")); assert!(!setup.exists()); }); } #[test] fn updater_is_deleted_after_running_rustc() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "self", "update"]); config.expect_ok(&["rustc", "--version"]); let setup = config.cargodir.join(format!("bin/rustup-init{EXE_SUFFIX}")); assert!(!setup.exists()); }); } #[test] fn rustup_still_works_after_update() { update_setup(&|config, _| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "self", "update"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_ok(&["rustup", "default", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); } // The installer used to be called rustup-setup. For compatibility it // still needs to work in that mode. #[test] fn as_rustup_setup() { clitools::test(Scenario::Empty, &|config| { let init = config.exedir.join(format!("rustup-init{EXE_SUFFIX}")); let setup = config.exedir.join(format!("rustup-setup{EXE_SUFFIX}")); fs::copy(init, setup).unwrap(); config.expect_ok(&[ "rustup-setup", "-y", "--no-modify-path", "--default-toolchain", "none", ]); }); } #[test] fn reinstall_exact() { setup_empty_installed(&|config| { config.expect_stderr_ok( &[ "rustup-init", "-y", "--no-update-default-toolchain", "--no-modify-path", ], r"info: updating existing rustup installation - leaving toolchains alone", ); }); } #[test] fn reinstall_specifying_toolchain() { setup_installed(&|config| { config.expect_stdout_ok( &[ "rustup-init", "-y", "--default-toolchain=stable", "--no-modify-path", ], for_host!(r"stable-{0} unchanged - 1.1.0"), ); }); } #[test] fn reinstall_specifying_component() { setup_installed(&|config| { config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_stdout_ok( &[ "rustup-init", "-y", "--default-toolchain=stable", "--no-modify-path", ], for_host!(r"stable-{0} unchanged - 1.1.0"), ); }); } #[test] fn reinstall_specifying_different_toolchain() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_stderr_ok( &[ "rustup-init", "-y", "--default-toolchain=nightly", "--no-modify-path", ], for_host!(r"info: default toolchain set to 'nightly-{0}'"), ); }); } #[test] fn install_sets_up_stable_unless_a_different_default_is_requested() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&[ "rustup-init", "-y", "--default-toolchain", "nightly", "--no-modify-path", ]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); } #[test] fn install_sets_up_stable_unless_there_is_already_a_default() { setup_installed(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "stable"]); config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_err( &["rustup", "run", "stable", "rustc", "--version"], for_host!("toolchain 'stable-{0}' is not installed"), ); }); } #[test] fn readline_no_stdin() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_err( &["rustup-init", "--no-modify-path"], "unable to read from stdin for confirmation", ); }); } #[test] fn rustup_init_works_with_weird_names() { // Browsers often rename bins to e.g. rustup-init(2).exe. clitools::test(Scenario::SimpleV2, &|config| { let old = config.exedir.join(format!("rustup-init{EXE_SUFFIX}")); let new = config.exedir.join(format!("rustup-init(2){EXE_SUFFIX}")); utils::rename_file("test", &old, &new, &|_: Notification<'_>| {}).unwrap(); config.expect_ok(&["rustup-init(2)", "-y", "--no-modify-path"]); let rustup = config.cargodir.join(format!("bin/rustup{EXE_SUFFIX}")); assert!(rustup.exists()); }); } #[test] fn install_but_rustup_sh_is_installed() { clitools::test(Scenario::Empty, &|config| { config.create_rustup_sh_metadata(); config.expect_stderr_ok( &[ "rustup-init", "-y", "--default-toolchain", "none", "--no-modify-path", ], "cannot install while rustup.sh is installed", ); }); } #[test] fn test_warn_succeed_if_rustup_sh_already_installed_y_flag() { clitools::test(Scenario::SimpleV2, &|config| { config.create_rustup_sh_metadata(); let out = config.run("rustup-init", &["-y", "--no-modify-path"], &[]); assert!(out.ok); assert!(out .stderr .contains("warning: it looks like you have existing rustup.sh metadata")); assert!(out .stderr .contains("error: cannot install while rustup.sh is installed")); assert!(out.stderr.contains( "warning: continuing (because the -y flag is set and the error is ignorable)" )); assert!(!out.stdout.contains("Continue? (y/N)")); }) } #[test] fn test_succeed_if_rustup_sh_already_installed_env_var_set() { clitools::test(Scenario::SimpleV2, &|config| { config.create_rustup_sh_metadata(); let out = config.run( "rustup-init", &["-y", "--no-modify-path"], &[("RUSTUP_INIT_SKIP_EXISTENCE_CHECKS", "yes")], ); assert!(out.ok); assert!(!out .stderr .contains("warning: it looks like you have existing rustup.sh metadata")); assert!(!out .stderr .contains("error: cannot install while rustup.sh is installed")); assert!(!out.stderr.contains( "warning: continuing (because the -y flag is set and the error is ignorable)" )); assert!(!out.stdout.contains("Continue? (y/N)")); }) } #[test] fn rls_proxy_set_up_after_install() { setup_installed(&|config| { config.expect_err( &["rls", "--version"], &format!( "'rls{}' is not installed for the toolchain 'stable-{}'", EXE_SUFFIX, this_host_triple(), ), ); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_ok(&["rls", "--version"]); }); } #[test] fn rls_proxy_set_up_after_update() { update_setup(&|config, _| { let rls_path = config.cargodir.join(format!("bin/rls{EXE_SUFFIX}")); config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); fs::remove_file(&rls_path).unwrap(); config.expect_ok(&["rustup", "self", "update"]); assert!(rls_path.exists()); }); } #[test] fn update_does_not_overwrite_rustfmt() { update_setup(&|config, self_dist| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let version = env!("CARGO_PKG_VERSION"); output_release_file(self_dist, "1", version); // Since we just did a fresh install rustfmt will exist. Let's emulate // it not existing in this test though by removing it just after our // installation. let rustfmt_path = config.cargodir.join(format!("bin/rustfmt{EXE_SUFFIX}")); assert!(rustfmt_path.exists()); fs::remove_file(&rustfmt_path).unwrap(); raw::write_file(&rustfmt_path, "").unwrap(); assert_eq!(utils::file_size(&rustfmt_path).unwrap(), 0); // Ok, now a self-update should complain about `rustfmt` not looking // like rustup and the user should take some action. config.expect_stderr_ok( &["rustup", "self", "update"], "`rustfmt` is already installed", ); assert!(rustfmt_path.exists()); assert_eq!(utils::file_size(&rustfmt_path).unwrap(), 0); // Now simulate us removing the rustfmt executable and rerunning a self // update, this should install the rustup shim. Note that we don't run // `rustup` here but rather the rustup we've actually installed, this'll // help reproduce bugs related to having that file being opened by the // current process. fs::remove_file(&rustfmt_path).unwrap(); let installed_rustup = config.cargodir.join("bin/rustup"); config.expect_ok(&[installed_rustup.to_str().unwrap(), "self", "update"]); assert!(rustfmt_path.exists()); assert!(utils::file_size(&rustfmt_path).unwrap() > 0); }); } #[test] fn update_installs_clippy_cargo_and() { update_setup(&|config, self_dist| { config.expect_ok(&["rustup-init", "-y", "--no-modify-path"]); let version = env!("CARGO_PKG_VERSION"); output_release_file(self_dist, "1", version); let cargo_clippy_path = config .cargodir .join(format!("bin/cargo-clippy{EXE_SUFFIX}")); assert!(cargo_clippy_path.exists()); }); } #[test] fn install_with_components_and_targets() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&[ "rustup-init", "--default-toolchain", "nightly", "-y", "-c", "rls", "-t", clitools::CROSS_ARCH1, "--no-modify-path", ]); config.expect_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); config.expect_stdout_ok( &["rustup", "component", "list"], &format!("rls-{} (installed)", this_host_triple()), ); }) } #[test] fn install_minimal_profile() { clitools::test(Scenario::SimpleV2, &|config| { config.expect_ok(&[ "rustup-init", "-y", "--profile", "minimal", "--no-modify-path", ]); config.expect_component_executable("rustup"); config.expect_component_executable("rustc"); config.expect_component_not_executable("cargo"); }); } rustup-1.26.0/tests/suite/cli_ui.rs000066400000000000000000000044171441327105200172540ustar00rootroot00000000000000use std::{fs, path::PathBuf}; #[test] fn rustup_ui_doc_text_tests() { let t = trycmd::TestCases::new(); let rustup_init = trycmd::cargo::cargo_bin("rustup-init"); let rustup = trycmd::cargo::cargo_bin("rustup"); // Copy rustup-init to rustup so that the tests can run it. fs::copy(rustup_init, &rustup).unwrap(); t.register_bin("rustup", &rustup); t.case("tests/suite/cli-ui/rustup/*.toml"); #[cfg(target_os = "windows")] { // On windows, we don't have man command, so skip the test. t.skip("tests/suite/cli-ui/rustup/rustup_man_cmd_help_flag_stdout.toml"); } } #[test] fn rustup_init_ui_doc_text_tests() { let t = trycmd::TestCases::new(); let rustup_init = trycmd::cargo::cargo_bin("rustup-init"); let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); t.register_bin("rustup-init", &rustup_init); t.register_bin("rustup-init.sh", &project_root.join("rustup-init.sh")); t.case("tests/suite/cli-ui/rustup-init/*.toml"); #[cfg(target_os = "windows")] { // On windows, we don't use rustup-init.sh, so skip the test. t.skip("tests/suite/cli-ui/rustup-init/rustup-init_sh_help_flag_stdout.toml"); } // On non-windows, we don't use rustup-init.sh, so skip the test. #[cfg(not(target_os = "windows"))] { let rustup_init_help_toml = project_root.join("tests/suite/cli-ui/rustup-init/rustup-init_help_flag_stdout.toml"); let rustup_init_sh_help_toml = project_root .join("tests/suite/cli-ui/rustup-init/rustup-init_sh_help_flag_stdout.toml"); #[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] struct Stdout { #[serde(default)] pub(crate) stdout: Option, } let rustup_init_help_std_out: Stdout = toml::from_str(fs::read_to_string(rustup_init_help_toml).unwrap().as_str()).unwrap(); let rustup_init_sh_help_std_out: Stdout = toml::from_str( fs::read_to_string(rustup_init_sh_help_toml) .unwrap() .as_str(), ) .unwrap(); // Make sure that the help output of rustup-init and rustup-init.sh are the same. assert_eq!(rustup_init_help_std_out, rustup_init_sh_help_std_out) } } rustup-1.26.0/tests/suite/cli_v1.rs000066400000000000000000000313421441327105200171620ustar00rootroot00000000000000//! Test cases of the rustup command, using v1 manifests, mostly //! derived from multirust/test-v2.sh use std::fs; use rustup::for_host; use crate::mock::clitools::{self, set_current_dist_date, Config, Scenario}; pub fn setup(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::SimpleV1, f); } #[test] fn rustc_no_default_toolchain() { setup(&|config| { config.expect_err( &["rustc"], "rustup could not choose a version of rustc to run", ); }); } #[test] fn expected_bins_exist() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "1.3.0"); }); } #[test] fn install_toolchain_from_channel() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_ok(&["rustup", "default", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); config.expect_ok(&["rustup", "default", "stable"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn install_toolchain_from_archive() { clitools::test(Scenario::ArchivesV1, &|config| { config.expect_ok(&["rustup", "default", "nightly-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_ok(&["rustup", "default", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.1.0"); config.expect_ok(&["rustup", "default", "stable-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.0.0"); }); } #[test] fn install_toolchain_from_version() { setup(&|config| { config.expect_ok(&["rustup", "default", "1.1.0"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn default_existing_toolchain() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stderr_ok( &["rustup", "default", "nightly"], for_host!("using existing install for 'nightly-{0}'"), ); }); } #[test] fn update_channel() { clitools::test(Scenario::ArchivesV1, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); } #[test] fn list_toolchains() { clitools::test(Scenario::ArchivesV1, &|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "update", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "nightly"); config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "(default)\t"); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], for_host!("\\toolchains\\nightly-{}"), ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], for_host!("/toolchains/nightly-{}"), ); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "beta-2015-01-01"); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], "\\toolchains\\beta-2015-01-01", ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], "/toolchains/beta-2015-01-01", ); }); } #[test] fn list_toolchains_with_none() { setup(&|config| { config.expect_stdout_ok(&["rustup", "toolchain", "list"], "no installed toolchains"); }); } #[test] fn remove_toolchain() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); config.expect_ok(&["rustup", "toolchain", "list"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "no installed toolchains"); }); } #[test] fn remove_default_toolchain_autoinstalls() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); config.expect_stderr_ok(&["rustc", "--version"], "info: installing component"); }); } #[test] fn remove_override_toolchain_err_handling() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_ok(&["rustup", "toolchain", "remove", "beta"]); config.expect_stderr_ok(&["rustc", "--version"], "info: installing component"); }); }); } #[test] fn bad_sha_on_manifest() { setup(&|config| { let sha_file = config .distdir .as_ref() .unwrap() .join("dist/channel-rust-nightly.sha256"); let sha_str = fs::read_to_string(&sha_file).unwrap(); let mut sha_bytes = sha_str.into_bytes(); sha_bytes[..10].clone_from_slice(b"aaaaaaaaaa"); let sha_str = String::from_utf8(sha_bytes).unwrap(); rustup::utils::raw::write_file(&sha_file, &sha_str).unwrap(); config.expect_err(&["rustup", "default", "nightly"], "checksum failed"); }); } #[test] fn bad_sha_on_installer() { setup(&|config| { let dir = config.distdir.as_ref().unwrap().join("dist"); for file in fs::read_dir(&dir).unwrap() { let file = file.unwrap(); let path = file.path(); let filename = path.to_string_lossy(); if filename.ends_with(".tar.gz") || filename.ends_with(".tar.xz") { rustup::utils::raw::write_file(&path, "xxx").unwrap(); } } config.expect_err(&["rustup", "default", "nightly"], "checksum failed"); }); } #[test] fn install_override_toolchain_from_channel() { setup(&|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); config.expect_ok(&["rustup", "override", "add", "stable"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn install_override_toolchain_from_archive() { clitools::test(Scenario::ArchivesV1, &|config| { config.expect_ok(&["rustup", "override", "add", "nightly-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_ok(&["rustup", "override", "add", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.1.0"); config.expect_ok(&["rustup", "override", "add", "stable-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.0.0"); }); } #[test] fn install_override_toolchain_from_version() { setup(&|config| { config.expect_ok(&["rustup", "override", "add", "1.1.0"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn override_overrides_default() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); }); } #[test] fn multiple_overrides() { setup(&|config| { let tempdir1 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tempdir2 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.change_dir(tempdir1.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "beta"]); }); config.change_dir(tempdir2.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "stable"]); }); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.change_dir(tempdir1.path(), &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); config.change_dir(tempdir2.path(), &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); }); } #[test] fn change_override() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); }); } #[test] fn remove_override_no_default() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok(&["rustup", "override", "remove"]); config.expect_err( &["rustc"], "rustup could not choose a version of rustc to run", ); }); }); } #[test] fn remove_override_with_default() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_ok(&["rustup", "override", "remove"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); }); } #[test] fn remove_override_with_multiple_overrides() { setup(&|config| { let tempdir1 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tempdir2 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.change_dir(tempdir1.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "beta"]); }); config.change_dir(tempdir2.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "stable"]); }); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.change_dir(tempdir1.path(), &|config| { config.expect_ok(&["rustup", "override", "remove"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); config.change_dir(tempdir2.path(), &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); }); } #[test] fn no_update_on_channel_when_date_has_not_changed() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustup", "update", "nightly"], "unchanged"); }); } #[test] fn update_on_channel_when_date_has_changed() { clitools::test(Scenario::ArchivesV1, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); } #[test] fn run_command() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "default", "beta"]); config.expect_stdout_ok( &["rustup", "run", "nightly", "rustc", "--version"], "hash-nightly-2", ); }); } #[test] fn remove_toolchain_then_add_again() { // Issue brson/multirust #53 setup(&|config| { config.expect_ok(&["rustup", "default", "beta"]); config.expect_ok(&["rustup", "toolchain", "remove", "beta"]); config.expect_ok(&["rustup", "update", "beta"]); config.expect_ok(&["rustc", "--version"]); }); } rustup-1.26.0/tests/suite/cli_v2.rs000066400000000000000000001304761441327105200171730ustar00rootroot00000000000000//! Test cases of the rustup command, using v2 manifests, mostly //! derived from multirust/test-v2.sh use std::fs; use std::io::Write; use rustup::dist::dist::TargetTriple; use rustup::for_host; use rustup::test::this_host_triple; use crate::mock::clitools::{self, set_current_dist_date, Config, Scenario}; pub fn setup(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::SimpleV2, f); } pub fn setup_complex(f: &dyn Fn(&mut Config)) { clitools::test(Scenario::UnavailableRls, f); } #[test] fn rustc_no_default_toolchain() { setup(&|config| { config.expect_err( &["rustc"], "rustup could not choose a version of rustc to run", ); }); } #[test] fn expected_bins_exist() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "1.3.0"); }); } #[test] fn install_toolchain_from_channel() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_ok(&["rustup", "default", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); config.expect_ok(&["rustup", "default", "stable"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn install_toolchain_from_archive() { clitools::test(Scenario::ArchivesV2, &|config| { config.expect_ok(&["rustup", "default", "nightly-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_ok(&["rustup", "default", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.1.0"); config.expect_ok(&["rustup", "default", "stable-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.0.0"); }); } #[test] fn install_toolchain_from_version() { setup(&|config| { config.expect_ok(&["rustup", "default", "1.1.0"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn install_with_profile() { setup_complex(&|config| { // Start with a config that uses the "complete" profile set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "set", "profile", "complete"]); // Installing with minimal profile should only install rustc config.expect_ok(&[ "rustup", "toolchain", "install", "--profile", "minimal", "nightly", ]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_component_executable("rustup"); config.expect_component_executable("rustc"); config.expect_component_not_executable("cargo"); // After an update, we should _still_ only have the profile-dictated components set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_component_executable("rustup"); config.expect_component_executable("rustc"); config.expect_component_not_executable("cargo"); }); } #[test] fn default_existing_toolchain() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stderr_ok( &["rustup", "default", "nightly"], for_host!("using existing install for 'nightly-{0}'"), ); }); } #[test] fn update_channel() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); } #[test] fn list_toolchains() { clitools::test(Scenario::ArchivesV2, &|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "update", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "nightly"); config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "(default)\t"); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], for_host!("\\toolchains\\nightly-{}"), ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], for_host!("/toolchains/nightly-{}"), ); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "beta-2015-01-01"); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], "\\toolchains\\beta-2015-01-01", ); #[cfg(not(windows))] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], "/toolchains/beta-2015-01-01", ); }); } #[test] fn list_toolchains_with_bogus_file() { // #520 setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); let name = "bogus_regular_file.txt"; let path = config.rustupdir.join("toolchains").join(name); rustup::utils::utils::write_file(name, &path, "").unwrap(); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "nightly"); config.expect_not_stdout_ok(&["rustup", "toolchain", "list"], name); }); } #[test] fn list_toolchains_with_none() { setup(&|config| { config.expect_stdout_ok(&["rustup", "toolchain", "list"], "no installed toolchains"); }); } #[test] fn remove_toolchain() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); config.expect_ok(&["rustup", "toolchain", "list"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "no installed toolchains"); }); } // Issue #2873 #[test] fn remove_toolchain_ignore_trailing_slash() { setup(&|config| { // custom toolchain name with trailing slash let path = config.customdir.join("custom-1"); let path_str = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "dev", &path_str]); config.expect_stderr_ok( &["rustup", "toolchain", "remove", "dev/"], "toolchain 'dev' uninstalled", ); // check if custom toolchain directory contents are not removed let toolchain_dir_is_non_empty = fs::read_dir(&path).unwrap().next().is_some(); assert!(toolchain_dir_is_non_empty); // distributable toolchain name with trailing slash config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stderr_ok( &["rustup", "toolchain", "remove", for_host!("nightly-{}/")], for_host!("toolchain 'nightly-{}' uninstalled"), ); }); } #[test] fn add_remove_multiple_toolchains() { fn go(add: &str, rm: &str) { setup(&|config| { let tch1 = "beta"; let tch2 = "nightly"; config.expect_ok(&["rustup", "toolchain", add, tch1, tch2]); config.expect_ok(&["rustup", "toolchain", "list"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], tch1); config.expect_stdout_ok(&["rustup", "toolchain", "list"], tch2); config.expect_ok(&["rustup", "toolchain", rm, tch1, tch2]); config.expect_ok(&["rustup", "toolchain", "list"]); config.expect_not_stdout_ok(&["rustup", "toolchain", "list"], tch1); config.expect_not_stdout_ok(&["rustup", "toolchain", "list"], tch2); }); } for add in &["add", "update", "install"] { for rm in &["remove", "uninstall"] { go(add, rm); } } } #[test] fn remove_default_toolchain_autoinstalls() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); config.expect_stderr_ok(&["rustc", "--version"], "info: installing component"); }); } #[test] fn remove_override_toolchain_err_handling() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_ok(&["rustup", "toolchain", "remove", "beta"]); config.expect_stderr_ok(&["rustc", "--version"], "info: installing component"); }); }); } #[test] fn file_override_toolchain_err_handling() { setup(&|config| { let cwd = config.current_dir(); let toolchain_file = cwd.join("rust-toolchain"); rustup::utils::raw::write_file(&toolchain_file, "beta").unwrap(); config.expect_stderr_ok(&["rustc", "--version"], "info: installing component"); }); } #[test] fn plus_override_toolchain_err_handling() { setup(&|config| { config.expect_err( &["rustc", "+beta"], for_host!("toolchain 'beta-{0}' is not installed"), ); }); } #[test] fn bad_sha_on_manifest() { setup(&|config| { // Corrupt the sha let sha_file = config .distdir .as_ref() .unwrap() .join("dist/channel-rust-nightly.toml.sha256"); let sha_str = fs::read_to_string(&sha_file).unwrap(); let mut sha_bytes = sha_str.into_bytes(); sha_bytes[..10].clone_from_slice(b"aaaaaaaaaa"); let sha_str = String::from_utf8(sha_bytes).unwrap(); rustup::utils::raw::write_file(&sha_file, &sha_str).unwrap(); // We fail because the sha is bad, but we should emit the special message to that effect. config.expect_err( &["rustup", "default", "nightly"], "update not yet available", ); }); } #[test] fn bad_sha_on_installer() { setup(&|config| { // Since the v2 sha's are contained in the manifest, corrupt the installer let dir = config.distdir.as_ref().unwrap().join("dist/2015-01-02"); for file in fs::read_dir(&dir).unwrap() { let file = file.unwrap(); let path = file.path(); let filename = path.to_string_lossy(); if filename.ends_with(".tar.gz") || filename.ends_with(".tar.xz") || filename.ends_with(".tar.zst") { rustup::utils::raw::write_file(&path, "xxx").unwrap(); } } config.expect_err(&["rustup", "default", "nightly"], "checksum failed"); }); } #[test] fn install_override_toolchain_from_channel() { setup(&|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); config.expect_ok(&["rustup", "override", "add", "stable"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn install_override_toolchain_from_archive() { clitools::test(Scenario::ArchivesV2, &|config| { config.expect_ok(&["rustup", "override", "add", "nightly-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); config.expect_ok(&["rustup", "override", "add", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.1.0"); config.expect_ok(&["rustup", "override", "add", "stable-2015-01-01"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.0.0"); }); } #[test] fn install_override_toolchain_from_version() { setup(&|config| { config.expect_ok(&["rustup", "override", "add", "1.1.0"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); } #[test] fn override_overrides_default() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); }); } #[test] fn multiple_overrides() { setup(&|config| { let tempdir1 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tempdir2 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.change_dir(tempdir1.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "beta"]); }); config.change_dir(tempdir2.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "stable"]); }); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.change_dir(tempdir1.path(), &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); config.change_dir(tempdir2.path(), &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); }); } // #316 #[test] #[cfg(windows)] fn override_windows_root() { setup(&|config| { use std::path::{Component, PathBuf}; let cwd = config.current_dir(); let prefix = cwd.components().next().unwrap(); let prefix = match prefix { Component::Prefix(p) => p, _ => panic!(), }; // This value is probably "C:" // Really sketchy to be messing with C:\ in a test... let prefix = prefix.as_os_str().to_str().unwrap(); let prefix = format!("{prefix}\\"); config.change_dir(&PathBuf::from(&prefix), &|config| { config.expect_ok(&["rustup", "default", "stable"]); config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_ok(&["rustup", "override", "remove"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); }); } #[test] fn change_override() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); }); }); } #[test] fn remove_override_no_default() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok(&["rustup", "override", "remove"]); config.expect_err( &["rustc"], "rustup could not choose a version of rustc to run", ); }); }); } #[test] fn remove_override_with_default() { setup(&|config| { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.change_dir(tempdir.path(), &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "override", "add", "beta"]); config.expect_ok(&["rustup", "override", "remove"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); }); } #[test] fn remove_override_with_multiple_overrides() { setup(&|config| { let tempdir1 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tempdir2 = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.change_dir(tempdir1.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "beta"]); }); config.change_dir(tempdir2.path(), &|config| { config.expect_ok(&["rustup", "override", "add", "stable"]); }); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.change_dir(tempdir1.path(), &|config| { config.expect_ok(&["rustup", "override", "remove"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); config.change_dir(tempdir2.path(), &|config| { config.expect_stdout_ok(&["rustc", "--version"], "hash-stable-1.1.0"); }); }); } #[test] fn no_update_on_channel_when_date_has_not_changed() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustup", "update", "nightly"], "unchanged"); }); } #[test] fn update_on_channel_when_date_has_changed() { clitools::test(Scenario::ArchivesV2, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); } #[test] fn run_command() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "default", "beta"]); config.expect_stdout_ok( &["rustup", "run", "nightly", "rustc", "--version"], "hash-nightly-2", ); }); } #[test] fn remove_toolchain_then_add_again() { // Issue brson/multirust #53 setup(&|config| { config.expect_ok(&["rustup", "default", "beta"]); config.expect_ok(&["rustup", "toolchain", "remove", "beta"]); config.expect_ok(&["rustup", "update", "beta"]); config.expect_ok(&["rustc", "--version"]); }); } #[test] fn upgrade_v1_to_v2() { clitools::test(Scenario::Full, &|config| { set_current_dist_date(config, "2015-01-01"); // Delete the v2 manifest so the first day we install from the v1s fs::remove_file( config .distdir .as_ref() .unwrap() .join("dist/channel-rust-nightly.toml.sha256"), ) .unwrap(); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-1"); set_current_dist_date(config, "2015-01-02"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); }); } #[test] fn upgrade_v2_to_v1() { clitools::test(Scenario::Full, &|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "default", "nightly"]); set_current_dist_date(config, "2015-01-02"); fs::remove_file( config .distdir .as_ref() .unwrap() .join("dist/channel-rust-nightly.toml.sha256"), ) .unwrap(); config.expect_err( &["rustup", "update", "nightly"], "the server unexpectedly provided an obsolete version of the distribution manifest", ); }); } #[test] fn list_targets_no_toolchain() { setup(&|config| { config.expect_err( &["rustup", "target", "list", "--toolchain=nightly"], for_host!("toolchain 'nightly-{0}' is not installed"), ); }); } #[test] fn list_targets_v1_toolchain() { clitools::test(Scenario::SimpleV1, &|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_err( &["rustup", "target", "list", "--toolchain=nightly"], for_host!("toolchain 'nightly-{0}' does not support components"), ); }); } #[test] fn list_targets_custom_toolchain() { setup(&|config| { let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "default-from-path", &path]); config.expect_ok(&["rustup", "default", "default-from-path"]); config.expect_err( &["rustup", "target", "list"], "toolchain 'default-from-path' does not support components", ); }); } #[test] fn list_targets() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "target", "list"], clitools::CROSS_ARCH1); config.expect_stdout_ok(&["rustup", "target", "list"], clitools::CROSS_ARCH2); }); } #[test] fn list_installed_targets() { setup(&|config| { let trip = this_host_triple(); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "target", "list", "--installed"], &trip); }); } #[test] fn add_target1() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", this_host_triple(), clitools::CROSS_ARCH1 ); assert!(config.rustupdir.has(path)); }); } #[test] fn add_target2() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH2]); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", this_host_triple(), clitools::CROSS_ARCH2 ); assert!(config.rustupdir.has(path)); }); } #[test] fn add_all_targets() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", "all"]); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", this_host_triple(), clitools::CROSS_ARCH1 ); assert!(config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", this_host_triple(), clitools::CROSS_ARCH2 ); assert!(config.rustupdir.has(path)); }); } #[test] fn add_all_targets_fail() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &[ "rustup", "target", "add", clitools::CROSS_ARCH1, "all", clitools::CROSS_ARCH2, ], &format!( "`rustup target add {} all {}` includes `all`", clitools::CROSS_ARCH1, clitools::CROSS_ARCH2 ), ); }); } #[test] fn add_target_by_component_add() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_not_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); config.expect_ok(&[ "rustup", "component", "add", &format!("rust-std-{}", clitools::CROSS_ARCH1), ]); config.expect_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); }) } #[test] fn remove_target_by_component_remove() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); config.expect_ok(&[ "rustup", "component", "remove", &format!("rust-std-{}", clitools::CROSS_ARCH1), ]); config.expect_not_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); }) } #[test] fn add_target_no_toolchain() { setup(&|config| { config.expect_err( &[ "rustup", "target", "add", clitools::CROSS_ARCH1, "--toolchain=nightly", ], for_host!("toolchain 'nightly-{0}' is not installed"), ); }); } #[test] fn add_target_bogus() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rustup", "target", "add", "bogus"], "does not contain component 'rust-std' for target 'bogus'\n\ note: not all platforms have the standard library pre-compiled: https://doc.rust-lang.org/nightly/rustc/platform-support.html\n\ help: consider using `cargo build -Z build-std` instead", ); }); } #[test] fn add_target_v1_toolchain() { clitools::test(Scenario::SimpleV1, &|config| { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_err( &[ "rustup", "target", "add", clitools::CROSS_ARCH1, "--toolchain=nightly", ], for_host!("Missing manifest in toolchain 'nightly-{0}'"), ); }); } #[test] fn add_target_custom_toolchain() { setup(&|config| { let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "default-from-path", &path]); config.expect_ok(&["rustup", "default", "default-from-path"]); config.expect_err( &["rustup", "target", "add", clitools::CROSS_ARCH1], "toolchain 'default-from-path' does not support components", ); }); } #[test] fn cannot_add_empty_named_custom_toolchain() { setup(&|config| { let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_err( &["rustup", "toolchain", "link", "", &path], "toolchain names must not be empty", ); }); } #[test] fn add_target_again() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_stderr_ok( &["rustup", "target", "add", clitools::CROSS_ARCH1], &format!( "component 'rust-std' for target '{}' is up to date", clitools::CROSS_ARCH1 ), ); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", this_host_triple(), clitools::CROSS_ARCH1 ); assert!(config.rustupdir.has(path)); }); } #[test] fn add_target_host() { setup(&|config| { let trip = this_host_triple(); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", &trip]); }); } #[test] fn remove_target() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_ok(&["rustup", "target", "remove", clitools::CROSS_ARCH1]); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", this_host_triple(), clitools::CROSS_ARCH1 ); assert!(!config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}/lib", this_host_triple(), clitools::CROSS_ARCH1 ); assert!(!config.rustupdir.has(path)); let path = format!( "toolchains/nightly-{}/lib/rustlib/{}", this_host_triple(), clitools::CROSS_ARCH1 ); assert!(!config.rustupdir.has(path)); }); } #[test] fn remove_target_not_installed() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rustup", "target", "remove", clitools::CROSS_ARCH1], &format!( "toolchain 'nightly-{}' does not contain component 'rust-std' for target '{}'", this_host_triple(), clitools::CROSS_ARCH1 ), ); }); } #[test] fn remove_target_no_toolchain() { setup(&|config| { config.expect_err( &[ "rustup", "target", "remove", clitools::CROSS_ARCH1, "--toolchain=nightly", ], for_host!("toolchain 'nightly-{0}' is not installed"), ); }); } #[test] fn remove_target_bogus() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rustup", "target", "remove", "bogus"], "does not contain component 'rust-std' for target 'bogus'", ); }); } #[test] fn remove_target_v1_toolchain() { clitools::test(Scenario::SimpleV1, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &[ "rustup", "target", "remove", clitools::CROSS_ARCH1, "--toolchain=nightly", ], for_host!("Missing manifest in toolchain 'nightly-{0}'"), ); }); } #[test] fn remove_target_custom_toolchain() { setup(&|config| { let path = config.customdir.join("custom-1"); let path = path.to_string_lossy(); config.expect_ok(&["rustup", "toolchain", "link", "default-from-path", &path]); config.expect_ok(&["rustup", "default", "default-from-path"]); config.expect_err( &["rustup", "target", "remove", clitools::CROSS_ARCH1], "toolchain 'default-from-path' does not support components", ); }); } #[test] fn remove_target_again() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_ok(&["rustup", "target", "remove", clitools::CROSS_ARCH1]); config.expect_err( &["rustup", "target", "remove", clitools::CROSS_ARCH1], &format!( "toolchain 'nightly-{}' does not contain component 'rust-std' for target '{}'", this_host_triple(), clitools::CROSS_ARCH1 ), ); }); } #[test] fn remove_target_host() { setup(&|config| { let trip = this_host_triple(); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&["rustup", "target", "remove", &trip]); }); } #[test] // Issue #304 fn remove_target_missing_update_hash() { setup(&|config| { config.expect_ok(&["rustup", "update", "nightly"]); let file_name = format!("nightly-{}", this_host_triple()); fs::remove_file(config.rustupdir.join("update-hashes").join(file_name)).unwrap(); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); }); } // Issue #1777 #[test] fn warn_about_and_remove_stray_hash() { setup(&|config| { let mut hash_path = config.rustupdir.join("update-hashes"); fs::create_dir_all(&hash_path).expect("Unable to make the update-hashes directory"); hash_path.push(for_host!("nightly-{}")); let mut file = fs::File::create(&hash_path).expect("Unable to open update-hash file"); file.write_all(b"LEGITHASH") .expect("Unable to write update-hash"); drop(file); config.expect_stderr_ok( &["rustup", "toolchain", "install", "nightly"], &format!( "removing stray hash found at '{}' in order to continue", hash_path.display() ), ); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "1.3.0"); }); } fn make_component_unavailable(config: &Config, name: &str, target: &str) { use crate::mock::dist::create_hash; use rustup::dist::manifest::Manifest; let manifest_path = config .distdir .as_ref() .unwrap() .join("dist/channel-rust-nightly.toml"); let manifest_str = fs::read_to_string(&manifest_path).unwrap(); let mut manifest = Manifest::parse(&manifest_str).unwrap(); { let std_pkg = manifest.packages.get_mut(name).unwrap(); let target = TargetTriple::new(target); let target_pkg = std_pkg.targets.get_mut(&target).unwrap(); target_pkg.bins = Vec::new(); } let manifest_str = manifest.stringify(); rustup::utils::raw::write_file(&manifest_path, &manifest_str).unwrap(); // Have to update the hash too let hash_path = manifest_path.with_extension("toml.sha256"); println!("{}", hash_path.display()); create_hash(&manifest_path, &hash_path); } #[test] fn update_unavailable_std() { setup(&|config| { make_component_unavailable(config, "rust-std", &this_host_triple()); config.expect_err( &["rustup", "update", "nightly", ], for_host!( "component 'rust-std' for target '{0}' is unavailable for download for channel 'nightly'" ), ); }); } #[test] fn add_missing_component_toolchain() { setup(&|config| { make_component_unavailable(config, "rust-std", &this_host_triple()); config.expect_err( &["rustup", "toolchain", "add", "nightly"], for_host!( r"component 'rust-std' for target '{0}' is unavailable for download for channel 'nightly' Sometimes not all components are available in any given nightly. If you don't need the component, you could try a minimal installation with: rustup toolchain add nightly --profile minimal" ), ); }); } #[test] fn update_unavailable_force() { setup(&|config| { let trip = this_host_triple(); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&[ "rustup", "component", "add", "rls", "--toolchain", "nightly", ]); make_component_unavailable(config, "rls-preview", &trip); config.expect_err( &["rustup", "update", "nightly"], for_host!( "component 'rls' for target '{0}' is unavailable for download for channel 'nightly'" ), ); config.expect_ok(&["rustup", "update", "nightly", "--force"]); }); } #[test] fn add_component_suggest_best_match() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &["rustup", "component", "add", "rsl"], "did you mean 'rls'?", ); config.expect_err( &["rustup", "component", "add", "rsl-preview"], "did you mean 'rls-preview'?", ); config.expect_err( &["rustup", "component", "add", "rustd"], "did you mean 'rustc'?", ); config.expect_not_stderr_err(&["rustup", "component", "add", "potato"], "did you mean"); }); } #[test] fn remove_component_suggest_best_match() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_not_stderr_err( &["rustup", "component", "remove", "rsl"], "did you mean 'rls'?", ); config.expect_ok(&["rustup", "component", "add", "rls"]); config.expect_err( &["rustup", "component", "remove", "rsl"], "did you mean 'rls'?", ); config.expect_ok(&["rustup", "component", "add", "rls-preview"]); config.expect_err( &["rustup", "component", "add", "rsl-preview"], "did you mean 'rls-preview'?", ); config.expect_err( &["rustup", "component", "remove", "rustd"], "did you mean 'rustc'?", ); }); } #[test] fn add_target_suggest_best_match() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_err( &[ "rustup", "target", "add", &format!("{}a", clitools::CROSS_ARCH1)[..], ], &format!("did you mean '{}'", clitools::CROSS_ARCH1), ); config.expect_not_stderr_err(&["rustup", "target", "add", "potato"], "did you mean"); }); } #[test] fn remove_target_suggest_best_match() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_not_stderr_err( &[ "rustup", "target", "remove", &format!("{}a", clitools::CROSS_ARCH1)[..], ], &format!("did you mean '{}'", clitools::CROSS_ARCH1), ); config.expect_ok(&["rustup", "target", "add", clitools::CROSS_ARCH1]); config.expect_err( &[ "rustup", "target", "remove", &format!("{}a", clitools::CROSS_ARCH1)[..], ], &format!("did you mean '{}'", clitools::CROSS_ARCH1), ); }); } #[test] fn target_list_ignores_unavailable_targets() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); let target_list = &["rustup", "target", "list"]; config.expect_stdout_ok(target_list, clitools::CROSS_ARCH1); make_component_unavailable(config, "rust-std", clitools::CROSS_ARCH1); config.expect_ok(&["rustup", "update", "nightly", "--force"]); config.expect_not_stdout_ok(target_list, clitools::CROSS_ARCH1); }) } #[test] fn install_with_components() { fn go(comp_args: &[&str]) { let mut args = vec!["rustup", "toolchain", "install", "nightly"]; args.extend_from_slice(comp_args); setup(&|config| { config.expect_ok(&args); config.expect_stdout_ok(&["rustup", "component", "list"], "rust-src (installed)"); config.expect_stdout_ok( &["rustup", "component", "list"], &format!("rust-analysis-{} (installed)", this_host_triple()), ); }) } go(&["-c", "rust-src", "-c", "rust-analysis"]); go(&["-c", "rust-src,rust-analysis"]); } #[test] fn install_with_targets() { fn go(comp_args: &[&str]) { let mut args = vec!["rustup", "toolchain", "install", "nightly"]; args.extend_from_slice(comp_args); setup(&|config| { config.expect_ok(&args); config.expect_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); config.expect_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH2), ); }) } go(&["-t", clitools::CROSS_ARCH1, "-t", clitools::CROSS_ARCH2]); go(&[ "-t", &format!("{},{}", clitools::CROSS_ARCH1, clitools::CROSS_ARCH2), ]); } #[test] fn install_with_component_and_target() { setup(&|config| { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok(&[ "rustup", "toolchain", "install", "nightly", "-c", "rls", "-t", clitools::CROSS_ARCH1, ]); config.expect_stdout_ok( &["rustup", "component", "list"], &format!("rls-{} (installed)", this_host_triple()), ); config.expect_stdout_ok( &["rustup", "target", "list"], &format!("{} (installed)", clitools::CROSS_ARCH1), ); }) } #[test] fn test_warn_if_complete_profile_is_used() { setup(&|config| { config.expect_ok(&["rustup", "set", "auto-self-update", "enable"]); config.expect_err( &[ "rustup", "toolchain", "install", "--profile", "complete", "stable", ], "warning: downloading with complete profile", ); }); } #[test] fn test_complete_profile_skips_missing_when_forced() { setup_complex(&|config| { set_current_dist_date(config, "2015-01-01"); config.expect_ok(&["rustup", "set", "profile", "complete"]); // First try and install without force config.expect_err( &[ "rustup", "toolchain", "install", "nightly", ], for_host!("error: component 'rls' for target '{}' is unavailable for download for channel 'nightly'") ); // Now try and force config.expect_stderr_ok( &["rustup", "toolchain", "install", "--force", "nightly"], for_host!("warning: Force-skipping unavailable component 'rls-{}'"), ); // Ensure that the skipped component (rls) is not installed config.expect_not_stdout_ok( &["rustup", "component", "list"], for_host!("rls-{} (installed)"), ); }) } #[test] fn run_with_install_flag_against_unavailable_component() { setup(&|config| { let trip = this_host_triple(); make_component_unavailable(config, "rust-std", &trip); config.expect_ok_ex( &[ "rustup", "run", "--install", "nightly", "rustc", "--version", ], "1.3.0 (hash-nightly-2) ", for_host!( r"info: syncing channel updates for 'nightly-{0}' info: latest update on 2015-01-02, rust version 1.3.0 (hash-nightly-2) warning: Force-skipping unavailable component 'rust-std-{0}' info: downloading component 'cargo' info: downloading component 'rust-docs' info: downloading component 'rustc' info: installing component 'cargo' info: installing component 'rust-docs' info: installing component 'rustc' " ), ); }); } #[test] fn install_allow_downgrade() { clitools::test(Scenario::MissingComponent, &|config| { let trip = this_host_triple(); // this dist has no rls and there is no newer one set_current_dist_date(config, "2019-09-14"); config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-3"); config.expect_component_not_executable("rls"); config.expect_err( &["rustup", "toolchain", "install", "nightly", "-c", "rls"], &format!( "component 'rls' for target '{trip}' is unavailable for download for channel 'nightly'", ), ); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-3"); config.expect_component_not_executable("rls"); config.expect_ok(&[ "rustup", "toolchain", "install", "nightly", "-c", "rls", "--allow-downgrade", ]); config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); config.expect_component_executable("rls"); }); } #[test] fn regression_2601() { // We're checking that we don't regress per #2601 setup(&|config| { config.expect_ok(&[ "rustup", "toolchain", "install", "--profile", "minimal", "nightly", "--component", "rust-src", ]); // The bug exposed in #2601 was that the above would end up installing // rust-src-$ARCH which would then have to be healed on the following // command, resulting in a reinstallation. config.expect_stderr_ok( &["rustup", "component", "add", "rust-src"], "info: component 'rust-src' is up to date", ); }); } rustup-1.26.0/tests/suite/dist.rs000066400000000000000000001734331441327105200167600ustar00rootroot00000000000000// Tests of installation and updates from a v2 Rust distribution // server (mocked on the file system) #![allow(clippy::type_complexity)] use std::cell::Cell; use std::collections::HashMap; use std::env; use std::fs; use std::path::Path; use std::str::FromStr; use std::sync::Arc; use anyhow::{anyhow, Result}; use url::Url; use rustup::currentprocess; use rustup::dist::dist::{Profile, TargetTriple, ToolchainDesc, DEFAULT_DIST_SERVER}; use rustup::dist::download::DownloadCfg; use rustup::dist::manifest::{Component, Manifest}; use rustup::dist::manifestation::{Changes, Manifestation, UpdateStatus}; use rustup::dist::prefix::InstallPrefix; use rustup::dist::temp; use rustup::dist::Notification; use rustup::errors::RustupError; use rustup::utils::raw as utils_raw; use rustup::utils::utils; use crate::mock::dist::*; use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder}; const SHA256_HASH_LEN: usize = 64; // Creates a mock dist server populated with some test data fn create_mock_dist_server( path: &Path, edit: Option<&dyn Fn(&str, &mut MockChannel)>, ) -> MockDistServer { MockDistServer { path: path.to_owned(), channels: vec![ create_mock_channel("nightly", "2016-02-01", edit), create_mock_channel("nightly", "2016-02-02", edit), ], } } fn create_mock_channel( channel: &str, date: &str, edit: Option<&dyn Fn(&str, &mut MockChannel)>, ) -> MockChannel { // Put the date in the files so they can be differentiated let contents = Arc::new(date.as_bytes().to_vec()); let mut packages = Vec::with_capacity(5); packages.push(MockPackage { name: "rust", version: "1.0.0".to_string(), targets: vec![ MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), available: true, components: vec![ MockComponent { name: "rustc".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: false, }, MockComponent { name: "cargo".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "i686-apple-darwin".to_string(), is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "i686-unknown-linux-gnu".to_string(), is_extension: false, }, ], installer: MockInstallerBuilder { components: vec![] }, }, MockTargetedPackage { target: "i686-apple-darwin".to_string(), available: true, components: vec![ MockComponent { name: "rustc".to_string(), target: "i686-apple-darwin".to_string(), is_extension: false, }, MockComponent { name: "cargo".to_string(), target: "i686-apple-darwin".to_string(), is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "i686-apple-darwin".to_string(), is_extension: false, }, ], installer: MockInstallerBuilder { components: vec![] }, }, ], }); for bin in &["bin/rustc", "bin/cargo"] { let pkg = &bin[4..]; packages.push(MockPackage { name: pkg, version: "1.0.0".to_string(), targets: vec![ MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), available: true, components: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: pkg.to_string(), files: vec![MockFile::new_arc(*bin, contents.clone())], }], }, }, MockTargetedPackage { target: "i686-apple-darwin".to_string(), available: true, components: vec![], installer: MockInstallerBuilder { components: vec![] }, }, ], }); } packages.push(MockPackage { name: "rust-std", version: "1.0.0".to_string(), targets: vec![ MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), available: true, components: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-std-x86_64-apple-darwin".to_string(), files: vec![MockFile::new_arc("lib/libstd.rlib", contents.clone())], }], }, }, MockTargetedPackage { target: "i686-apple-darwin".to_string(), available: true, components: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-std-i686-apple-darwin".to_string(), files: vec![MockFile::new_arc( "lib/i686-apple-darwin/libstd.rlib", contents.clone(), )], }], }, }, MockTargetedPackage { target: "i686-unknown-linux-gnu".to_string(), available: true, components: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-std-i686-unknown-linux-gnu".to_string(), files: vec![MockFile::new_arc( "lib/i686-unknown-linux-gnu/libstd.rlib", contents.clone(), )], }], }, }, ], }); // An extra package that can be used as a component of the other packages // for various tests packages.push(bonus_component("bonus", contents)); let mut channel = MockChannel { name: channel.to_string(), date: date.to_string(), packages, renames: HashMap::new(), }; if let Some(edit) = edit { edit(date, &mut channel); } channel } fn bonus_component(name: &'static str, contents: Arc>) -> MockPackage { MockPackage { name, version: "1.0.0".to_string(), targets: vec![MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), available: true, components: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: format!("{name}-x86_64-apple-darwin"), files: vec![MockFile::new_arc("bin/bonus", contents)], }], }, }], } } #[test] fn mock_dist_server_smoke_test() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let path = tempdir.path(); create_mock_dist_server(path, None).write(&[ManifestVersion::V2], false, false); assert!(utils::path_exists(path.join( "dist/2016-02-01/rustc-nightly-x86_64-apple-darwin.tar.gz" ))); assert!(utils::path_exists( path.join("dist/2016-02-01/rustc-nightly-i686-apple-darwin.tar.gz") )); assert!(utils::path_exists(path.join( "dist/2016-02-01/rust-std-nightly-x86_64-apple-darwin.tar.gz" ))); assert!(utils::path_exists(path.join( "dist/2016-02-01/rust-std-nightly-i686-apple-darwin.tar.gz" ))); assert!(utils::path_exists(path.join( "dist/2016-02-01/rustc-nightly-x86_64-apple-darwin.tar.gz.sha256" ))); assert!(utils::path_exists(path.join( "dist/2016-02-01/rustc-nightly-i686-apple-darwin.tar.gz.sha256" ))); assert!(utils::path_exists(path.join( "dist/2016-02-01/rust-std-nightly-x86_64-apple-darwin.tar.gz.sha256" ))); assert!(utils::path_exists(path.join( "dist/2016-02-01/rust-std-nightly-i686-apple-darwin.tar.gz.sha256" ))); assert!(utils::path_exists( path.join("dist/channel-rust-nightly.toml") )); assert!(utils::path_exists( path.join("dist/channel-rust-nightly.toml.sha256") )); } // Test that a standard rename works - the component is installed with the old name, then renamed // the next day to the new name. #[test] fn rename_component() { let dist_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let url = Url::parse(&format!("file://{}", dist_tempdir.path().to_string_lossy())).unwrap(); let edit_1 = &|_: &str, chan: &mut MockChannel| { let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") .unwrap(); tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: true, }); }; let edit_2 = &|_: &str, chan: &mut MockChannel| { let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") .unwrap(); tpkg.components.push(MockComponent { name: "bobo".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: true, }); }; let date_2 = "2016-02-02"; let mut channel_2 = create_mock_channel("nightly", date_2, Some(edit_2)); channel_2.packages[4] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec())); channel_2 .renames .insert("bonus".to_owned(), "bobo".to_owned()); let mock_dist_server = MockDistServer { path: dist_tempdir.path().to_owned(), channels: vec![ create_mock_channel("nightly", "2016-02-01", Some(edit_1)), channel_2, ], }; setup_from_dist_server( mock_dist_server, &url, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = [Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), true, )]; change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/bonus"))); assert!(!utils::path_exists(prefix.path().join("bin/bobo"))); change_channel_date(url, "nightly", "2016-02-02"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/bonus"))); assert!(!utils::path_exists(prefix.path().join("bin/bobo"))); }, ); } // Test that a rename is ignored if the component with the old name was never installed. #[test] fn rename_component_new() { let dist_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let url = Url::parse(&format!("file://{}", dist_tempdir.path().to_string_lossy())).unwrap(); let date_2 = "2016-02-02"; let mut channel_2 = create_mock_channel("nightly", date_2, None); // Replace the `bonus` component with a `bobo` component channel_2.packages[4] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec())); // And allow a rename from `bonus` to `bobo` channel_2 .renames .insert("bonus".to_owned(), "bobo".to_owned()); let mock_dist_server = MockDistServer { path: dist_tempdir.path().to_owned(), channels: vec![ create_mock_channel("nightly", "2016-02-01", None), channel_2, ], }; setup_from_dist_server( mock_dist_server, &url, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = [Component::new( "bobo".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), true, )]; // Install the basics from day 1 change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); // Neither bonus nor bobo are installed at this point. assert!(!utils::path_exists(prefix.path().join("bin/bonus"))); assert!(!utils::path_exists(prefix.path().join("bin/bobo"))); // Now we move to day 2, where bobo is part of the set of things we want // to have installed change_channel_date(url, "nightly", "2016-02-02"); update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); // As a result `bin/bonus` is present but not `bin/bobo` which we'd // expect since the bonus component installs `bin/bonus` regardless of // its name being `bobo` assert!(!utils::path_exists(prefix.path().join("bin/bobo"))); assert!(utils::path_exists(prefix.path().join("bin/bonus"))); }, ); } // Installs or updates a toolchain from a dist server. If an initial // install then it will be installed with the default components. If // an upgrade then all the existing components will be upgraded. // FIXME: Unify this with dist::update_from_dist #[allow(clippy::too_many_arguments)] fn update_from_dist( dist_server: &Url, toolchain: &ToolchainDesc, prefix: &InstallPrefix, add: &[Component], remove: &[Component], download_cfg: &DownloadCfg<'_>, temp_cfg: &temp::Cfg, force: bool, ) -> Result { // Download the dist manifest and place it into the installation prefix let manifest_url = make_manifest_url(dist_server, toolchain)?; let manifest_file = temp_cfg.new_file()?; utils::download_file(&manifest_url, &manifest_file, None, &|_| {})?; let manifest_str = utils::read_file("manifest", &manifest_file)?; let manifest = Manifest::parse(&manifest_str)?; // Read the manifest to update the components let trip = toolchain.target.clone(); let manifestation = Manifestation::open(prefix.clone(), trip.clone())?; // TODO on install, need to add profile components (but I guess we shouldn't test that logic here) let mut profile_components = manifest.get_profile_components(Profile::Default, &trip)?; let mut add_components = add.to_owned(); add_components.append(&mut profile_components); let changes = Changes { explicit_add_components: add_components, remove_components: remove.to_owned(), }; manifestation.update( &manifest, changes, force, download_cfg, download_cfg.notify_handler, &toolchain.manifest_name(), true, ) } fn make_manifest_url(dist_server: &Url, toolchain: &ToolchainDesc) -> Result { let url = format!( "{}/dist/channel-rust-{}.toml", dist_server, toolchain.channel ); Url::parse(&url).map_err(|e| anyhow!(format!("{e:?}"))) } fn uninstall( toolchain: &ToolchainDesc, prefix: &InstallPrefix, temp_cfg: &temp::Cfg, notify_handler: &dyn Fn(Notification<'_>), ) -> Result<()> { let trip = toolchain.target.clone(); let manifestation = Manifestation::open(prefix.clone(), trip)?; let manifest = manifestation.load_manifest()?.unwrap(); manifestation.uninstall(&manifest, temp_cfg, notify_handler)?; Ok(()) } #[derive(Copy, Clone, Debug)] enum Compressions { GZOnly, AddXZ, AddZStd, } use Compressions::*; impl Compressions { fn enable_xz(self) -> bool { matches!(self, AddXZ) } fn enable_zst(self) -> bool { matches!(self, AddZStd) } } fn setup( edit: Option<&dyn Fn(&str, &mut MockChannel)>, comps: Compressions, f: &dyn Fn(&Url, &ToolchainDesc, &InstallPrefix, &DownloadCfg<'_>, &temp::Cfg), ) { let dist_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock_dist_server = create_mock_dist_server(dist_tempdir.path(), edit); let url = Url::parse(&format!("file://{}", dist_tempdir.path().to_string_lossy())).unwrap(); setup_from_dist_server(mock_dist_server, &url, comps, f); } fn setup_from_dist_server( server: MockDistServer, url: &Url, comps: Compressions, f: &dyn Fn(&Url, &ToolchainDesc, &InstallPrefix, &DownloadCfg<'_>, &temp::Cfg), ) { server.write( &[ManifestVersion::V2], comps.enable_xz(), comps.enable_zst(), ); let prefix_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let work_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let temp_cfg = temp::Cfg::new( work_tempdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let toolchain = ToolchainDesc::from_str("nightly-x86_64-apple-darwin").unwrap(); let prefix = InstallPrefix::from(prefix_tempdir.path().to_owned()); let download_cfg = DownloadCfg { dist_root: "phony", temp_cfg: &temp_cfg, download_dir: &prefix.path().to_owned().join("downloads"), notify_handler: &|event| { println!("{event}"); }, }; currentprocess::with( Box::new(currentprocess::TestProcess::new( env::current_dir().unwrap(), &["rustup"], HashMap::default(), "", )), || f(url, &toolchain, &prefix, &download_cfg, &temp_cfg), ); } fn initial_install(comps: Compressions) { setup(None, comps, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/rustc"))); assert!(utils::path_exists(prefix.path().join("lib/libstd.rlib"))); }); } #[test] fn initial_install_gziponly() { initial_install(GZOnly); } #[test] fn initial_install_xz() { initial_install(AddXZ); } #[test] fn initial_install_zst() { initial_install(AddZStd); } #[test] fn test_uninstall() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); uninstall(toolchain, prefix, temp_cfg, &|_| ()).unwrap(); assert!(!utils::path_exists(prefix.path().join("bin/rustc"))); assert!(!utils::path_exists(prefix.path().join("lib/libstd.rlib"))); }); } #[test] fn uninstall_removes_config_file() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.manifest_file("multirust-config.toml") )); uninstall(toolchain, prefix, temp_cfg, &|_| ()).unwrap(); assert!(!utils::path_exists( prefix.manifest_file("multirust-config.toml") )); }); } #[test] fn upgrade() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert_eq!( "2016-02-01", fs::read_to_string(prefix.path().join("bin/rustc")).unwrap() ); change_channel_date(url, "nightly", "2016-02-02"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert_eq!( "2016-02-02", fs::read_to_string(prefix.path().join("bin/rustc")).unwrap() ); }); } #[test] fn unavailable_component() { // On day 2 the bonus component is no longer available let edit = &|date: &str, chan: &mut MockChannel| { // Require the bonus component every day. { let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") .unwrap(); tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: true, }); } // Mark the bonus package as unavailable in 2016-02-02 if date == "2016-02-02" { let bonus_pkg = chan .packages .iter_mut() .find(|p| p.name == "bonus") .unwrap(); for target in &mut bonus_pkg.targets { target.available = false; } } }; setup( Some(edit), GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = [Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), true, )]; change_channel_date(url, "nightly", "2016-02-01"); // Update with bonus. update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/bonus"))); change_channel_date(url, "nightly", "2016-02-02"); // Update without bonus, should fail. let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(e @ RustupError::RequestedComponentsUnavailable { .. }) => { assert!(e.to_string().contains("rustup component remove --toolchain nightly --target x86_64-apple-darwin bonus")); } _ => panic!(), } }, ); } // As unavailable_component, but the unavailable component is part of the profile. #[test] fn unavailable_component_from_profile() { // On day 2 the rustc component is no longer available let edit = &|date: &str, chan: &mut MockChannel| { // Mark the rustc package as unavailable in 2016-02-02 if date == "2016-02-02" { let rustc_pkg = chan .packages .iter_mut() .find(|p| p.name == "rustc") .unwrap(); for target in &mut rustc_pkg.targets { target.available = false; } } }; setup( Some(edit), GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); // Update with rustc. update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/rustc"))); change_channel_date(url, "nightly", "2016-02-02"); // Update without rustc, should fail. let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(e @ RustupError::RequestedComponentsUnavailable { .. }) => { assert!(e.to_string().contains("rustup component remove --toolchain nightly --target x86_64-apple-darwin rustc")); } _ => panic!(), } update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, true, ) .unwrap(); }, ); } #[test] fn removed_component() { // On day 1 install the 'bonus' component, on day 2 it's no longer a component let edit = &|date: &str, chan: &mut MockChannel| { if date == "2016-02-01" { let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") .unwrap(); tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: true, }); } else { chan.packages.retain(|p| p.name != "bonus"); } }; setup( Some(edit), GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = [Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), true, )]; // Update with bonus. change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/bonus"))); // Update without bonus, should fail with RequestedComponentsUnavailable change_channel_date(url, "nightly", "2016-02-02"); let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(e @ RustupError::RequestedComponentsUnavailable { .. }) => { assert!(e.to_string().contains("rustup component remove --toolchain nightly --target x86_64-apple-darwin bonus")); } _ => panic!(), } }, ); } #[test] fn unavailable_components_is_target() { // On day 2 the rust-std component is no longer available let edit = &|date: &str, chan: &mut MockChannel| { // Mark the rust-std package as unavailable in 2016-02-02 if date == "2016-02-02" { let pkg = chan .packages .iter_mut() .find(|p| p.name == "rust-std") .unwrap(); for target in &mut pkg.targets { target.available = false; } } }; setup( Some(edit), GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = [ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; // Update with rust-std change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); // Update without rust-std change_channel_date(url, "nightly", "2016-02-02"); let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(e @ RustupError::RequestedComponentsUnavailable { .. }) => { let err_str = e.to_string(); assert!(err_str .contains("rustup target remove --toolchain nightly i686-apple-darwin")); assert!(err_str.contains( "rustup target remove --toolchain nightly i686-unknown-linux-gnu" )); } _ => panic!(), } }, ); } #[test] fn unavailable_components_with_same_target() { // On day 2, the rust-std and rustc components are no longer available let edit = &|date: &str, chan: &mut MockChannel| { // Mark the rust-std package as unavailable in 2016-02-02 if date == "2016-02-02" { let pkg = chan .packages .iter_mut() .find(|p| p.name == "rust-std") .unwrap(); for target in &mut pkg.targets { target.available = false; } } // Mark the rustc package as unavailable in 2016-02-02 if date == "2016-02-02" { let pkg = chan .packages .iter_mut() .find(|p| p.name == "rustc") .unwrap(); for target in &mut pkg.targets { target.available = false; } } }; setup( Some(edit), GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { // Update with rust-std and rustc change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/rustc"))); assert!(utils::path_exists(prefix.path().join("lib/libstd.rlib"))); // Update without rust-std and rustc change_channel_date(url, "nightly", "2016-02-02"); let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(e @ RustupError::RequestedComponentsUnavailable { .. }) => { let err_str = e.to_string(); assert!(err_str .contains("rustup target remove --toolchain nightly x86_64-apple-darwin")); assert!(err_str.contains( "rustup component remove --toolchain nightly --target x86_64-apple-darwin rustc" )); } _ => panic!(), } }, ); } #[test] fn update_preserves_extensions() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); change_channel_date(url, "nightly", "2016-02-02"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] fn update_makes_no_changes_for_identical_manifest() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let status = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert_eq!(status, UpdateStatus::Changed); let status = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert_eq!(status, UpdateStatus::Unchanged); }); } #[test] fn add_extensions_for_initial_install() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] fn add_extensions_for_same_manifest() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] fn add_extensions_for_upgrade() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] #[should_panic] fn add_extension_not_in_manifest() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![Component::new( "rust-bogus".to_string(), Some(TargetTriple::new("i686-apple-darwin")), true, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); }); } #[test] #[should_panic] fn add_extension_that_is_required_component() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![Component::new( "rustc".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); }); } #[test] #[ignore] fn add_extensions_for_same_manifest_does_not_reinstall_other_components() {} #[test] #[ignore] fn add_extensions_for_same_manifest_when_extension_already_installed() {} #[test] fn add_extensions_does_not_remove_other_components() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/rustc"))); }); } // Asking to remove extensions on initial install is nonsese. #[test] #[should_panic] fn remove_extensions_for_initial_install() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let removes = vec![Component::new( "rustc".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); }); } #[test] fn remove_extensions_for_same_manifest() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); assert!(!utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] fn remove_extensions_for_upgrade() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, ), ]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); assert!(!utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] #[should_panic] fn remove_extension_not_in_manifest() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let removes = vec![Component::new( "rust-bogus".to_string(), Some(TargetTriple::new("i686-apple-darwin")), true, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); }); } // Extensions that don't exist in the manifest may still exist on disk // from a previous manifest. #[test] fn remove_extension_not_in_manifest_but_is_already_installed() { let edit = &|date: &str, chan: &mut MockChannel| { if date == "2016-02-01" { let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") .unwrap(); tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), is_extension: true, }); } else { chan.packages.retain(|p| p.name != "bonus"); } }; setup( Some(edit), GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); let adds = [Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), true, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/bonus"))); change_channel_date(url, "nightly", "2016-02-02"); let removes = vec![Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), true, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); }, ); } #[test] #[should_panic] fn remove_extension_that_is_required_component() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); let removes = vec![Component::new( "rustc".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); }); } #[test] #[should_panic] fn remove_extension_not_installed() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); }); } #[test] #[ignore] fn remove_extensions_for_same_manifest_does_not_reinstall_other_components() {} #[test] fn remove_extensions_does_not_remove_other_components() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &[], &removes, download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/rustc"))); }); } #[test] fn add_and_remove_for_upgrade() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &removes, download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(!utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] fn add_and_remove() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg, false, ) .unwrap(); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &removes, download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists( prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); assert!(!utils::path_exists( prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") )); }); } #[test] fn add_and_remove_same_component() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), false, )]; update_from_dist( url, toolchain, prefix, &adds, &removes, download_cfg, temp_cfg, false, ) .expect_err("can't both add and remove components"); }); } #[test] fn bad_component_hash() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let path = url.to_file_path().unwrap(); let path = path.join("dist/2016-02-02/rustc-nightly-x86_64-apple-darwin.tar.gz"); utils_raw::write_file(&path, "bogus").unwrap(); let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(RustupError::ComponentDownloadFailed(..)) => (), _ => panic!(), } }); } #[test] fn unable_to_download_component() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let path = url.to_file_path().unwrap(); let path = path.join("dist/2016-02-02/rustc-nightly-x86_64-apple-darwin.tar.gz"); fs::remove_file(path).unwrap(); let err = update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap_err(); match err.downcast::() { Ok(RustupError::ComponentDownloadFailed(..)) => (), _ => panic!(), } }); } fn prevent_installation(prefix: &InstallPrefix) { utils::ensure_dir_exists( "installation path", &prefix.path().join("lib"), &|_: Notification<'_>| {}, ) .unwrap(); let install_blocker = prefix.path().join("lib").join("rustlib"); utils::write_file("install-blocker", &install_blocker, "fail-installation").unwrap(); } fn allow_installation(prefix: &InstallPrefix) { let install_blocker = prefix.path().join("lib").join("rustlib"); utils::remove_file("install-blocker", &install_blocker).unwrap(); } #[test] fn reuse_downloaded_file() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { prevent_installation(prefix); let reuse_notification_fired = Arc::new(Cell::new(false)); let download_cfg = DownloadCfg { dist_root: download_cfg.dist_root, temp_cfg: download_cfg.temp_cfg, download_dir: download_cfg.download_dir, notify_handler: &|n| { if let Notification::FileAlreadyDownloaded = n { reuse_notification_fired.set(true); } }, }; update_from_dist( url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg, false, ) .unwrap_err(); assert!(!reuse_notification_fired.get()); allow_installation(prefix); update_from_dist( url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg, false, ) .unwrap(); assert!(reuse_notification_fired.get()); }) } #[test] fn checks_files_hashes_before_reuse() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { let path = url.to_file_path().unwrap(); let target_hash = utils::read_file( "target hash", &path.join("dist/2016-02-02/rustc-nightly-x86_64-apple-darwin.tar.gz.sha256"), ) .unwrap()[..64] .to_owned(); let prev_download = download_cfg.download_dir.join(target_hash); utils::ensure_dir_exists( "download dir", download_cfg.download_dir, &|_: Notification<'_>| {}, ) .unwrap(); utils::write_file("bad previous download", &prev_download, "bad content").unwrap(); println!("wrote previous download to {}", prev_download.display()); let noticed_bad_checksum = Arc::new(Cell::new(false)); let download_cfg = DownloadCfg { dist_root: download_cfg.dist_root, temp_cfg: download_cfg.temp_cfg, download_dir: download_cfg.download_dir, notify_handler: &|n| { if let Notification::CachedFileChecksumFailed = n { noticed_bad_checksum.set(true); } }, }; update_from_dist( url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg, false, ) .unwrap(); assert!(noticed_bad_checksum.get()); }) } #[test] fn handle_corrupt_partial_downloads() { setup(None, GZOnly, &|url, toolchain, prefix, download_cfg, temp_cfg| { // write a corrupt partial out let path = url.to_file_path().unwrap(); let target_hash = utils::read_file( "target hash", &path.join("dist/2016-02-02/rustc-nightly-x86_64-apple-darwin.tar.gz.sha256"), ) .unwrap()[..SHA256_HASH_LEN] .to_owned(); utils::ensure_dir_exists( "download dir", download_cfg.download_dir, &|_: Notification<'_>| {}, ) .unwrap(); let partial_path = download_cfg .download_dir .join(format!("{target_hash}.partial")); utils_raw::write_file( &partial_path, "file will be resumed from here and not match hash", ) .unwrap(); update_from_dist( url, toolchain, prefix, &[], &[], download_cfg, temp_cfg, false, ) .unwrap(); assert!(utils::path_exists(prefix.path().join("bin/rustc"))); assert!(utils::path_exists(prefix.path().join("lib/libstd.rlib"))); }); } rustup-1.26.0/tests/suite/dist_install.rs000066400000000000000000000261301441327105200204750ustar00rootroot00000000000000use std::fs::File; use std::io::Write; use rustup::dist::component::Components; use rustup::dist::component::Transaction; use rustup::dist::component::{DirectoryPackage, Package}; use rustup::dist::dist::DEFAULT_DIST_SERVER; use rustup::dist::prefix::InstallPrefix; use rustup::dist::temp; use rustup::dist::Notification; use rustup::utils::utils; use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder}; // Just testing that the mocks work #[test] fn mock_smoke_test() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![ MockComponentBuilder { name: "mycomponent".to_string(), files: vec![ MockFile::new("bin/foo", b"foo"), MockFile::new("lib/bar", b"bar"), MockFile::new_dir("doc/stuff", &[("doc1", b"", false), ("doc2", b"", false)]), ], }, MockComponentBuilder { name: "mycomponent2".to_string(), files: vec![MockFile::new("bin/quux", b"quux")], }, ], }; mock.build(tempdir.path()); assert!(tempdir.path().join("components").exists()); assert!(tempdir.path().join("mycomponent/manifest.in").exists()); assert!(tempdir.path().join("mycomponent/bin/foo").exists()); assert!(tempdir.path().join("mycomponent/lib/bar").exists()); assert!(tempdir.path().join("mycomponent/doc/stuff/doc1").exists()); assert!(tempdir.path().join("mycomponent/doc/stuff/doc2").exists()); assert!(tempdir.path().join("mycomponent2/manifest.in").exists()); assert!(tempdir.path().join("mycomponent2/bin/quux").exists()); } #[test] fn package_contains() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![ MockComponentBuilder { name: "mycomponent".to_string(), files: vec![MockFile::new("bin/foo", b"foo")], }, MockComponentBuilder { name: "mycomponent2".to_string(), files: vec![MockFile::new("bin/bar", b"bar")], }, ], }; mock.build(tempdir.path()); let package = DirectoryPackage::new(tempdir.path().to_owned(), true).unwrap(); assert!(package.contains("mycomponent", None)); assert!(package.contains("mycomponent2", None)); } #[test] fn package_bad_version() { let tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![MockComponentBuilder { name: "mycomponent".to_string(), files: vec![MockFile::new("bin/foo", b"foo")], }], }; mock.build(tempdir.path()); let mut ver = File::create(tempdir.path().join("rust-installer-version")).unwrap(); writeln!(ver, "100").unwrap(); assert!(DirectoryPackage::new(tempdir.path().to_owned(), true).is_err()); } #[test] fn basic_install() { let pkgdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![MockComponentBuilder { name: "mycomponent".to_string(), files: vec![ MockFile::new("bin/foo", b"foo"), MockFile::new("lib/bar", b"bar"), MockFile::new_dir("doc/stuff", &[("doc1", b"", false), ("doc2", b"", false)]), ], }], }; mock.build(pkgdir.path()); let instdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefix = InstallPrefix::from(instdir.path().to_owned()); let tmpdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( tmpdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let components = Components::open(prefix).unwrap(); let pkg = DirectoryPackage::new(pkgdir.path().to_owned(), true).unwrap(); let tx = pkg.install(&components, "mycomponent", None, tx).unwrap(); tx.commit(); assert!(utils::path_exists(instdir.path().join("bin/foo"))); assert!(utils::path_exists(instdir.path().join("lib/bar"))); assert!(utils::path_exists(instdir.path().join("doc/stuff/doc1"))); assert!(utils::path_exists(instdir.path().join("doc/stuff/doc2"))); assert!(components.find("mycomponent").unwrap().is_some()); } #[test] fn multiple_component_install() { let pkgdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![ MockComponentBuilder { name: "mycomponent".to_string(), files: vec![MockFile::new("bin/foo", b"foo")], }, MockComponentBuilder { name: "mycomponent2".to_string(), files: vec![MockFile::new("lib/bar", b"bar")], }, ], }; mock.build(pkgdir.path()); let instdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefix = InstallPrefix::from(instdir.path().to_owned()); let tmpdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( tmpdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let components = Components::open(prefix).unwrap(); let pkg = DirectoryPackage::new(pkgdir.path().to_owned(), true).unwrap(); let tx = pkg.install(&components, "mycomponent", None, tx).unwrap(); let tx = pkg.install(&components, "mycomponent2", None, tx).unwrap(); tx.commit(); assert!(utils::path_exists(instdir.path().join("bin/foo"))); assert!(utils::path_exists(instdir.path().join("lib/bar"))); assert!(components.find("mycomponent").unwrap().is_some()); assert!(components.find("mycomponent2").unwrap().is_some()); } #[test] fn uninstall() { let pkgdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![ MockComponentBuilder { name: "mycomponent".to_string(), files: vec![ MockFile::new("bin/foo", b"foo"), MockFile::new("lib/bar", b"bar"), MockFile::new_dir("doc/stuff", &[("doc1", b"", false), ("doc2", b"", false)]), ], }, MockComponentBuilder { name: "mycomponent2".to_string(), files: vec![MockFile::new("lib/quux", b"quux")], }, ], }; mock.build(pkgdir.path()); let instdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefix = InstallPrefix::from(instdir.path().to_owned()); let tmpdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( tmpdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let components = Components::open(prefix.clone()).unwrap(); let pkg = DirectoryPackage::new(pkgdir.path().to_owned(), true).unwrap(); let tx = pkg.install(&components, "mycomponent", None, tx).unwrap(); let tx = pkg.install(&components, "mycomponent2", None, tx).unwrap(); tx.commit(); // Now uninstall let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); for component in components.list().unwrap() { tx = component.uninstall(tx).unwrap(); } tx.commit(); assert!(!utils::path_exists(instdir.path().join("bin/foo"))); assert!(!utils::path_exists(instdir.path().join("lib/bar"))); assert!(!utils::path_exists(instdir.path().join("doc/stuff/doc1"))); assert!(!utils::path_exists(instdir.path().join("doc/stuff/doc2"))); assert!(!utils::path_exists(instdir.path().join("doc/stuff"))); assert!(components.find("mycomponent").unwrap().is_none()); assert!(components.find("mycomponent2").unwrap().is_none()); } // If any single file can't be uninstalled, it is not a fatal error // and the subsequent files will still be removed. #[test] fn uninstall_best_effort() { //unimplemented!() } #[test] fn component_bad_version() { let pkgdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![MockComponentBuilder { name: "mycomponent".to_string(), files: vec![MockFile::new("bin/foo", b"foo")], }], }; mock.build(pkgdir.path()); let instdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefix = InstallPrefix::from(instdir.path().to_owned()); let tmpdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( tmpdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let components = Components::open(prefix.clone()).unwrap(); let pkg = DirectoryPackage::new(pkgdir.path().to_owned(), true).unwrap(); let tx = pkg.install(&components, "mycomponent", None, tx).unwrap(); tx.commit(); // Write a bogus version to the component manifest directory utils::write_file("", &prefix.manifest_file("rust-installer-version"), "100\n").unwrap(); // Can't open components now let e = Components::open(prefix).unwrap_err(); assert_eq!( "unsupported metadata version in existing installation: 100", format!("{e}") ); } // Installing to a prefix that doesn't exist creates it automatically #[test] fn install_to_prefix_that_does_not_exist() { let pkgdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let mock = MockInstallerBuilder { components: vec![MockComponentBuilder { name: "mycomponent".to_string(), files: vec![MockFile::new("bin/foo", b"foo")], }], }; mock.build(pkgdir.path()); let instdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); // The directory that does not exist let does_not_exist = instdir.path().join("super_not_real"); let prefix = InstallPrefix::from(does_not_exist.clone()); let tmpdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( tmpdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let components = Components::open(prefix).unwrap(); let pkg = DirectoryPackage::new(pkgdir.path().to_owned(), true).unwrap(); let tx = pkg.install(&components, "mycomponent", None, tx).unwrap(); tx.commit(); assert!(utils::path_exists(does_not_exist.join("bin/foo"))); } rustup-1.26.0/tests/suite/dist_manifest.rs000066400000000000000000000070751441327105200206440ustar00rootroot00000000000000use rustup::dist::dist::TargetTriple; use rustup::dist::manifest::Manifest; use rustup::RustupError; // Example manifest from https://public.etherpad-mozilla.org/p/Rust-infra-work-week static EXAMPLE: &str = include_str!("channel-rust-nightly-example.toml"); // From brson's live build-rust-manifest.py script static EXAMPLE2: &str = include_str!("channel-rust-nightly-example2.toml"); #[test] fn parse_smoke_test() { let x86_64_unknown_linux_gnu = TargetTriple::new("x86_64-unknown-linux-gnu"); let x86_64_unknown_linux_musl = TargetTriple::new("x86_64-unknown-linux-musl"); let pkg = Manifest::parse(EXAMPLE).unwrap(); pkg.get_package("rust").unwrap(); pkg.get_package("rustc").unwrap(); pkg.get_package("cargo").unwrap(); pkg.get_package("rust-std").unwrap(); pkg.get_package("rust-docs").unwrap(); let rust_pkg = pkg.get_package("rust").unwrap(); assert!(rust_pkg.version.contains("1.3.0")); let rust_target_pkg = rust_pkg .get_target(Some(&x86_64_unknown_linux_gnu)) .unwrap(); assert!(rust_target_pkg.available()); assert_eq!(rust_target_pkg.bins[0].1.url, "example.com"); assert_eq!(rust_target_pkg.bins[0].1.hash, "..."); let component = &rust_target_pkg.components[0]; assert_eq!(component.short_name_in_manifest(), "rustc"); assert_eq!(component.target.as_ref(), Some(&x86_64_unknown_linux_gnu)); let component = &rust_target_pkg.components[4]; assert_eq!(component.short_name_in_manifest(), "rust-std"); assert_eq!(component.target.as_ref(), Some(&x86_64_unknown_linux_musl)); let docs_pkg = pkg.get_package("rust-docs").unwrap(); let docs_target_pkg = docs_pkg .get_target(Some(&x86_64_unknown_linux_gnu)) .unwrap(); assert_eq!(docs_target_pkg.bins[0].1.url, "example.com"); } #[test] fn renames() { let manifest = Manifest::parse(EXAMPLE2).unwrap(); assert_eq!(1, manifest.renames.len()); assert_eq!(manifest.renames["cargo-old"], "cargo"); assert_eq!(1, manifest.reverse_renames.len()); assert_eq!(manifest.reverse_renames["cargo"], "cargo-old"); } #[test] fn parse_round_trip() { let original = Manifest::parse(EXAMPLE).unwrap(); let serialized = original.clone().stringify(); let new = Manifest::parse(&serialized).unwrap(); assert_eq!(original, new); let original = Manifest::parse(EXAMPLE2).unwrap(); let serialized = original.clone().stringify(); let new = Manifest::parse(&serialized).unwrap(); assert_eq!(original, new); } #[test] fn validate_components_have_corresponding_packages() { let manifest = r#" manifest-version = "2" date = "2015-10-10" [pkg.rust] version = "rustc 1.3.0 (9a92aaf19 2015-09-15)" [pkg.rust.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." [[pkg.rust.target.x86_64-unknown-linux-gnu.components]] pkg = "rustc" target = "x86_64-unknown-linux-gnu" [[pkg.rust.target.x86_64-unknown-linux-gnu.extensions]] pkg = "rust-std" target = "x86_64-unknown-linux-musl" [pkg.rustc] version = "rustc 1.3.0 (9a92aaf19 2015-09-15)" [pkg.rustc.target.x86_64-unknown-linux-gnu] available = true url = "example.com" hash = "..." "#; let err = Manifest::parse(manifest).unwrap_err(); match err.downcast::().unwrap() { RustupError::MissingPackageForComponent(_) => {} _ => panic!(), } } // #248 #[test] fn manifest_can_contain_unknown_targets() { let manifest = EXAMPLE.replace("x86_64-unknown-linux-gnu", "mycpu-myvendor-myos"); assert!(Manifest::parse(&manifest).is_ok()); } rustup-1.26.0/tests/suite/dist_transactions.rs000066400000000000000000000603621441327105200215440ustar00rootroot00000000000000use rustup::dist::component::Transaction; use rustup::dist::dist::DEFAULT_DIST_SERVER; use rustup::dist::prefix::InstallPrefix; use rustup::dist::temp; use rustup::dist::Notification; use rustup::utils::raw as utils_raw; use rustup::utils::utils; use rustup::RustupError; use std::fs; use std::io::Write; use std::path::PathBuf; #[test] fn add_file() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let mut file = tx.add_file("c", PathBuf::from("foo/bar")).unwrap(); write!(file, "test").unwrap(); tx.commit(); drop(file); assert_eq!( fs::read_to_string(prefix.path().join("foo/bar")).unwrap(), "test" ); } #[test] fn add_file_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); tx.add_file("c", PathBuf::from("foo/bar")).unwrap(); drop(tx); assert!(!utils::is_file(prefix.path().join("foo/bar"))); } #[test] fn add_file_that_exists() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); fs::create_dir_all(prefixdir.path().join("foo")).unwrap(); utils::write_file("", &prefixdir.path().join("foo/bar"), "").unwrap(); let err = tx.add_file("c", PathBuf::from("foo/bar")).unwrap_err(); match err.downcast_ref::() { Some(RustupError::ComponentConflict { name, path }) => { assert_eq!(name, "c"); assert_eq!(path.clone(), PathBuf::from("foo/bar")); } _ => panic!(), } } #[test] fn copy_file() { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let srcpath = srcdir.path().join("bar"); utils::write_file("", &srcpath, "").unwrap(); tx.copy_file("c", PathBuf::from("foo/bar"), &srcpath) .unwrap(); tx.commit(); assert!(utils::is_file(prefix.path().join("foo/bar"))); } #[test] fn copy_file_then_rollback() { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let srcpath = srcdir.path().join("bar"); utils::write_file("", &srcpath, "").unwrap(); tx.copy_file("c", PathBuf::from("foo/bar"), &srcpath) .unwrap(); drop(tx); assert!(!utils::is_file(prefix.path().join("foo/bar"))); } #[test] fn copy_file_that_exists() { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let srcpath = srcdir.path().join("bar"); utils::write_file("", &srcpath, "").unwrap(); fs::create_dir_all(prefixdir.path().join("foo")).unwrap(); utils::write_file("", &prefixdir.path().join("foo/bar"), "").unwrap(); let err = tx .copy_file("c", PathBuf::from("foo/bar"), &srcpath) .unwrap_err(); match err.downcast_ref::() { Some(RustupError::ComponentConflict { name, path }) => { assert_eq!(name, "c"); assert_eq!(path.clone(), PathBuf::from("foo/bar")); } _ => panic!(), } } #[test] fn copy_dir() { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let srcpath1 = srcdir.path().join("foo"); let srcpath2 = srcdir.path().join("bar/baz"); let srcpath3 = srcdir.path().join("bar/qux/tickle"); utils::write_file("", &srcpath1, "").unwrap(); fs::create_dir_all(srcpath2.parent().unwrap()).unwrap(); utils::write_file("", &srcpath2, "").unwrap(); fs::create_dir_all(srcpath3.parent().unwrap()).unwrap(); utils::write_file("", &srcpath3, "").unwrap(); tx.copy_dir("c", PathBuf::from("a"), srcdir.path()).unwrap(); tx.commit(); assert!(utils::is_file(prefix.path().join("a/foo"))); assert!(utils::is_file(prefix.path().join("a/bar/baz"))); assert!(utils::is_file(prefix.path().join("a/bar/qux/tickle"))); } #[test] fn copy_dir_then_rollback() { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let srcpath1 = srcdir.path().join("foo"); let srcpath2 = srcdir.path().join("bar/baz"); let srcpath3 = srcdir.path().join("bar/qux/tickle"); utils::write_file("", &srcpath1, "").unwrap(); fs::create_dir_all(srcpath2.parent().unwrap()).unwrap(); utils::write_file("", &srcpath2, "").unwrap(); fs::create_dir_all(srcpath3.parent().unwrap()).unwrap(); utils::write_file("", &srcpath3, "").unwrap(); tx.copy_dir("c", PathBuf::from("a"), srcdir.path()).unwrap(); drop(tx); assert!(!utils::is_file(prefix.path().join("a/foo"))); assert!(!utils::is_file(prefix.path().join("a/bar/baz"))); assert!(!utils::is_file(prefix.path().join("a/bar/qux/tickle"))); } #[test] fn copy_dir_that_exists() { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); fs::create_dir_all(prefix.path().join("a")).unwrap(); let err = tx .copy_dir("c", PathBuf::from("a"), srcdir.path()) .unwrap_err(); match err.downcast_ref::() { Some(RustupError::ComponentConflict { name, path }) => { assert_eq!(name, "c"); assert_eq!(path.clone(), PathBuf::from("a")); } _ => panic!(), } } #[test] fn remove_file() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let filepath = prefixdir.path().join("foo"); utils::write_file("", &filepath, "").unwrap(); tx.remove_file("c", PathBuf::from("foo")).unwrap(); tx.commit(); assert!(!utils::is_file(filepath)); } #[test] fn remove_file_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let filepath = prefixdir.path().join("foo"); utils::write_file("", &filepath, "").unwrap(); tx.remove_file("c", PathBuf::from("foo")).unwrap(); drop(tx); assert!(utils::is_file(filepath)); } #[test] fn remove_file_that_not_exists() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let err = tx.remove_file("c", PathBuf::from("foo")).unwrap_err(); match err.downcast_ref::() { Some(RustupError::ComponentMissingFile { name, path }) => { assert_eq!(name, "c"); assert_eq!(path.clone(), PathBuf::from("foo")); } _ => panic!(), } } #[test] fn remove_dir() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let filepath = prefixdir.path().join("foo/bar"); fs::create_dir_all(filepath.parent().unwrap()).unwrap(); utils::write_file("", &filepath, "").unwrap(); tx.remove_dir("c", PathBuf::from("foo")).unwrap(); tx.commit(); assert!(!utils::path_exists(filepath.parent().unwrap())); } #[test] fn remove_dir_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let filepath = prefixdir.path().join("foo/bar"); fs::create_dir_all(filepath.parent().unwrap()).unwrap(); utils::write_file("", &filepath, "").unwrap(); tx.remove_dir("c", PathBuf::from("foo")).unwrap(); drop(tx); assert!(utils::path_exists(filepath.parent().unwrap())); } #[test] fn remove_dir_that_not_exists() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix, &tmpcfg, ¬ify); let err = tx.remove_dir("c", PathBuf::from("foo")).unwrap_err(); match err.downcast_ref::() { Some(RustupError::ComponentMissingDir { name, path }) => { assert_eq!(name, "c"); assert_eq!(path.clone(), PathBuf::from("foo")); } _ => panic!(), } } #[test] fn write_file() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let content = "hi".to_string(); tx.write_file("c", PathBuf::from("foo/bar"), content.clone()) .unwrap(); tx.commit(); let path = prefix.path().join("foo/bar"); assert!(utils::is_file(&path)); let file_content = fs::read_to_string(&path).unwrap(); assert_eq!(content, file_content); } #[test] fn write_file_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let content = "hi".to_string(); tx.write_file("c", PathBuf::from("foo/bar"), content) .unwrap(); drop(tx); assert!(!utils::is_file(prefix.path().join("foo/bar"))); } #[test] fn write_file_that_exists() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let content = "hi".to_string(); utils_raw::write_file(&prefix.path().join("a"), &content).unwrap(); let err = tx.write_file("c", PathBuf::from("a"), content).unwrap_err(); match err.downcast_ref::() { Some(RustupError::ComponentConflict { name, path }) => { assert_eq!(name, "c"); assert_eq!(path.clone(), PathBuf::from("a")); } _ => panic!(), } } // If the file does not exist, then the path to it is created, // but the file is not. #[test] fn modify_file_that_not_exists() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); tx.modify_file(PathBuf::from("foo/bar")).unwrap(); tx.commit(); assert!(utils::path_exists(prefix.path().join("foo"))); assert!(!utils::path_exists(prefix.path().join("foo/bar"))); } // If the file does exist, then it's just backed up #[test] fn modify_file_that_exists() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let path = prefix.path().join("foo"); utils_raw::write_file(&path, "wow").unwrap(); tx.modify_file(PathBuf::from("foo")).unwrap(); tx.commit(); assert_eq!(fs::read_to_string(&path).unwrap(), "wow"); } #[test] fn modify_file_that_not_exists_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); tx.modify_file(PathBuf::from("foo/bar")).unwrap(); drop(tx); assert!(!utils::path_exists(prefix.path().join("foo/bar"))); } #[test] fn modify_file_that_exists_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let path = prefix.path().join("foo"); utils_raw::write_file(&path, "wow").unwrap(); tx.modify_file(PathBuf::from("foo")).unwrap(); utils_raw::write_file(&path, "eww").unwrap(); drop(tx); assert_eq!(fs::read_to_string(&path).unwrap(), "wow"); } // This is testing that the backup scheme is smart enough not // to overwrite the earliest backup. #[test] fn modify_twice_then_rollback() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); let path = prefix.path().join("foo"); utils_raw::write_file(&path, "wow").unwrap(); tx.modify_file(PathBuf::from("foo")).unwrap(); utils_raw::write_file(&path, "eww").unwrap(); tx.modify_file(PathBuf::from("foo")).unwrap(); utils_raw::write_file(&path, "ewww").unwrap(); drop(tx); assert_eq!(fs::read_to_string(&path).unwrap(), "wow"); } fn do_multiple_op_transaction(rollback: bool) { let srcdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); // copy_file let relpath1 = PathBuf::from("bin/rustc"); let relpath2 = PathBuf::from("bin/cargo"); // copy_dir let relpath4 = PathBuf::from("doc/html/index.html"); // modify_file let relpath5 = PathBuf::from("lib/rustlib/components"); // write_file let relpath6 = PathBuf::from("lib/rustlib/rustc-manifest.in"); // remove_file let relpath7 = PathBuf::from("bin/oldrustc"); // remove_dir let relpath8 = PathBuf::from("olddoc/htm/index.html"); let path1 = prefix.path().join(&relpath1); let path2 = prefix.path().join(&relpath2); let path4 = prefix.path().join(&relpath4); let path5 = prefix.path().join(&relpath5); let path6 = prefix.path().join(&relpath6); let path7 = prefix.path().join(&relpath7); let path8 = prefix.path().join(relpath8); let srcpath1 = srcdir.path().join(&relpath1); fs::create_dir_all(srcpath1.parent().unwrap()).unwrap(); utils_raw::write_file(&srcpath1, "").unwrap(); tx.copy_file("", relpath1, &srcpath1).unwrap(); let srcpath2 = srcdir.path().join(&relpath2); utils_raw::write_file(&srcpath2, "").unwrap(); tx.copy_file("", relpath2, &srcpath2).unwrap(); let srcpath4 = srcdir.path().join(&relpath4); fs::create_dir_all(srcpath4.parent().unwrap()).unwrap(); utils_raw::write_file(&srcpath4, "").unwrap(); tx.copy_dir("", PathBuf::from("doc"), &srcdir.path().join("doc")) .unwrap(); tx.modify_file(relpath5).unwrap(); utils_raw::write_file(&path5, "").unwrap(); tx.write_file("", relpath6, "".to_string()).unwrap(); fs::create_dir_all(path7.parent().unwrap()).unwrap(); utils_raw::write_file(&path7, "").unwrap(); tx.remove_file("", relpath7).unwrap(); fs::create_dir_all(path8.parent().unwrap()).unwrap(); utils_raw::write_file(&path8, "").unwrap(); tx.remove_dir("", PathBuf::from("olddoc")).unwrap(); if !rollback { tx.commit(); assert!(utils::path_exists(path1)); assert!(utils::path_exists(path2)); assert!(utils::path_exists(path4)); assert!(utils::path_exists(path5)); assert!(utils::path_exists(path6)); assert!(!utils::path_exists(path7)); assert!(!utils::path_exists(path8)); } else { drop(tx); assert!(!utils::path_exists(path1)); assert!(!utils::path_exists(path2)); assert!(!utils::path_exists(path4)); assert!(!utils::path_exists(path5)); assert!(!utils::path_exists(path6)); assert!(utils::path_exists(path7)); assert!(utils::path_exists(path8)); } } #[test] fn multiple_op_transaction() { do_multiple_op_transaction(false); } #[test] fn multiple_op_transaction_then_rollback() { do_multiple_op_transaction(true); } // Even if one step fails to rollback, rollback should // continue to rollback other steps. #[test] fn rollback_failure_keeps_going() { let prefixdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let txdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let tmpcfg = temp::Cfg::new( txdir.path().to_owned(), DEFAULT_DIST_SERVER, Box::new(|_| ()), ); let prefix = InstallPrefix::from(prefixdir.path().to_owned()); let notify = |_: Notification<'_>| (); let mut tx = Transaction::new(prefix.clone(), &tmpcfg, ¬ify); write!(tx.add_file("", PathBuf::from("foo")).unwrap(), "").unwrap(); write!(tx.add_file("", PathBuf::from("bar")).unwrap(), "").unwrap(); write!(tx.add_file("", PathBuf::from("baz")).unwrap(), "").unwrap(); fs::remove_file(prefix.path().join("bar")).unwrap(); drop(tx); assert!(!utils::path_exists(prefix.path().join("foo"))); assert!(!utils::path_exists(prefix.path().join("baz"))); } // Test that when a transaction creates intermediate directories that // they are deleted during rollback. #[test] #[ignore] fn intermediate_dir_rollback() {} rustup-1.26.0/tests/suite/mod.rs000066400000000000000000000003221441327105200165560ustar00rootroot00000000000000mod cli_exact; mod cli_inst_interactive; mod cli_misc; mod cli_paths; mod cli_rustup; mod cli_self_upd; mod cli_ui; mod cli_v1; mod cli_v2; mod dist; mod dist_install; mod dist_manifest; mod dist_transactions; rustup-1.26.0/tests/test_bonanza.rs000066400000000000000000000000251441327105200173350ustar00rootroot00000000000000mod mock; mod suite; rustup-1.26.0/triagebot.toml000066400000000000000000000002701441327105200160150ustar00rootroot00000000000000[relabel] allow-unauthenticated = [ "bug", "duplicate", "enhancement", "forwarded", "performance", "question", "security", "tracking", "website", "O-*", "A-*" ] [assign] rustup-1.26.0/www/000077500000000000000000000000001441327105200137655ustar00rootroot00000000000000rustup-1.26.0/www/fonts/000077500000000000000000000000001441327105200151165ustar00rootroot00000000000000rustup-1.26.0/www/fonts/FiraMono-Regular.ttf000066400000000000000000005250501441327105200207550ustar00rootroot00000000000000 DSIG GDEFO QV,GPOS4GSUB}iLzOS/20q1`cmapw}2(;cvt O-wTfpgm gaspLglyfeF5n .head{b><6hheau>t$hmtxZ:>4locaX4AU maxp ah namewaEpostNg5{prepm F /056}~ ~ VDFLTlatn @ AFK JAZE TCAT ^CRT hKAZ rMOL |PLK ROM TAT TRK     markmarkmarkmarkmarkmarkmarkmarkmarkmarkmarkmarkmkmk mkmkmkmkmkmk(mkmk2mkmkHDHZHZJZJJ J&J,J2J8J>JDHPHVH\HbHhHnHtHzHZHZHZHZHHHHHHHZHZHHHHHHHHH,ZZZ,H HHHHHHH"HHHHH(HHH.HH HHHHHHHH4H:@FLRX^djpvv|vvFvLvRvXv^vdvjvpv  RRHHHHH HHHHHHHHHHH$H*H0H6H<HHBHHHNH*H0H6H<HHBHTHZHHHHHHHH`*06<BTZfflfrf*f0f6f<ffBfTfZfx~H,^*,]FA?BG++/-,4/4,8Svd4 8*uD$iU?wD RSK pTJxD),,6<+3,pL8R7!!2[\tD _>!<!N,!+!I[-\D/!,?!!:[\ O .!UJ[.\0vD0"-6D&l ZttthtzhtttttthtnDJDJDJDJDPDJDJDJDJDJV\V\VJbhbhbhbhbhbhVntzVntzVJVJVJVJVJVJVJVPVJVJVJVJVJVVVJVVVVVVVJVJVJVJVPVJVJVJVJVJVJVJVJVJVJJJJJVJVJVJVJVPVJVJVJVJVJVJVJVJVJVJVPVJVJVVVVVVVVVbhVVJVVVVVVVVVVVV      V"V(V(V(V(V(V.V(V(V(V(4:@@@@@VVVVFFFFLFFFFFRRRXXV^djdjdjdjdjdjV^p^p^tp^p^p^V^V^V^V^VvV^V^V^^^V^V^|V^^^^^v^^^^^^V^V^V^V^VvV^V^^^^v^V(V(V(V( p^V^V(*,,4,i^m6%FAA4JJ^BGL++)2//4?$$5,SX..66r%--p2,i -,,8K3s JHHHHNTZHHH`HHHH(.4:@,+4DP D" JXXXX^djXXXpXXXX 8>DJPV\bhntz/-G!!;[*\00"~=?G'IK0MW3Y^>alDnrPttUvvVx~W^u !-6@B {{F+-.(0*~    2DFLTlatn2 $0<R^jv@ AFK `AZE CAT CRT KAZ MOL  PLK ,ROM NTAT pTRK   %1=S_kw&2>HT`lx'3?IUamy(4@JVbnz)5AKWco{*6BLXdp|+7CMYeq} ,8DNZfr~ !-9EO[gs ".:FP\ht #/;GQ]iuaaltaaltaaltaaltaaltaaltaaltaaltaalt&aalt.aalt6aalt>caltFcaltNcaltVcalt^caltfcaltncaltvcalt~caltcaltcaltcaltcasecasecasecasecasecasecasecasecasecasecasecasedligdligdligdligdligdlig dligdligdligdlig$dlig*dlig0dnom6dnomnumrDnumrJnumrPnumrVnumr\numrbnumrhnumrnnumrtonumzonumonumonumonumonumonumonumonumonumonumonumordnordnordnordnordnordnordnordnordnordn ordnordnsubs"subs(subs.subs4subs:subs@subsFsubsLsubsRsubsXsubs^subsdsupsjsupspsupsvsups|supssupssupssupssupssupssupssupstnumtnumtnumtnumtnumtnumtnumtnumtnumtnumtnumtnumzerozerozerozero zerozerozerozero$zero*zero0zero6zero<                           >FNV^fnv~ ,4n$"8RnBxzttbgfbgR^   0V\VNHN: $IQ;JS\]^YZ[1k2*+,-./0123_`abcdefghijklmnopqrstRwxyz"X bg  VW7;=?EJN777777777777777777777;;;;;;;;===================????????????EEEEEEEEGJJJJJJJJNNNNNNNNNNNNNNNNNNN*+,-./0123_`acdfghiklmnoqrstRwxyz**8DP\ht;\YbeXIJ]ZjQS^[pb ALwVW7;=?EJN777777777777777777777;;;;;;;;===================????????????EEEEEEEEGJJJJJJJJNNNNNNNNNNNNNNNNNNN`f EK^ = HH PP  99 HH PPJD2 !"#%&'()4579=>?@DEFHKLMNOPRTUWfhmn"  ?OPSTk!)?OPSSk JOUk7Wk!2EEJK^^``ff   ??JJOUk|} #%)4577=>@@DFKORRTUWWffhhmn9?HP= JJ??JJOUk mXXKX^2<  @8CTDB  ` $,$  !NbPD9=a%&+.9;=CDJVXY]chstyzl_5 INlmh(fcedgLR?RE>)GMWT@NFU$4 TU  7}#  n O "0235KMOikm pp /9~~7 '6BEwz}#/359:Cy/'/7?EMWY[]_goqsuwy{}      " & 0 : D J p y  !!!"!.!T!^!_!!!!""""""")"+"H"a"e###!#(#+#%%l%%%%%%%%%%%%%%%%%%%%%%%%%& &<&@&B&`&c&f&k'+ 0:7 &5BEpz{~$046:;D (08@HPY[]_`hprtvxz|      & 0 9 D J p t z !!!"!.!S!U!_!!!!""""""")"+"H"`"d### #&#+#%% %q%%%%%%%%%%%%%%%%%%%%%%%%& &:&@&B&`&c&e&j'+R wnlw@ suwyzch`c_ WVUT PzTWW=wsiWUE;HJ:KE#!nFHOILFC=!޺ ޽3%n.Zf$(*@DPRVLTXJRRLNLNNT0@<@@ !NbPD9=a%&+.9;=CDJVXY]chstyzl_5( )`fdFehRGc   " 5023IOKMTNKRmikl{W7  #!$('*)-,86174/:<>@?ABEGFHQLPUZ\[^a`_fedrojqnpv|}   Sbg~XY|}Z6OPQRTU`abc[\]^_d{efghij"#KCBEFG@AH+(5<6798:;>?=DIJLMNOPQRSTUVWX)*+YZ[\]^_`a01bdefghi23jklnopqrstuvwxy45z{|}~      !"#$%&'()cm/-,.xuw~uvstw4352>INWXchutvs   mnSJ;QI:'%{uwy}~|vxzWH6UL<E>BAtru!  KE ICQD NJ$PBMH#&9 <)':GA=*(;O@ L,?%8F>+"01/.-365247 ,  !!""##$$!%%N&&b''()**++P,,--..//09::;;<<D==9>>=??@@aAABCDDEEFG%HH+II.JJ9KK;LL=MNCOOJPPVQRXSS]TTcUUhVWsXYyZZ[[\\]]^^l__``aabcddeefghhiijjkkllmnooppqrssttuuvwxyzz{{||_}}~~5( )`fdFehRGc   "502IOKMTNKRmik{W7            #!$('  *!!"")##$$-%%&&,''((8))**6++,,1--..7//0041122/3344:5566<7899>::;;@<<==?>>??A@@AABBBCCEDDEEGFFGGFHHIIJJHKKLLQMMNNLOOPPPQQRRUSSTTZUUVV\WWXX[YYZZ^[[\\a]]^^`__``_aabbfccddeeeffdgghhriijjokklljmmnnqooppnqqrrpssttvuuvv|wwxx}yyzz {{|| }}~~ $Sbg77~      &&''5566BBEEppXqqrrYsstu|vvZwwzz{}~~6OPST7HV`a[\]^_d{efh"KCBE  @  H  +  (5< $*#,$%6&&9''8():*+>,,=--D./I034569::;CDEFFGGHIJKLLMMNOPQRRSSTTUUVXYZ[[\\]]^^__``LaabbMccddNeeffOgghhPiijjQkkllRmmnnSooppTqqrrUssttVuuvvWwwxxXyy)*+YZ[\]^_`a0bdefghi2jklnoprstuvwxy4z{|}~                !"#$%&'  !!(""##)$$c%%&&m''(())/**++-,,--,..//.xuw~k  '((/07?8?@EOHMPWYYY[[]]__`gihopqrs&tu0vwGxyUz{a|}qx6wusw4352>JINdWchutvs                    !m " " & & 0 0O 9 : D D J J p p t y z zS { {J | |; } ~    Q I :  # " ' % &!!j!!k!"!"g!.!.i!S!T!U!^!_!_!!{!!u!!w!!y!!}!!|!!v!!x!!z!!!!!!q""M""T""W""H""8""6""U""?")")C"+"+@"H"H4"`"`L"a"a<"d"dE"e"e>##p##V# # B#!#!A#&#&t#'#'r#(#(u#+#+s##v%%%%%%%%!% % % % %% %%%%%% %%%%%%%%K%%E%% %%%%I%%C%%%%%%Q%%D%% % % N%!%!%"%"J%#%#$%$%$%%%%P%&%&B%'%'%(%(M%)%)%*%*H%+%+#%,%,%-%-&%.%.9%/%/ %0%0%1%1<%2%2)%3%3%4%4%5%5'%6%6:%7%7G%8%8A%9%9=%:%:*%;%;%<%<%=%=(%>%>;%?%?O%@%@@%A%A %B%BL%C%C,%D%D?%E%E%%F%F8%G%G%H%HF%I%I>%J%J+%K%K"%L%L0%M%M%N%N1%O%O%P%P%Q%Q%R%S%T%T%U%U%V%V%W%W%X%X%Y%Y%Z%Z%[%[%\%\%]%]%^%_%`%`%a%b%c%c%d%e%f%f%g%h%i%i%j%j%k%k%l%l%q%q/%r%r.%s%s-%t%t3%u%u6%v%v5%w%w2%x%x%y%y%z%z%{%{%|%|4%}%}7%~%~%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%& & S&:&<T&@&@W&B&BX&`&`Y&c&cZ&e&f[&j&k]''++++oZ%1 @ -' 0+!!!#"&556654&#"#"&54632632#"&5\n7 5&# ;"<3-2<x4 T!)!$  @35:-IC 1@. Jb9K:L  +7#3#''7VnZ7qrOHoC"C{"C"Ck"C"C["Ch@JKPX@b9K:K[>L@b_9K:LY@#$+327#"&547'!#3#'4"*8B17Vn(qr6D7+^6OoC"C{"_G@DJa  aY9KY:L +7#!#3#3#''~1Yx 34$I]GGGIt_"@` /@, Jc[9K[:L!#!)!!+$##32&##325&##3265miJ7wFAgoOEur?X\\Q]>K s3r:4E?81@.J[AK[BL$$$+&#"3267#"&&54663\,1=T]ur_4J"-MUNOQ!"938KSqpR?8"D?8"A?8,}@)(, JKPX@#h_[AK[BL@$p_[AK[BLY@ $$($$+#"&'73254.546632&#"3267/G37%<*,Hp?OQ@\,1=T]ur_4J"-J|=/ /3 0)Z YepR!"938H.?8"A?8}"BS)@[9K[:L!%!!+$##32.##3265)]=Z:QRYwpw'{2 <@9a[9K [:L   $ +###533654&##3#3IIowv[QRĘ@B/{mBS)"2~ )@&aY9KY:L+!!!!!!:p GFH~"~{"~"~"~k"~}"~"~["~u JKPX@)aY9K Y:K[>L@&a_Y9K Y:LY@#% +%#327#"&547#!!!!:H4"*8Bi FF6D7+P3HG  #@ aY9K:L+!!#!!W xFH->@; Ja[AK[BL$%%+&546632&&#"3275#'3#PMAX+4 E+Tp[\I: 4iD rQ$(6!I-{"&-"&-"&v-}"&S !@a9K:L+!#3!3#WWWWG#OV6@3   a  a9K:L +##!##5353!533!!VQVVQQVVQFBrrrrBoS"+g #@ Y9KY:L+#3!53#5!vkEE&F x(@%GY9KY:L+665#3!53#5!'%n*Pdd =CkPHH!H Ag".g{".g".gk".g}".g".g[".gi JKPX@# Y9KY:K[>L@ _ Y9KY:LY@#% +3#327#"&547#53#5!XH4"*8BikE6D7+P3E&FFg{".I)@&JY9K[BL$"+%#"&'73265#5!o}3a(&KJHN#e 9/[QGI"92iK &@# J9K:L+333#iWe mOpiK";n@9KZ:L+3!!V; zM@"="="=x"= 3+( ,@) J9KZ:L +%!'737 y9%^V%MM $99Wg8}:(@%Jp9K:L+!#&5##33:TtW|R'ypkzh_$<\=(S@ J9K:L+#3'&53# Qv Qz]]${hYOS"DS"DS"DXS,3@0 J9K:K[>L$!+#"'73255##3'&53MD@1" ># Qv QM%9 KC]]${hYS{"D/) @[AK[BL$$%!+$#"&546632&&#"3265)z{:rQzZNUUNOTUN¾iZ/)"J/){"J/)"J/)k"J/)"J/)"J./)["J/)#(B@?&% JH G[AK[BL ( ')*$+#"''7&&5466327&#"654&'3 +@(Ja[9K:L!$!+###32&&##3265\Lh]V\LQdeMOa'b`B:?Ef>"Yf>"Yf>"YW+((@%J[AK[BL$,%"+$#"&'732654&&'&&546632&#"#.$!YM3R-('8>42*$ZY+"]+"]+9y@-,  JKPX@$h_[AK[BL@%p_[AK[BLY@ $,$$&+$#"&'73254&'732654&&'&&546632&#"th./G37%<*,S4&]#.$!YM3R-('8>42*$ZY+"]+"]U(/@Y9K:L+!##5!#VW fKK(//@,aY9K:L +3###535#5!VW fC^CKK(/"c(/C@@  Jp_Y9K:L$& +!#"&'73254##5!#H/.G37%<*, 9/ /3 0)afKK(/"cWH@9K[BL##+%#"&&53326539gEFf7WHDDIXEk:;jE+NNNNH"hH{"hH"hHk"hH"hH"h.H["hH!T JKPX@p9K\>L@p`9LY@ !!##)+327#"&547&&533265SIE3"*8BWYeWHDDI.Uw4D7+J0 }`+NNNNH"hH{"h?@J9K:L+3#3WZ]OS (@% Jp9K:L+!##333rOPu`WISjORPRS"tS"tSk"tS"t9 &@# J9K:L +3##]d] 7oBA@J9K:L+!#33XX`Z ZYA"zA"zAk"zA"zH /@,JY9KY:L +%!5!5! CkKKF KHH"H"H}"?8"dS"D!/)"J#+"](H"5E &F@CJGc[DK[BL&%'##$'+$&'#"&5463354&#"'632675#"3MU4O[wN@=K&PX@#("<  J@#("<   JYK&PX@,  ca [ DK [BL@6  ca [ DK[BK [BLY@ 43,,:83>4>,2,1/.+*$"$$%"+!3267#"&'#"&5463354#"'6326633&&#"3267&5 U55-'!B)3H#E1FRmi.[,#G@8AA/1,*1E:("1|[S:-,0)XJSV+i ?"-+)/FL]VS7325.3-C_" dzKPX@ J H@ J HYKPX@[DK[BL@[DK:K[BLY@ ($ +32#"'#7654&#"3Ze`nc[4JTG?>(C>%|B7 ohgfh0$ $[4@1J[DK[BL#%$+$7#"&546632&#"3D(!b.u;pMfJ)ECGUUH=.7"}O~H>7.ieec[", 3+[ ", 3+[*@@=%*)& Jc_[DL$#'$$+#"&'73254&&546632&#"327/G37%<*,cm;pMfJ)ECGUUHED(U+=/ /3 0)X rO~H>7.ieec.7!.[ ", 3+[", 3+D`@JHKPX@[DK[:L@[DK:K[BLY@ '%#+#'#"&546632327&&#JL,de2]?W7GA>K7>$E((O}G?hgfiT!#C,/@,J Hc[BL$*&.+&'77#"&&546632&&'''&&#"32651D\9F3>YS;kG@h<:b9a< C1O;P1;NM@GS BI,@=ĐT|C:oMHp>E?f#P((*YVW_me'e"223+FE#s@#JHKPX@"Y9K[DK[:L@$a[DK:K[BLY@ $#%" +##'#"&5466325#5353&&#"327EOJ9\de2]@W6TO>$BF@>K7UEPO}G?zDT J!#hgfiTO 9@6Ja[DK[BL%%!+63267#"&546632!6!&&#UA'A%'%_0r8hEdsQLF=X8 }O}GxWTSXO " O  " O " O " O " O " O " O " O &-{@ JKPX@(a[DK[BK[>L@%a_[DK[BLY@'''-'-%%#*" +!3267327#"&5467&&546632'&&#" UA'A%'*=G3"*8B42it8hEdsRF==LWX8!5D7+&>xO}GxSXWT^*]@ JKPX@a[;K:L@ca:LY@#+&#"3###535463D2?n U}}mS?WkC]CiGWB,*F6A@ , J6HKPX@1pc [DK[:K[>LKPX@8  ppc  [DK[:K[>L@6  ppcc  [DK[>LYY@777A7@'+4#%5$ +##"'332#"&5332654&##"&5467&&5466326732654#*F6\j\/ F_4U1xzdL?:UQB4^AD-+5_>Z^8>?>8;u *[Pc -%B(MSLS!',+$'9+1G35S/jA88CA;xB,* "B,*"B,*- CN@C 9 JKPX@:p  c  c  [ DK[:K[>LKPX@A  pp  c  c  [ DK[:K[>L@?  pp  c  cc  [ DK[>LYY@"DDDNDMJHA?41-+('%# +&54773###"'332#"&5332654&##"&5467&&5466326732654# 32F6\j\/ F_4U1xzdL?:UQB4^AD-+5_>Z^8>?>8;uV"ku_ *[Pc -%B(MSLS!',+$'9+1G35S/jA88CA;xB,*"d'@$JH[DK:L#"+6632#4&#"#7Z0T,0)LTT(,}/+4# f@  JHKPX@Y9K[DK:L@a[DK:LY@# +#4&#"##53573#663T,0)LTQQTZ0}/+4#UDI RD(,d"223+y bKPX@![CKYCCCy !@YLKPX@*_ [CKY#C86NG3%Y@  JKPX@[#&F"$dEJKPX@[4$d" !KPX@ J@ JYKPX@[;K[4$d"d"Yd-H@ JGKPX@[54&&#"#36632!A9'* '()LTH_0,X5=#?8);>4#J(-d"I ,@)[DK[BL   $+#"&546332654&#uwlmvwmEDCEECCD|}EfhheeiheI"I "I"I"I"ID"'I"I~#F@C!  JH G[DK[BL#")$+#"''7&&546327&#54'39wl%F*6:wm&F)4v3vU|{ V}y}'΄.}e΄-I~"I"g -4T@Q  Ja [ DK[BL..!!.4.310!-!,'% $$%" +!3267#"&'#"&5463263326654&#3&&#Q34+' A(6KC7UTSV;A*b**1!&)/-%/}[S:411452gDdkm`(YMkcL]WRd,h@ JGKPX@[}< J),D0$#ehhfd,;@8 J H G[DK[BL+#"+6632#"'732654&#O,hcV6TT\C?%BA:>%(}<  0$#ehhfI, e@JGKPX@[EyyT 056/!E#=/ /3 0)Z S<:Bw B-,9.O"dE JKPX@L@_, @ JG7#33bla 5?$YW[l C %7,1>,">,">,">,"f /@,JY7733333#33dSM;@@"3IBHL7 "I3C/Io&&4m#2@/  J]YKL+%#'##'>7!#DA*64&"$'IIܓf̜H#@4}/,;@8  JaKK[L$#+#!#"'73253!WMD@1" >WOG{IM%9 K#Ce /@,cYK[L  "$!+!32##!32654&#W}cPVYLheiqf>RHB` @YKL+!#!Wd "4 C*@'IrYKL+!#!5 WDCMm53@0QYKYL1 +%#'#!##53>76677!#!5EE+ZX +IܓIx T~ )@&aYKYL+!!!!!!:p GFH~"!~k" L6@3J aKL +#33333###hT{lS[F`@xOI}-6RkPYXIy i?USB6V*04qC-R5=K YK>b82/1IJBBAI9859!!3++S@ JKL+33#47#SVpVrVs>dO]|S"&S"&Sm_ I@FJ Hc ]KL   % +&'73267##7#47#33R: +$$. 9T<5ND#ArVp;5  5;@ؓ]|Vs>d_K -@* JaKL +#333#VVQ^iF"F_K"*#@ JGYKL+!##'>7!W*64&"$'\f̜H#@4}/:CS+/) @[K[L$$%!+$#"&546632&&#"3265)z{:rQzZNUUNOTUN¾iZS@YKL+!#!#WWeOt-V?8(/cD @ JGsL+%'>7#33q;\G JHIlhnoOGPX9@6J GcYK[L"#+2###'>7!32654&#c|y^^ %",HDEFgmiwz791h^x@VOC)X1@. cK\L"$ +2####333326654&#b}yzs^VVU0;DFbhpd]>:K?+]:2>@;Ja[K[L$"$+&#"!!327#"&&54663^*-DQVq 1rZ]B.$gEWKOQ"!51s|H<9!*RqpR*;@8 Ja[K[L"%$+#"&'73267!5!&&#"'63Br'4%M0[`/\U5M%0by/)1! }J~p#3Ug.gk".I961@.JcYKL#"+#632#54&#"##5!>RV3<*;WhhI/C"nKPX@!a [K[L@)aK [KK[LY@"!$ +#"&'##33663326654&&#V[dfVDSSEX_&++&$,,$ؚG6{kl{66|ji}7! 2@/Ja[KL&+!###&&54633"33Wb;=PM{)a?bkFAEy64@1J GcYKL-!+632'>554&#"##5!#AOUX,/3<*;W <̖cfE(9.H>hIII(.@+JpK\L#$$#+#"&'#"&&54667332655332654&'3 )/;90+J.T##\$U()-$"T^4444=nY]=\s4986drb 8>@;a cK \L$ +###53533#3654&##3|WGMVSNIEifpmA^^AfBRKB(S"@" JKPX@! a[K [LKPX@% aK[K [L@) aK[KK [LYY@ %"" +%#"&'##336632&&#"3#3267SE2oFXXG k-=*(TH"*%ٔ9 J|C /@, JbKL  +####3#':R:YZn]pLL""Ol%W<@9J bK L +######3373# $I$AQFiVVkZoUP9:###1xOh&22@/ JaYKL +!#####3'5!3#7#2PR9U9SPpHQ{77{::tE@B JI a  Y KL +!#######33'5!3'7tRM.V.NRYtVVswIjk777::R*(I@F!J&IHGr_YL('%$#" +'77&&5467654&##57!5!3&}~%Yuw\35%$ ?@RUKMjl+QQ+li^[m  #8B*17G?@II KM/)RG@  JKPX@[KL@K[KLY$ +#"#36632! `]}8/"&y948R"V3+ ,R (r@ "J(GKPX@[KK[L@"[KKK[LY@ $#!    $ +&54632#>54&&#"3667#33QHHSSIIS  !),3 #TL=8MSQC B;|ji|<<}i EB1XeB? -@*aYKL +3###53!Vee dCnCMU-%!H@E JIcYKK[L! !%$%+#"'732654&#"#3!!663jMD?1!! ?>5N$VV* $Y9sdIM%9 %&,DH((I%' mbA@>J  a  ] KL +%#'#7####33333bGfF`];// /3 0)V1+1IJBBAI9859!!3++-R5=K YK_mK6@3Ja]KL +%#'###333KEPVVQ^IܓF"_K6@3J a  aKL +##335333###0UU06[a6Fݬ#F%H6@3Jaa KL +!####53533#33HiPVKKVssR^F F``F|" W3@0 JaYKL+##5333#XyG^iEdM"ESmB0@-a]KL +%#'#!#3!3BEAWWWIܓG#-QI@F JcYKK[L#$% +#"'73254#"###!63NPA@1">F.*WWA+:UHJO%9 KhO$c(SmB *@']YKL +%#'#!#!BEAWIܓe#F+6A@>. JGc[K[L)&$%&*+$&''#"&&546632&&#"33&&5466326674#"FE>%9'$%T~EJ|H;S*-"A%MfdX "%)J/LU+,H%'$".+0.RrpR #93[/2P/_M3R*YAe82?8*:@7('*Jp`[L$$,$#+#"'732654&'.546632&#"3267C:9(&&KsBOQ@\,1=T]ur_4J"-Am:F2=3 !< YgpR!"938A (m/ *@']YKL +3#'##5!V=EA fܓfKKAzA)@& JaKL+3##5#5333ucXb`Z?DDrZm>/@, J]KL+%#'##33>E]d]ðIܓ7oB 3mB8@5Jc]KL##+%#'##"&533273BEA.P5ZnVFB`=WIܓ"o] DC=M3k@JIK.PX@cKYKL@caKLY@!+##5#"&535367W;5: ZnV{:A/O, kdo]  -MS$0@- JIcKL#+#54&#"#3663jW>>5N$WW$X:sdDH((&&SmY8@5 Jc]KL""+%#'#54#"#3632YE>0IWWAj^jIܓ('Gre7!(B@? Jc[K[L"""("(%$$" +!3267#"&'&&54733>32'&&#"7RA)4")J]j?E'9 332'&&#"7RA)4"),/H4"*8BXZk?E'9 3@CWWc]vm^IM%9 KHF 0mE2@/  J]YKL+%#7##'>7!END#B*64&"$'>Eؓf̜H#@4}/S-5@2JaKK[L$!+#"'7325!#3!3MD@1" >WWWM%9 K#HmA0@-a]KL +%#7###333AND#BWWWEؓG#3m8@5Jc]KL##+##535#"&53327?E;.P5ZnVFB`=O"o] DC=Mm\<@9 JpnsKL+%#7#&55##33\MG#-fWlQ'ya\z#Eؓh]v !:9(C"Ck"~"!-'6@3Ja[K[L%%"+!&&#"'6632#"&57!3/XP.H'-+d>~=sNw RQ}v3%(qWt-'k"{ Lk"$#k"%F?@< JIcYK[L#%%+#"&'732654##57!5!3q=pKG`--)I6LPxh[@b6%%7LE|@IIS["&Sk"&/)k"//) 6@3a[K[L   %!+$#"&546632!&&#67!3)z{:rQzMAMNQMOP¾iZyzzyˀ/)k"*k"DD["4 Dk"4 D"483k"8m  (@%]YKL +3#'#!;E? dܓM9k"??-GL@I  Jpa  YKK[L$# +3#3#"'73255##53!;ee dCۆIM%9 KBnCM-@=@:JpKK[L$"+#"'732655##333@MD?1!  %]d]ðB=IM%9 %&B7oB 9/@, JbKL+!###53333#9d]Ŝd]7NB! B1 0@-cK\L   $+2&546333#7#"3xnWtpPVlffrOGIK I(=@: JpcK[L('$#%## +#"&'#"&&54633332654'3675#"3I[J+@0T*F+qn.W"+W-0?G.&8>*&P.\Bq{03Yg;F*,TUF@?$8@5Jpa[K[L(!$!#+#"&''#532654&##53232654'3?`R?T!OSHMOPXZ[JLG,!+1V;EB:Fc_HZ4*Zl7FIm+8@5Ja][KL!$!+%#'##532654&##532+E%MOKQ\LIܓ'D?EB:Fb`JaK/@,JpYK[L##+#"&5#'>7!32654'3K\J>O_ &$2 "+W8>XTB$<7-03Yg;F1N4@1paKK\L##+#"&55##33332654'3N\I>OVVV"+V8>XT"03Yg;F-&8*@'pYK[L"$+#"&5#5!#32654'38>`4G] P5DV::Zx9_WLLT\k5F;5)?@<"Jc[K[L)($$!$%+&&#"33#"327#"&&5467&&54663q30*V6BST@u qIXYPjU4,JOv?`F=Q7!MD@1" >*64&"$'\M%9 K̜H#@4}/`+@( J GYKL+!##'#'>7'33`X|vTk: %$3 jgS3pB$<7-7M?@< Jc[KL!# +!###3273654&##3R/%SxdspVJE>'"A"u_FFPGHVE@BJa  a[K YL& +%!##&&5463!#3##"33VFuc=>rG ĜY*BIA87!63HPB@1"><*'V_ &$2 *5R@JO%: JhO#hB$<7-(-Q#O@L!  J   caKK[L#"#$% +#"'73254#"###33363NPA@1">F*VVVV+;UHJO%9 KhOG"(9>@;a cK \L$ +###53533#3654&##3|__WfLVSNgcifpm?__?gBRKBt-G@DJppd[KL%!"+#'###327'3654&##3-USO@6CYVYOCWKc`k;jkk n#FMEE K '6@3#JHc[L'&!&+#"&54667667663654#"3W09eCd|"SL@B#$B]/8#W6Gq.OF>:mISw=s`A)#,SG-5NUj26@^gp"D@AJc[K[L"! ("$+##632&32654&#654&##3B`]Uds0;/l7;=AERK:r_@;NNHB+9%,)$n 74& @YKL+3!!n G8 "( MIK PX@fYKL@rYKLY@ +!#!5 TG8m:@7 JQYKYL +%#'!#53>77!#3!FF$ TU"  Eؓ=n_6_dn>O 9@6Ja[K[L%%!+63267#"&546632!6!&&#UA'A%'%_0r8hEdsQLF=X8 }O}GxWTSXO " O " D1@.JaK L +%##'3353373#'##9hRzqV\8J8_SqzUh6J6%@ JK*PX@%hc[K[LK+PX@&pc[K[LK,PX@%hc[K[L@&pc[K[LYYY@$" %%+2654#"'6632#"'732654##75=|)V$((n8[v:1 ?)kY(QjGJj5,$Q2"KE*98%QWI6:4-VDd@KL+!#5465#33T gTiJfU?md"d"]m1 v@J HK*PX@ ][KKL@c ]KLY@   % +&'73267##7#547#33R: +$$. :T=BE": fRg;5  5;ՓtW?:q3j9 '@$JaKL+3373#'##jTi_fgTj9"*#@ JGYKL+!##'>77!T 22$!eȋvx>6-jf*.)@& JpKL+!#&57####33.QiStP&nqhpZ3-c#5Phd !@aKL+%##3353#TTTTI ,@)[K[L   $+#"&546332654&#uwlmvwmEDCEECCD|}Efhheeihed@YKL+##!#TT6d,[A@YKL+###5!T7F>, @ JGKL+'>7#33bla 5?$YW[l C %7,1>,",;(5Q@N,+%$ JH G [K[L)))5)40.(''$'$" +632#"&'#"&546323267&32654&#R)9J=KE.L0EIAK6'o##%$ &%%6~ 4anob>dmp_8 U)@&JdKL##+!#5#"&553326753TV0LOT,0'RT QJ5.!^m? )@&RKZL +%#'!333?FsTTEؓ761' @KZL+!!33333' ONN771mo-@*RKZL +%#'!33333oFONNEؓ776dm #@ sKZL+!##'#333FTT7~ )@&cK\L  "% +32##332654&#gvg8\6Tq9?:Ii]W;7!! /@,cYK[L  "" +32###5332654&#?}HCJHDiA2A;5> -@*cK\L"$ +32##3#332654&#$ltvjtPQQt'CC@FdUX]Z4A;4P9@6JGcYK[L"$ +32###'>77!32654&#k hpre_j50  >;:AdUX[\̋p}B8/k`3B;3HS1@. cK\L"# +32####3353326654&#n ioaSSQ*3:BHMV ++9,J[A@>Ja[K[L%"$+&&#"3#3267#"&54663J("B&?V VD'B"'"`-w;qM:7VQBZX6 }P}H[A@> Ja[K[L"$$+#"&'73267#53&&#"'663x~u1a$)@LFRNBN?(#b1 80W[BTS/6 yy"`,88nKPX@!a [K[L@)aK [KK[LY@$ +#"&'##3366332654&#\[\WXCRRCYU5((56(*4ppzEamgffhkb5 8@5Ja[KL   +#5##7&54635#"3Tp_}]mcmo;:5=,dNR,03--"a@J"!HGKPX@YK[KL@a[KLY@ .#+3#6632'>54&#"##5357[/"A8'*,0)LTQQTD%+GX5=#@8b1,4#UDI >:8@5HcYK[L$"! +3#32###535732654&# zFWWTPCJHEDpDm 0@:4LH#KPX@  J@  JYKPX@" a[K  [L@* aK[KK  [LY@#"%"% +$67#"&'##336632&&#"3#3..M%]q@TTA n^)F"+09? D8<4rj4UQBYX$5 /@, JbKL  +%##5##3#'?H=OVaYiHH4R8@5J bK L +%##5##7##3373#''D$:N>gSS|LfQM45B2@/ JaYKL +!#'##5##3'5!3#7#BMUKSITOm[yzah+..#d;@8 J a Y KL +!#'##5##7##33'5!3#7#dPI%N$KOPuRRkck<Y..P*(9@6JIH('Gr_YL"/+#'77&54676654&##57!5!329(||@T\ZK-*JT_79&'!ww)ZZ2D*27 (/,'@HBIK $8),9I 9U>(@% JcKL#$+36632&#"#9\|/.(f>;0849UD"&,] #n@ JGKPX@YK[L@"K[KK[LY@#" # +4632#667#33654&#"3KOKOiPM&^YBAV\wiN''' @E1ek  aooaapo`^+ -@*aYKL +3##5#535!}}T^^o ȚCCGf- >@;JcYKK[L#$$!+#"'732554&#"'#!#6632OC@1!B-(,BTB Q2BUM%6 K20,(C#)THmYA@> J  a  ] KL +%#'#'##5##'3353373Y=#d6J9cSvlUX8J8[RlYEؓ68Y@ +*  JIKPX@2  hhc_[K[LK*PX@3  hpc_[K[LK+PX@4  ppc_[K[LK,PX@3  hpc_[K[L@4  ppc_[K[LYYYY@54%#!##$( +$#"&'73254&'732654##732654#"'6632kY./G37%<*,yV(QjGJj_5=|)V$((n8[v:1 ?)UU 0/ /3 0)VE6:4-VD,$Q2"KE*98%jmK6@3Ja]KL +%#'#'##3373KF$gTTi_Eؓj96@3 JaaK L +3353373#'##5##jU-.X\.-Uss||'18@5J HaaKL +!#'###53573#3731fSTOOTU_7Dh rDL-@*JaYKL+!#'###53373Lf^T}_^AVm60@-a]KL +%#'#5##33536EQTTTEؓ6^m> *@']YKL +%#'###!>EQTEؓ66-T I@F JcYKK[L $$% +#"'73254&#"###!63 IPD@1" C! *VWH,B]L>IN%7 K%(7*9&%1A@>(JGc[K[L*%##%)+$'&'#"&546632&#"33&&546326654&#"&>7(+92*+m}6fEE5"&.AHODBN&&  !i04+=2T~E=hf+L&?WPB'A%J/%+.'[(:@7$(%Jp`[L$#*$$+#"'732654'&&546632&#"327(C:9(Jhs;pMfJ)ECGUUHED(H&=$2=3 B1 uO~H>7.ieec.7 Am *@']YKL +3#'##5!VQFQ |ؓFF:,@GL+%33YWRR :,*@' JGKYL+%3#5#5333kSiRiSYWHD DR8m  7@4 JsKYL +3'373#'#'#738«dz{afF$uⓓEKm08@5Jd]KL##+%#'#5#"&5533267530EQV0LOT,0'RTEؓ QJ5.!6U;@8JdaKL!+#5#5#"&553536675T(<6 LOT(*65%QIQJ2/e+@( JHcL#+#4&#"#7663UT.+.CTTP2VJ/92/- '+`m@4@1 JHc]L%#+%#'#4&#"#76632@EQ.+.CTTP3HTEؓ/920- ',VJ +#)B@? Jc[K[L$$$)$)$"+%" +!3267#"&'&&5467336632'&#"+P>#3 (S-dxAC5  rY_iQs8DVY5q<2. jyWU +28@%$ JKPX@(p c[K\L@%p c`[LY@333838$"+#*" +!3267327#"&547&&'&&5467336632'&#"+P>#3 ('.H4"*8BXScAC5  rY_iQs8DVY5 6D7+J0 e<2. jyWU<D"k-9@6 JcKK[L%$%+#"'7326554&###3373LOC?1! "177!-BE#= 32#!HBՓȋvx>6-ke3d-5@2JaKK[L$!+#"'7325##3353OC@1"BTTTM%6 K.fm00@-a]KL +%#7#5##33530BE#>TTTBՓ3Um8@5Jd]KL##+##535#"&55332675FEFW0LOT,0'Rء QJɿ4.!4mU7@4 Jp]KL+%#7#&547##33UBD"1YTeQ&n^Vo"BՓFA'l/E6h3E "E "O " I @@=Ja[K[L"$+#"&547!&&#"'6367!3r{lgvgFE*A (NgFEIAy [S:>O\NUI "D"6">,>@; JcYK[L$%%+$#"&'732654&##57!5!3}=lEJh'3$K6FNOQW faAc6-.-#NIF>?HBd"d"I"I =@:a[K[L    $ +#"&5463!&&#3267uwlmvwm?EC>B>>C|}ETSRU[UVZI" [">,">,">,D"'U"m  (@%]YKL +3#'#!QFQn }ؓG>"]-+L@I  Jpa  YKK[L$# +3#3#"'73255#5#535!}}POC@1!BP^^o ȚCIM%6 KDCG.-&=@:JpKK[L$"+#"'73255#5'#'3733&NC@1!A#`«dz{a?=IM%6 KD8 /@, JbKL+!#'#7#53'3733# f`zwdy|ayx;;D N -F@C$#JHp[K[L!!!-!,&'%$#+#"&'#"&54663232654'367&&#"3NZM-D<#NT)K3"0T!')V.'+1*&8;/02*|PG 27Xk8F(. iefg @#8@5Jpa[K[L(!$!#+#"&''#532654&##5323254'3@gS@T'0jc8<@B{|ip;;22!cV8;?EXA-31-BRN7G[* 8FLm8@5Ja][KL!$!+%#'#'#532654&##532F/877!#32654'3M_L=Sd ,*$44&.V8;TP2v}@8,lh%/Zi8F-R4@1paKK[L##+#"&55##335332654'3R_L>SVVU%.W8;TPT}%/Zi8FIA@>Ja[K[L"%%+&&#"3275#'3#"&54663wQ)1 ;'*F+IF77!OC@1"B 22$!e=IM%6 K vx>6-jfV+@( J GYKL+!#'#'#'>77373VYghSZU ,)$VXQ|w|@8,lh,,T#KPX@  J G@  J GYKPX@YK[L@"K[KK[LY@#"#$ +!'#"&'3663273$654&#"3j,E)%2XK9) dVx3%+1,Jn< @$';cjo_'&"#S#-@ JKPX@(a  a [K[LK.PX@6a  a [K [KK[L@4a  a[K [KK[LYY@,*'%#"!%! +%#"&'##7&54633632!3267#"3$33467#"SMYOe 1dRj\nY TW=3%2#,Y-6d76Fr3>ub*cK[ zZXrUV/77!63 IPD@1" C! *Vn ,*#,B]L>IN%7 K%(ȃv}@8,lh*-T$O@L"  J   caKK[L$#$$% +#"'73254&#"#5##335363 IPD@1" C! *UVVU,B]L>IN%7 K%(*F>@;a cK \L$ +###53533#3654##3n|rVVT vN2I}L@OULUYD??D=.;`g,&@"# J GKPX@$ps[K\L@(psK[K\LY@&%&#"+$#'#"'366327'36654&#"3**KO7W4TGP/eZ_OK;=(C?$y#yY< J),{R@gg0$#3m2#Q@N  J  aRYK ZL##  +%#'!#53667&&5537!35#5#2GG&"4 F@Txx0FٓSX VNZW-3 7 ~O[ma$(S@PJ aQ Y K YL%%%(%('&$#+!#'###'##53>773353373%#335#aSZ9C B0NNLZ3 |~0&ferf]5$m62@/  J]YKL+%#'##'>77!6FQ 32$!REؓȋvx>6-jf6,;@8  JaKK[L$%+#5#3#"'73255#335TOC@1"BT>IM%6 KED\ '@$aYKL+####333TWVViG#YZ '@$aYKL+##5##3353RTTT6m41@.RYKZL +%#'!#5!#334GZ xYIܓiHHim+1@.RYKZL +%#'!#5!#33+G}S vVEؓCCz6__I9C 1@. Jb%K&L  +7#3#''7VnZ7qrOHo` C@J%KZ&L+!!3!CnQ~ )@&aY%KY&L+!!!!!!:p GFHHS !@a%K&L+!#3!3#WWWWG#O/) /@,a[-K[.L$$%!+$#"&546632&&#"326553)z{:rQz[MUUMOSUMͷ¾iZHHg #@ Y%KY&L+#3!53#5!vkEE&FiK;>@J%K&L+!##3>ZWn`:CSDM  )@&aY%KY&L+!5!!5!!5!X1Q;BaPP{Q/) @[-K[.L$$%!+$#"&546632&&#"3265)z{:rQzZNUUNOTUN¾iZS0t- #@ c[%K&L!$!!+###32&&##3265-|YV]WKdaNWjjjjJEDSI -@* JIY%KY&L+7!!55!!`:IIIKJ>(/cA@J%K&L+!#33XX`Z ZY M7@4 cc%K&L +#5&&5467536654&'V~y~yTxz}TMLURULMTb}QQ|OYkg^p_flY9y K @ J%K&L+%&&53366553#zyYFUWSGYzxY eOSZSNd /)!0@-J[-KY&L! &+#53&&5466323#56654&#QA@wAA>rMMr>BAx?ARQstAF"mnRRnl#FAtC"L""+".)"JA"z)"Ngk".Ak"zr#@ a%K&L+#3!WWGG$5 ,@)pY%K&L +#5####5LWLe S&/7)KJK.PX@[-KY*L@][-LY@ ''+$#5&&546632654&#"3)lfUgl:rQzNNUUNOTƽiZ捍J*&@G[%L!-+6654&&'&&546633#"D127:peBt su"MC[OIQ0$.zZYJrG\?$F86M$~3@0JcY%K&L%!+32&&###!Օ#-'W d BMG @b%K&L+!#!'3!0ccX6K0K? &@#J%K&L +!''#'3hZT*Z&T p T]Od->;@8  Jp%K&K[*L$#+3##"'73255#33dWWMDA/!!?"g?O=IM%9 KBpA&T`KPX@  JH@ JIYKPX@[%K&L@%K[-K&LY$ +#"#3766320W`j:1u B0 @T"a&Tk"a/)>k,W?81@.J[-K[.L$$$+&#"3267#"&&54663\,1=T]ur_4J"-MUNOQ!"938KSqpR:C 1@. J[-K[.L$$%+#"'732654&#"'663NUM-!K4_rgbWJ*1`AqSK939"!?8%E@BJc[-K[.L%$ $$$+&#"3267#"&&54663#"&5463\,1=T]ur_4J"-MUNOQ ""!!!"938KSqpR!!"! $E@B Jc[-K[.L$#$$%+#"'732654&#"'663#"&5463NUM-!K4_rgbWJ*1`A ""!!qSK939"!!!"!C"7G 3+C"77 3+C"7 3+C"7 3+C"7 3+C"7 3+C"7^ ^3+C"7^ ^3+ C"$C"8C{"7C["7DC"7DC"7#G 3+DC"7#7 3+DC"7# 3+DC"7# 3+DC"7# 3+DC"7#  3+DC"7#^ ^3+DC"7#^ ^3+"; 3+"; 3+E"; 3+G"; 3+T"; 3+["; 3+"""= 3+"= 3+"={ 3+"={ 3+*"= 3+1"= 3+"=w^ ^3+"=y^ ^3+"+"+SD"=D"=# 3+D"=# 3+D"=#{ 3+D"=#{ 3+*D"=# 3+1D"=# 3+D"=#w^ ^3+D"=#y^ ^3+"? 3+"? 3+^"? 3+Z"? 3+>"? 3+D"? 3+"?^ ^3+"?^ ^3+".".g{"?g["?)"E 3+)"E 3+)"Ey 3+)"Ey 3+6)"E 3+=)"E 3+)"J)"J-"G 3+A"J 3+A"Jr 3+A"J_ 3+A"JF^ ^3+A"zA"zA{"JA["J)"N 3+)"N 3+)"Nz 3+)"Nz 3+5)"N 3+<)"N 3+ )"N^ ^3+)"N^ ^3+)"N)"N/D)"ND)"N# 3+D)"N# 3+D)"N#z 3+D)"N#z 3+5D)"N# 3+2%!EeJDuFDtFW|>)AIfNVb`YB>6^ )--4:9'VM:? <+CJ4&()%+Do!%<@ JGK.PX@ Y'L@U[OY%$# +'6654&&'.54667##7!xHA=>@%MP"22+2KX-GxW) dh|?1:&/&8V$=0#6TADYEI,3@0 JHG[0K&L'+4&&#"#4&'7663T '(*LT O `0 ;>4#b/S+ 7*.I =@:a['K[.L    % +#"&5463!&&#3267t5fHnuvnEDBECDDBƳu^˳F? /@,JY(K[.L$"+73327#"&5? W*/G#HUHsG= PG;W1 #@ J HG(K&L+&&''#4'3!/(53M)I ,@)[0K[.L   $+#"&546332654&#uwlmvwmEDCEECCD|}Efhheeihe.2@/JG[(K&L%+&&5##'663!9/U":0'!E?=I8@ G^, ;@8J G[0K[.L %+#"'46332654&#i.]CW5TliG:@%AD?AL}J< トCkk "idiff*$%@"JG[0L$#%+&&#"'6654&&'&&54663Q**":*,I+AC:B$IR$1309d\?pG8-X?39)#2&6M$<-"$k`Sy?I/,@)J[(K[.L4'+'#"&54632654'"3/{)/5fEkxunCG@V1DD kOOxB~|+ei="YRhe',@)  J[(K[.L"%#"+%#"&5#"'663!#327G#GT-&4("J08V)/ PG< B GGg @(K[.L#$+#"&5332654'3 Ck?SfT62>WVYx9XTn78^o",6#I@ GKPX@[(L@(K[0LY@#"+5&&546734636654&#OOokT;LEQ!SC#- }rSBV~7`b lpC,C: egn]6.)!@ J H(K*L+'.'7)XV*?8)R1N,YM^rI*2s'),9@ G(L+%>54'35&&533U:>U ptPnnT=KP?0P@sGJt nQNAA>$'@$J(K\.L&%$#+#"&'#"54733255732654&'3>NK4? ?02K"*KP!)+"Mw;98<OQW^~ SY^WLR? ("? "? "g ("!g "g "I("!>("!; (" S"("1I,(" m  @Ha&L+%!#4&'7! T M/b6P' !\K,8 !@ JG[(L%#+#"'663!8T )&n C _ @ JG(K&L+$&&5467#3390gTbc# ?IC'2V?my[4@1J[0K[.L%#%+&'732654#"'6632#Q''$6'KU+> ),Q4Km:=pJ 6bf6D~UTz@[#H@EJc[0K[.L#"#%$+$7#"&546632&#"3#"&5463D(!b.u;pMfJ)ECGUUH%"!!"=.7"}O~H>7.ieec """"[&H@EJc[0K[.L&%!%#%+&'732654#"'6632##"&5463Q''$6'KU+> ),Q4Km:=pJ"!!" 6bf6D~UTz@R""""I,&@#Gs[0L'*+$5&&54632654&#"3aXTY`wmmuCCDEDCE q}eihefhheE*A'@Gi''+'6654&&''.5467>5,K:;B$HI8C'JQ#30(*AX8e\C7;6A" 8129' 2(4M#:,  2ZGWf&%~+.@+J G_[(L$%#+32&&##433("-'T (. = AEW0+%'6677'7667%' J #W`&!I #P2MW)4[CXFLX)4\BXE-@ HG&L+'654'#&'#&'77M fMWRM? IE]71; ^L<>-%'@$J%!   H[*L$"+#"'73255&&5477'4&'7ND>1"@d:/d;JN%6 KY:/)8Jp&" @IC.3&# O  -G@D*Jc['K[.L!!!-!,'% %% +#"&54663263&76654&#654&#"3K4eDvgKxCIFUM8D2gZ F0B4# ;Iy^ q[d cZWd| O04@1)( J[(K[.L&%T$$+##"&'#"&547'667533!&'325573265O?,MH1> >.HI),+1g  'EQ'( fxw;98<{we >g:8f@W^j vSY^W>0+$&&5477'4&'7:/d:/db" ?HD)8Jp&" @IC.3^) )Q@ JGKPX@[0K[&L@_[0LY@ )(,%-+.'&&54632#"'32654&#B N-1YPkijp0]B\/9=Q;?&AF@=q&  MUsFzKFO+* >klX%(j\X^[4@1J[0K[.L#%$+$7#"&546632&#"3D(!b.u;pMfJ)ECGUUH=.7"}O~H>7.ieec`,[d,,/*@' JGp(K&L+33#&5#Sqbmq+StQl%Q|]^xoH0 -,,#C@@JG][0K[.L!# +'3#5#534632#'32654&#"5TQQlhpj.]C@%BD?BD: <8643&1&0,*%$ $%$+&'&&#"'66323267#&54632#2&54632#73327#"&5F  55" 67!   W*/G#HU '. ))HsG= PG;I%" I%"I"I"I"&I"I"I"^, %"&^, %"g %" g %"g "g "g "&g "g ["g \"g "g "g "g  "g "g v0q@ JHK!PX@[ 'K(K[.L@ c(K[.LY@0/+)&%" ( +'7#"&54632#"&5463#"&5332654'3O#5 {Ck?SfT62>WV!}2 Yx9XTn78^og v0q@ JHK!PX@[ 'K(K[.L@ c(K[.LY@0/+)&%" ( +'7#"&54632#"&5463#"&5332654'3#t   {Ck?SfT62>WVD}! Yx9XTn78^og y%1F@  JKPX@0c c ['K  (K [.L@.c c  c  (K [.LY@$&&FEA?<;86&1&0,*%$ $%$+&'&&#"'66323267#&54632#2&54632##"&5332654'3V  55" 67!  vCk?SfT62>WV '. ))Yx9XTn78^o>%" >%">">">"&>">[">\">">">"D>"D>""D>""D>%"" D>%""D>""D>""D>""&D>""D>[""D>\""D>"#XDXDXK  =@:c[AK[BL    $ +#"&546332##"&5463ssnnssn&&%%D &&&&_ )@&J9KZ:L +%!53'73s%JDDD_;;0@- J[AKY:L'+!!5>54&#"'663L_2;wD f2B:1A":)eD2W7@xmHDh::B!(-304(?@<&%Jc[AK[BL('$!#%,+#"&'732654##732654&#"'63L`4N8A[MM=@:JpcY9K[BL$#%#+!6632#"'732654&#"#!9 Vg9jFsU4?UELD9,$Dmm rfDh9R3@TONE XO $E@B!Jc[AK[BL$##%&+#"&546632&#"663654#"3Y4:b=xm=sPK>!18OY R4*Cz0MDI0aGJo:r\)80,y[Q5-ula@JY9L+7!5! ]FAA'3(@%+J[AK[BL*,+(+#"&&547&&5466326654&#"&&'3265<;FN:lGHj795>_15^;#:;3-A;;A,GFkJFFNJ!^=8Z23X7sAI9;R'&O:,-9,4=<77 2j;CF8B!5@2 JG_[AL! ,+'667#"&&54663676&#"3pKS69Y2;e?#JBJCD?;~c&@'w%06cCHf50,xcRPLI4$=';@8cc[BL'&" & +#"&&5466332654&##"&5463vp>>pJJp>>pJF\\FF\\F$$$$=D[\EE\[DDlssnnssl$%%%i3#@ JrZ:LP+!##5#53'733T%JEa<L=.@+ JcY:L'+!!5>54&#"'663^_4&4 i)D6.I$7*fB=-Q24RnQHDcoH-1;$+-44?=(B@?%$JccW[O('$!#$,+#"'732654##732654&#"'663S[.I8EQ9jFI4G,GM4 %:J@:*B#,+^9=.K-9Q UM:\5X1# J@~C?7/6 4'&?"=1@.JHUaYM+%##7!53733"dOFGd)<KU3@@=JpacW[O$%%#+!6632#"&'732654&#"#!6!Yj:iE?`)4 E/DMH;,Dn pcAf9*)2!RLJD MO $E@B!Jc[AK[BL$##%&+#"&546632&#"663654#"3Y4:b=xm=sPK>!18OY R4*Cz0MDI0aGJo:r\)80,y[Q5-ul\3"@JGUYM+'!5!MIFA'3(@%+J[AK[BL*,+(+#"&&547&&5466326654&#"&&'3265;_14_;#:;3-A;;A,GFkJFFNJ^=8Z23X7sAI8;Q('O9,-9,4=<77 2j;CF8B="<@9 JGcW[O"!-+'6675#"&&54663674&#"3pKT89Z2;f>&IBHAFA;=d&@%` '-7c>@c5,0q\REFLK  :@7J[AK[BL    $+#"&5463&#4'3ssnnssn< :DqD, rBW+4$=8@5Jc[BL&+#"&&54663&#654'3uq>>pJJp>>pJF\)&F\1!-=FY\EGY[DDlss9 @ns4{0+%6G#I-\#4#$c#-##=#;Y#7#([#j#2l#'# #7#)s#Q#|#J## M#C#$R#k#'d#-#g#_#]#c#]"#G VK PX@c[BLK PX@c[:L@c[BLYY$$$!+$#"&54632&&#"325QIIRQJIQL(&&))&NZ``GH^^H825548lA@JY:L+%'73#J`#9G41UG(@%JcY:L$'+$3!5>54&#"'6632 GB ^I!7(3L*>G0/!=590&0'"$:0G#@! JK PX@cc[BLK PX@cc[:L@cc[BLYY@#""!$$*+#"&'732654&##73254#"'63pF ',)RH+O.,@&&$$.#@>62(@UG0) "#.7'(+),&)4K,@)JHaY:L+!#5#573733#I=saC7780VV;A@JK PX@"hac[BLK PX@#pac[:L@#pac[BLYY@ ""#!+$#"'73254#"#53#632QAX8.(9G>%7##5A5;7(%94 ;?72G"@JK PX@cc[BLK PX@cc[:L@cc[BLYY@"!'$$$ +#"6632#"&5463232654&#g-..4#2FT=LSWP?1#d- &$"&! 02415@^JG^0X %A"@JGUYM+77#5!Μ<1G".a@ % JK PX@c[BLK PX@c[:L@c[BLYY***&+$#"&5467&&546326654&#"&'3265F,+VIJS)'O=@M$/#"!#48*('. '-76,' "%00# ~ G8@5JGcW[O*$$+$67#"&54632'67&&#"3[ .5HX@JRz)*(#*G?)+413=I@[Z8v%'/lr r3+p@JY9L+'73#J`#9G_52Up`@ JK PX@][ALK PX@][9L@][ALYY$'+3!5>54&#"'6632 GB ^I!7(3L*?F,0/!<491&0&#$;0j$@"!JK PX@_[AK[DLK PX@_[9K[DLKPX@_[AK[DL@c_[ALYYY@$#"!$%*+#"&'732654&##73254#"'63pF ',)RH+O.8&&$$.#@>80(@U0) "#.7&*),&*4p1@.JHUaYM+#5#573733#I=saC77p80VV;jk@JK PX@!hc_Y9L@"pc_Y9LY@ "##!+#"'732654#"#53#632QAY7.*7#$>%7"$5A;7'%4 ;@72jp p3+h@JGY9L+7#5!Μ<2jp p3+kp p3+9? ?3+?"? ?3+?? ?3+9? ?3+?? ?3+9? ?3+9? ?3+7? ?3+9? ?3+:? ?3+5; ;3+;|"; ;3+;; ;3+5; ;3+;; ;3+5|; ;3+5; ;3+3|; ;3+5; ;3+6; ;3+`@  G<L+7''7'7'3GnDWQGjTj?P$x40$P?5#)0+wDV)p @W[O$!+632#"&53%&22&%3{22%&33& @W[O $+6&54632#FE55EE5F44EE54E| L@cUYMY@ $+#"&5463#3G''''1a H(&&'H5#I@Fa  9K Y L)( $+#"&54633267#"&&5466766553`''''.) '&B4&KL+!5!!5!&  OOOp '@$[AKY:L $+#"&5463#3I'(&&1a H''''Di )8@5Jp[AK\BL)( $+#"&54633267#"&&5466766553`''''.) '&B3'LJ  ,*D),  J>xD)@@= JccW[O))+3"#526''&5467&&54776#5rGRSFrZ=D2863D>J  -,D,.  J>C$,20 31 PC/"@aUYM+3#3#/HG/"@aUYM+#53#53sGH}tH 0+6.54667.[L+Xo;;oX+M[-{D.GooG.DzgtH 0+&&'7'>5-[M+Xo;;oX+L[.ŗzD.GooG.D{fz; ;3+t; ;3+un: 3+n: 3+% 3+% 3+}j> 3+j> 3+s 0+$.54667$"-/$**$/-"T`C0GY9:ZG0D`Lz 0+$&'7'6653"-/$**$/-"`D0GZ:9YG0C`Kzr r3+tr r3+!=7((3+X]@UYM+!!X]H!7]@UYM+!!!]H#5]@UYM+!!#]I}]@UYM+!!}^]H]@UYM+!!8]H=X((3+!=7((3+}=((3+=((3+#5]R7  0+%'57'57337= = R7  0+7'?'7R33H33 = = 70+%'57j27=70+7'722= mJ$@! JWYM&!+6632#7&&56632#7&&5{. - RB) -,RB) V,+++'m $@! JU[O&!+#"&54773#"&54773, ,QC) .,RB) ++&,+$l< JKPX@ [CL@WYMY&!+632#7&&56632#7&&5z. , QB( -- RB) +*++ @ JU[O!+#"&54773q-,RB) ++$4 JKPX@ [CL@WYMY!+632#7&&5. - RB) +* J~@ JWYM!+6632#7&&5. , RB) V,+RdA--3+RdA--3+dA --3+dA --3+L jg/@,J]Y9L$%+&&#"3267#5&&546753uD5* 5CHHC7)4EK^bc^J+8hffb<, so "I6b'h@  JH GK!PX@_[DL@cW[OY@ '&/,!+6327'#"''7&547'732654EI0Y7W$%X7Z2FG4Y7Y#"X7BB>>AA>!!Y6X6GD;W6X Y6Y8FF5[6|LDDKKDDLEgP '--@*-('& JrsBL+&'#5&&'7&&54667535654&'Z7^"3;IXhd\DAj&2GXZ])S;Do3)5De.7+56 bUKm 5%8G]D)J11)%0g+<~8 J@GJGbc_9L & +!33#"'463654&#"3E[HDB>/?E?" ""'(,WSIa+QU9>@:;CY#%U@R J  aa [  AK[BL%$"! $"$ +&#"3#3#3267#"&'#737#73663K#*7;DT OI+I?Qh]HXP^="T[;\<^WW#{<\;uk-[@JGKPX@[;KY<L@cY<LY@$+&#"3#'>5#5354637 ,/+% w@;'*UUTL >&(KC[M[7<#@8CICR?//@, J9K\:L)"+%##5'75'73773267/m:R8RV@K\ Y[5&K7&L6XLM6W:H@37@4 ca [9K:L$! +73##5#535#5332##532654&##ⰰWKKKK~}FNOWWKR:ww:LBrjjukBFWKDT 0+#3###53267!5!&&##7!=TOONiooKL MFn vB;KZF@;Ja[AKY:L! $ +&&#"3#!!5>55#5354663>8:%57$R P%# RR.V:W+=@j# @HY:L+'%%7!5!"A"aqZDFJw]"-n@ %JK!PX@_  [<L@   cW[OY@###-#,*("!$$$ +#"&'#"&546326633267.#3254#JIL5JG6OKJL5HG5H&/*/!1HHlYYk?C>DlYYk?C>DDED(+$FDG*M, S2@/Jc[>L%%$+&#"#"&'732654636 --( IC$0 %"NFS ?''DF ? $! IIM,M0+#"&'732653MIC$0 %"QJDF ? $!1  S 0+&#"#4636 --( QNFS ?''LIIJ*>0+#4&&#"#4663jg=H*G))G*H=g>>2)G**G)2>h<X!R0+%5%a-=Wj# @HY:L+%5%!5!A#`qߋ~HZJt\@sUYM+%#5!5!MpHX,-@* JGD!@)jN{E5dC@e7+#h#?NEIUbb-(G+7JK PX@)c  c [AK[BLK PX@)c  c [AK[:L@)c  c [AK[BLYY@",,,7,620+*%#) +'#"&&546332654&##"&&546332654&#.'5O#C--C#PC'' %% RO#C--C#PC &' %% L#]D+J,-I+D]<41/73341]C+J--J+C]<22073440E &2=Go@lJc   c [ AK [BL>>33'' >G>FCA3=3<86'2'1-+&%!   $+#"&546332654&#'%#"&5463 #"&54633265432654#II89JJ9$?$$II99II9MII8:IJ9?%%@$?J=>JJ>=J3.&X0(&.9J=>II>=JJ=>II>=J3.&W.)&..&W0'Tt &@#UaYM+3##5#5353RNNZFFm^[? ?3+t UK.PX@aY >Gb@Ja:L+##73_Iyt\0+!#5!Mpe -@* J IY9KY:L+!!!55!*sg>IJJ](?((3+J"@aUYM+%#53#53??@UYM+%#53>. &@#UaYM+%3##5#5353MUUAVVA>YY>ZqYp p3+3Zp p3+[p p3+_4+-5t1?7j9X!R=j#>w]?M, S@X!RDj#Et\Ft^H}KjRLYMGNEOtPtR,=TbUeWe0+'&&'#'e3 T 399!&9'N1X0+7#"''7'&547'})  P   M_(%Az  0+'767!5!&&''7A?#U?I6'J 6'+150+%&5477'76327  Ph  )8%(_M 8ew  0+%667773V 33(TR99!%%'+150+632%71)  hP  } M(%Az  0+!!eU#?D J'611'N1X0+'%#"'|  P  )}8%(M 84^ 0+%'767!!&&''7?t!#w?>wu>45$'55 5Gea 0+'&&'66777'e3 33 39J8!88/8X@  0+###"&5#333'X,kk*# e  0+5!"&55463!5!!7'L-9԰ܠoX  0+334633#7#ԩkk*,ݽ# e  0+!2#!5!5!5L*,,ԿooX@  0+###"&5#X,*X 0+334633ԩ* e  0+!2#!L*,, e  0+5!"&55463!5L9԰ X0+!5!XX  X0+!!XX w X>0+!!XX 2 X0+!!XX  X0+!!XX  Xq0+!!XX e X,0+!!XX   X0+!!XX X0+%!!XX,X0+!5!XX, K0+#3KKK  0+#3  0+#3  ,0+!!,,  w0+!!ww  0+!!>   0+!!  , X0+!!X,  X0+#3XKK  ,0+!!,, , X0+!!X, ,0+%!!,, X0+%!!!,,, X0+%!!!!,,, X0+%!!!XX X0+!!!XX ,X0+%!!X, X0+%!!!!X,,$ X0+!!!X,, <2 X #'+/37;?CGKOSW[_cgkosw{}@z~|zxvtrpnljhfdb`^\ZXVTRPNLJHFDB@><:86420.,*(&$"  <0+#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53#53d22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22 22d22d22d22d22d22RKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK[ Xw{ #'+/37;?CGKOSW[_cgkosw{sA}|yxutqpmliheda`]\YXUTQPMLIHEDA@=<985410-,)(%$!    }|yxb&[0+3#3#3#3#3#3#3#3#3#3#5##5##5##5##5##5#535#535#535#535#535#535#535#535#535#5335335335335335335335335335335353533533533535353353353353535353353353533533533533535353353353353533533533533535335335335335353353353353353533533533533535335335335335353353353353353533533533533535335335335335353533533533535353533533535335335335335353535335335&222222222222222222231313131313111111111111111111121313131313>13132213111311311221131 131322131q131313131q1113131q131313131 131313131q131313131 131313131q131313131 131313131q131313131 132213131c1131311q11111q131322131q11111LJLJLJLJLJLJLJLJLJLKKKKKKKKKKKLJLJLJLJLJLJLJLJLJKJJJJJJJJJJKJJJJJJJJJJKJKJJJJJJJJKJKJJJJJJJJKJKJKJJJJJJKJJJJJJJJJJKJKJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJJJJJJJJJJKJKJJJJJJJJKJKJKJJJJJJKJJJJJJJJJJKJKJKJJJJJJ. X=AEIMQUY]aeimquy}a@^¿~{zwvsronkjgfcb_^[ZWVSRONKJGFCB?><&.0+#3#3#3#3#3#3#3#3#3##5##5##5##5##5##!353353353353353533533533533535335335335335353353353353353533533533533535335335335335353353353353353533533533533535335335335335X222222222222222222222222222222X222222222>222222222>222222222>222222222>222222222>222222222>222222222>222222222>222222222KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK:0+#"&&54663u|II|IJ|HH|JI|IJ|HH|JI|I:0+#"&&54663326654&&#u|II|IJ|HH|J4W33W43X34W3I|IJ|HH|JI|IP3X34W33W43W4:0+#"&&54663326654&&#u|II|IJ|HH|J4W33W43X34W3I|IJ|HH|JI|IP3X34W33W43W4:0+#"&&5466326654&&#u|II|IJ|HH|J3X34W3I|IJ|HH|JI|I43W43W4:0+#"&&546633u|IEwG J|HH|J4W33W4I|IHzIH|JI|IP3X34W3|:0+#"&&54663!4&&#u|II|IJ|HH|J4W3|4W3I|IJ|HH|JI|IP3X33W4:0+#"&&5466332665u|II|IJ|HH|J3W43X3I|IJ|HH|JI|I4W33W4,0+!"&&54663,J|HH|JH|JI|I,:0+#u|II|II|IJ|H:0+#"&&5466335u|II|IJ|HH|J4W3I|IJ|HH|JI|IP3X3: 0+#"&&5466335#326654&'u|II|IJ|HH|J_O PhD3X3V@I|IJ|HH|JI|I`P6AU3W4Ch: 0+#"&&5466336654&&#5u|II|IJ|HH|JCiAU4W3 O7I|IJ|HH|JI|IPUAiC3W47P : 0+#"&&5466353&&#667u|II|IJ|HH|J4W3UAhC(6O I|IJ|HH|JI|IP3X3Dh@V P7: 0+#"&&546633267#53&&'u|II|IJ|HH|JiU3W4ChP P6I|IJ|HH|JI|IchC4W3UA6O :+ $ 0+#"&&54663326654&&##"&5463u|II|IJ|HH|J4W33W43X34W3-AA-.@@.I|IJ|HH|JI|IP3X34W33W43W4PA-.@@.-A:+7 @ 0,$ 0+#"&&54663326654&&##"&546332654&#u|II|IJ|HH|J4W33W43X34W3-AA-.@@.   I|IJ|HH|JI|IP3X34W33W43W4PA-.@@.-AP  :#  0+!!!326654&&##"&&54663:j>>j>>j>>j>(E))E()E((E)(>j>>j>>j>>j>P)E()E((E)(E):  0+#4&&#"#!!4663:(>j>>j>(E)(E)>j>>j>x)E((E): 0+!!3326653#"&&5!:(>j>>j>(x)E()E(,>j>>j>)E((E):0+#4&&#"#4663u|IP4W34W3PH|JI|I3W43X3I|I:0+$#"&&53326653:I|IJ|HP3W43X3P|HH|J4W33W4, 0+"#4663,4W3PH|J3X3I|I,: 0+#4&u|IP4W3I|I3W4P, 0+63"&&53n3W4J|HPW3PH|J,: 0+$#526653:I|I3X3P|HP3W4:0+ ::0+ 7':򝝜C @ J9K:L+#33cc3YXYA0+!!!:0+!!!::0+!!!!5:4|Pnn0+!!!3}P| ]0+!#!]PY6z6]0+!!3T PP T0+#!5!TPY *P6T0+!5!3T P6Pb ]  0+!!#!5!3T P PP*Pb ]0+!#!5!]Pb6*P6]0+!!5!3T  PPPb ]0+!!#3T PPP T0+#!5!3TP P *Pb6]0+!5!]b6P T0+#3TPP  T  0+#!5!5!5!3TP  P PPP   0+##533#3PPPP *Pb$  0+####5!PPP **P T 0+#!5!5!5!TP Y PPP    0+!533#3##5!PPPP P$$P 0+#3#3PPPP $   0+#!5!##5!PP zP6P  0+%!5!3!533WYPPPP6 0+!53333WPPP6PbbT 0+%!5!5!5!3T  PPPP ]  0+!!!!#3T  PPPPP& ]  0+#33##3PPPP P]  0+!!33!3YWPP6PPb ]  0+!#!##!]PP &*]   0+!5333!3!5!PPbPPbP ]   0+!5!##5!##!]bP YP P6PP&* ]   0+#33!3##!PPPP Pb&*]0+!5!!5!]bbPP ]  @  0+!5333!3##5!##!PPP YP PPb$PP&*]  0+!!5!3!5!T  P bPPP6] 0+3!53333PPPPPbb ]  0+!5!!#!5!]bPbP&P ]  0+#####5!]PPPb6**P6] 0+3!333WPPPPb] 0+!!!!3T  PPPP ] 0+!!!#!] PYPP& ] 0+####!]PPP6*z ]0+3#####53333PPPPPPP**Pbb ]0+!!!!#!5!5!5!3T  P  PPPP&PPP:0+!!!::0+!!!!:4|P|:0+#"&&54663326654&&#o((owxn((nxTNNTTNNT(owxn((nxwo(PNTTNNTTN: 0+!!!!#53:4|PP|0+!!!0+!!!35}Pnn:0+!!!3:P|:0+!!!3:4P|:0+!!!!:4|4|:0+!!!:4|P|: 0+!!!333:4PP||: 0+!!!#!!35:|P|: 0+!!!3335:4P|斖: 0+!!!353535:4斖P斖: 0+!!!!5#5335:4|PP斖:0+!!::0+!!,:0+::0+!::0+!!::0+!!::0+!!::0+3! ]0+###5!]b6*P 0+##5! *P  0+3##5!3TP P*Pb ]0+##!]6z ] 0+!##33T PPPzb ]  0+!###5!3T  PP*Pb ]0+%!#!5!]Pb& T0+#!5!TPY   0+%##!533PP& ]0+%!#!]PY& ] 0+3!##3PP& ]  0+3!#!533P&]0+7#53#53 0+#3#3$ 0+#3 z ]0+%###5!]b& 0+##5!  ]0+%##!]&]0+%!5!]bT0+%!5!TY]0+!!!5!T YPP]0+%!5!]Y60+#36]0+3!5330+%!533W 0+###3PPP6*]0+3!3W 0+#3  ]  0+3###533& 0+##533  ]0+3##3& ]  0+33###5!3TP PPP ] 0+!!#!5!T PYP] 0+!!!5!3T  PPP ]  0+!!#!5!3T P PP ] 0+%###535!]&*PP] 0+3!5#533WPPb ]  0+3###533&*Pb ]  0+3###!533PPPP&8j R  0+''7'77 9999998j R0+%7 9Q9998j R0+' Q9Q96]0+!5!!5! Y 6PPP T0+#3#3TPPPPb$*6T0+#3TPP66T0+!5!TY6P]0+%!5!5!5!] YPPP6]0+!5!]Y6P T0+#3TPP z 0+3#33TPPPzb ]  0+!###53533T PP&*PP ] 0+%!#!5!5!]P Y&*PP] 0+!!5!5!3T  PPPb ]  0+!!#!5!3T P P&*Pb ] 0+3###5!P] 0+3#!533WPP ]  0+3###533P ]  0+3!##5#533PP&PPb ]  0+3!#!533PP*Pb6]0+3!533PPb  0+##!533PP6*Pb60+!533W6Pb ] 0+3!##3PPP*6]0+3!3WP ]  0+!###5!3T  P&]0+!!5!3T  P  0+3##5!3TP P6T0+%!5!3T P ] 0+!##33T PP&]0+!!3T P ]  0+3###533P*Pb 0+##533 *Pb ]0+3##3P ]  0+!!#!5!3T P P& T0+#!5!3TP P  ]0+!!#3T PP&CX'<HT^s@sbXUMIA=2(0+#''5&54663'#"&54677&5463 #"&'576633265432654&#75&&##"&547'"&5463276632##"&''7ZJ*++*J.3      3Q#"mF   HU  E(F+9-4KK4,:+F( 7        3 R+"    ''    "+*:+7C@ =90,$ 0+#"&&54663326654&&##"&54632#"&5463#"&'33273u|II|IJ|HH|J;`88`;:a88a:=:N::N,MM,H|IJ|HH|JI|H4;d:;d;;d;:d;`MM9YY:'5 @ 1( 0+#"&&546633265432654&##"&'#3267u|II|IJ|HH|Jl 5))5 +R::RH|IJ|HH|JI|H)00)8MM8:'3,(&0+73#'#5&''7&'#5367'7675332654&#D$ Q$QooO$O &0(O$OooP$P!$0H==00>>0R$P)/(N$PnnP$N"#/( P$Rk@00??00@$ 0+3##5#535&&5466332654&#WJ+M;``0``;N+I,0==00>>0*H,>0c-9,G((G,,H**@00??00@F 0+#"&'3!737#"&54667V55-&7(4(7&-55V[WV+09 "AA" 90+VWTF*0+6632#"&'3!737#"&54632&&5463d@/2;-$4(4(4#-;2/@80/$- =8.; "AA" ;.8= -$/02&0+.54632663??bYYb??51==1H47~ij}84H2442h0+ m0+'4&''#"&546323-$" 9#18I4 06'(& ;9*$"&4 A0+%#"&54632#"&54632% 9#18I4  9#18I4 K*$"&4 7;*$"&4 dVQ'@ri+#3QKKfQ'"@aUYM+#3#3QKKKKcsc J)6N@K&%43 J cc_[AL***6*50.)(&%$%% +#"&&7##"&54632326654&&#"'66332667&#@;'6(aNHe_%@(%!^Y9p:1AFy4$+ ) #庆Q0Cb{jJ9Isgr[(4985^fOW!6>#/H@E.Jc[AK[BL%$,*$/%/#"#%% +&&#"3!##"&&54675&&54663"32675HO'27$46!9"ZdMJe0QK?9.V91B HG*B +.8.5C%7:^6McN3,L-.G&ci4+_*3$!!NN8ANKPX@_[;L@cW[OY@&+#"&&5466332654&#K--K*+J..K*%44%%44%%G10G%%H00G%<1//32//2G 0+!32673#"&'5663!5&&#P9aDc1 w\{a6+N-h/3.>I{gh'*W#0+$7#"&55'75463236654&#/2KFTB!iTL=GenW5"E:;=MDg4+ShnH9WSGuJPAoD" G &*@  JKPX@# c Y 9K Y:L@' c9K [ AK Y:LY@*)('&%! # +#"5463###33&5332654&##53 =?Eln ?nl ?z{rkhuCLDy|[.NUVNMWUN}FJH@Jri+#'#3[ZJ9Z EK*PX@9KYDKQW^djqx'@$xsqojedc\XURNLGEDA>:851,*&$"0+#"&&54663567&'&#6767&'367&'3&'35&'3&'67&'36753673675767&'&'667&'ffggffgG%?8(@8$GY"!1";4Fh]5(5;??C::,1%?C(>F%1+;]>#I ( H#>:#"Z3;">=3Y" 2gggggggg(LXWM6&Q@DMOcFDEEdQtAii@?cSBHC<jj>AFDRdUOPT.'6 >RNB 6'(0  0+!%!5'0|44ԫX@  @  0+#!#333'##"&55!35X,kkר*&u*QQ0+#!"&5463!'77'7'lCCCC!ECCEt  0+#!!'77'7'RTCCCCDAECCE  0+!"&5463!'77'7'RCCCC`!ECCE &y/?Vfv@}wmg]WM@60&  0+#!"&5463!"3326554"3326554"3326554"323326554&#"3326554"3326554"3326554&#"3!26554&#Xv J b J b J k  6  J b J b J   yK J J J J J J J   J J J J J J J J v0+#!34633#55![*gi3*+,oojfAh[m[n[{@ri+#30^sAcN0+'7U-p>Jj %@" HW[O %+&'73267#S: -"#0 :V<JA6 $$ 6A1Ew@Hi+#'779&}~ll*PPcF 3+1Vw!GKPX;LiY+''73%~}&9+QQ+k#A A3+ %@"JWYM +#7&5463 32G"ku/ $@!JU[O +&54773#23 /!kv 1zwD0+'7'7lJWmaKNz'$5rY 6KPX@ [AL@W[OY@ $+#"&5463""!!!""!]H0+'7'-UH1Dw ʰ3+Nn[ ʰ3+e5C ʰ3+Y@UYM+5!CC(Y@UYM+5!(WCC1z %@"JWYM +#7&5463 32G"ku"H5KPX@ Y;L@UYMY@ +5! @@@s9L+3#(^sA0+'7U-pv  @ H[9L %+&&'73267#@%:A#. 9&A+v)>! U-( !>)u@Hi+#'77φ9(||ww)ZZ0@-JcW[O$&+I#"&'7325T*,:./G37%L@W[OY#%+!327#"&5467H4"*8B[`6D7+5Pk8 /@,cW[O   $+#"&546332654&#\??00??08<++;;++?/0@@0;,+;;+,;. {4@1 JWc[O$$$+&'&&#"'6323267#S  5)B 5&D  T RA0+'7)hؗ0+'7)ig0ys@  JKPX@[CK[ALKPX@_[CL@Wc[OYY@ $%$+&'&&#"'66323267#  55" 67! '. ))mD< g@  JKPX@f\>LKPX@r\>L@rW\PYY@ "+&5533267#:K*!-47//-* -"Tk(0+'7kA<$ p0+'7pGA% sS@ JHK!PX@['L@W[OY@(+'7#"&54632#"&5463;;M   Nf% %@"JWYM +#7&5463F 32%#kuNf%Xl l3+Nf% %@"JWYM +#'&5463@23 %uk#> +@(JGWYM +#7&5463'7 329DM#ku> +@(JGWYM +#'&5463'7 329DM uk!> +@(JGWYM +#7&5463'7 32D90#ku>+@(JGWYM +#'&&5463'7 32  D90 vk A[%;@8 Jcc['L%$$$$+&'&&#"'6323267#&54632#U  6)B  5&DR  3 T Rk !#B\(D@A 'Jcc['L((#!%%$+&'&&#"'66323267#'&&54632R  56"67!C   &.  ))#  >svS@ JHK!PX@['L@W[OY@(+'7#"&54632#"&5463O#5 !}2 svS@ JHK!PX@['L@W[OY@(+'7#"&54632#"&5463#t   D}! ky%1~@  JKPX@ cc  ['L@&ccW[  OY@&&&1&0,*%$ $%$ +&'&&#"'66323267#&54632#2&54632#V  55" 67!   '. ))0+'7'-UH0+'7U-ps@  JKPX@['K[-LKPX@_['L@Wc[OYY@ $%$+&'&&#"'66323267#S  55" 67! '. ))q0+'7qAGc p0+'7pGA% \ =JKPX@ [;L@WYMY@ +#7&5463< *2  "Yy = HK*PX@ [L@W[OY@ %+&'73267#R9 +$$. :U<;5  5; %@" HW[O %+&'73267#R: +$$. 9T<;5  5;*nAUr{A ytkaMB;C0+7>323632#"'#"'&'&#"#"&'.'7''667'667'67'674'667'67'$7.'#'3267735&&'7667&&'73657''4675'.5667667&&'&'&'#&'.54736654&''76654&#"#&5467665&&''&'"&5477&#"#.''766323&#"'67'&&#"632#&&5'675'76635'7&&#"'%#"&'7|O;[o8 Z-/$&1(7:8 ;%:$# #4@'?$4"", K7uDyI" 5,,!   $ 1 8/V@85 %,:  ) %'  L.z b  63R  52K5$O  32T@,CL,&  M:e  !F?B4X8 [3  O0. ##/-e $5( >.>-$/   % ";5" %-0, &7)  ) 4<3<@=:$B 9^%ZE(3/  &/4 (  6  $&11 $ ' /##-7  <>o $    65$  "4?W!  O" 1{ A 0+66326632#&#"3&5463237'73&5432&&'&&''''#7.5473&&'&&''667"&'&&'"&'7''>7'#"&5476654'&'&&547776737&5467''673&546?&&#"6327'5677.'"%3277'>8X4B?F!  e:M  &,LC,@T23  O$5K25   R36  b z.L  '% )  :,% 58@V;. 1 $   !q  3[I-/## .0$ (N?4"  $56    $ o><  7-##/  ' $ 11&$  6  ( 4/&  /3(E   !WY1 " 5 gI',5@H@GA>72-,+&!  0+&'2#"'65572'64677'7&&5632&572'  # 3#  A+  ) 9D  *      &knAUr{?A}vskaMB;C0+7>323632#"'#"'&'&#"#"&'.'7''667'667'67'674'667'67'$7.'#'3267735&&'7667&&'73657''4675'.5667667&&'&'&'#&'.54736654&''76654&#"#&5467665&&''&'"&5477&#"#.''766323&#"'67'&&#"632#&&5'675'76635'7&&#"'$#&5467'327#"&'77&&#7&&#667&'667'667&&#665&&#"654&##O;[o8 Z-/$&1(7:8 ;%:$# #4@'?$4"", K7uDyI" 5,,!   $ 1 8/V@85 %,:  ) %'  L.z b  63R  52K5$O  32T@,CL,&  M:e  !F?B4X8 [3  O0. ##/.' '  $5(+$  &(  ) / >.>-$/   % ";5" %-0, &7)  (4<3<@=:$B 9^%ZE(3/  &/4 (  6  $&11 $ ' /##-7  <>o $    65$  "4?W!  O" 1m   / /%  !&4H9_<a> XXXZXXXXXXXXXXXXXXX`X?X?X?X?X?X?XSXXSXX~X~X~X~X~X~X~X~X~X~XX-X-X-X-X-XSXXSXgX XgXgXgXgXgXgXgXgXgXIXIXiXiXX@XXXX(XXSXSXSXSXSXSX/X/X/X/X/X/X/X/X/X/X/XXtXkX-XfXfXfXfX+X+X+X+X+X+X(X(X(X(X(XHXHXHXHXHXHXHXHXHXHXHXXXXXXXXXXXXXHXHXHXHX?XSX/X+XHXEXEXEXEXEXEXEXEXEXEXXXdX[X[X[X[X[X[XDXCX'XFXOXOXOXOXOXOXOXOXOXOX^XBXBXBXBXBXdXXdXyXyXyXyXyXyXyXyX0XyXyXyX`X`X`XjXjXdX<X<X<X<X<X<X3XdXdXXdXdXdXdXIXIXIXIXIXIXIXIXIXIXIXXdXdXIXiXiXiXiXJXJXJXJXJXJX`XOXOXOXOXOXdXdXdXdXdXdXdXdXdXdXdX<XXXXXX8X>X>X>X>X>XfXfXfXfX[XdXIXJXfXXXfX`XX5XXXXXeX`XXXXX~X~X~X X#XSXSXSXSX_X_XXXSX/XSXtX?X(XXX XX3XSX.X.XSX^XXXX)X+X:X*XgXgXIXX/X!XXX X(XX%X&XXRX X/XXX XBXUX X#X_X_X%X XSXXSX#X?X(XXXX3X3XSXSXXXgX XSXXSXHX3XXXX~X-X-X X#XFXSXSX/X/X/X*XXXX3XXX?XXX1X XXIXX1X-XX;XXX7XX-XXYXXXXtXEXKXpXXXXXOXOXOXX6XdXdXdX]XjXjX*X*XdXIXdXdX[XAX>X>XX8XUX^X1X1XdX~X!X>XXHXJX[X[XyXyX`XX8X5XXX:XLX$X4XX#XPX)XIX9X9XX^XfXX6XjXjX'XXVX^XX9X[XAX:X:X8XKXUXeX`X X X<XXkX*XdXfXUX4XEXEXOXIXIXX6X>XdXdXIXIXIX[X>X>X>XUXX>X]X.X8XDX X XLXX-XIXXSX*XX,XXIXXgXXXFXgX3XX$XXDXYXXXXXIXX`XXX~XHXSX/XgXiXXXSXMX/XSXtXIX(XX XX X/XXXXXXXXgXXrX$XSX/XJX~XGXXdX&XX&X/XkX?XX X?X XXXXXXXXX XXXXXXXXXXXXXXXEXGXTX[XXXXXXX*X1XXXXXSXXXXX*X1XXXXX^XZX>XDXXXXXgXgXXXXX6X=XXXXXXXXXXXXXXXX5X<X XXXX/XXXXX5X<X XX;X[X+XDXSXoXIXIX?XWX0XXX XcXIXX^XfXIX'XgX"X6X)XX?X?X?XgXgXgXIXX;XSXIXmXX_X[X[X[XIXEX~XWXX>XOXMX-X X>X^X[X`X[XdXX-X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;X;XSXSXSXSXSXSXSXSXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIXIX?X?X?X?X?X?X?X?X?X?X?X?X?X?X?X?XIXIXIXIXIXIXIXIX^X^XgXgXgXgXgXgXgXgXgXgXgXgXgXgXgXgXXXXXXXXXXXXXXXXXXXXXXXXXXKX_X;X4XHXMXOXaXAXBX4XiXLX?X?XUXOX\XAXBXKX4XXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`X5XXXXX XXX5XXZXiXXXX5X^X2X2XXiX5XXXuXXXX}XXXXuXXXX}XXXXXX!XX!X#X}XXX!X}XX#XRXRXXXmXmXlXXXXRXRXXXXXXXXXXXjX"XEX~X#XkX?X@XTXJX'XjX"XEX~X#X?X@XTXJX'X_X+XXtXXjXXXjXXXjXXMXMXXJXXXjXtXXXtXXX}XjXYXXXtXXtXXXXtXeXXXXXXXX_X+XtXjXXXjXXMXXXjXtXtX}XjXYXXXtXtXXXeXX'XX'XX'XX'XXXXXXXXXXXXXXXXXXXXXXXXXXXX,X XX,XXXXXX,XXX2XXXXXXXXXXX,XXXXXXXXXXXXXX,XX,XXXCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8X8X8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXCXXXXXX<XFXFX2XhXmXXXX X6X+XX.XjX XAXXWXXJXZXZXX(XXXtXXXXjXAX[X[X[XXc>1c115]1Ne(1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0mXXXXXXXXXXXXXXXXXXXXXXX*XRRRR.:F".:HT\Rz bn "".:FXXd T ` l  L  $ 0 * 6 B b & 2 > J V b n z @LXdp$0<HT`lx(r~(:L.Zfr~>T`lZf\FRBfr ,8J8D8D2>J&r P \ h t !j!v!""V""#.#:#~#######$4$@$L$l$$$$$$%*%6%B%N%Z%%%%%%%%%&R&'l'(*(()4)x)))))**d****+L+x+++,,*,Z,b,j,,,,,- --f-n---.,.X..//T///0:0B0N0V001<112223(3f344"4*4n445*556P6677:7p778d8889$9\99:4:r:;f;n;z;<>P>\>h>t>>>>>??l??@8@@AAZAbAAB_`h`p`x`aarab8bbc:c~ccddDdLdTd\dddddde"eheeeeeef0f8flfffgg"gXgggggggghhh2h`hhhhi,iRiijj*j6j>jFjjjk.kkkkkkklll&l2l>lJlVllllllllmmm*mnTnjnnnnnnooo$o6oHoZolo~ooooooooppp&p2pDpVphpzppppppppqqq(q:qLqXqdqpqqqqqqr r rrssvst,tpttu*u`uhuuv.vpvwwJwwxxFxxxxxxyyyy(y4y@yLytyyzzrz{{V{{{|F|}}T}}~N~~~~~4@LXdp| ,N\vĝޝ@`v̟ Z$,,,,,,,,,rDbpȣУأ|ȥBRbĦ@Ħ.Ļ(x,̩JZ6^n~ƫΫ֫ޫ&.6>`̬6Z:dخ 2DVhzί&8J\p°ܰ𲄵P@Vnҹ Fƺ\к ":Rfλ&:Nj¼Լ4LpĽܽ,Jhھ$B\zĿ޿@Pj0Hh .>N^l‚–® 6Jb|Òêú.>NdvČĠİ,D`xŐŪ $N^z֖֠*Xt׮DVpؘtن٘6pږڨ,pۂ۔FXj(^ݔ\޾j~Djt[\$4w VP PY` { 4* & % ; :Q  E W 6e     h TO L , , tG 4Digitized data copyright 2012-2015, The Mozilla Foundation and Telefonica S.A.Fira MonoRegular3.206;CTDB;FiraMono-RegularFira MonoVersion 3.206FiraMono-RegularFira Mono is a trademark of The Mozilla Corporation.Carrois Corporate GbR & Edenspiekermann AGCarrois Corporate & Edenspiekermann AGhttp://www.carrois.comhttp://www.carrois.comLicensed under the Open Font License, version 1.1 or laterhttp://scripts.sil.org/OFLDigitized data copyright 2012-2015, The Mozilla Foundation and Telefonica S.A.Fira MonoRegular3.206;CTDB;FiraMono-RegularFira MonoVersion 3.206FiraMono-RegularFira Mono is a trademark of The Mozilla Corporation.Carrois Corporate GbR & Edenspiekermann AGCarrois Corporate & Edenspiekermann AGhttp://www.carrois.comhttp://www.carrois.comLicensed under the Open Font License, version 1.1 or laterhttp://scripts.sil.org/OFL2$bc%&d'  (e   )*+,-./01 !"#f2$g%&'345()*6+,-7./0182h345679:89:;;<<==>?@ABCDDiEkljFGnmHEFoIJGKHpLMrsNqOPIJQRSKTULtVvwWuXYZ[M\]N^_O`abcPQdefghxRyi{|zjkl}STUmnoVpqrWstuvX~wxyz{|YZ}~[\]      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456 ?" 7B89:;<=^`>@ >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnaop qrs!tuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~_# ACnullAbreveAmacronAogonekAEacute Ccircumflex CdotaccentDcaronDcroatEbreveEcaron EdotaccentEmacronEogonek Gcircumflex Gcommaaccent GdotaccentHbar HcircumflexIJIbreveImacronIogonekItilde Jcircumflex KcommaaccentLacuteLcaron LcommaaccentLdotNacuteNcaron NcommaaccentEngObreve OhungarumlautOmacron OslashacuteRacuteRcaron RcommaaccentSacute Scircumflex ScommaaccentTbarTcaronuni0162uni021AUbreve UhungarumlautUmacronUogonekUringUtildeWacute Wcircumflex WdieresisWgrave YcircumflexYgraveZacute ZdotaccentCacute.loclPLKNacute.loclPLKOacute.loclPLKSacute.loclPLKZacute.loclPLKabreveamacronaogonekaeacute ccircumflex cdotaccentdcaronebreveecaron edotaccentemacroneogonek gcircumflex gcommaaccent gdotaccenthbar hcircumflexibreve i.loclTRKijimacroniogonekitildeuni0237 jcircumflex kcommaaccent kgreenlandiclacutelcaron lcommaaccentldotnacute napostrophencaron ncommaaccentengobreve ohungarumlautomacron oslashacuteracutercaron rcommaaccentsacute scircumflex scommaaccenttbartcaronuni0163uni021Bubreve uhungarumlautumacronuogonekuringutildewacute wcircumflex wdieresiswgrave ycircumflexygravezacute zdotaccentcacute.loclPLKnacute.loclPLKoacute.loclPLKsacute.loclPLKzacute.loclPLKuni207Funi052Cuni052Auni052Euni0528uni0410uni0411uni0412uni0413uni0403uni0490uni0414uni0415uni0400uni0401uni0416uni0417uni0418uni0419uni040Duni048Auni041Auni040Cuni041Buni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0423uni040Euni0424uni0425uni0427uni0426uni0428uni0429uni040Funi042Cuni042Auni042Buni0409uni040Auni0405uni0404uni042Duni0406uni0407uni0408uni040Buni042Euni042Funi0402uni0460uni0462uni0464uni0466uni0468uni046Auni046Cuni046Euni0470uni0472uni0474uni0476uni0478uni0492uni0494uni0496uni0498uni049Auni049Cuni049Euni04A0uni04A2uni04A6uni0524uni04A8uni04AAuni04ACuni04AEuni04B0uni04B2uni04B6uni04B8uni04BAuni0526uni04BCuni04BEuni04C0uni04C1uni04C3uni04C5uni04C7uni04C9uni04CBuni04CDuni04D0uni04D2uni04D6uni04D8uni04DAuni04DCuni04DEuni04E0uni04E2uni04E4uni04E6uni04E8uni04EAuni04ECuni04EEuni04F0uni04F2uni04F4uni04F6uni04F8uni04FAuni04FCuni04FEuni0500uni0502uni0504uni0506uni0508uni050Auni050Cuni050Euni0510uni0512uni0514uni0516uni0518uni051Auni051Cuni051Euni0520uni0522uni048Cuni048Euni0430uni0431uni0432uni0433uni0453uni0491uni0434uni0435uni0450uni0451uni0436uni0437uni0438uni0439uni045Duni048Buni043Auni045Cuni043Buni043Cuni043Duni043Euni043Funi0440uni0441uni0442uni0443uni045Euni0444uni0445uni0447uni0446uni0448uni0449uni045Funi044Cuni044Auni044Buni0459uni045Auni0455uni0454uni044Duni0456uni0457uni0458uni045Buni044Euni044Funi0452uni0461uni0463uni0465uni0467uni0469uni046Buni046Duni046Funi0471uni0473uni0475uni0477uni0479uni0493uni0495uni0497uni0499uni049Buni049Duni049Funi04A1uni04A3uni0525uni04A7uni04A9uni04ABuni04ADuni04AFuni04B1uni04B3uni04B7uni04B9uni04BBuni0527uni04BDuni04BFuni04CFuni04C2uni04C4uni04C6uni04C8uni04CAuni04CCuni04CEuni04D1uni04D3uni04D7uni04D9uni04DBuni04DDuni04DFuni04E1uni04E3uni04E5uni04E7uni04E9uni04EBuni04EDuni04EFuni04F1uni04F3uni04F5uni04F7uni04F9uni04FBuni04FDuni04FFuni0501uni0503uni0505uni0507uni0509uni050Buni050Duni050Funi0511uni0513uni0515uni0517uni0519uni051Buni051Duni051Funi0521uni0523uni048Duni048Funi052Duni052Buni052Funi0529uni04A4uni04A5uni04B4uni04B5uni04D4uni04D5uni037FAlphaBetaGammauni0394EpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiuni03A9 Alphatonos EpsilontonosEtatonos Iotatonos Omicrontonos Upsilontonos Omegatonos IotadieresisUpsilondieresisuni0370uni0372uni0376uni03D8uni03DAuni03DCuni03DEuni03E0uni03CFuni03D2uni03D3uni03D4uni03F4uni03F7uni03F9uni03FAuni03FDuni03FEuni03FFuni1F08uni1F09uni1F0Auni1F0Buni1F0Cuni1F0Duni1F0Euni1F0Funi1FBAuni1FBBuni1FB8uni1FB9uni1FBCuni1F88uni1F89uni1F8Auni1F8Buni1F8Cuni1F8Duni1F8Euni1F8Funi1F18uni1F19uni1F1Auni1F1Buni1F1Cuni1F1Duni1FC8uni1FC9uni1F28uni1F29uni1F2Auni1F2Buni1F2Cuni1F2Duni1F2Euni1F2Funi1FCAuni1FCBuni1FCCuni1F98uni1F99uni1F9Auni1F9Buni1F9Cuni1F9Duni1F9Euni1F9Funi1F38uni1F39uni1F3Auni1F3Buni1F3Cuni1F3Duni1F3Euni1F3Funi1FDAuni1FDBuni1FD8uni1FD9uni1F48uni1F49uni1F4Auni1F4Buni1F4Cuni1F4Duni1FF8uni1FF9uni1FECuni1F59uni1F5Buni1F5Duni1F5Funi1FEAuni1FEBuni1FE8uni1FE9uni1F68uni1F69uni1F6Auni1F6Buni1F6Cuni1F6Duni1F6Euni1F6Funi1FFAuni1FFBuni1FFCuni1FA8uni1FA9uni1FAAuni1FABuni1FACuni1FADuni1FAEuni1FAFalphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronrhouni03C2sigmatauupsilonphichipsiomega iotatonos iotadieresisiotadieresistonos upsilontonosupsilondieresisupsilondieresistonos omicrontonos omegatonos alphatonos epsilontonosetatonosuni0371uni0373uni0377uni037Buni037Cuni037Duni03D9uni03DBuni03DDuni03DFuni03E1uni03D7uni03D0uni03D1uni03D5uni03D6uni03F0uni03F1uni03F2uni03F3uni03F5uni03F8uni03FBuni03FCuni1F00uni1F01uni1F02uni1F03uni1F04uni1F05uni1F06uni1F07uni1F70uni1F71uni1FB6uni1FB0uni1FB1uni1FB3uni1FB2uni1FB4uni1F80uni1F81uni1F82uni1F83uni1F84uni1F85uni1F86uni1F87uni1FB7uni1F10uni1F11uni1F12uni1F13uni1F14uni1F15uni1F72uni1F73uni1F20uni1F21uni1F22uni1F23uni1F24uni1F25uni1F26uni1F27uni1F74uni1F75uni1FC6uni1FC3uni1FC2uni1FC4uni1F90uni1F91uni1F92uni1F93uni1F94uni1F95uni1F96uni1F97uni1FC7uni1F30uni1F31uni1F32uni1F33uni1F34uni1F35uni1F36uni1F37uni1F76uni1F77uni1FD6uni1FD0uni1FD1uni1FD2uni1FD3uni1FD7uni1F40uni1F41uni1F42uni1F43uni1F44uni1F45uni1F78uni1F79uni1FE4uni1FE5uni1F50uni1F51uni1F52uni1F53uni1F54uni1F55uni1F56uni1F57uni1F7Auni1F7Buni1FE6uni1FE0uni1FE1uni1FE2uni1FE3uni1FE7uni1F60uni1F61uni1F62uni1F63uni1F64uni1F65uni1F66uni1F67uni1F7Cuni1F7Duni1FF6uni1FF3uni1FF2uni1FF4uni1FA0uni1FA1uni1FA2uni1FA3uni1FA4uni1FA5uni1FA6uni1FA7uni1FF7uni1FBEuni037A zero.tosfone.tosftwo.tosf three.tosf four.tosf five.tosfsix.tosf seven.tosf eight.tosf nine.tosf zero.zerozero.tosf.zerouni215Funi2153uni2154uni2155uni2156uni2157uni2158uni2159uni215A oneeighth threeeighths fiveeighths seveneighths zero.dnomone.dnomtwo.dnom three.dnom four.dnom five.dnomsix.dnom seven.dnom eight.dnom nine.dnom zero.numrone.numrtwo.numr three.numr four.numr five.numrsix.numr seven.numr eight.numr nine.numruni2080uni2081uni2082uni2083uni2084uni2085uni2086uni2087uni2088uni2089uni2070uni00B9uni00B2uni00B3uni2074uni2075uni2076uni2077uni2078uni2079uni204A underscoredblexclamdown.casequestiondown.casenumbersign.tosfuni208Duni208Euni207Duni207Ebraceleft.casebraceright.casebracketleft.casebracketright.caseparenleft.caseparenright.caseparenleft.dnomparenright.dnomparenleft.numrparenright.numruni2015 figuredashuni00AD emdash.case endash.case hyphen.case uni00AD.casefiguredash.tosfguillemotleft.caseguillemotright.caseguilsinglleft.caseguilsinglright.case anoteleiauni037Euni2007uni2008uni00A0uni200B space.frac uni2007.tf uni2007.tosfuniFEFFuni20AFEurouni20BAuni20BDuni20B9 cent.tosf currency.tosf dollar.tosf uni20AF.tosf Euro.tosf uni20BA.tosf uni20BD.tosf uni20B9.tosf sterling.tosfyen.tosfuni2219uni2215uni208Cuni207C equivalence integralbt integraltp intersectionuni00B5uni208Buni207Buni208Auni207A revlogicalnot infinity.case equal.dnom minus.dnom plus.dnom equal.numr minus.numr plus.numrapproxequal.tosfasciitilde.tosf divide.tosf equal.tosf greater.tosfgreaterequal.tosf infinity.tosf integral.tosf less.tosflessequal.tosflogicalnot.tosf minus.tosf multiply.tosf notequal.tosfpartialdiff.tosf percent.tosfperthousand.tosf plus.tosfplusminus.tosf product.tosf radical.tosfsummation.tosfarrowupuni2197 arrowrightuni2198 arrowdownuni2199 arrowleftuni2196 arrowboth arrowupdnuni21E7uni21E8uni21E9uni21E6uni2B06uni2B07uni2B05uni27A1uni2581uni2582uni2583dnblockuni2585uni2586uni2587blockupblockuni2594uni258Funi258Euni258Dlfblockuni258Buni258Auni2589rtblockuni2595uni2596uni2597uni2598uni2599uni259Auni259Buni259Cuni259Duni259Euni259Fltshadeshadedkshadeuni25CFcircleuni25EFuni25D0uni25D1uni25D2uni25D3uni25D6uni25D7uni25D5uni25F4uni25F5uni25F6uni25F7uni25C9uni25CE invcircleuni25DAuni25DBuni25E0uni25E1uni25DCuni25DDuni25DEuni25DFuni25C6uni25C7uni25AE filledrectuni25ADuni25AFuni250Cuni2514uni2510uni2518uni253Cuni252Cuni2534uni251Cuni2524uni2500uni2502uni2561uni2562uni2556uni2555uni2563uni2551uni2557uni255Duni255Cuni255Buni255Euni255Funi255Auni2554uni2569uni2566uni2560uni2550uni256Cuni2567uni2568uni2564uni2565uni2559uni2558uni2552uni2553uni256Buni256A filledboxuni25A1uni25A2uni25A3uni25AAuni25ABuni25E7uni25E8uni25E9uni25EAuni25EBuni25F0uni25F1uni25F2uni25F3triaguptriagdntriagrttriaglfuni25E5uni25E2uni25E3uni25E4uni2530uni2512uni2527uni250Euni251Funi2541uni252Funi2511uni2529uni250Duni2521uni2547uni254Duni254Funi257Buni2533uni2513uni250Funi2501uni2578uni257Euni257Auni2579uni253Buni251Buni257Funi2517uni2503uni254Buni252Buni2523uni2545uni252Duni2535uni253Duni2532uni253Auni254Auni2543uni2573uni2572uni2571uni254Cuni254Euni2577uni2574uni257Cuni2576uni2575uni257Duni2546uni252Euni2536uni253Euni2531uni2539uni2549uni2544uni2540uni2538uni2526uni251Auni251Euni2516uni2548uni2537uni252Auni2519uni2522uni2515uni2542uni2528uni2520uni253Funi2525uni251D lozenge.tosfuni2620 smileface invsmilefacesunfemalemalespadeclubheartdiamond musicalnotemusicalnotedbl estimateduni2113uni2116u1F310houseuni21EAuni2327uni232Buni2326uni2328uni23CE section.tosf degree.tosf dagger.tosfdaggerdbl.tosfuni03F6uni0374uni0375 acutecombuni0306uni030Cuni0327uni0302uni0313uni0326uni0314uni030Funi0308uni0307 gravecombuni030Buni0304uni030Auni0336uni0335 tildecomb uni0326.caseuni02BAuni02C9uni02B9 caron.alt acute.case breve.case caron.case cedilla.casecircumflex.case dieresis.casedotaccent.case grave.casehungarumlaut.case macron.case ring.case tilde.case acute.loclPLKacute.case.loclPLKuni0342uni0345tonos tonos.case dieresistonosuni1FBFuni1FBDuni1FFEuni1FCDuni1FDDuni1FCEuni1FDEuni1FCFuni1FDFuni1FEDuni1FEEuni1FC1uni1FEFuni1FFDuni1FC0 uni1FEF.case uni1FFD.caseuni02BCbrevecy brevecy.caseuniE000uniE001uniE002uniE003[[EE,,[[EE,,[[EE,,, UXEY KQKSZX4(Y`f UX%acc#b!!YC#DC`B-, `f-, d P&Z( CEcER[X!#!X PPX!@Y 8PX!8YY  CEcEad(PX! CEcE 0PX!0Y PX f a PX` PX! ` 6PX!6``YYY+YY#PXeYY-, E %ad CPX#B#B!!Y`-,#!#! dbB #B CEc C`Ec*! C +0%&QX`PaRYX#Y! @SX+!@Y#PXeY-,C+C`B-,#B# #Babfc`*-, E Ccb PX@`Yfc`D`-, CEB*!C`B- ,C#DC`B- , E +#C%` E#a d PX!0PX @YY#PXeY%#aDD`- , E +#C%` E#a d$PX@Y#PXeY%#aDD`- , #B EX!#!Y*!- ,EdaD-,` CJPX #BY CJRX #BY-, bfc c#aC` ` #B#-,KTXdDY$ e#x-,KQXKSXdDY!Y$e#x-,CUXCaB+YC%B %B %B# %PXC`%B #a*!#a #a*!C`%B%a*!Y CG CG`b PX@`Yfc Ccb PX@`Yfc`#DC>C`B-,ETX#B E #B #`B `aBB`+u+"Y-,+-,+-,+-,+-,+-,+-,+-,+-,+-, +-), .]-*, .q-+, .r-, +ETX#B E #B #`B `aBB`+u+"Y-,+- ,+-!,+-",+-#,+-$,+-%,+-&,+-',+-(, +-,, <`--, `` C#`C%a`,*!-.,-+-*-/, G Ccb PX@`Yfc`#a8# UX G Ccb PX@`Yfc`#a8!Y-0,ETX/*EX0Y"Y-1, +ETX/*EX0Y"Y-2, 5`-3,Ecb PX@`Yfc+ Ccb PX@`Yfc+D>#82*-4, < G Ccb PX@`Yfc`Ca8-5,.<-6, < G Ccb PX@`Yfc`CaCc8-7,% . G#B%IG#G#a Xb!Y#B6*-8,%%G#G#a C+e.# <8-9,%% .G#G#a #B C+ `PX @QX  &YBB# C #G#G#a#F`Cb PX@`Yfc` + a C`d#CadPXCaC`Y%b PX@`Yfca# &#Fa8#CF%CG#G#a` Cb PX@`Yfc`# +#C`+%a%b PX@`Yfc&a %`d#%`dPX!#!Y# &#Fa8Y-:, & .G#G#a#<8-;, #B F#G+#a8-<,%%G#G#aTX. <#!%%G#G#a %%G#G#a%%I%acc# Xb!Ycb PX@`Yfc`#.# <8#!Y-=, C .G#G#a ` `fb PX@`Yfc# <8->,# .F%FRX +-g,:+?+-h,:+@+-i,;+..+-j,;+>+-k,;+?+-l,;+@+-m,<+..+-n,<+>+-o,<+?+-p,<+@+-q,=+..+-r,=+>+-s,=+?+-t,=+@+-u, EX!#!YB+e$PxEX0Y-KRXYcpBD0*B7#*BA-* B  * B@@@ *D$QX@XdD&QX@cTXDYYYY9% *DdDrustup-1.26.0/www/fonts/FiraSans-Light.woff000066400000000000000000002426741441327105200205730ustar00rootroot00000000000000wOFFEGDEF]n9=OGPOS2\˩GSUB4Q%Rs_pOS/2FW``vcmapFt6&rNcvt ?,U@ fpgm? pYgasp?$glyfY?head66~hhea!$hmtx(ؖo;locaD > n2maxp# j name#)"post&4|prepEyx˱ @P( hBq$K6œ=7GK`|B7׊M;׍;A%e[/ I]vxqx |Ty$2! [[ U Z ZTVVmVKZVm5Z:wS+.)k;L2 s=s{gRʯʳaR5o]Ɵv)juZ<(::Tw5$)GT?RCw 5iQ 5{ƒ*CUKNyUyT@aΚWUp|@Z*TqZ*RH53^仅q#18x6;xi͉ϞxVgdqwk'y_֣5;>CkO QzI֩a;j$q}x:c1~bI>XiHr?gG\_2/Nu|񱫧bi-ҤqӒĢ6FIkr,喫yfk/FpK$%~Kڜx1uo4a$e:G//8Ly%w}ޑ(9}q50{鶯{fssxN;qk֍m{+u_gzswz&ym=WVi퓺103D#_kOLf^Bgn;+OG[&sXiKJi\c]g}ڒ- \a^79]k&wָOfH渹; 5]Gve%2Gg٭zwO34Z?KzL}7]wYgNB]rǿuϩl(猓"cMymlVG@θbp> G䙬4GiwGҡLU$C뺼6e.Ӂ֌ϟhNWӫ7g2 ~4gO w#iKԥS2Es\nw-{zmCݝwϽ3C($ ƧI2 kztqw#1ߝCvF.i4;)sC>MnUls?YӔ{/3_a9eHt4(a OG[)?z߉%=gS.ߨ<Rw^]7O؍yH9X?qկ$+8~;N͉oH?̴'}3|/U ̡ 1'{ݞ0t_v%훡wCTc_wvE!!λ!)#)КS}yMJ/ }qdz=m=i[egɎ YSN$=]6șmK[TZFJ:H5Aε8ccV:Ygjou[,"F{eo{O~('})"sٴo:kϦE]ױ!-߻hVƞn-y&?la@?SP zfVl8卓 .Jb 9,OU{jzn}5i϶WtY1^ì?|3ȴדMt$PMg-=i&=5jK` :C;nU=MѨ ˟ndjB.o1fӌcas+kg-}yiQ[Ow{qU=Lw{4',mv}_ _d;BiH7"In89v4ΪGn츐OWZbg5ߚNek2osC庘jﭏT|ަEe_-wXĐwӜC9Lo/ǣFŽlJNe3׷Fzg$I^?MH}.U9܇o~-1U,OVxǬs|uo-ҹH*־˱ZH!ݎ amHO = GW:yH .J@#}$'WQnۘ=on+ڷw!_sn5<{%o?}juB!##EuyTsyTBs'!MCGϓ)8pߩ_t\utyr=cO?kgM9R;:.w6]s;znѷmkSnyY*iy$H8ڑ>۞+N:ץ! Bpc2qs'tdgyl\k]ÜK}|:u;V]LOig%-kϜm%_y.v~9FWov\uk}s}̽R>xn qSt澙ۖ?[v:> VpB.ICF U!Mpc&Ѯ,g?3]Ej@ZȜCVE:Q>'ʹ RtMJ=cݞ.]sͻ&ھy''or%CmtݮvZ?=f+VQI?;)܇r?[)7QJs[(|) odQJ甍ߦ\CCK( <=ְr i!V,J/1y1í?9P.<4zv.\~+3Z.FY wmM,s8q{{],n9`C)1re.嵔7@SKy6aC UGn nrʡy]oN`my!ڋm./Bs+;>g82%Nbb}bMbMbMbMb0'ɹ)#̟2(Sȯِr-[̜].?_qj.My5Ϩ")2|=WaKr|G<d+myR1R2M[{qH9e#B/E/=x5ۜH"]t9O򡋝r_ooo> i|_][I}xʵs!/DɏwK]ұ]_^3Xy~n_OzGӱꩾn.>ZPDYyC9 'Cy(Y&$(,?$ A]A ɿ9WjG-;qU}k<ˤw H|ȿ[bpglMJcW&Z{AE:B˝^H6(ېnGڄtV::= Z_ J?~Wik "]ٞtI=w;mqtz7oB?M99БfqQPnu=UDb{4 iҴ,t}`>ϗIN}c:Y_\G&} =k"taz\gϬf{ZmM{qڅ[weYaOSH+w_BM9vϢ:(`0Kʐ?As¥+&ggd 5&/qILHGtb49t?/'kZǒ\|#,r>i=.83 vk)Kgg7Y>}Éz;\Os91YK=H\$7"{3;2= h7q$3H/"$&}3?i=H~BQnKk~^sZMe(5Lw\ɻ5lowu~ͼGԏfޮg -x+>'R&R!#eTi2Sd̓Xr'D9I.7P.Kr|O~ rH~"mrR6ƫPckjj:PmPp{q/;SjzI[t]uW s1z>Q|L׿JߥAH?T?ԗYC+}G#j~Cߠ?;gf9>w#FBrį7I-t{e ѿ*2I?$eL1.zK̖z̕O'd?id,r_ckr_3 -9K;OrnuN]^rܤ? rH~.u.rܩ%w&l_˽MwG)?_,O e?de|T'>_L]̐d_f lSbdnF|3LL3LtS+"3̖f/MfY FH1MffVgVF#y&x&*.O{|";6"SlI1s;f?`Sit sRfLilR~2jyU 3Tyݼƙw;jy_M0DPM2]Ufl>5)UxtOT*}^?t_5}95\ ЯvTUQjzWGo/C?WMR*j u rX2\-2Z_8VOjd:ZUڞ 9B(uqbe?8g˹9_.F5\'W·VU?`?amo`[`r-2<~jzף *RAoDyw h!S۞vow ܉>(V!H |ȧSgg "ʼBm?TKs!XGI ? TВZ!4uBy2 dbY?ZVg!jz‰r"Irȥou*.@2  ?^m(# ~ܪ| mȿòQSfoE:H5e+𙩎S'眉7 \(s>3^9@; UF9ԿϞ3X$*<u@T.f"F5-N Fr T5{ ,ljL䜣ρ<__+Tp}QAooӷ8d9[3nlқ`~{V6a΅~яmT>N_nǾFK[™y< ok8el}Uװ(~zW{XnUCwP|z'/ uL aҕ-ZH6y36qREK\GK\RJ{<!X lP5IFQ??8гdEq2N>lux+8c^ZRZ:R빠 <'qi?ˁFYdh£99Ke)θL+d #HW%ParXWNHQdtlsUu\f!'Rs*xr\'}z_Pȇ|;K1r3i93I~&?ÑCP7Pw#Z._'deGxU[P^i8ɔ#A#E6S&Ww $2 PeՐt3rUH[T45?Y2A4>L)R7SAߥofH!JԹKRo^L=@vaw䫇!)Z㿂O xBo,0*K'&mdmV:p$nb!G3@eL2/H =H@QG~=w'h#zC~=b 6&r>\M+X#ע _g~f ;#9,AT _FVnwEX4CfEU_!^GV}jb_cRFF[V5dm Y%k+md F2HrFE?|X!k );T_CrR6߁d`~Wg[CzIܡ#1|֐ĭ$w65-\Ʌ-"}mVYV /-$q$n5:d-$Y+dw/ C<d%n;ĭ8em!);w?O !kIal#yX[AVKl:dH$YH*;xmZ$' X>QT5| YI$edjL ArtPfq,Gg%$dRsYDjVAPs-)E~%,/`ހ`s[qw7<خA8o:XG@kURFǐM֖d!k_/(/JZ֐<K\wU^Bɗ%|K8Wڤ =x/, e#k es4⬰'6FBWc -+ ;Si]AfWf"i]8u2(;LQ>R bܳZCJ '#VR'R3*&IA$ጇ&1jR?g &rh&94K G@3- o954òțFF7>DC:H SDH&Ri"u4 L&Ku)`y&o7i-$iJI0HS9䊍ºA/ŲظaPD|>dal,[ƄqJI%$DfII3 4SIa$$i`%cHG%G1#erRYAˆm4#e| R9qaܶq2,θm2q2q2$er Y,9,i EImŌN'E"ȏ&FFoI&2z+| ªZ42ۇ/Y lĨnSD41AFuɑa2mHFH,8og#cd -4D2 q˧yLj"ȤLK!4[m,8.T#9dUYdDXKJ֒U6RAF$KP61.Bn1.e%b֏ aȰr4VYFF_!Jɳ&Qj9Y`ƎǑmCk5X…L pGHɣ%mLy\"`2 [-HK%$*\ka9Q+E%e܏,eܹZn/K}NR)f :JʭSd$23NgMr܅2aFMJW1*==e,kG lz8ZL6hANc̺=Ivx+ yBL h19z z @j'1~m"5HI&@>PF9$h|"ҎeK<);dj|*"FM|"_s&mR6m2SgMdm#Yh [6 IRw![J35˖a ݡ7.5uN%dp}&FO$}; 8`3@lJ6`ȷln4GcQOK&:{*nfQ O4gu(Qx2s9x\?j\7_j.Ug3F_g%?gRXjZ`Fe~ Of^è=QG2j}.g0j˨Q{=zF՞'a~cݛ 2q|`γzFh~ {{y>{}þ(ط&+)0!S\{?=6>` ŐazT?Pj7oG(@Q-ZzF4=#?}"?=2z=zz*T@NtXOOOOz:Ō2I M?O?=?=< j/)SȣwGPK/ H/ H/#$-$$G#kI $si\IIÕ$p% '{B y[?y+$mI'cKHJU#Jt )'3'5T FM I!M ?'gɖ~dK?l%[*J-Ǖ!UJTN'I?< 'yRK82`9 |s`7dYS)Rm:3mj car,@ɱ9֟!Y9id?6L%Fia2-@ɴdFޫWrҫWxC{W1b]H{NR}#Ō瓍ydcl,f= MG3Vդe=j2̬gD=r򳚱DR'EK Kz,'KgMdiYZLVMdi5I<5L'琫ar5L$W Wj\ j\xxZNH ]k5w[ Fy&o@6@0y$o2]@d#{d@ 3]F2]@(w8G&2y &1vbZŨQ뾤Qbz6)=Qh*'f*Fa.fTMaR=xl3ܗr/&ǒ|1cJr'gH&zjr&gbrE· &W/'$&$|%$|d{lBa=L& BƁU$|!پQ_#:FzyaL#gWŸ`$}BaxU3jIRNr򲂼\BRHA$e?r0I9,"#Cd2KFHĹ$$Pdz|қXHGXJa)}d-B3:ΡEP!;)zAc)fZ,UJ-=u>4FR&M|ZH+i}hd *G:զFԊb+EY4fQ! !)fĚ+Tn.N(FS6MٴV1!!5[?523^ 1"aVFkci18ؘle0''3BrfV1ø˸Ǹ]Gƣ) Izx>hcv0`jiojL13RƍyƕƵƍ-RӓR猻/061~qi9ㅀ34F1:ƆƦۍw;g&4c188;#ԸڸθqƝYc2{6~`xeָ,bj 33Fkf*Qk3gCQ cqqXh\;,3fV6.>RJbb1r1ab1XvTeb1X*+bbȲ:IYd*= {ZCF N9>Gp4W\~X"{XBP{<ԞB٢GqЙ~jSBP.Ԯ ]a o }/~;PԺBVZmTEm=E^Qʅڨj2Q86n݁|i83^\1Vk62.Mr|Y+r%EypT't gTй 㨪vDG^W^}rR>׻uF W值& ySYZ;hO1M1!s[)]얋rIiǘ(yTl'e<%O^5IɨKD)hMʢgE5;F]~Q əC8~<ʟ:!c%S$[I 2QZCoZZ[[kmQ;ivѮMkGj?uѡS>=+dL<*d̐rDaݵޢu?K:RGhM1隡c5S4[i :QsuN)vzXܝ w̒2G d̓@J,ŲD2Y.+drBKk6[6&Tism-Tu|u\u.B]u.e\WJ]]ZOzݠF}@7郺Y->}W#~G;r_IWEZt|?G?G];v*\Ac: |#bM\oZ\->7|+7۸17܌s nmwr{廸wܙ wnܝnɽ7ܟ@ăyNd)< x.r!/ż2^+x%|꽼u7n↺\Rp7thƸ42X\rx7Mtn즸<7Ms 7Yn \7w BWn[ꖹn[Vֺ:mp=6-a7=sq'6ROKi(J#MKi*ͤJn6VrX&ݥ%]tY⥧GJ? e $Ia"#d4/+EyW{^%Wū]Ub ^ErWwON+XO/ԂέͿ%[j.XPGXRgEu YWoV5j$H5k$Y)䨜o|+_ZRKi&Rҳo~_W+!S8iUCfΑh~__\.RY-/bX"Ϸ|N*Ɗ5o쿘G?ʳ!]"j-U j N7y+o>~S|s|"]EWu-]'\ ?v~;쎸q;.Jh*ե&j&Mc$!3_lv%$([vBۏ0jFv^.("-Z]k"m !EV^Lv ͺU{PgGbwy'FEG~U_ӯ7mX?ΏD?9d_5w^UY3|#9<LD7jS6o`-m VWuWe\qE),u-,(KIu4Tp!Aś tpfst,XSS4 plphPǃ:c ɨ'b$pQ?O[+C(>X~hG] vR؃)U(2*Ā`7xh/>t_fp>-P}B1;]a cGXp1G&,81qCl}^Yt O=}x@?_ <*k0/gyK0Jֳ`~ Vs`e-x~^/F/`E)]}[xe-@p\yLOڌB l$>3 i^LD8/fCv!K°Oeb5#؈'E㬿nOcO4N&Xj&} & O0*u * O]tis(xEQ;/FiDmZQąuE|D=0d sDPj-}nZgvtFt.|?Ճ`CI w K=`](Cm+i*4/ ),$q9Le2ՆL ]SLtُ=_QwIh*"_+|/~;D1o`M_p?J0*T 󑅵wF]@oAnNH}\VVuV=V߁yO>S23)CiвȤ0cD?ϐzBO"?*]gH#w.] g+Owºc#8Dw|zQ 4]dttא44p4pw3%t~8SΛ J ZF}xΡF 4+@c|_1~(>oq%_(0W zzՄ>t7'q}z\謜Ui=Do\ vs53zƵ@6Zzy׹ &ԃ- #7L2ZؓB%oQIx\*ȳ v$>^MHT>ç,"1*huBl H**o^"xL--Ҵ6_hv u"C#_om c~b@AC//_6H>LOiSO_<=IEI ްʢht ~J[+di_xc`f`a`e`b```Ќq F L,̬@H3%9ʼn̂5'H1)001q3R xwVՕ^. }\E" h'qD E+E~? p]{qf)c_nZ>FD"F;VT1n |Wkcu\o {''.j!n#xk-;*nv]7۸r|KZI{*"r/b$Wȕr\+ߗ[6KFɏqyƭ/1yBgd([2KY*dTV)֨GO͸בLZE[n< 2^&$Q$7yYR Y'{0S{e-y:ܸfc]+ه6HJ?eC]lUv]nmh.:%,dMY_flXø942P[0&ԚYs;^rO|:Z#kʹ-+(;ҮڒygRr('%r)\/?Kvl'նN"V;_ʽR 3@1:AkBpjx- :~l;ğDZ8.NOgsqz|7.4m-g'vuq_H{)jo`6IUIoNGwwϧ3WW7ti&-}`V'k5ZfY1̺fݲوlLv`6pTX,t) ( - +L,l-|TM͋-Ūbwvl[vQiuTU{c<``J7yUhQaeX+֍Mcei8>NOǩqZ|>3x:G[Y;j')ӭ]c8{؞mamU'vVNGwә{.O7[q__fzYE:~ZoFefcq qpޏĽ_{ƽ'6 kz\״wÇ=4o{вCQCiriJҴ5B͖kKJMK᧔N^ks_7w*-/]=:FfRjQzWuD߯^Xz ]\uqMD6TgC׵iBIr-D>z 30()qx58*cu}(qe\7ƭ/v`7b-ĮMv;~ecϰ۾j̿˿s'}[uM6^a+QC߲u2*e_KPmw QJP :utR=&A7D/ڧ'ujr mCy-t ƣbODOT 3+<a9 ;O֠3ޠlD+K'㤉<"rNʣr^Z<)mdN "Y SLN+/o?/ߖ)/3T$/j!o;r̖sd-s\yWΓyr,\(e2Y-dUi*)"e mO d|(ߕO;KOJ~$\nrt۱Fk-Gպr֗h=i$V= =hKށvmOZMID#:I^%$ I!)Eai51Akz:UI] t(F| ށ7so턑9_r,sGFn`M&{(8'g A+0N&3|o՟|B6dן"/g|O%,Z#qَـ<| 2^d,^LRWb|)?(N DoyK5j9wru异5yJzn5c r'œװENrHcIOボ׌e9s^GVy-Xڱ*fuXu9_s{zؘ{0Wn,TCnl =rÜrܽlǎ|ם]Z48|[~yܛؗyOv1e>r'{=_uc !GEu1*! RERmk/1!?0"_~w4!kY$u )Y!/ ehBmHHӢZ1!aIڢM6%YڌED=YJ z&yҳkM/yMɳv'zy hړE5X/&mz y^K/%ڛ֥=)֣lJv3J+)() _yP+/'( KDd⸜xfՀZчS߈<FD;-9=Q缜_\沺e>grx5)2E'yv\ܻ;b>Kk)rLsBfث Џ_IQl_׋)`{8|/!EﰥK𵶜VPVQդ'胶8 I] } ~Y}^r/bc,~|_t?{ #{j`uzc}~Jyd:\sք=_,YF=07ƓSxp(=0y/)TJK Xgk{{1 TEosCH2?MȳK9y*MUlI39XE=3N:Ty*"km+{Q佪mGԶo]DޫO#;yd8kuDkZUW&׫~&B.:s0B=_xڼ} `[ŵhMnKeIEȲĻۉ$v8ξ8+d)!$<ڰe)Kh(PB_-m)Ifޫ+I{Ι3g>g",B:CU(5\\ת1RnmtgZ-f<PMMc?Uow ZePAek Z9=4;Z1h0֕RS *wkP& %#݌AKɨ4Zj;ZUHZ0$aԅPr<3$[Ic <~}EYh\M>$921$~F؂$jGjJNǻG\GӾ3tX]29!66w;FGX*Hu4yP4iQiVuUTÝ0,]ٳJx*-?PŞSu G3VW]Z`VZS[zlpӌz Fx@UE3E _[Geݤoi*DftIG!Z* 5QGXǣ0| P'|ID蟌$WMqn*H&_.|Nxr&&';!8q9DU 2KcxM㼃[hy_\Ұ)FN-hb\{[2;JWc0?`σwcbh߶'o?\Y׎:t^>?6J̟ܛliih Otկ }yhҝuK*[y]\}a/9Wp ;o߼n F7^B?A3~9bxbSQc6Cy"Z%+!6!F;ڥtj+Vxϟx+?):mWh Zr+0: >\)η疔뻷'uEka^QśnYn^#LMH'ѽ 8'ʯ5za?A/H1ݐ輚$K?U&y.=QAr3 .>O a@hc{uHfM°[!SSJum03dƏ|pO(h ">1h⊣Q,.!a\8A'r-I@gRAx]|&(JLԀ(bb嶀='/܊ɀp"- :1;{J ~kJ:r8YW2e,`rOTpEUFFQlz=rY<+O~#ɢ>LabhU,͗ .%̂_ o%i2XK$OaId'SD 3ɚLE?F|`d@x3م rS!s:Lt!` S!ʌL|`Kt7f|\BGq2^_{yxe5s~1qޞ91jl7x? O͹cۦgȟ?RaastVd2JҀ0]<#E _&p,Fhf`x$C]$*JDZ"=8#JAQxw1 CeT gPSJϓW3tT=bpJ/~.g#=>Jg<'̟:Kcl8t CŸ}@Larn',@\̨T5evQd6ȍ36U2g7$V[끊6yׇ#ɋ}l{JSFI|+fF*Z颡.тDB0sg6dWfFr{RsIC%H_X2FzNH I IS*k`Yr4j5x*Uw%!Hm41"z ,'KO3``w *Jө{&C߅͛S?cgΧONC/%V yyn= w.uHEfZnlPه (7{pp\^m rQh:'OlZE9!#O4d 4@ 邹-5g滠փ="A- C%6#-L+QY*fbf({ڏ?b`6;/X{+ &KkQbFuPVipHLFs)3>N# 3i4d$<YOu5[pP59!,V]oKeѝh* ,K'[@"6 q#Q$,eJz˦ƍl9D %{[]u6׭n;ϯ^ּvap9!od/ { z[kʞÕm8goÜǐ87[:mjN,TJ, d9UzJX}5JFY,)1ihH\hE\iۼxZ7ǯƚ5jEifnF'G9{E;!h |ɠSr밥W:cr#<h"©7Tp)Li N=8ZӰ{7}sxo08M3<-|Ԃq] ZO#D%'ir+7HVk#]"WPcêh2QFN7W SSRdaQ(&:ؚٜDlN+AO&M:/&m;88q]ÿx콌k=c=2$:зt =/?m3pf#=2%r?n1o4[– y|􌏰o}ԄM=ڤ,j=UԴRgu̬7,; 2]~o_4/ ҽ3g<¥EL!UQ}ƌgm+X8sq=9Rӏ7jEpr@SF~Aff3S.snqan0Jr>l3l^GzNЇ x=QFg3Yt^2338AL=:ŧZoe~MQx[ _c m],bR13#)Fr%\7K(= ]b#O(+d%Jx=mxzD=@υHA8JZCr[Uza1WBQ]cw.e>C<ROtr^DQϼZ3Oղ r뼫eCm2n h:x ުG[@9 h АҨi`55Yiy,mRt^(7fRF>ȓ~,mm[A-lغ7G gytEC:9{`SSMӚJ\q%\-L 7pr\X%hOzɩ8k bYi[mȳSf*a<&,b,K34m)a }H6RSksQ ׀fڪmκxv˜CZ{&*JqM@NTjipfg9lV)ӘhVɘ2r\|W>"Jh^Kkoݶlɖ%-pOu=m~2sq{W_;z⊫nؿi9W6 r[&sdmjNg'Y ̧9;P8⿢ p3G Aw2bE8)ېp*VT6ɿ7ȟ{{s̎JG_z7F[*CۢF[G@0|u@fV4PRNl\JOQSςM =r)\ҁUۧ]ZPT[nC*2VcHK:&'|FbUdXI,X:)SM+##= Dq`ůUot8ݓA݌^1IG Kҡ$0Z5@َjKII!ضLHڬ⌟J7rO͕Y]]\TX\f*n PcfNP+sd?Z:je ٲy<4n^SKNvYɺKTd hh=K 5:{; J¬ro0:#MmM|?u`AoGp {ؠY4/b"Zz}]wۛwFT-NOf9شT%SJ.>2L&1|-8ngzL铒밙3et $Q#1R8++f5.v1Iu8!Y <W3"Xc tSi>yx1$ EӲj Zrsf-&aƎvH#4&r9=TO+m1侮pEqi@kJ@Xw, ڪZЏVdbq`p*~4Lu!pc(fҡ:2%0St`LPYh\PXN4>'R{b=<&%>Yj"{=;dk?GkZWה[+,uMӔ5UMV ?[ܜ ;_U$|}5g{󺮴:_.:= y_ [*-eaN~JkvEOF?:NWs6(xmIIIMIȗhph$E⢴b Ҏ. f Zfg0j]ipg' kb|kss+㻖aےJ);h1lVX3KW jk=İ}F(Sײ=wG{ /b\Wu\k;#j\u;TZ-Q`O۬JOS=iWGk;ΛYsw;ẁ*buY‚b}F{' ֖ZGO}{B1鼣1yZ#EWH-k5RHх//ZO*̞+iL hnh>vcE{F9I St,Ȧ D @EyIj2j[`x1.")jk$N 4aTyugW]%Ng+R^5=;\ȶ8M6#{(W뵻*.wWM'Υ|2aCZ 3` FB7i̧i/FS ,X L D"CCt{ E~/PwIpoy(n\mr$(аMGhh,$%Id;h8~%fdhx٘=_ t8BvH8ÐSqbE w`)0v` RQ] Bt,bg$z]!`,e4Y/Jc{ '+&釐^?]?Oz5j|DܙY4)>M6ei [2OzŢ+scCpBb)Gߐ|TG{yL:C5´ݰxsxL,Wֹ$>Uӆpz)x@>_8X+ѿ# Z|R 2M 89.4E*%2==/YնM@@ rEMo2s|227f;;@"9-<&x*=-mB2 nWL#.@ZԺ77=N B!~.=ԓ;=SkFJ~i=#v1^"u$xAyp [ĩe`Ni^m77>L)K-afěJA]S3ϻ:%jF^6+|e@J8q8\N+O9.k& ?/V jH6 seb/ Σ@֯f!lmxBZe!++6yX[ ;Ky r ^wyWST?qr|fsff4:{Zg@{G)\]Q].ľ HÙjr!KYC9{ ݪܜZի4/o4cc i.ߵc^в}-/x&=!se0KgHs%nNͥ=K%\cB8Z)_z>P"O8c?}@%jյ2=dzX)=dx b;'cnWFFq^y-%$pm]t_Jn_a ׍=V7uu fLmv͛/΋/8Բ({]WXyGp|^y7vǍ#ǍFqC^ݺ_|qBxt/ $e|2}WLsi+ "nec.?d18=b_6N6gh9cq ~Lhi7úHky2so@1%_ ku33}V>3؉yχqUtc|4] Z/<}d 5EXta:SJsV6.l4gieExO'7RT5?lv9+7_Ҷeipvs`}vd?S!|ZqEfr}Ƈ3? x Ras62<|>g{IAԜׁͦadaliG$/=lra#cga9sa]WGG/a _X@.?kJsFvN=l;qj|4b05od>d!)[^CPɗᦸ5E2*LFpmet%__(J=vn?bfa{ y)/R?,㒇./cJlќO͞xwƍ˖ .Xtgb֖}j;9P*e ]p p wF(]2)s|$]WMs ok8CsTf%y@~s; x[FDs+n/O]{r nu:\cG;v=T%Ĺ"b46sΣv8b$pf4IYcZƁ(,gMeqO2b$aq}wqGҧkHlz㔛cMor܋21daZ)7?ǩ춣9GEO5y#y.J'W^Kpc}sΙ$e%mIZ*7y*ܵk;vk OgNmX4}v4VeXbΞ7ọ/e)gGvj}v7t$\9(,hLu>;NώWٚ^-L{6;-}oN8lphle"̋ yv"(iEVc' =vd+8- 4kŜI5mt$_#7֭1ճ~͎g ׆kC@ߗ+"ؕr ֑Ə #q5. [2%$L)sov>phֹ@';R;һ|i9N3>kaG+/R_'NjKxqyHGΕX:z3j)-9cW/p <8S!HsD|:V9qaH!MdH3#ѭH+V1w:t|T9Sk{'KI}q4gSPX^'13,-&>q .ϐؕv[&틣I,V,]"~Ux>ak5Ba?;پL!y 79HD Z~ށ.Ucs}%Ýb >ǏdK(`sCQ?o̾PB5s5͍ز%K\*+eɸ8抾e䝏5w{B.E._#t=ߊ 5!svw5_xQކ E/Xw1PȀ׫@07Cbku H[{O6sq}kloͮo~[3hX'|˱/RϚgXl=g$hJOHYiaB_5 Fg^$í5JZ;Zд^YsCy{(q@'a8 +*i3\̥ps.i>Qr9ZZlc"u:HMuGhv=툭Ӑ1Ԇ5bYV$**|.[OJrX E C &gMƔl^ch삂֯/܊28XC85M-96 9KfYh%5J-j jܔsrIG\` Y-78t>77 #뗎o[2^>w9}};FXu\Lڳ&mljzQvtdd0c#u %ʵi{/mP/qxѣVܰ *Ix&cdȱ^TNXXʴ[`Ob$c/ 'Q{AQcԒju^VDc5ԛ- =7Ujh ⏓g#+ P,y'A';[lqEl4NK=k322l62Ϻ 6Z7SF`<*O榔:r8~py\xСC?qtRt<`. t]iNHboBbI&X;?ihh4~ L,%)%xS1^ Ҷhnh֏>#e_L }q>L/cL4G#|ԫKOONVIHr%hI.)RJ6tZqN- "Z,u]<4Фn蚹pN~9dv7U7<\?klmmmO퍝#G{: V)Ԭu 7/M.?Ww zYa]qJ=R0VjڲfT;b.0C˒VM\,o/΋D6<] ~Q[qLvw~Tܻ/k4Du,km6ft/G;V͙(YL3Uud]댕eC%bO9A-2璳خmإ^mRИHb^O+|L.F9q0A[*X8}^{{^kŬ FbVzqvUkP^]h#w}VajYJNC=?t<~{?dvB^'S66\?Ejzݛ D*uECvZ4p:rmFo4Рΰ'ċCAH5;7O{owO8pxC{5]mmtE_S ݎOػkjA%>cU =`gARg Bp*hSbWzf5C"VMdC#9z׎dYB'LDbARb YW"4Gpc!KɮK}cE OOP+xl׀j5xj̀NdZTU.O)#H /Ûȴ@R#%n/ݛWwxoV|Ͳ%W·pù߄v z2X)RS@h{O*)1Gn|)L:e Q+"bp 7Sk?/١F_~gc+]*c+`ѶR{d9Q6-6_b-Efͽ❑ajdvҹ%y%ؑ]P򽎜|ACztaw尸%~̈"a-AEnA9VTtáB"dEv:?n]g - )/NT=i~dfڀ:PdͶcua*'לeg %KemETCIn98cCU[w޲ȱ'{ǁ=_ƽ}^S~S.4h$<\.ʡ"ǧXnKX^iJKM t w{3-t?0O ,pԡ$-4ŕR~NҗUe+s}Co/ݻC2+j3hym$Txh#!Y1;S72kx* f>xi淀){86s>Rh=C~o,N G7l³P9 cgH#h-H,H :TAz . e]=JcF X`pLT_cyСKH/YvEED bjKGEޚ'GHEԠBc.h&<25. ilĜj@rdK7*Fr6ΙcexRyGE@w;=uv`*"X6ipkFiZH nsT/J$ )d _ɕd STޛFt 'UZ.n ؈ OE'6W0^EiebfK4E:/6ڎNGMkICGhǐ|&vR߃6C6JX4ș u)hVOn8`[6I{qsHJqYnߍAyQ;ci] 7IY890a%^v sS::xLr]!g6i4gSQW!V0Pqo2a ·\}/԰Vt| kh90V-Jdr[ɧ1 HwG J4G̒v;|uߟk OL3HJymL*Oq AiՂv^̀7%LxvEHg`OO&Z"ċV"ŝOϿ+h0(MDgl@.~jōYa1Nw?0-&j LqTjLbݩ} C`:Sە r'&a7Ik2v!=+{Oob0ҽ|h8v!e1 i!Ӛlf $YɫZfIh4j݌v{軏B _;h&3oKq?S YrqNUґPlf<'؝2l?oS`$HʢL5.eB(˗uG^quza*<(rȢUvն. wT7\:ZG@/~M{/7\YK^S`IYhh8iQӳTV;cJtjІQZ-rf<m^q\W0#Ƿ-ogʕ+w:cjw6~t|еE_G\-{],gbng> C@Y$ .9G 9ir̲0l|bIA|`׈U:ˢVT. W Zk{:l-PcA>"{z'iz'f}M@ϳ]Ϝw37`~PmPGn^5\;[Dv l807ϧ0 p VHs>9r qBWk批b `imMKpӨ?3+ݤD;jʢ^Wbjj0#'4㻸s9styzk~O->\?TrCz3ǛK=[Yw,^<B Xɫw Z|Ohۅ^}Y}8TsEiJ:.ל LsIHĔP͋ /j[ <04L GW[Son癫cߜbEF>r`ZEldV'clZO3Wcfhs>礸9+7]a a`H=>gW/L_ss#P?;1 nRtuJ8ePLvdMW&erح留IoaF乐VbDxa6?[wg?5Y>:3OONON1\sfOOnPY9NV;V,Ժj>T5ԙ_vp+FW > s<2"\qy+}HQGw4ݭMlOo%^yɾC9mV/t|-L+vD 頊TVZL-E*]NŽ>3#BΠi'*%U' 9W{|z_?sOX>tb>{s`lzK8UiXmm<~dᜏDy<ETm,!4,ͣhA!kص7b_њ?x,_ 8d_pT#GգǮ%ZF;kj[#Ժmsĵ;t\:ie/*ߦ]Gxq"wD)褜dM,U$ƫ[lcG5%?2]=^HbgXÙyfspWKIxsiOۂ~q!m2袨h5XYAMcNQ4ȀhN)` mRzR5f[@igw(Ѐw@חv)Z-8`Ri'f_㲯imn X[!c?3]}|6 Y;4yw?f@m l3$=h?>9߈ߓ`.Cmw?ݧcW5r{Sڄ/&ZJW={H A#[Ԭ̬AnF]L0ktҩg︥?տ&@?-hޑF&*F]x2rgM-L)[֯]8@s@~1I4ysyM{{yJy9 3=qj3仫q9DѶ*{G<5b}1e ߢ|1wqexrf3 25ՔBISCq l99A#@2|Œ+WI㼃ϝ^ʸ,߱ؗg\/mp..oYNsjjmiT1:16O\ v$c4i,Fq2Օз4Z{Nk^po޶0J] ,\@U~oחb} |no|V"Cơ$n){2l:$%(w'}jaxV-`GbXs i8ԒHƎ%͑c.67\po&&oo]ق%+gM*}p9K.s\& #3Scߎ0vM CU>?x}i|+"Ĝ.Ә.? >UH=ؔ9%71{V7%k)ZTY\l՗EKڼ$mvF{ ܮu\*vy)}y3>> qfθlT0)e Xul fuKA15+oiM-[0,hnCc?Dl6Z+;枚LJReUbյ&7UsYUz35d(`U=&.MYڍLsܮy?i'&Xс{Z | Jq^clfz}Sg>jZ5SεɲPraliC7<9Pd(My xwHZ#7>VmvypkVkј$?hfs+ yF<W];M[7UaͅP_sj|vl2P 䓌7ȵӟx$ ˇÞʞnGWpϫ4DQI29;1+l$Mf8Vqc)0\e`bXsl\)WiW*DU׿Zz}\{Jc|8%υe,튌%#LpԠlOTLpPLMͫP3tp4-(U_> ;cS^LyJl_@qӦ#t6:~CSwAE!6KIѐ:1;gl07F."fllAY~e[R{5/x2q`R)֮csvEӡ[Jn>KW~D9K.rSU b+[{-%boQ{,59_Y;[+H9&2JО=Q$mȢڼ_u `O҉҆vc7jXۯpXrNg5Ӟ[{Wq5 ߙM]Jj%mתzŒ"wmlM)C `!Bh@޼L›XWߙ1$Zigr̙sR(uaa_q_1_tY5|@sJ>:/ {mA?^ }'/m+m,Sl*1[UP$Awt|O'Z3ıL *Q@),CeDi/ғy=il=toL香tP9 L\zLFR^8ƂxK/~uˀcJdҘ2T% jW¾*yct;%`]$ ֞ b9uqX[ *͕O ;G6[ =O0g&(O>⒩aDTX(ky)@iUݑۮȣHy5_뫯+?$L\@y}+NC:\!wx˅. SLjLgt} s~%5ʟ$&OԦTҷNbkXQ-S:GÏ'_y5t,iz3ej6RQ}X.18>Qvk2bKZ'Uvtt;IGi*])%WŕEu|UbvC;f JE{(l ШsXIpGeiF% +*F&1vø0 p;_xˆ #aMG/*X ϋ4tp,QNNN*Ng, Ώ^)M/db\~`p5= r "?Ez\Jn$ r̀ OO8)#pGƢj:W̡95K92ZBʴɩzNtKU B*jJTӓSԿ:QP+:UFʿ 7~fyFG_ ܷ8v[VLVdy+'r>_1I?*+TS{>^LIqƢ* ,,@f4F88a!3QA+Џ*iinȘgl=@,mR:f֛JVU*>|~hO_ˇˇ;}nP1O&&y*clAd6"_?YEɗw5`tt4ϏtK{X/D~y~j͛։zi_?ILoMLqFq}+  1;x70D4!pfJTN{WJ47`ʧUt,XMq1O/pIBCw>sO?n<~{b::Gř%A3v]Q3D}暙YĂXҸ1 Qci vZ B$Kp]1n=? ª(NV 5IC^K<*w/]?>w_eIBj>V X,4>Bu<#SHZ>ļ_~+=8b&:"?C*ՠ )Iz4Q.vfiBANHKM%rΉS""p-AjAts?#p3*8{|v<[zƲ*Ѽs0O`U"fjW7I>J @Ұ$ Ҷ_G>@,'D%r;< Xxf[[$ίQYHsc?P]j{)VgzB+/s1MD񎔟GGN+2/IڡMْ-Ə q'-?\PJ(ǒOi=4 %HK>^~BC+ZSIO0/< --:P~BsuUS>*i>>g1_H c{.i mfؙX*Vm]ܠm"GIO0?M=Y@$93UJYIKx6ޛ.iij -zFVD\8Y~RlE卞 GEQDSsLxF ~}vq؅֡G}[{3V/Xx޾[Wn UtE] ,cQ+RISs(/ҁoݻ9ƭ-[ yWLÞF צ2yNH毅 8¦s|sCchrzHmՏX((PծpP whhOc'lʇ*eRYqhu* p4CQ-UO䥗s׿y hp5eeJ-3Ĕ lN&MBm6ubK<$l6v frku9|1+m:}[)bUi+>sg僝臛9*>׮)-bꞈE+O#v釈F> A@Nɔ צHIkwvFmDHЋX&JnaKkVT6ՔFƮFV E&z̞+)y+G]hiT W v <)ZP@y H n1Xv4bP|v>1NaXe֝<[ Ur/Ph7jYz++Ejc4^֐kP[N32G u 4L2 £,cThUN Jg=@ :rrB-{,ilinuM>Jg^}Yy'heu: Q4'krʝ4OF$6Nd?\!ר |zA!`!iF@*Dbpޠ1:t?( B't!frGx8o7}TALsfB.GLʡȤb'TW\.t#VKR[y7_V.e&wZ owDhJNUs55-fNr:Dw4K@[NHFV4vYVYYQ0Rij,M-2:wY$⻖ ݃A¨ QNQ4JhuwDG};~1?RSXa>#$SD @* Dy-T^1^S$".IFj,4WW+; ՕQ#ݳj=va 7סQ+jM*N(;hTE+2Z*[FO7qfơQ|-qhr>Dž'zV`d$jG5PJ]-l+zYkP;_L)4i4B.$DVRgf1Ьaޙ|^ӳY< gUejTn H@̶@!Uʻs+W,noRVe)rr)7_g{sȇXMW9MWK˜,uV҈G>Q<񔏍6b-u۰"\1ct]cZ[XI<|o4c=[ɕC!]F646դ]diHO94!tMY`ΝK6 y9K  g,:l|n3$;q#+ɠae}\Dfǐ'8x3OĚgX#` GRY!o4 lB1D,h0w&*;TЖU5 um»kʏ/'sV$Sɐ]D.̳\EB#º\=I,&*e_BCeig+;/W}6Z@\pJ v{>ɕ옂QAPQxk}呒ֲ kKh_v$_prʹ}da7sҹRVa s$9d~q~A`@&|E&Q%&)gY r*~?}Gh_h9i dHHb%)/ǟ Xj]$e ~v&3S'q']AOď?S? 9gy3ƹ~>ϯѦs݄w¯G|u—OAΗwvR"I8gr kgFB#4u- ( DU>Ч!EJ$.9aM $q O>)Yf8pV+ܙ(7Iã/Va,d#Vjz(d]ߧOGo]沄)xrY>X% IwnB9ŷ)a]Dx^\rfͣ+SȯL +CD/ c:׏Hc<^e"bJXTX5tUIzdoIx>p_$;cP8cxU 9w3f1d'7wr9de~"N)u 'qݭ4Qw?Wk|šC3L@/Vk%yVV͝u?n}ob+\ɿ_Tie,Cͷx%AgwNzZۋ:F"1Bg` –h0E.FBd*[\Wg#o%1xsW6^4cs=\wS/12Q_+'(w'=}4=v0bI/kc=VZ_+R4Rp{#.} 2BRrvڢJ@_{ACs-w',썭T̿MI4}%Fc+R@T:  ;8[yrhFo(/ޤ_KwAw{~V4ЁK#nVVJ,_AF~/gr}#C,wSRM2^iJu[TVD FʾMق_E&oH[~*m NZm’@vZskm]{jjVmjX4ghqCe5Uоg o~Ӓ[ٲdC^nK ҏ(ѦVN9m4hAG-cK6z/5oWg,?YhX" F_oJ$6eBy%Y CşZNpB9o ? [l6xճ!ڏA|P 7b1=^kJ4DXHnc5SYh%9LF:u qИ0 %9 T$@x?Ke 7 0n!@UirhM&YL%P\FTx9)<ދ..k.4;] kq</9UŠYtgB}&#A2{RT2Bo_Way.GMAn^qxۼR6=1q(;νx*]t9St/Yq:=$YEUqO 6%WWx'oC} t}5|򎫷e]~`(* ίrYnjnqX drºn|ѾAp>HqzbKcf׉w֬9,$?CGx^>l8~%7 5Kcg oto>-o=1.ffՑ[Si!'(A\׎y{JK*7luO􌎶Eڒ6x^>Hp .TZZ:J5%UFP?_ }#察y_o\K/u}3c|84/ KΪ̋wS^w9=4zLmVNvJz_?^iEպ.gu~nyxx1n\no ؎ݵ+;FU»/[ƭZ1gNu \>C>sѼZgP0ڟgS:\CmS: tt&I.W12|okhpp7KvcQU!KKHn UuDB=.Gw8mZK5{j8C{Cm1#o TO y/'-+97k`h^P2=q_wX\߲\3k7#=:/ቧh{Sh0 '>mO{uppYStf uuumvSтJ\F{3v-v`K{kY}mxcހӿN .-?9+fnu4؂FO95o 4egWo\QUk5xMsI:B7#q۪oxϜ7D!rABy7?NNGn|KtRmtyLrP}hl0xaE^{rt5t+ wVW-\@gxs<=rp(Qĸ~@Y{N.c: tp7zÍQ%G~j-C?x5U}ۉ[/z⏈1LlNjUF՗O=g,,gb9+PƱ$GXIP_"i=y0%?LҾX7i=(IDŽXIH%# KҾX ZPףg,R59Fߢ2/#75q=%%[kWnY+ Ν;80w|]DcnyGyޑBwq ZQmP".8]ڮ d}A~rZV[ Q::E#C&S3AL}by08qyr74 (O2gjixi< e\ j*s{zE!w(W[VCސ_mGߪ,u8Tn/.u9v-G]4xW87  ܚY}O]Tr>bŊv&7m*tK?mK24mMgfs|A*Uk0=Rڨr^ZSk+Pe)Q|'c1 dUc|_"7Kl1n/h%DN/yN׮1},.EMUwZT*-VSDt' :~kSVL||u[8A'x sM쀅Kmdf O֕44gh;]&RPle6W(sJ,呰Thΐ+y&{0۔)YVקtr-5ڞR*O| ǜDQzIo|3h!WIQi1l9=gi`[u]樿S a(t{,IyǑ7/ 4e!,iEi4NHЊ*[P9q2@:FUBM$&ӓU8_^gNt#>Vu6th:x)P~hz"mѤx??t fIs=B&m U}Po|C@ףKR_&~?S%'|_C\#b=;'v>"t5~>?֡[PL. 24ڏN ΰ闇ur4txt kR"sn@-{+ Dn=pmodqNʆUhO1`O\^'qW4De$a铘}Q:ZcWifͼɶM&tm*ߦba\oL6~#zqLdgR;9Dl_[D@%aȪ(v竇 jkd ?'ϩ$.ށ8}4k$vJmz݊$ A0g$Kk%Ros5[mb,Y^J̈[@eU~Ѓ~ҀEZ'LN9jJ&>7R[{ƮLH}ޔRRF۲2X\1qB5F9jFJBpQ|W?ƒgyeC^^G!۳)n_CџGchGk>??m=nmH{ ~z`jpTnWWٵR{i4y&o#]OqY(QZ(45aK1a4-#O=#O]zR\-N/Y: P9{a_i ,If=7 \E@Ix~!䣮z=h}nj e⠍Lw+zV]9rX(=#8$RW SҧY/=}K3!^D+s?&8'I|߳ {.Cm[tV1[J J;ЂxiasH7 lnS{/~Xҵtp?*; >Sfp#%)^xzXHV3F!DF3)WJ~C UTFO8F=kH\pAvWEVZTdw:MlSӊ|)gqM5TP]N'GKN?^ #%!!!x4t<@āӒf22a\l5 Cp!؋'}Ein/c̿X'#g\Q;5Bd@`O JI5nn?*c/<`o$&]Dg IZ eƍwo(c+O-4 e͌bٓ{B$6ݯNg4hm--mnmŊ۶kݺvjG)TBĸ+|#@o,+6s+RJF+5q0w"d{ H R`c2%':$ =ý0{;#J ^Ni#~b `I,ix$߄Ye.%El@i٤_-V^3g'7;׶o[dr%5E!-$r5m!M"iloZ$ ɫ؍bA 1/CPl$PD0S}srvza HLTQA~vIE {x.b'T`F*?w깾 @1:/`_r֭צ\sԩUoZvV碴,GSv輴9inV($t4z2[SSh\DI# n nޙp&?r!Z#Q~ur/ml]>[kq]PZݶ^hk6K'vm_SS!tNDo~PoI/4*r N>Hp",)UҔ\1PC[GmHhƒ!m86v=~jm/̯i\v^ 8Tƹ%6ݜ_zl9Ҏ &hLÌZ-Ig#qLAO(GXGIGiiLjh,"M|Ii!I83@bY/rja~hxqN?dLj].LkOkW_+g/^^>kFr^|a^~qh3p<fNOsJfn2t>7ߪ4(U|CUM)ND~2tinRcťCe\߫/V#襲ڶ|7;i^ÌIƬTFcJE$6U[AFQCa(G Yɹ-3YQTr%6g\ub eV?BrkNKr mHBL )Ts3F^f*WO$XDEW,a: ш|o 9ڿl~ñ꾏`a>P+p 1x%|)n`CuC,c3x%.]\ ,)` =4/Ig΍VB_ƌ}e!c}nآ^nuv76 z֮H;xaw5*`K؃N܃{pd\S}R7/޷o_5l6DR wXjk<价5{( .3@&`Wy(dP^1wƏꘝl F4D)!RVH>rS)$ao$8QnYȟP(Œ H|p)5ͷو)vUlXnPܶ3M .5Z5pOtXa|TvX`vhhmG]c˚uѦ.~Mߝ^&!a>?fqGrfءyL͌(QN3 "1Q0\M5Ϊ;Zj,{twjkf9/Anp< >T~o9zZ&o:D.KgZAZR7̊AyHR)IѺAqGvx|>ؓw+Z6EU¬$AO`z$젪}fѯ4D`tv:a?|ȿӶe X{#2Y*E39"J.1, PU)" :gL9#?=f~l[n mB4k;UgpBꕗE*ٗBa&~>'p}`iDhn;]Z+{QvS/U/\[R?;%k7wN}p>FۓV GT$MC@)3]G~.\|5+-! ӢHg:-l*F |>C?/ MR/)F;:OqS0D~gG;;G*`Βisc:u0oyg3Y2-MQHS&2S2 FhGKS.P[Yq h| h$>#IxtPBwPmv݃J|>[/lŒ=w5/ g 8_ԗeG?v})i%߯?(Mr?ѭ ?-_᳘FQVY= ?/}|zؙcqaBaM=>~[^UxN(ym &_&fY2xNqΆ+?߬|i*ґvnd;]ct}Nhg0n2r*#yf6\qY17P1:R lGNW(B0?(˜}HA;$5߇dv:ÈZ|vXg,luY1m$7ͥ:]vCm_Mt88@er!#LlF@Fn#kg(yM5QwoZ6 0R1GR{!bc"$dB(TJ1D}\L{^xl /H_wk"YtԠp k>!} :.{Va6ž62dh&±4fVArgo0&DZjh hu_%:j]Ryf=ձ 0uu]vA9~0s|>M+Z옌yL@)lL iQ $ Fi4rO o &f+q&dU6,( H jSnwxAug`Ecm7޿p}̫†/^,.k 㫎:pCm2ipcvo޽57bKYNΚs'Թ'k  cK#Sh}ogʤ,NfH^L\+[VX\X-dRBKƩ$`>W3lE1':m^Q5nk Uu54U#Cg.>glJ3/{F<5G\x~N²)g5GyܦVe:?0v^u޺v|&MzhrYay2f8:`AyK%倈l+r8&5>3/.%$)b{=n=0(Ov ^,!`o}uSCMguJ2u ōNv%×\:$OFM!(J,a$γc@B_3M#Nq٠_Ȗ }ޏaV) Zؙ6>8fj-Nn? k,^p@ogksCMUE)r,z&+ C=LN *a/᤿~R(qR ~!p2:RaV_[$/}ScQ-vyo?9bni_sXwY!ULd~̵oN~Л>{lmކ|NT,sd˹Үng;u.oօOu>`55yCMKUƽ ,) vf#mc C=}z}c£}VnUٽ+tMz6%wd)$ 7eHCެj*irD D+SdeGʛ@:SOqW[-d3~U#%/jFD("ΚAtr']2;9^&?x#) :.oƛy17I96MNA|a:Go_Z_2)9F<}ӧSE;='CgOvT9~5S!Svzf'ss; :>!-r3X#r|CMd +E{# ?}ssla60@rd SaIfKP Z$5|$m눈n ,|7_ - o /^ޓ|?E_ɇ/'O4mgǷ+'o?Y#Ş?S+l6LQ(U RZ|C<ܶ#ЯͿJ#gYǒ 6kC^ ~yQ@YP[iUyzFu9ha sҴeg^26 U ~$3i?v:#ǏGwoSEAgyF.mKARlDdqhf 'DI"P8~Ǿ6bXx|d\/R|P=0`0 zzWf3c3㱋HW<|eE4+ L7gMY9c^-i-j-l_5m\" Tle>'J훟]w޹yUơ!#?8sLģHN:jiX ~p*"أJFguX~ͷ=՞rS/t:\R-`>.aiZkσM[Y8<\ |=rim[L AF9E938oe|3CVog]*^OsrGă$V0.By;v? BO5`s>5W\o='~Y@4挨BcF;2m<;;]v/ݙYo6!4!Q6i RP 4J6Hq*RQU)-JBCQ/B VH!4U؞ٝd7ɦQCk=ϱ~99s O/DpgK:x%yp(4dZ-TimE2[Ú[+Ǥvlwm_^H}e_i4:Þ$J ftċa8"畊WDpJJ\KS`;<'奛4;!Pdž+۹'wG+|t(XSm<O[%ʼnaoUZN3^,B7 )CWިij*f}_ 5xih^q;<{x&GcB GG(|7It2q%H$yYo9$0Փńkzj%l\4r>9\G.;1U YD!sϡ+ډ9UYb8ۥvdK2vũ.Sl qsũqَ,g=he<$2woY=7Up-+őrn]7k _]+\O ^Z`2a+{$`z)F,6]y'+,@\o{cOhO8w?\۲- 6?/j𶈺V|zZCzEq=RW/.y&aJ~^QM:ߑ J!9%9T@Ϫ`Q '!E!!~ސd?fNHd?fBH5ُ9T3$Z$z $F /x` Ky ρπ`#X'=-mIl6']]c]&&iEVΟn D? a:XjX|}ZX_ŀcsq_QGb'wJ DQT;D%F_Ljz1:2L E xILNY]O2uaw?u^ >6c h~(#%om;k~uw p†C%/ͯ,Sݤu@ƚɧ= ([>n ҵQL\m;΅ǐ-_Sڣh'gX)B3  JoCs'JA2ɍј aR-q!%U&|E k!zr †FhrmLeHL֜,ãp(= K;́jA'f,E9nNi0M"jz]k}@Ki@%`iN=D`uJAd5ݲ24iMsޠ2d9;zypF7L~N ] -68rr2k}6"l:Z2" ~dD'(#AL lp}ǐF>ViEc6] uEx&SeQ1o _,0+&{ ӭVdfh1`db )'2f+am&*IJ,7X];#g.wfg!CƮp\jG*:t 2E TK iGDwB`Ve_6^;bZn6 ]Gܞd)Df}rgaP_>T3EQ-<{ssʶ4R̻CB^-VIPڰ Lͤ1}rzFq @h~LU$u~mRpM6V5* H qߎojÝ3plODGNIv qTILyHe~Y^&]F7h=Vp^odUƍvR^ 2:( Nc4dNqB8̐ [AvTR1Ѳ<6Z0T)m Rq}^xaM` pI\2юӊMR`Z:R+Q4 S@`2IP \p&(ošn@[LƎy.ߐ(bVUZL.XPSčRKr/$x~~ ސf+ :x/SgSI-uUp|YJ䣤Ǎ.v NqEjRU%e6Ԭ;/P[w?ƛ7=^AJW)6xft;|S9e T Uq,w}&>GYFLVJg:s- d0 16&2A̩|j1(_<#$YPxc`d``u @djwxŗ{lUǟs~o+[um7頵u]/kkU昬B\ID51Xd" PP 7D6&yK&aO;9?,w nQQ2P%V%ҾI+WuU{6LW;-q䝃S^fA Ƽ.uLSS_"ѭUQ~DZlT q'i7YξJm!L_)."#0-?$Yo`WH[# ݿ^~4ue7;VB~6;A;8TPV׆mhcϳ*evƍF1~:ۘ_~/;IYR%]!J>_N?{d:/H{O斂]Q8S Д`s_I|_D6ʍ`S>! v}.o%^JĽƾ)=z(D!I ^qswDTU 4zeRcZ>t쾠hi P:Ya8=B gj}>[cs\S}rt:d~Ŀ[mO&'QGuZ E>d}zڜnP<7+? i]%G}Q>,/t (\)>tMuNn4aڵx_?yMkBe`Ԙ~ :qS|M# ?ٌIIֱ3;'s'xcS2w2`gݜdLx,ȟwZ!_9l{Vb{_Ez:,֩GuZln^{?wfd|;q~L6#*'}ȝSVoT 0},|r/ c(oOz" 9CyI΂컶˒;=QW q&cqG|ϗ| kowLr~Lo f}Yd|CON 3_JO8UA^$m槺7үF{YH'8Z6ݰz{5ڡ?7>CO0vE>D/ݛ*E;qߒnv hCSQֿB_K[&Cjߎ}G+vcOKuZ`hU[\!S׹-A^nPfNף=kKT%~6wjL7z!! ʒ|>UiaXovֹ̙ӽ>šzW72MKiiHGNȗ˛Y7g-$h0n1?BcCzL썁[zw1w=7L;έd4ն=!dw_C|3j!3nھ![mv9_նCm8,wMH5Ka\_ {r^߻2Q_aQXV%uPU[7˸'MR+m$};`H{);UDhweķ G)Em5PTG_}y0cGC֮~CW퀝0Vv|)gQ3/JOxML҉pRD2D8%$B$E ͊F K`ݧmc81y1b'& h1Ƃqa&0SM%@&%;Gceƕq8mgcaX ہ`9l{|rFtF~Lә38"Wq:[SYxA|I<__Kógg8C&Vek87{n&6'S ; АКН0L8&EbI$Hj#I+iKD%bɉDnbKzN>9&yb&7[56y<!_o_,@ %{hOF B% ~[N +Bd!_H. 5BW᪈ JDQ+qrƢ"_LQ{^ё.faxN۝@qH1wuW|Wr}7xQ./9ǿ3,Jgޯ?uHR"i@)I JcFBR*.=(de2mTf*mdI2DɌrt9Q..o$9].;#M]Q*D JQ) ~[QL+kmP l J Z#`YYZxy|P`V T:*=<<{xPEV9))J(KR*!IiW*}YrMST(VEV1T*JՌj^ZR]Y][TQmQǪMaW=^P=?B=b?mi`ai24ufF,=?F=fFO3z^:}ޡ? @$ eC"H!~Lzio#嶹۱vankwwt:]|ngl'Sکl\7 Tǐm Vհe v(].nWqudDF0MFYw߲rw#+{0=՞ÔnV^^UR{1 fy4bz޶^ozeXL}>v_fOէO跼x]zm{=zӊ2k:eݲ p0L , |8m(Ɔm4`SjmZ[߶m;D S;C;>F›=ufǨp8Cg$:)N3uB'4:-Ns9\p~u W5B u mX7wGa=~x[vuD0b9(f?Z?:<&k3lcq8}d0ngƷ' ;̻ww{Ϩ'>}{/ƛy:o~ addݤc;97QǠx>D~Z~g3&P.MZ"0VE=Q*N<SiFIi"Jj]Vs@y<[wN :L%Ŧ5]9Ou[MBU,{]r twNkI}r!J/i)=S0ꬪ2Ӎtt5b^ oDc*Hw7+K_rU=K;&7+zgg7nշiEbX߀U=oS-w{y00xmZxGޝ-4efNmKeIi4IM"Kdq _(<μ;:ZEt5^ cbFDN\+ubm[+zel/dDFz–I<.)"[b%G1bC+6K2C<'r1N(6+ɕ*rUhr5\C)XkDxI-'uuz߈Hr1^n(>ʍƢS6.&exE WeLeBKl*'.Ll&6_]r R|*[n=2%"% D^̗6 rHG QbRNRl#vr(&bV.gșr,g9b;IΕ;]vrW*w{i2+ɜb{i>/#bD{b@rP#n3ŷb- (rOKdY!ȊʅrX(^roI̕E|-wʃĮ`, |J>-|I,_ |K-ߑH~,? J~-I,K-AuP (4B4C ( c`) K2,,'+ʰ  kڰ l066hA0:atl[l ݐHA20I-L)Li00fl̅ava\Ep1\ep9\WUp5\up=7Mp3mp;w]p7}p?<C0<c8<OS4<s</K2k:o[6{>|G1|g9|_W5|w=?O37?/QD@DB 0Ql&llQ8R8epY\pE\ WUpU\ W5pM\ up]\ pC7q a;v` ؉ 7psĭpka{0iD'8v8tgLq;N8w]pW w=00hc:4 0/A,`pO,a+XŅ8p^7~?Ax0ax8GQx4qx<'Ix2ix:gYx6yx>^Ex1^ex9^WUx5^ux=ހ7Mx3ނmx;ށw]x7ރ}x?>C0>c8>OS4>s~ cbXN,/V+C?OS ?/K o[G _W'?/ n"3YT'~R=5PHML-Jh4h,-Mв-O+ЊLЪNkКMкOІmLhQDmN8%DJ<$+Vq qBaXEIO4hSڌ6=Ėbk(h ڒC)JS&64I$nm(LSh;Jh:mO3JqO<"n[maIh6@shGډδ J8X#)+.Si娗l~  HC'LqRUN ih)8Q-V'ioCqt -8gKtHtJtIGt KtH't JtIgtKt]Ht ]Jt]IWt ]KtH7t JtIwtK=H=J=IO =KH/ JIo;mzޥsQHq8ZD[(#>O3/+o;~'Y#wX7B7?/-aI ,Ȳ:j"VjfjFY1RXkikkYk9kykkEk%kekkUk5kuk kMk-kmkk]k=k}kkCk#kckf[V̊[ kiMMPv}`6W*껋ł;U+vcO)媃}y{QXrvRe]mJ]Zcԥ`4է5mP'hMsQNmhyR6"Lڶn rM2pu*Nn6:9Z7EP7E4%4ES\uST3?54B4y@П-Uj~qZ9-L Όp3MgBcm,m0+g:|Zp:ڻff00]rXgR1"szdrݜRvݸS0Y͢sUh 7ǰb.?}䖩Zqf8F˚rYmbυ:.t\8۴mZZf8[?Tp6Zm TGG_0MJ7wav8?hAQӂmB˛ɛɇ[ Z`/dJ84`n!ܖEŚ(r[CmYbbpJ,wPpLՖӖڶLUM՚\*eUeմq7ja-M[aĴbӖkvVV,֊Zqq};h,缙х3R%G( 71}r- UWlzZ:uѬ)6dU].YYn{h)%?HN/˹̬$WIsBQgWygrW"bfj2m"myS ]3C*>G*tI m uM*zu(ˁKR.y+>:76K'eu{D/I˙sT?Ո,)) ji֫GksC>h Kz‡+٦7 ~E%'RkVY1^FrLEbrK(Ծ KʌTj#%ߢ'TMthvUiU}U~U.YjqPzJ_cX m0\s8sIKG؈o9Rk9-+?G#4/W=oocδi6]^iyͳϴ]bL=K3:_gtp|WQ<}/~?68g?iG.b.qvs]~˿==d$'?np)O1NSb)biOsiK]lf ˰}3l1 bcl~Ix3^5 ox]co"^\'{zW./ƽxY?q98W܋(8_'~/8q37Q8]쿋q_7ף;}ݬq~IO2~񓌟L2]+')Iy'i3viK]laϰ}3]ߞ?^T"}-DvttDHw|v섀 Rhw0&642KlgAd xqA;d, $A d2p(`=T "KE*7tH!H(Đ gL88VO,%J֚udCe'_,\wI#59;4כ0 ;: 1Pf:Y6P8}^+BĞzLD92k!AxJypi]ɬ)S)ϰa9iO|L%_/A^`Z5YK%X oPEN} KhY)dfCa]*a8:,j2]B6EEA9S6XxsKcLLlN^YC+ju }v)TA}ڙ'5)m\vKڷo\ ۗ4o=^spa\.[GY˪]{+}aIm,,mRLӣ%|cH)PZ{hW ~BiOn tIC[ҰΎ:=X ?N?_5yR2MNqG8uvfi)/y-Ig2 uSZrQBg9Bb5jsTkJ_o힭l잶֝MݣyDcRgPct8m`e^-`v^ju:ew;be0[h*#emlY~8\TԺ% d:i(_-N腊jHs :jxʀC=-3-=}|+-7l9M^71F|uKQM~xF<=Ӡ.t#+&/;lP1Q*j ;훨Wq^OVV lW{թ*ݬ{9mXrs\ufN9Tq6ꋞ^eYShvJ)((TDqy p=(rP5\gJK^%\Q5uPUWBo ~,՚zڊb51W.A((wBꣿ Fͯ I7RCf^K)*l=*C3e}ЄR_m=fͼb;Z%TrR7q꧕+YhwXmb6˕1APD劃7L/b|I˂_4ƗdxNx+N`ёLL#:PĠ&h<-1 @w]F{㨇g"|S,.q  w ^w __5P[:=_oPM%|IIcQGo`/?67bR&o6svLcE xc` ԿELhd`2Xΐi-7P,W&_!<5ϙ@R:i&pu;xڭViwFyI,% -jaiF&l Ac ];_ds7~Z/$pweZ 둔/&< MQ|(;{!eQڷDD"PDYd|QF˶WM-=.[AU~:ʱ;f3th=%UUH=RҦe+I+WPˆN"iHgh5(l(R$Ay ͐ʧأVK/yw9?)[-9#;8;]V7d; U[6;տ٣L/4#X*_!O(HV SѨlDzO8bJ\3FtwtBu =w„DzQ 'DJM6XI٢Jj+&Ny_v38ԝCVNTrF2l 쓘Log($mlgyN0urp0.QQC"yRSq"{T4F썰w!8R?T h)7T/l6!caYVcJɶ B>ROk/Q'Un?3N߂`o8H]d[ʩk͡Cuq5M7ib.X6i) R(2w w(U}l>ϕ8o's0쿣1t{͉O7pLWыS,]nhVG\S8=\ `bZF)|s0h2sl3g `9 v`9 `:a2AOx8j;cpC3FƎ S\6x8Y:C"@J"'ÅÈ]UGk ,\+#rustup-1.26.0/www/fonts/FiraSans-Medium.woff000066400000000000000000002441541441327105200207370ustar00rootroot00000000000000wOFFHlTGDEFWh48*GPOS3S`GSUB5,Q%Rs_pOS/2FW``xcmapF6&rNcvt ASC fpgmB4 pYgaspAglyfZ_&Эheadp66phhea!$hmtx%ploca A n>Ϲw&J+EY\s-ROԸSW,?S8 Q uT[ Eh_ *O!jr8{Q0?8DX85tX>ͩoT5|5Ys+*P%ΚGeB5YKS٪g 5PP#O>koQKBrVj+ٔ)SQSVRVS:g:ʅK)QCy1啔Rzg\C0z ((P'R@GûKw=W\MQ3VNUY, ̳BE[Be*v?OD}74V_f׶ ..5fu޶M_B}%Ij?mkrCGoŰV1Nv1E_C*2/¥(Ӿ5޶&^}q6\}mdy]m5]̹e}|WSꁾxz-/2j#MV=Zښ4'hgs%R{mZY5w:zp?KW65t_]_K>h{YGmH%Z`gȊ8ۙ؆7Bv3O-vL5&ۖ^& [:W>nᳫVEظ+,9Cc붠nc jvge;t*k3>E\7E⹱o۞9ۅl= DckO`=8h!-leg6iwLa Yb;e Ȉu}F{ q)Yg~5#5[^m̞̎]M=ʏٯ}َ M!?)KvՋN [) c1~b]Q>XёC-q}u<ƿz;}=>ұg] t}RX(z퍎¬\\#7X|UXmH\9,_u$ S=_ۑuI0ֿNwyƄsG)!:F_"q~K5wٜ/jvĚtaދY޵Q^oKwەszmό%Ⱦ/xk5Em;+myyݷLگb/MX{YDMOxw6fma̖.sMLAm[|#>rLdk'֏gH򸹳; )]7ۅ8+f7>:}3ѰčacSjb5 {FnJtvwD}E=oEdv^*ơ7<9KSӁ7Go$ݑZ{Ҙ~좝>)܏8N>ud\)Wh˧]y kC_Rlc:׸83/. I)A3$c5Z^l;:1N쐝% w ܐO[h[Ći?i{Ùa9uHh4D6Z_eį9(GeNu*gFy`Qh5Ig;_^GR|m튯=y|~#6RG;Qzy~m'Gc7#-lMwڶ"jn{Kh} YM9GBWI&dlІȻ!*Q]}*nHsݐVݐߑh)}yM>Wy82n:^tDD)pqBJzi~ycM rA[H3@`stHȳu=8_%:GwY/=:C/Oyb[h8sAN+Ez5$[]p췞{'Csg wu#bv8˜'Hlcۑw|V)3ͱY7PC#v;)OQ%\K!Kҙe R~?&gBO@79O{Y_oxc6pQ>h'ou3kBV#c G>WPZ UYŔ߂o:{HӅ,~7^~?imE({l/_z;֤1pOqV Z V%Fs#Ǜ*L5 v/m㿵&[mIAtm;#ǐ{ 9߉ 4weвy7:Gn&D _n[ok7[,{5O h }~̻{jn ]etoB o~E]}Tk"+ɯ8ۖ v樝QyvL }ZK_Η0l.whMPB˼Ѥwf_Be q~};):)G}w.iJ>Yf]UuK6;Y1"GG^o_E>m7,Ze]#Hzzj)ڝ_: "~`*xSmzDnl#߻hVƗiݺ֐=|F?Kna9煟}p ǼqZB^'w} :,Uko5iMU+>Hy}+)5=Qo;]47Jg 'DODPM3!zKbGlzjWK f7^C3 Y{y]$ 'WCNo1&;Ӥca]WY[>>=ݣw{pѪnG_;/5',-7u~_9;^cG/0vNCTu즶Gz U|>hNv*_.7&o/-ɜWkgqzc"6*jqN |7ٿ}}&|[#cAC[6+Pkml GHܷF9I^F&>e9܋[׻ Ik4M,xG7RN5RiH!TƳ\LjzDZtNۑBZڇFZiR=N-H/"mCz }Y}NOF%=dls6ϝm-z!=Yϟ=w2EڍԂǩο44k񑗇ΓVԫvYq/ŵ7iG-a='3usIq̝~˜9Mɯ _V;uvs9.'_Cpzg}C,̒A r=Kҹ凳r!z\š!ɒBʁn.c;?qBQ&.h۠`,?[` cpypyee%in-C}lHO@:k~,H#BZizcpwssguy#%HuH1?W2}%j~vȻV:>nS>N<|ğ/YnG|{vN79yC'1np7S7$Sn-0=? {{:HbXX:S|HK> H8;+6[wJl"riw>Q孄]>H]W%V^ ye7ZCMLNxsVMhJXblZ76Rw]^,}6&sH sy5Uez[pރt?Zg[QHO"=4f0g"KfIh~1k;D/vegC3.ԒWk!J&r~|h>5؉X7,9$ j>.Gbzg;ggϸ;M,jv!srNdmd #D?͠AY}DП{>Off֧uqAtCz_~Hr\,=Ԅf qfu6%-h{ܟG̽B)٪%{-pC|JZx^_olf38$,gUʎːDZnFjH686!w Ivu$kGډ)^nw7w}MlԳi/BϏ^FO2h)Rߧw1Pm~~B7ԛu~Fo+z~O/".qGIJ)2Z*dTKL2Zv/'Ir,t\,ߖd\!W]$7-rD~*?y@~%eh5M W5`5AP՝o7Ga֨[ ܛKpsۺ?lc.ԵjP= }>Aӧ:/EoT5e!Jwr,>W*2>6kY_;-OK_(!1b/1޼zdI^/y|))7R&c2N&M2Q&?d(SJ?-R2CoY2K?'ʁy9H r(Urߒr~[Ζs;rߕB'JܻK\#Y-zQ[VKnnC5UyPe+/<yVr9| 2p/E>Kv$>SL}L6 dxW o efSn\Sa&Il&ˡfzSkjeaf3̑k̗#L,#T29ƜjΒoFFɮrW,ɟd_`7ҏ*ޠqIeC7<UyU^FUߦ@﷫b_5 z?L[=VuVB)u84|:BHZ$d:RF(utL-ROTI%R%Udj9xN8%'UsV"T@-[2(mퟩaaR%ʯ//`A6B % \tf٬ҤATrH^Bw.WNMp)`&cDS5S TjuZuWu2t\x[F^j*Qs"XYV+TP].VK@sԭ`!Gjz ]-Y,jBڡ]|]2]3t}O+`oG`?wh_4 uKWnw;O|]Cm1\]tw< I Aiy~ z~rފ꿢{=GOA{ģ %`OU>T )%dT@N[&ate6\ [Q~ay2irr:4R9`׷La 1hHc1MrYn^@XO88rtyZ+.}4V @IVB:LիȗSNVBg.G!m|ГUH]Fk𩆾 rր@^= m6Ж_ 0@g@hJ>S5:KgAfl5]@Qj*H[Y`'Adž@@l}b}yOP#A PRT_WkTQߨZ Tm65z35[߃`~n -Yuk~X?z-,ٯ`kkU7Uö=~;ع<{{htãJ"q.% 80Hc1)ľ C8V)r c@9WU#FSA WJU‘?C*Z-I rQ} x(r; 1AKNǴ }82|(> ay{R~嵲#8kpi&BN~IyҡQ5h],<x^q/ FiD%,,/c6*4_y;}|o;g5vc+|4X/U A/,/_ifs$禂sUͶޙ l4A?ٜ-3PܜU:k.0sHbVP--26 ev17POO38lU J| W+O]jkkVGפֿ[:zoKݥe4 Z*!d@*_+eK> y@ шaFՂCg "a1>JcYR ?X$4\1䊋\q+;3@SJTS(s5|_ݨu >%]A;ߩ~i >Jue"ۨ !ۄ㓩fD<#<g(H&x?;Oj!Ƒmɶ m6l~WCIn4 7dX<fe˼j2O@=Py@Lz4h)X5=Qנ1iI &n6;-!q+H$k6Z"dL)Gd4xO ז%$8\٤8R`߃ 5 eha`m Y[.²$u<:d!Y|<k*bl_4/ &es׉djL~rt8QVDZ&AIЉPR3,!5Z,[R<">rF~oRs";@TWJV"ۄ#K)F{?Q< Z[֦n$?Դܭ$welEo:;HG^G7ĕ$qH 4x씝:lƗCIJ;{m5K 8%PLt 9]bRPo !-K23ތ5-Ne'ICTD2<,ۙ5D%I$z>'B\b⼖%|;їvueXǐaxrI\i ŤY0RHRj )u:)u4c\)dH2xh㡠>EP594]=&Hٌ7 ͛XS'o4y#j7"4R'GIR' :NѤNiI.`MYH e5) i M-g+6 :6 ҄cX>U icHs-c[C$Bf%ɤ A&fZAijcu'gUda)gDXEVM`D$%( &rVѲg GȰ d<9 +N=#ogY<+`Ը\ΐlcǓɶ!rK\pnsN 0IN&A}Anul$z<:L r5(Cc`TdTQd`cY;#eףI\Ҵ4$G&GsO'ucE4]" 25L#GsII:R3Q,F#Oc>Q{-ZF+\~_c 2q|pγZFkh~4 ?ZIuSG]EnLTneC^o{lxw=A gsaAT?PU7UoAG(Q*zFY4=#/}"/=Bz=zzJx@NhXGOKOz:ŌRNߤ~яD* U)d;#PEg~>? U}9_EkRK^K I^Hޥ.%v>%.$KRR|.%^4.% XR^!u䭗k)Idi)Y:Jp2IXAN!I RHB^Kxə ҏl"[Ȗ~J6RqeH?#U*Iyҏ<"O|䉏L/y2<)&OCȓR<)$OJ9]I)y2y0y%OJ/y2<)$OyI!y%I$I)IRJxɐaPI0aC.mZRu/mCiЊ/ϡ@;=1{h3igWg vGjm(ѴQi h h hfziq hqKiq hq hq hqhq1(CCxE~E+g_YQ F_iW>E;sc 9˲b ;ȵ28hHao7 {k6o>>-`obo90ox́|s`~te>W+Ghq UE",t IJ}`0z{y{y{y{w ^uA3\M&DӢ<ᇑ?OcKɢ 1Q$R-c:דQA2j:5 *ER!2HI`F]Ōc>crG19֟Y1ig~0VDUiɴ#dZ2m8,FeHlFeH!u1Үa=3*>Fs 1@62IBH QC3eiYP2 G1>`,=%EkI YZKVdt4H\, %Q3HcFZD>rGU WZxx.ZLHW#]}k5w1[0FS[y omy#o}mEu璺Eu璽do-"{$pܹ䰏Qrdr6#G2y,c2FeZkHJ{. gC0,sOm0,c9FsIz '#I!!s\r~ 9_A2,%+HZ~: _NQO%+\rNO's r~:9 $|_BH ~>>>@}0^L#}d{l&۳d|F}ej!煑ՌF0қHq|a0xe y") I,$)I~$ rICF摑H7!g-B3:ΡEP!;)zAc)fZ,UJ-=u>4FR&M|ZH+i}hd *G:զFԊb+EY4fQ! !)fĚ+Tn.N(FS6MٴV1!!5[?523^ 1"aVFkci18ؘle0''3BrfV1ø˸Ǹ]Gƣ) Izx>hcv0`jiojL13RƍyƕƵƍ-RӓR猻/061~qi9ㅀ34F1:ƆƦۍw;g&4c188;#ԸڸθqƝYc2{6~`xeָ,bj 33Fkf*Qk3gCQ cqqXh\;,3fV6.>RJbb1r1ab1XvTeb1X*+bbȲ:IYd*= {ZCF N9>Gp4W\~X"{XBP{<ԞB٢GqЙ~jSBP.Ԯ ]a o }/~;PԺBVZmTEm=E^Qʅڨj2Q86n݁|i83^\1Vk62.Mr|Y+r%EypT't gTй 㨪vDG^W^}rR>׻uF W值& ySYZ;hO1M1!s[)]얋rIiǘ(yTl'e<%O^5IɨKD)hMʢgE5;F]~Q əC8~<ʟ:!c%S$[I 2QZCoZZ[[kmQ;ivѮMkGj?uѡS>=+dL<*d̐rDaݵޢu?K:RGhM1隡c5S4[i :QsuN)vzXܝ w̒2G d̓@J,ŲD2Y.+drBKk6[6&Tism-Tu|u\u.B]u.e\WJ]]ZOzݠF}@7郺Y->}W#~G;r_IWEZt|?G?G];v*\Ac: |#bM\oZ\->7|+7۸17܌s nmwr{廸wܙ wnܝnɽ7ܟ@ăyNd)< x.r!/ż2^+x%|꽼u7n↺\Rp7thƸ42X\rx7Mtn즸<7Ms 7Yn \7w BWn[ꖹn[Vֺ:mp=6-a7=sq'6ROKi(J#MKi*ͤJn6VrX&ݥ%]tY⥧GJ? e $Ia"#d4/+EyW{^%Wū]Ub ^ErWwON+XO/ԂέͿ%[j.XPGXRgEu YWoV5j$H5k$Y)䨜o|+_ZRKi&Rҳo~_W+!S8iUCfΑh~__\.RY-/bX"Ϸ|N*Ɗ5o쿘G?ʳ!]"j-U j N7y+o>~S|s|"]EWu-]'\ ?v~;쎸q;.Jh*ե&j&Mc$!3_lv%$([vBۏ0jFv^.("-Z]k"m !EV^Lv ͺU{PgGbwy'FEG~U_ӯ7mX?ΏD?9d_5w^UY3|#9<LD7jS6o`-m VWuWe\qE),u-,(KIu4Tp!Aś tpfst,XSS4 plphPǃ:c ɨ'b$pQ?O[+C(>X~hG] vR؃)U(2*Ā`7xh/>t_fp>-P}B1;]a cGXp1G&,81qCl}^Yt O=}x@?_ <*k0/gyK0Jֳ`~ Vs`e-x~^/F/`E)]}[xe-@p\yLOڌB l$>3 i^LD8/fCv!K°Oeb5#؈'E㬿nOcO4N&Xj&} & O0*u * O]tis(xEQ;/FiDmZQąuE|D=0d sDPj-}nZgvtFt.|?Ճ`CI w K=`](Cm+i*4/ ),$q9Le2ՆL ]SLtُ=_QwIh*"_+|/~;D1o`M_p?J0*T 󑅵wF]@oAnNH}\VVuV=V߁yO>S23)CiвȤ0cD?ϐzBO"?*]gH#w.] g+Owºc#8Dw|zQ 4]dttא44p4pw3%t~8SΛ J ZF}xΡF 4+@c|_1~(>oq%_(0W zzՄ>t7'q}z\謜Ui=Do\ vs53zƵ@6Zzy׹ &ԃ- #7L2ZؓB%oQIx\*ȳ v$>^MHT>ç,"1*huBl H**o^"xL--Ҵ6_hv u"C#_om c~b@AC//_6H>LOiSO_<=IEI ްʢht ~J[+di_xc`fd m٘Y hgJ0@sk2N`bS``cd 0 xwVՕ^. }\E" h'qD E+E~? p]{qf)c_nZ>FD"F;VT1n |Wkcu\o {''.j!n#xk-;*nv]7۸r|KZI{*"r/b$Wȕr\+ߗ[6KFɏqyƭ/1yBgd([2KY*dTV)֨GO͸בLZE[n< 2^&$Q$7yYR Y'{0S{e-y:ܸfc]+ه6HJ?eC]lUv]nmh.:%,dMY_flXø942P[0&ԚYs;^rO|:Z#kʹ-+(;ҮڒygRr('%r)\/?Kvl'նN"V;_ʽR 3@1:AkBpjx- :~l;ğDZ8.NOgsqz|7.4m-g'vuq_H{)jo`6IUIoNGwwϧ3WW7ti&-}`V'k5ZfY1̺fݲوlLv`6pTX,t) ( - +L,l-|TM͋-Ūbwvl[vQiuTU{c<``J7yUhQaeX+֍Mcei8>NOǩqZ|>3x:G[Y;j')ӭ]c8{؞mamU'vVNGwә{.O7[q__fzYE:~ZoFefcq qpޏĽ_{ƽ'6 kz\״wÇ=4o{вCQCiriJҴ5B͖kKJMK᧔N^ks_7w*-/]=:FfRjQzWuD߯^Xz ]\uqMD6TgC׵iBIr-D>z 30()qx58*cu}(qe\7ƭ/v`7b-ĮMv;~ecϰ۾j̿˿s'}[uM6^a+QC߲u2*e_KPmw QJP :utR=&A7D/ڧ'ujr mCy-t ƣbODOT 3+<a9 ;O֠3ޠlD+K'㤉<"rNʣr^Z<)mdN "Y SLN+/o?/ߖ)/3T$/j!o;r̖sd-s\yWΓyr,\(e2Y-dUi*)"e mO d|(ߕO;KOJ~$\nrt۱Fk-Gպr֗h=i$V= =hKށvmOZMID#:I^%$ I!)Eai51Akz:UI] t(F| ށ7so턑9_r,sGFn`M&{(8'g A+0N&3|o՟|B6dן"/g|O%,Z#qَـ<| 2^d,^LRWb|)?(N DoyK5j9wru异5yJzn5c r'œװENrHcIOボ׌e9s^GVy-Xڱ*fuXu9_s{zؘ{0Wn,TCnl =rÜrܽlǎ|ם]Z48|[~yܛؗyOv1e>r'{=_uc !GEu1*! RERmk/1!?0"_~w4!kY$u )Y!/ ehBmHHӢZ1!aIڢM6%YڌED=YJ z&yҳkM/yMɳv'zy hړE5X/&mz y^K/%ڛ֥=)֣lJv3J+)() _yP+/'( KDd⸜xfՀZчS߈<FD;-9=Q缜_\沺e>grx5)2E'yv\ܻ;b>Kk)rLsBfث Џ_IQl_׋)`{8|/!EﰥK𵶜VPVQդ'胶8 I] } ~Y}^r/bc,~|_t?{ #{j`uzc}~Jyd:\sք=_,YF=07ƓSxp(=0y/)TJK Xgk{{1 TEosCH2?MȳK9y*MUlI39XE=3N:Ty*"km+{Q佪mGԶo]DޫO#;yd8kuDkZUW&׫~&B.:s0B=_xڼ`8>3{E]OI:ݝN]SIV$[Ŗ%X+c0%!Buz B !_HH18@ffwoO _ .z͛ ҠI& HhJ ebt4&%z]huz.++// d@}*{77|#kair\\rYvi'Vgd[CaJ_DpiN6X,d3BdG;9ơPn `܂4Z, aJ0B XxL/.֖ԻM~'=^]wSwEMO DYHAs] q){# O4V7p8&v|b&ӟ/G}Zds2d:M2#Ahi?n#"@M+PEh~0nK *sB@(Բ|b ܜ@@S33Λ^}3n@Z~faՌd%2]_^>CVk22Pk5ZFP < }0Vף>i/Cl3xce[6?5`+"wr/QVP^;5K~$Í*n@pKDVBXC;|H4wk1l6[-b-3nK>r_?~o٭Wx/,;ZRwKgNcѦArxmg|ҜXP[=]$1$L -~ h+F!+ xxCզ nt6]y~|늂Ρ9uu ˯X|h)9..si]%lvJ; ('Ut 4?T5B~&j "m" ̪ @ʼB?{TL]lJUB LR4(MtI@t^GmF1#5/$aaW}t u>,q$c0Oԑjn)sM߮My0Q /a| VGXxFdYSGvv}*oU-9q calq`kBZ " ?1nX ;H21ĔDkcwEQ [oogHpC=aʏ^f`f<`c{rN?O'9CB{JPm]iSߵw~O6GS9)W_Xx=Y6]*} ct="c+Iޡ6w>B~roinA@9v扴ǦW#|' * Bj q9[;7738S'2Yg.`krx <w9*=E\},~oZd7Pl*$,|+FyV\?sW 怲ɃtetNqܦW8d % 6;ETG`i[?.(4ۖB̯.&mSct<;1Vr)zt[XrTfeƗ?F0J,r &=rGކ?c<mji"eҘ'`2YYZQZz M_< ^5ek[)OKpAo`Ipa 3n1;$PI62$@\ezAx^ъ#EObUx8@Ewʒ09P75TILI^BzBKQp `JPd1v+N~{ʱAO؀O7d B( ە˵/\t4BT("ZM ]{Ӻ]x0ͩsǍzI,f(٭.~ׇ#a}ޗt4J j\6ZE1Y?~0Y\.HTCc5]ŋkRb/;xo36FpUC]K"}ᶻ [`xjϡ{]uvD7(*} ct ~DE|' +):opCn T@91TowʛnIOIM7%ӳ6Hi}IFsW3B.w[{ID=;K.{ԧ{oN glt\{\O1 MUu >Gʼ# / Ii#cӡ8=aOm?n'ˣd$}n}_x9RCQl=| EE TK!06\Ta튏MwE&.9@:|hm/-'ZImN.i%l'-9V7^#}W:6T*a  Z`vEC<+z2G'^ݓ7ii!'lپ4WK@hK+hF􂁵0pdHY&Qb"s; 7lv·ն7Ԅ?py2iir"V6Y-yaJO[ l<5x, ,Tm_>pe0𡶥Z.*`@!e,R*N=0 b3u1-IJ86f[I+֓F/hm0X35-O -+?+b9CNK):d9!y#s6ܢ9YTmy J*4%)uFmc>Z:&İ}uT(E BACqKM'S" n`>ݰ1$C, Yl6]dB'SLO{|jp^5t} AeZĦcRS֍w:pݏɞ𯰿60l]V i\[pk,  lK'x &5>DEǡx<|n+]T^&fKrM S3ƉloK4kԐ] 2MY.C G/ m왜=p+mmbFnI~dCr}c'R(;М}M}Wιu:s=2Q8 J.caÑ3&إ,ŒH@(hhvV?Ç̎"*il,=ʁqw=/9 :2}[5}4Ps}%нЂHmr]!yo~z&¯_)RTn`\BXY L&}b4F\x'[Wov7Qs:yT(cC;9> e?+3 Og! SN1 uU/~So > _CiLi;C)1Z"D^ͥ]M)ǮѣG^0/Wx]Qg;o*Ov5gx#`&8Rf>n?"!A˰ey7$KqS6o_fdco߯~?S.i^rzpB)*?UG}mB n:>5n'X{4d4E B!+Ht:PHijXC-P@:Nu=94-ANy:bY4kV&v.ڻ8PՓ>>=޲=Θ7Ƿo0ݟ,?ocsa8 Ul.E*xM QoWww AFjrXVi"!|R]._qLpM*8LJ.W3}dMq@Rx}ORt6Xm-%lKsڮ뽦l_&g@9!0'y jIL )5 e]qhP?U,bsFN#u-Ed0yJ,Š6Q*Q?oI6@WU>O'GoɛIy|y+VTrIgqGbJn=ZVשׂt;G;.dg.2NuKjYI_˲J"d*7ew>`y4ԣ&˫k^ì8UÇC`NjOjxd+Q5kռry4>8I8;P'!Un*@(<ueV+ݦ˔D!蜷r]`eʉ6[qz:P ˀ*qBfôH& fry Y0ҒLKuaLql@jTƌ܌;Nd@Ҙ#,72f 6/H JR4$uCUo묿] 'uL:h1[d;oF~!<2^HK%1`UJJm^/8&#)&4dD- e*nGQkլ^knh(_Y {Zx'fĄn?QZ=^t(=!m9T>g'?6yنx0[j"Hqd&fU.Xa4RWYZ3ؿޓj uW-+941`%h)*jp9K +CKdWZPT2n*I{[+2 tWUAZhWg暂`QIEt0 S{j! e_8k">xv\*9ӽt)rg#  Tѓ$"ƒm @8^d6?5,wLL̾excqvnvy#1ElD=Rhec=d$& 飪e%ԉQI8P/f-2.7&@OebsE>«@fn0Dy :#HG2Яԑ sS_1Ļ/ako<x=b&79ω+aոhPʹo>ЪG2k"zc̲UhAZ8MF}6JS)[hA9->o>oQ8?<OqhQ(k,3O|o?$t Eti b%(hbͰ,'wNE7>M%b׿y3s,GVSs:h j`|01l `wdXG +5jc],rZȭ2ϓ ɩ%5&rcSٜ4sZN5'L~;Z1/a ѳH{Gk / "Zm5 WU𷤚oIq~:~s e? sH/3i\KJB(ɞd3`W'+>b,fWɚm+ yow_GGv'[\9#d1({pQӥ茶a|OU3dz>h?sڠ^yΧN ~: iY,PN'ocDT(@~EA11s?>2zBofWϜWSE!Bxz% n7Kr΅VV2xw pGo= | t0u1dNv؉Fa2- `T 6 *!ZQDHLSf&YtϗGo8rފ%&.Чw[C2NoOy>''tldkyah 5$<ZrLY jF1JP y y '93HN qGPr(hh"'Ǫ$?4ΊW2@$`#^rR^T.tOe Gա?:`p,,b!ZHIXւ@^ @h#q=P4Sj(-)PiW:$%d:hvV\nؚ`j-&=M᷈VRUn7S9 t#js] 6Գ^ !XdQEOHUEY+KB9⢾މ} Ӽ@wn~gn[mrmeYiTkuquAs lov1 Tofؼ*D-_\5msd!9*[]̊F]Dci|tPS)2eogK4~4vՒtԀl |;+3S@(.SEa׭΍Xu@[ 4@GTՖN$5A63Ve,Y?0$ʱf8SRl< 739ysɚiKoWޓOY3ݹv;5ý{-b_ɾr3Pk1IKZbQE8HzܢI48ǸiQaAAm,؝%ՙ*Ԛ6tQP}_7jtzˉcq:F,ޥi"U<B2i)4Ih>RkF#-qU 1 lI!꟒? j\;Q{jz^yE es:mBzz>oaՔFN㡕|u ϏJT._^cIKHͦsCgsd{?>͗R?xt1QWENbơt3E1Wht,狪֣XsqyKDFVm‚exOvcg?bv:1(;pQP O Sf(WFgv:[XhBdu@BA]7e*gTsny6ERQe:qhTacJ B+tX_2} |?jaYZ=5vL>@p6J/qZ/S󢉅CW,Y{;Zx%ټڈ(/3>M;Yya1z&ӛM;|oG9~w: MB!g<R=FK4$N˷%g yhVmq]Swӹ`=|/px@ ;jx=k V#6~ڽs\61|ὫV UCt,N6V,ǹ MTءKHU&QqT>&U6ZbL-S;qv f3w%h&nCBq@.=΅YxFf9p3isGlʪM Mq-._j}Y+*ƾ'zzV%g:ܾIHfJgg$吚B|L1BF1P"3Gh s"43>q!MQ}'‰.J yDGj ^HdS&B99fT&%5Bυ@`hg@|JՔ8uդ]x Ԁ|U3I[35q ;=x˽xhZz`LAk RÛD /V5Aҿ2^=#7Е:葺@dW],o'y>ǓUuVi846ǩ=K|U;:Hu׮M:HCFܱU춅9MW<$U=6J{$8њG(hO(5C_jrN[N$t->M;B~i|tNw)Μ ,m(cBHe~Ru/HE',Yr`=R:甞Y7b{Ǯ;ٽwn|ɷ1;ںz]_rR-ݼq1MRkq}sޱGz8;DzG9Uk2ԯ9k"QYy( Qre߮8"uSz ,;Ew#őbc}wJGtwoW'?\;x0HocK|_njpps@ oVր3yWKp_AsᓺN|w.ڡICU os9HghK &PgdYy9KdԼ| HL@K V>sakꛣX/>~<ա7eaZm"6La <sK@N<ƇjE=yҔ0~'-+G"21?ËD?۸'{[+7pp3G֓X;[+OqG,jԏ^A',R? g1ywؼ3-ico傟n%u Є질g6"eg~;ЫL6Zoh}utKTÙ̳q)"vlMgy#F굣ޏW˟3j>0.я 0^XȺg={ǡ꽣Uz<5k@#Hz?t9#)K1ڟN_!i>|=olohXPOncw^~edt݆׍Fb=J,bEhȃ5jxwT < ݩ')p'[Dk5,(Psh‚1'l")6F];5×mM:s~rHJ>ՀO_fsf_fZuͺ`պ s`5DŽU)W\s6ecȆKx; oc _KJ@ y)\ፏH3p xj;i;N2zEzg2gYw>=VvF/ˑ2=~Ҕ?򓙞Ա.?Y]~ܼˏ\;љŻ?*ڱx?M=PgϞ+lM\^3|b瞓tyq>vj:.ظU;*}[eO#x GIsh{huixpZR~|isJG&OI3ll/XnX#m}?z;sS*YӁaT_puLTS4^T*;GOMNBbJ&[+VHi("K.H PQY?%*Zg8[_톋gnn,wvJzR35o_Q[FҔ'S)_:O;?^0394t?B_ SIx%Ci.Iw@Wa:P2ULG W NJ/6ڑ4m'Վh|:kG^[3}jK=2C#QPQt"\qr_t*JMwO$~l}dO GkG+}sۼyڵݷEqҢ]<'_lܲԲe}~>?V7hg8OC2%'R Õ I(+5NEm<6?;lh<`Cxw`_>{{^ @DjDi[j s>/Q>w,c#8aLIVq^gЅrB^j/ bNͰ>Y=nV똣O̞? pt'՘b6.ݡ)C9+s\5'vncӌR:TZsT_} ڳ7v^_/ά/Qﲯݿ`e /W~!D 7Jv㯕^aínX;1{MS$aW]z+kXҿ27֩'_uCB-OO7[) RJ] ?f:Zc=-p'?'qx?jD?#= Q-$l-Ki`7 f-,O׺}TH0XD`03*'y410>)šcǎaе/MoHo*~KQ9+9fqjB)b#Xa@:<'Zm)?haxn7ijH`kfbAMٲkG~_itہqXizƕIV-t\o;('RIVODiD*1OɏUcv~f5Q s"=`nF)SӟZHT4F0&Ĭ&XD)\4d+x{K%0g$ yr+;@;xZ>f9P`@ʸXt [ ґʥ'ϷRS/xv dt8 hb|nw?/Pja 5,* 9z|F~UmWw̭GnM۶Pk#^Zy:mSMt [_XSbeW2`f- $î紶۠."[ZdbY8oWTV.7㜩b2-Rt%JՌE߻TIM&=uԜ;,&K e2*Eڙ/F{\S)nb{):x3 gS)iQw>5Ǚ3K$/gg?@ ͝flEƏ:؎V#sٍ)EۍJdJlwc-o1_]fMO?;[/'9'r)~aKf+u!2Rt;;#xl00ch+,j3\!kT!"=[W:XGBcחnzNgee{fkv5"`D.$(TX ^1ڄ#UU)vFoL~F#0mF^yrp^Kuv/z-;6u5]5-v_oF7n)^%,ښFiZ$1EʪIcB,^Ujo4):n6'Y3I$)i() ߠ'AFRVEi$,$9reYx y޻%/rErYriR w[.JkkSo~8fEt[!B!|lQ?tӁe7䍒 vŬoMҖ@R.$zTq1`L %%8Ӊ/Kg:[ә K}xig<\\Ok]0cYqu h)4,W+Wjz)]d֗}N׶Tq G:W}sy-pkRIky}ʂ4GmQl Wq{u#@-$L5E|#̈́-7Xї Bi򓍣b~^$V+a<3C&Amӂ.ڋ#1lHp!؉A:7GilZ6H/\kNLxLW1\o ÿy8U6lC%ԯnARfښIǥrygL9;<36ulh5fc| 3; _һA##Txri\MKsyo^fR;'%[Al\*oӀsٹ5\%k,3ԼRhxVR ~ϫt#Z%˫DXh֒bqجdY6vޕ6ikWp{ҭLJ-}啩Oc J8P5sLUF7(v݌mgSF5W S&=j(dI>VM, LW@-rfjIɱ3l_H|>rۿ;TwdW)qv 9RQ ;`zXRHpKFJ+^ZBi9iLVʌ;]`9?XDOOZҶw9w{Q(ڤ.)[_U[;zvο7H5g8lncrN[`xUަ=K:Lˤװ6khtS'VGEviHO3 ֜H UrHԂ}_ط@5e#eFPյ[Z 1|}fA6'CD9HϞІ״},{BON<W{s>D}(}wKwJ7*3|̀S)\QFr̬,`wwx7L9ʩET" FEǃWzsV;6;6p[74꫕UJz~vo=楸k@`yi0&ד׮Rr}{WWoYg; ?#f_k`Otd>|YD{/4i}\k`vU/Ū.dz#ށ_w$w[ҙllgsl?lgsg`޺R I7|gk%x,P^*^#d5Xt֐ю|eY *' e811D|^?_/ju^xf/U^J]QŒ LH>U_Mp7k9%Aja?Doq@&2R뛸`#֑_&/{X8Z} Lg `Ol4w,*Hh51i ?]nҞ]XCD7WRvzeolo¦1k{c) rbg+h/.)(3/;ۋyi$K5< 83/9p4•yXX?OE/=>Li ~6Q]3,)KO_>Ä;cv֚&^t-x^]iaJS|r hߙX|,(K+W)_:ܹ oeO 6&ۢrʁEN6,N^0FX -)|NypX !ӂMع[; >ΓSVەh\4r+uS҆gg6,zl&8q>"4K ,笑H? BRNި!鮡nqAC@Df|՟lx$!9H.ZkuvEY4=)hi@%+&!H)P_4wɾmkk8pI~eEYdް|!BƤKzV?<+d{q;Ooy ֤=tM @/_\.s3Lϙ̝I$֭--x2׿veU$.D;E9칂6?ywX̝n8vL T˫c2z#9X+ii qZr0v$nyUK#fO6Ν#DP$6ڭJ^7n^ ]73>^%5:{L5a ]v;W8WZ-Oűԏ{d]4ƢP;5G&FF70*BM7v lBJc@8%)6Yk||[Q#taq2nNQ.ǛIFT_cq83[tGp*_]+h3fR3ű}?}4?ay|Vߦ^T\}GE77H9'fءճJjKt:*aMnvYUVnjoPI2MMr+4U+5,l:: :bB-;ÑΉ9IhޑEU{cg錻9rWc#9QyjMfI$ѐɱz?mdk$65*;ڪWolmhoWU`>-qx9ӣsFr ݿ|o3Ğwrsv"MDU5Hy7Ea`ey$y?E,pc0뵕{Ә<#kpԔHaN;^4vOl ސz㚭յxe#|G<;LO9 N9jgc_܏7KbRZGy&'q RMr7QrBW33+åDV}3tv͕絫򼎙y^9Ciq$clތh\uC|e텍&^duTo9{Jfiߜl|da}T9\Ľ56S_y=4Rn#]Zҿ븁}ی| N{Jܬ酘3iLkmtW ,(e$bmza.>3+ FqM.kLgb¯F\G必O894NhQt0b(Ĭ-ڃ8DaHfXԆڰt!?^|񑝛WطW^UU첯)pwp kx8_}0sňr/}dAx51`TQVA2^Ybs ̅IWZ]Q‹rEƅgTWm5}_K}_y&7(TZIsˑ D2{~qu00]dKt_eVQhf=BFFؒ!W<kmED˗/@Z%B3RaRx0Lո•łoF_$(Gd2z%Ie#D_f/PUz<0u#+Rp$-d8lJk&]rgr|gL6[NḈ+MKxj#M C!a6X6AMA}1gC_R]dR]8.SoNrgޝNw.go8xN#<_]HJ$ "|w㚻q^?Ac $t]"=R9R1J2|3Mb1U__l[\BU#͡u|3z% #1OB4K97,H=`LŜf%Rt,l[uI򾲠*:?}YJ"GGFP7}dv׈|ȟON / pSD\3ˆY!YU_k!;^>v,:Mp z`^(i:M,Fw|s(O)PyZ5 aob4(V+UW"JR Y=j)ÀuDZKs^]+~O, >pXnB&'7}k؁nuN:*{?aù@V]Ӊ|X| ܰ/8*WT 98C}]'SW2>5:g1HwFgd3ܶ慃Bϱ XϞgRȽ܇pW.\['~* ?rxE'CrJ Ք&w4pQo}mSZ) ē+=zhQSS m7 csbsM ~OcQ{~πmfB=&(I$I̒& z3L@q ;*āO.Oo6tܵ(/ Iac  2!򋉟Xm#1;%3?Xj /Yl&kyS&yƊOu|O qI< ^u|Uq|, _J)!_kZeaZ9}#˜[r x :x(4`Z!,M{7$hPSG2>L0QD$d*'q:rw )^zH{'d>|^k/UgbQ Ҏ<"4oDKX8lݫA4am].ݠ #;kM(];X|3K|1muoF\O`W18uM!(E25ݞ؎hg[I~בvD'cJ@"bzqLM'IER R<&GPY0lۚ͛oox+5bJ}yRy8Ҙ,-4M1*a'OMMMOMWܔ6aѻ]{'G7"nj^{8;htzFŁ#KNp`2a**E$:Q0&A?x\Mغ]=kIkke 0e ˺X֒^f҄z51FڮPcūz[1kޫtv[_SrVO\5wێmwؕ6:j</Xp/9uaP;y*9\BۿJ 'q"0r#_Ŝ5~ [R5y(<r@XU?2Cq8"o}4JPGC4?_#b=V")m]6W1P%s>J:6D˟cD)υ_|gh9(cøA|۱1d \5bf?ј#$XԿFKTsb|56 ۱E;tWZ`l9]sRVjXMs48$3pbTw&6UZuvvLYrICa~\aMoh%€ܚp߲uX7Iak/"Eqd15!n–ʴv+/!7aw]t="ek5{vLUMjƎXkו3о|*Ll[m\nuUc`Yu~{+zaHr"CdFN:p۵;=;mL++܆kU*כTίyl\,2 y dWq8:jh Y`Fi̶fcGU%V$;:5V>ۺ ǘ͏-ӭl%6QYڤL6teZ@LGeO[c)^8681;+,$9E||kDX``j{a~.T3`/6MYw+Ra+q)uIqņa֨7(cjd4m馞ܚ6^?* Xz *NN~HN(2^Y&c:+Kq2aH~[ J6G᫰fI~nrU1)Jljv;|j WM ݩs1UqE퉬/ߞi\tȍ(}ty!$o1Ej o|*T<nϒκջ.z{/-a 4NW`g?ᣏ<죧xhh]FG7{O kXFs:,3T{csHbK3 K 0/8B:6EYsň4Sn RcܦjyiG8x( W1c1NH\(܅tӻ4&E2 q2Xo:(IgIWC qگܶ*ֽ*HkXWl{@M:cMM$~l%ދRq8, z0#,UYc Nbj+mպsUm2{ \? |s; Eʟ X1StLZ0yJ=XU }=nd|1RӖטnAHh-1l ]SR(CwFgGTWSSXM--Va:ٵyOMɹ•,]{h 5]CقV'5Igܶ8Ke"k;'w B*wkɆ¦Ph2O:XAm\&ͫ25uBXí6l)$Cq :&Y0DWTFm ⰳhӒRi0{PR GW̝%囫Xetk'Ҁkhhodr̲13/c OQBu$;}ƑH:/H.دkXϊ:Ƨ-ݻjaS3}혯9)3s0O<B0 BVa+\sv(i 5*K~Yﲃ^/BV|*5b4n4^U-bfA2y| aLnMRz]vɺbv /{5/_9,nck.ZpQ"ʨ,2W_ÕPB4vFEs @L̋a i \l. vv~A p9r#D>$d/Si;nѻ( LX\([VNh{kqqw7D/k [?(Bز UW(7i+P^Jڡn9EN2_Ay*/-5|^r?S#uG.@{bGoPEw"[D ӭ.֭C?3vu)@!.Krޢ/խ3BvfU#eԜl[-7Ty95"Smb&IF5hP_PO l[;3UХb;A4tb^:&#"uZKM ӥC]ч/Y3~ƃcdgԥjC3RT*TbKXxi#ߎ83#u#IrE -Pu]]uMMmuؚq>U>}Å{.znΎΓ[V"./7U*B×[DD_o4}0cOS<.vbƹ6r_ރWIW_+HA*zT)|MХb\M\.UKiaXjsUESz;C;}Yu~򰯸zg[Scu:;2P= 'r͓<ҷ׍\]W[ghP;: uRN!s+z=ftCC#9;C#$%}`+Ƈ-=cXcȉ:}iDyyc4XS>+2o9G7#ʓ.x2D-O^^O # -O }k@˅5bݯՔ7k!d-İZol0D.x4D7^ ss?2nm(4$ ;`~AdJ6F/x-NNS7F-Of +r#Sa81$ؤVghdTM:YN#,VZ޽m%;:y;r䒭[.<8c1?~ԒSH>c[Ƚ.xJΉ9m ydR_mfCGiRx%P*VPi̓ٱơЈc45$5y*٠ N ۈsGsAޢ^OڹF.3P6+71^&}N 8h/Gą e[/jNVW{e\X=7#[.i)כҢ!oAV!bLĆm0fR`#L0!px6`wLs@6S~ M>zh?z<+;'p?J v"*928 0"ñ\_#5{%#a `P$[v,& T(%FcJcEZkWgwTdjС$TDmLYa^}uCx46 2Mz9GW#gBb1"RYȞZS-]Pxa}pܫ=}Ekk/^g oW ƺsZU뚬o:v;W:;u[V쬫/7POd_$DΖut1l/z-*SP@yw@$Y %.JRtMh$A}#fjm|Uk* c ,7A}{[[6ϭ*[U^1U6{UQ:LEyJo}Fߋ|nWo׷WJ[V|qn'59X#kYx|Jߣ%S!Lul3JAI}^A/Zve$*U’bG[c̎5xjWn_WWUS)Qk4ֵx X"3Z"DDQ/ӽlO}ݞՊ?[xhppsCckLLYCahB|8)_JpCC}ɥ@?y☉atKX.QTF{v*1{k߼Cvfs6Գ5]ES#ë"18A8zEO$_&EM=eڎt5eeWWTzmE|; ؚerQ/!Rϰ+/85Ɵ5at,Rj[ڳw~wߕ@? QbGskg '-O ,{x:Ȣ6^PXl0"u&B_Ly-u@ Ysvv*J&Z46is<*[WgGޞšNqXOaR˺˖VJ&6/ڌo ͋p~|ہ_oﲳk:|p UEt8U_. ̦*yÞ\wFFS%߹xwMAx뗲'OdziמqU^)SZWX}_KɲuZ~]!gWܟ^U߸|~?kFȾȇ}KZ0i41c 1Uv+Wђ.k(`ݥ9תFƦ-V.O%8I8-1dxLv> ^ \cj|$ GZ(Jٜ6B#Ѩ0%cWlniX6)(*[|S4arz+LG U ]%;J ]9#+ ;eyn:N][*ki0Hk>'cjQF}6YL#p رt/Omtx‰Ty";}}W1 ZΛ6}uMJW69SHȟ7&֞ڔdj"Z|ŕ[;;V^ 5_dPsK6:2336633cmA;yX߯6@=r}MmPzb,_^V=wc] ?* >cb[/Z?QXSv^]ZgY;LweIϝ$ÒQ8+cg6tn8K叟x=a}" 1938tq.q$Bʅ%EU[ -wfi1a\KlrMY\eYЧ{ρ6Yڽkg\Ⱥ>ޛ>h3ay>9ΕO^@d;7itQtݿF9 1ΎBC̜2zbSKˆ}S@'ˀDÚ*Ƣ\_.ʇ)|^o[*HY@em\6Y& xSA+4d^2JW>p77 yIW?Kߗ P0F%a@9`w;K%361`KjOm,G66#tS=)/HΎNx{xl{ 1U)<> >2atܳHpBG|ҽn:su4 ݷ]B1k wd3o߇ bnS;myژ[!)dlUSe*['JxlW0tiO'ڠ'SY,zG@懲+vy|xWysy.M836͌3ha`b?S'Xl-u `;} Ìk"sChε7aa$ X PX:)aλbқ )=#MkK)+F4xLem퍭EiM~3ckR+>:lx&0\R3$栓{sJFמ_).NN3mVk6ldU{8Rf{T CNIzfFq2,g +F{zfkQRON0|aSԭ(@q1 D`>fa!H59iRTbF޷ RWfqeG_YlL58?_ɲ[~@e$4xt; A2s$QMAcZ6YL#u$m68{MfY{(+23jgC7vXw?C+>"juX#i0d&LAMyE[6U[49pŜ"Y ]m˫{kjV4tDu>dphrl}VsTl*JJGZ.VJ lЦd4Lֶ>Yd8?L! >fPj|,=Rz%43MMeRY [YvE$h{/bbg؇E`Z(h%+GJe8 h%JF@ RdϑTv6d%DHHeK<. Ε@i5Z H"g'@+0Jl&>V3J4D,)|aSD+i$K`ĆI$KdBԒݰZbܶüfaH'`rA._E Im3ͤB\}1O@e o[pw޵FFPLGP?A޿/j}ZogIg|@z;ϐODB6ZA0fI -ھyv]<|CC@@GQx}.OZ&ǣYRߟ&?3Q_|~Rm(oc2CIl|{ %$wC Ӭ>8'RPy$uw4ԧ+Wr(ykA5.vn?ӿp-:d@1Szn_ 3̳0~=M&9fWǧLx ̦|:T`2?t6uS /0t,|+0z#4$:<\P2{ {L %h~|buG9|MEq=s7{ڵ@!o&ţBEF%a(x*3iynIIXqsqV5Kf3} T18|?n㽓Q'zwWbVCX&Բ\ˀ?yq"E+)]ʣyf3j|F2_ID b|5LV nj.Tq(&IIXV&"nZؙ=e1/DtSh]ZV'  KH<.璒?_Н-)R̤7*)()+ۣF_. t?KҚ%x\-Q/|bbL&NK1̈6*$0@q``Ca1{lܰR $lxNo _5YY4fen7d}X~lS9-9%V.nvVd^=qS2q cnÈInY4RD2VV^K K/}KE GX~ 82_0e9v{_"htVKfnMSAD`Z'$yq9s<,'[gh_,qE2{"J޼~FzyzݍӸp Q1i]:8T?ؓmCF1[ I͈@A$X?VKM 32FBc?Y*, #}#[N~~=cf N]gxw,asZkua:lC.a\ZI|D]GIUp1܌1%wn2gPK( ȒJ8޹ؗu=SnRbx $eDsmi<.Daݶۓ{:֠Y(1E}mm˲֢e쉧о{gg7SL$)%nnws]]s7{6mgn7?s5"R|*P_H>u.R&P:+)IGK #xI]*eO"V/=#p .,а8sQa}׉A/ oV_88xauk'h=`0K~ 6.i>{ X=ww"Pv.Mv?s `iױKP7ADg/`Ċ/;IeY c%K$f>XM\)"(>YGF4.8uox"xF2TtSӗM\I\0Ʊï_6 A8zdk+&t Ɣɯ.:}Op=b?')!F'A!]}?lk52 3(E76LY;V:&_ ,|Oq)ܭUmvWmӘϺvUe~^6@\ı$ imDRqwqP6jl5; R 8KBI8ҸB=uc:MgȪEjSSv^xrbZMʖֹ tqy˞j2(2?b{o DR8Y8Q cpdH,p]L*g$IʫSmVdLIISN@_[;3V9bV9┩Xdr}`idu'H*Mq`8KA2 Ef>ITp8atA$b_X#0޼>K) P]x+@+yÊ켼D'+r)޳"j#+ 0`E4^#>x.t !ñ8 Łq8,+fwL$MOu65u#S&\]Mw~zEuons ʶɸGsKY=dHV*O 9o%YJ.ԋ_j5V . FW|CY9*{qP6Sd46gwR;A>)I/δ|@|& 9y=|3M6S[ž=R/sw9mvڏS¶To'(p1p/ 8yi1>l{8EGHCGz&Zܵjkx`cECSn\mF~:qΝgg(,-jK3' :)+ng^*ypnHg:3N=y6rYώ#YZcɮZD&}YRz߽ usX$Կ^ƈqS}z6@a$M@ (fdkc:7|c8xzP2z/pzU3Cƈ;Z>Zwx#43jL!SOƊ84 sd8Q(uz)N[q.'0{Ex} $ :j6T8VVԓ̕{+M΢ltsQavnUOOWWo5ŕUPWUQi-CvR8oGkL"JD##("XmVjaƟt8>6Ys-NGɖcX ${S,yӊI:7+Hlȵ$wwbc\l~xFcב'h'oa7-6yfB!Q;ms^7ODŽ,(ܾ/zL`\^= &깪oazTwin=p^ $6h޾yD.|R)FyDV\/y,) ^Q+@&CT a%#`?ڒڹ^ASmAF4b S~F|\ԇ]yX,~)E̖${=&-m~O7qNco>{챹=Mի|-S;MWmWZ,-2`4_!((abE8`Jұ-/$rSRD<_]cRiZRHq 5zLœOЋԄ~ݑEY=cr.Ovwt4qB8nCt3[xXȖ"Iȉqr.F&K 'Wԏ!@RJSwSWG[eceC]M^E^׽B !ߢs(? sh7r#H8\*6^{FwM7- b**-ج͌RH""~]t m+ q.|㘲F*>&n eZBm1N&%ryŻYQ_cjAosS qb[7g8 S*M*Uwl Ҳ }g`^|q<\:u0,nOǃ$07fQSǎ!sG?B?u|j&VQ~m0& wo7P×5P>Ю?ꀫԏ򸻨PF3Cb2"in+`2LZb D,ꉁ@%4b61_i!@{F}L_ @O/.e8*a ő[33_1)YGde{D}#<' rlHFcv',4SڪL۳wc#X9-̊]oMO?%)ɒ̂l!T!I*Jcã"[;&*ynx?A{͊hX:\\B)ˊ(yT"oodE+,Zfo xl44#8W|=Bf&G,,jT]$<Ƌ(0}(;&"p<" |PQ i`O&Pz -M ,ҳ3ҳ.gPzFz;J~gQzF끞3Vg2'y2'~#$t#S::s t ~9v2_9Ӳ@3 x N?  /z4Nt{:2қфN)gʷoȜc e :tG؏%2YdN/\'紖PK?m> [&u #H\Hd% %"$!רm"G>@Dȇ{s,}GO>Uͩ}|!@_Š.6[n>?,Bj,#cs[T_q?qDV\EŠ,wA},{?.bm]qok>??ׯc$ ]Ԋszeh xOuA}rm^1D:^O(Wl\zÁ'8jWo9:id}96i6·]HV;IH-1(m8ŬWǥߊQ8 жeJ*?'0p?I m!hd:͜/kdCO mVTj[\`,u K>gP ލA?۠ l't#ŝ6en#u9mS2KZwj ]'(l Xn }h\:pCGRB0t"`M~&?hliڴH)ͦҁ_^/|^Q2D1:E8G2BbA)NІNrinE'\wG^>DفWr|7Y U3qu+{-~d_@C.\XBb^ywշ>}4yU> 2&)ELJMG=P"Jni-)B#Ǔ:\b|r*`x( ax`]2Ǹ?%'?"6)da.Z4 B6 聘n#)Y 5̧ R1!b?`T0Y琢+xcNԍ| }L3JvRFs.Ias),?9.#cbEǨ~1ݯ c)a0WžXe!1&S 0%폅d).$f!4{< tYY1%:X=%UQ`C/(wpuhuqu;smoΐ[pWmyf&]_=e^}j3}vUdZr[.yun gm~钋~sF/|>d8mH%1MDŽia Nc6,ĤS5MIYGbd>4TX}Ԕ2k󞿺o†uu 93Úe;`l-˺Ǵs'b(ՒӜӗiN/MӍFXldۭ y{1x3k 4I]?0pC  [44W]}^CיlVrv=kX^~w}}\W[W'ŗV&KHb/[,q1/4#LpY*^?ФpgX +eH&7(E &R.gzK&oąy:Nb\άL6D }B'@6A2*BlhڼaWsPQPI5X>ֱ?&Qae' Zl]jIsv][UߘlX,-:|ە_Ӹ۫W1>vY>`9X1w䏵2 ) "Fq!v!S$ɪr_,M7Fcbv$~S+ ̲O^իJ+o'_

&** ygKIa<qW}y|<Z:JtAOo.BP2;r &S'Yf}̫̓gHȎLX # F4W= ̳q[-u̕Af9ĬbƘ ׃t#. vHR%}%}7:,[36Mb0:]|E?9oCz/?//#CsnaAdYx_ֹ4Xrȿ+ٳL}N4سx1^K@qrWPd*YA8E oPv]#Q-`I# $E援(lN޾vvgG1\|fu-3u2ɘk6cCgd,s-Ҏ&btl=?Ep}L?KfWqy -W[vҞka[^:u)\9 }5/];*|/5[_:p⩋/q}=NӺXؑs-[6jctdOIg+v6.^g'~gi'??@g@%Oge+د׿Z,plǂSyoYu[_A$;\, @?Յq$Y=ƜCqt+{n1ħs3bot},~w_m= d7bk{U̡;GM JLh*y/|wԎ\s9-(܎cGL+3"7v9| eA} kUs+}^ӆ 7l扭7߼I;XsnW&LLpMϡHLH Ϩ1 (xfw/QO3{$4|Wp>)$W B4 .XҝȜnh\& >դ(n"JtJ[sk~ \+jO{[Mz8ܓ>n@,tIܧhRG~kôwNMvv>߳zt;OpK10> uK!#[z _Fq~"%$fȽG3[]|Ƈ.>>#߂xn4VÆ/]Jq߂˯lmF5m).mp|8ߏ!Wx𕚯 ƍ7Ii@>n }>p70E`Cz)뇂/>D[or7'z 5k}jЮLvSv GK:?N7HtgJήI{I^^HkCp/n*^,3LG l>:ͅԁ}m߸o'ۖuqa1d]mnN m.[n=Ə} bQt<OG BrDSe/V?XpjML~:&9'/o`\? }jU 6\l A6z,\v?} fx?e7;l].;eg kvY<~urUv\ut0WWWep~ƧO]ɧ/W*\O/W6l5WՎsn&'kdȒufǀ}[F9qGnLpC2ݏ_EXz]L`H$e^Q%uQww\tJ)r0LWu?L3\3_#[1P|ps5`\Eu`yݴrC͢>􈐞U)tݍnE;Ѥ787;S9^{vM H)jH=/4̋a+ony`F~QNb\-kɛ߰ﰽB6ص4k7}q;^p?5^sr;8jۆ+)7݂ʟPq]/΋k|v\'vr`*w|5,oSkslnXen#=8yDaqRkXavMHwl,FtќŪ1c["uXUF)Cr܌;]eHfڛ(Cd. dP(Q%""L S08^QP<1*r;^!FTIG&Uf\VH)t(qTWsL; )hTS6bbIe,%X »0֬jF݊+$#LEb"gg4UiI%GH$hE&ҨEtHo.LL ZeT)5bXY3TT1^bd+IDlTCb4[s: BHK.ΈJ ]zgU\=cS1D1f&F/vT0j"`V1&(*qYIad((Y5Li(K#bDϺj)h<)9UĚ: Å.)*tb+e;r&e##Fhk$S$jj*I-JmJtd\(l(i` xM#^=+G7_ TUD¿ oS5mpU^=fw Ã[y齩Q!ɶ#I6.`WɄvFÝnEgb;w`706^0yOcOyұXFZ Z <DSKN8=_,,ۆoE!CS͚a< љɱb1J}c-5 3 o[r~4:,$TnR.ۢ LRUr2^!DqrDjCM@Ju`"J,J".B5$;1G%.3-')JGFdOl튡ƿ1݊4f %EYө klHpRYIl,' ENLYdhIjN,tѸ"[v7ט""!fF h*I@.r̬Jq,B*AؑtZV oD B5U,FBX4XN3 aDKQP[%dN !/f=3cNuƆ6׊cDC" Adp'09nwxEmxL6nC8pJ$D)JrBe1YhsTAZMgBbx*& , 0B-\P 3$Ţ.:d`UA3ǓCDOez5{}az6^C/K?GFDB_G_C_EgЃX9+,Yx]˔8]pi тNaY3/`7pDtN6G_*$"aZuc&+~N>1rtR"8øs‰K,G&*(,El%)i<=j68_<#$KPxc`d``u @dj5xŘlVWǟsn*n:VZ?iK36V kl22h̖&0d*aq&nE42ڡ̌9 8Bܾ}M^6{syι+_;bV'YeIAIݘ l؇iϓXԺ&?앮)> o8PQ<gB4URHGHDZY:YBT* |2\2~n|5&-_(!ڐa7.K K_}y.wR>k FVZq5{8l~xs8PI&R%!8 Hit;d$2DE}:ng~b܄a;"Fla%| PgoN'TLC7E~+u>ڊ9&G(qtE 7~W1Yw ߙ({ Xݏ<@mČu s|-O8*X ޅ u\afud{:'Xb5Q܆CS?DSĝ Ý"qZ=/L/^?ΞWD4^c\u{Ūm9%Yj6n1ΪQ ]{E5,zδ&Yo^\۫/^3ebJ5Y2<[ߞu3=uvI2c-q Їg ݫ?!ۈ#&.kޒZ$Ju$Z!1IZ}2D;[ɣ˰2R>_҇1yG*GlZrP-BܯSƦMwagbR_%GYhG(K7]i EFC>tMuNޤg\J5%|{|:_Q]ȕh&=Ġ$Fj)=9yX9Jz/P^>_gZ1ShzGC7_g}>sMc3ALLi^Xֺ6{:s%!.;+sڣ9bG,_&wG͋peS% Ypgr[rw2/ ox?c~Kn\5!r9/SU\.giiǍϓhasxƈqtJv16s1{24-\sGE"Q*CPw57bJ ,Ig5P.@O r5_&:쏅˹#˙0-O~-/RGrCʝ.7wIs8zW;Ą#p8Pۅ˔%Ix{`#yR&Wa+hjsCo$y]r]*%/OZ{=uo C/UWo mӢo|'-]"Gٖ̻>啷~zB2h3Zo~X2 'ɍHZG<) wP?5SwvՊ{LO?gj,vkkzqjjQKJZk]{,72!_"yɏ(kпf'3>>RELJ-CAۇU{LyUݳAk{;]79%w;߲70%#_͕ҟjtUTq6LnP[z?ػ~D?-3!>(dHˑ( JfA]P4 cxa갖0s= 󅍆M-`հl6[DŽ ÁpC% OφD8 ·> #s=El!hLQ#d ˆ: 9:<: E 89<6qlCQRFyQ#)?&{didU"99&hZhڃ'z }EitQQQQhb4-:881y1 f>bǔbfOO:9yr6+6`Xփ b'%SS+6O8N 8nnsztzt<_ƃx~?LJ[3gԟُ-=8xprvn$Ng<NtH|l<9!^ď$$ {KMN# 4B&G(!4+O"L˄5yD C, %Dh$&/]X&HRG&&YyD QؐOH\$Ch2L"Zl#^BRl9$LI I$gR_R:NdRx>EAi)vK%$'W''{{T(MSIT:5GQ'00IO1R)TxjLj\j{jgjo?uh"f҂)mvJ+Iiii:Υ "z>qzQsQ{q ~} tE^\a++D0Q(V$D-".E>тhU APqX(mEqH0!aCC,  @ ` `XցJH%WIdVr* **%J]e{*QjǬ?#!HOv5ZW81})[/-VIFAjiP:!.Iץ5T XCɬՔԈkd5YY)JdbLdڪZMvvvQ,9 Og:X]V]cTgQ{Vl+mmh5ZZ7 XPfpD4\k6 #(6*qx mSn&Inj4M6h4mm)mm6Coom[n[kj3Ch32s65u gj zc#ԱѱqͶmYLem[I+mXV`uZŽc|nl,&)lZӶ;w]dۭv}ľ>Nd'y ;hC;t1p|sœ('8;߻(].qL|rnס B0. nj{{#Q{<=ŞPFNρFcyn UmwC`x>~X $9 Z@?!RRz}־o1UG=N_ȿXI0@xz`e`KM^z{+OOOOӟ>>9=?~o1J[``04`  }e^A h_󿪾:ƆapbX36 ۆ]ýؿ=?z6[$4s pxڍNA#$E R""261rRh- ߇X N*gH"oM «=vv濳CD[ND9;T*gmGEڠk F7}D5fy^o%v W ў:m:8)|sw-Z.P喋3> ~H[wK4pZ^n 7W\eyu(+:שVYޠ[i)2gZ cl獿SVf݁`DQ_GG늡#ԫ{-5ȲdZ^/zqؖ=8zP,C2DD(R1YfWnX!PV/)dʡ~jS$13 ar9G1%hre2ܡ7[T2]x#j!Via=J"X(?A.q9%-X<%4C:A$EاtjL*s g`4;=4_m^dv [;>^ ՎMN:^aZGtJ0<4|D~StWdߣ* ϼ 餐}sżЪfye:PFejn>Rwսwͻ1sfjRnN`y<~gxۙ2xmZxGޝ-4efNmKeIi4IM"Kdq _(<μ;:ZEt5^ cbFDN\+ubm[+zel/dDFz–I<.)"[b%G1bC+6K2C<'r1N(6+ɕ*rUhr5\C)XkDxI-'uuz߈Hr1^n(>ʍƢS6.&exE WeLeBKl*'.Ll&6_]r R|*[n=2%"% D^̗6 rHG QbRNRl#vr(&bV.gșr,g9b;IΕ;]vrW*w{i2+ɜb{i>/#bD{b@rP#n3ŷb- (rOKdY!ȊʅrX(^roI̕E|-wʃĮ`, |J>-|I,_ |K-ߑH~,? J~-I,K-AuP (4B4C ( c`) K2,,'+ʰ  kڰ l066hA0:atl[l ݐHA20I-L)Li00fl̅ava\Ep1\ep9\WUp5\up=7Mp3mp;w]p7}p?<C0<c8<OS4<s</K2k:o[6{>|G1|g9|_W5|w=?O37?/QD@DB 0Ql&llQ8R8epY\pE\ WUpU\ W5pM\ up]\ pC7q a;v` ؉ 7psĭpka{0iD'8v8tgLq;N8w]pW w=00hc:4 0/A,`pO,a+XŅ8p^7~?Ax0ax8GQx4qx<'Ix2ix:gYx6yx>^Ex1^ex9^WUx5^ux=ހ7Mx3ނmx;ށw]x7ރ}x?>C0>c8>OS4>s~ cbXN,/V+C?OS ?/K o[G _W'?/ n"3YT'~R=5PHML-Jh4h,-Mв-O+ЊLЪNkКMкOІmLhQDmN8%DJ<$+Vq qBaXEIO4hSڌ6=Ėbk(h ڒC)JS&64I$nm(LSh;Jh:mO3JqO<"n[maIh6@shGډδ J8X#)+.Si娗l~  HC'LqRUN ih)8Q-V'ioCqt -8gKtHtJtIGt KtH't JtIgtKt]Ht ]Jt]IWt ]KtH7t JtIwtK=H=J=IO =KH/ JIo;mzޥsQHq8ZD[(#>O3/+o;~'Y#wX7B7?/-aI ,Ȳ:j"VjfjFY1RXkikkYk9kykkEk%kekkUk5kuk kMk-kmkk]k=k}kkCk#kckf[V̊[ kiMMPv}`6W*껋ł;U+vcO)媃}y{QXrvRe]mJ]Zcԥ`4է5mP'hMsQNmhyR6"Lڶn rM2pu*Nn6:9Z7EP7E4%4ES\uST3?54B4y@П-Uj~qZ9-L Όp3MgBcm,m0+g:|Zp:ڻff00]rXgR1"szdrݜRvݸS0Y͢sUh 7ǰb.?}䖩Zqf8F˚rYmbυ:.t\8۴mZZf8[?Tp6Zm TGG_0MJ7wav8?hAQӂmB˛ɛɇ[ Z`/dJ84`n!ܖEŚ(r[CmYbbpJ,wPpLՖӖڶLUM՚\*eUeմq7ja-M[aĴbӖkvVV,֊Zqq};h,缙х3R%G( 71}r- UWlzZ:uѬ)6dU].YYn{h)%?HN/˹̬$WIsBQgWygrW"bfj2m"myS ]3C*>G*tI m uM*zu(ˁKR.y+>:76K'eu{D/I˙sT?Ո,)) ji֫GksC>h Kz‡+٦7 ~E%'RkVY1^FrLEbrK(Ծ KʌTj#%ߢ'TMthvUiU}U~U.YjqPzJ_cX m0\s8sIKG؈o9Rk9-+?G#4/W=oocδi6]^iyͳϴ]bL=K3:_gtp|WQ<}/~?68g?iG.b.qvs]~˿==d$'?np)O1NSb)biOsiK]lf ˰}3l1 bcl~Ix3^5 ox]co"^\'{zW./ƽxY?q98W܋(8_'~/8q37Q8]쿋q_7ף;}ݬq~IO2~񓌟L2]+')Iy'i3viK]laϰ}3]ߞ?^T"}-DvttDHw|v섀 Rhw0&642KlgAd xqA;d, $A d2p(`=T "KE*7tH!H(Đ gL88VO,%J֚udCe'_,\wI#59;4כ0 ;: 1Pf:Y6P8}^+BĞzLD92k!AxJypi]ɬ)S)ϰa9iO|L%_/A^`Z5YK%X oPEN} KhY)dfCa]*a8:,j2]B6EEA9S6XxsKcLLlN^YC+ju }v)TA}ڙ'5)m\vKڷo\ ۗ4o=^spa\.[GY˪]{+}aIm,,mRLӣ%|cH)PZ{hW ~BiOn tIC[ҰΎ:=X ?N?_5yR2MNqG8uvfi)/y-Ig2 uSZrQBg9Bb5jsTkJ_o힭l잶֝MݣyDcRgPct8m`e^-`v^ju:ew;be0[h*#emlY~8\TԺ% d:i(_-N腊jHs :jxʀC=-3-=}|+-7l9M^71F|uKQM~xF<=Ӡ.t#+&/;lP1Q*j ;훨Wq^OVV lW{թ*ݬ{9mXrs\ufN9Tq6ꋞ^eYShvJ)((TDqy p=(rP5\gJK^%\Q5uPUWBo ~,՚zڊb51W.A((wBꣿ Fͯ I7RCf^K)*l=*C3e}ЄR_m=fͼb;Z%TrR7q꧕+YhwXmb6˕1APD劃7L/b|I˂_4ƗdxNx+N`ёLL#:PĠ&h<-1 @w]F{㨇g"|S,.q  w ^w __5P[:=_oPM%|IIcQGo`/?67bR&o6svLcE xc` aCԿELN?hdqH* Lj`C?2)ᡨ$[i̲u0?xڭViwFyI,% -jaiF&l Ac ];_ds7~Z/$pweZ 둔/&< MQ|(;{!eQڷDD"PDYd|QF˶WM-=.[AU~:ʱ;f3th=%UUH=RҦe+I+WPˆN"iHgh5(l(R$Ay ͐ʧأVK/yw9?)[-9#;8;]V7d; U[6;տ٣L/4#X*_!O(HV SѨlDzO8bJ\3FtwtBu =w„DzQ 'DJM6XI٢Jj+&Ny_v38ԝCVNTrF2l 쓘Log($mlgyN0urp0.QQC"yRSq"{T4F썰w!8R?T h)7T/l6!caYVcJɶ B>ROk/Q'Un?3N߂`o8H]d[ʩk͡Cuq5M7ib.X6i) R(2w w(U}l>ϕ8o's0쿣1t{͉O7pLWыS,]nhVG\S8=\ `bZF)|s0h2sl3g `9 v`9 `:a2AOx8j;cpC3FƎ S\6x8Y:C"@J"'ÅÈ]UGk ,\+#rustup-1.26.0/www/fonts/FiraSans-Regular.woff000066400000000000000000002423001441327105200211070ustar00rootroot00000000000000wOFFDGDEFWh48*GPOS2a{^GSUB4Q%Rs_pOS/2F$W``wcmapF|6&rNcvt >4TBs fpgm> pYgasp>,glyfYhRS9head66~hheaT!$hmtxx, locad ? nB& maxp" r name">post%4|prepDyxM@Pv敍‹ bt%MZ@?; %[vLVm\),T0+p?JHOx |Tյ^{f Ƀ!!B`ƨZD P|ST֋VzZKUjRR/Ug>ST))k3L2 q|9ϙJ)zA6*9 Tߥ:O:ksN5_PG2P_T޽UQT@ P{֌Ūlnݜb5uc՜% Q*r\ʧ@gͭ*_;k g-MeB%rT_5D ] w)?lCK+{Ul<)K)GSVPN~5)P.Rʫ)os?WI)P>JYOtWݑ>՞^Ҹ]A*Q5i$Qad6뮣du{^(>_`J߸X}nM㸣XodWR8ܸm]4B#B##}o!߇Ԅ[wCp]ͥlQ[mGx=؇n'8  @ߊ`c;ln@* 2/RUH5FÞlvK;9LqsGJ]Mxdc ٕ<1ڐ@o{Ț6sln_I;Za ؇^-Mp-]h{NQe~ө{| D\7E⹱o KyR|F7M^ {RVc^cę5wmֶ*7[r3Fu3^hUCm&Ӏ7sPK61o_lqW[_X!to!ݷ͛p(X/v-lG1[h=F'<?o}oG_ @ou}F{ 1Z֖軅qm>&bqܑ͎wf7;+2{'#4:gbfzD؎ư !?)KvՋ+l 3|}x)c1~bQ>XMёCq: p8CoZ?iakbiԸibQ3&7:rU}3Ѱ acoSM&5 {FnJL1C;nWԛV "4$Y"H@*ơ76e-ӁƤ7[o$-=NiL?vTG֞.Zݜ, ?jt3o1}U=Mtvr\BRl|J /ɘkImGwi?f_vEz;|nȧʹOb4nD괾l:|$4g~Ff "OGz17R&&!KSLژv:86|>VA;O:؏yĦsd}}&+|72[qIxd&yPL1÷]5`sRI(yCZ}eGτoG Q WwC"8@cO;oTZ3đ9šhC·e֏ )驷c)=]6șmNXZGFj!ty["6:ou9#Z۞Qw|_G ]O|u{fBS0μQL4wВpg'3'-r -Qqusglv;`о$'q1^s 5ǿyӣjӇ=(FƶTR?,t'&CޝH8iS[[CT_N>z;zjJc`9r$ASw/ţu/Oyb[h4;sA-N;Ez5$[-p췞':Crgmwv#b;vn?ʜ'HlcM<uZR8gPb=oΡFv7Sߟd-{sC"# ~n_UELc~;Ѝރά[E9اnrF74omق7 M޹M!j+rnױ#_z[ U5T߂oj:sHӅ,~7^~?imEFg͜}ojiף޸o$;|ە_JЊ*19D}UywfIax,yAav~{"1E.Ő{- 9߉ 4*ˠe˯WAuӏ[P#]0u-]o:M&~?)cܣ1 d+~Vsk*Ox{_=WՇI1bځ묍jmI-mǵ|3-bmФЧ|KI^v=FkBz^&3*3C\ %=V)Go[c{E]Ҙ4Zo}tf]UuK6;Y>"GG^Ћ}ژo>Y']K7:mG o{]RxϷ+tc.o DST'{Jo#jG{4D~1[Һu!{>H~DbΧn{#YY7NRK]tpW c΢_UOS3yTݽOy=WSk{5osdI77SQ5~!FDLޜ4[6Ž6مPv[L7ECV+`/o=BKCSkA :ŰOwNm^QOg]nw|ovI-9G 4',w< >5lG w⍦c7>gCOx7ӕ1fͷ&S)LS߻|蜼=t\s^-11l4Eq914g@}Lo/D~Žl!PK;mlGHܷFꣴ)I^F&>e9܃ܳIkX&EdKTO<ˆOW.6J{AZZ̵ ePs5Z;uoGi#N =T'WN H:uDBz!߉K !OV垫&.ol` "}iS/'Ֆy۶e'-ϩӟrpZi|i 'N;Ӧǵ7i5isX 8~*{iGz|I\Qz / ɏpj8v3$.壐Ɔ$ʩ3j4)?Ov~}g8󂌋=iԌy~WU͸.?쌷uimN!_[/e8%a+Vcɂ|)B0vSneS!K䭔SS>Ay*?):rj V}A< Vjm,%,*1C Ȓ4n O`Pp׸|%ϫMHOduX^:,9)'#!w<=Tŭq\>9.lϨʭ7r~唓}\_l-u#l6g// /;)@]̕"G"!MpV#D:iSoF43C&')[tSg ҵJoE~=MJp qߡ.ud?beSҕ̯p_ i=ot y;FgǍ?^xp?蔇,?̇؂̷s=sGGNIz|K_8>AJo3?BYTA>.'#tҊwr=y~U'X.I~C:m qw;bUIp6;$.ѳr37J=wvt=)_&)v]s.{r6R^ `HS+s%'w=Y>kt8<ܿs:ޞ8$+Wǭ8v+/7"GNX GVNXz9qH5or1]Og,O*2r x"wdD*K!f25/kAJ$uߎ3+ ٻv*KSȭwvmt5YXhffC3 qY}m3~ɒ_B>cبb舙%Cv"?g#sYvgνm>ݙ[ξ¨<4:w\pۉ,ˣfɓF3㡘L@Lw>K|_n~.RDgXg WpG(f9Ǡq9͙ٷ˙5n %"mY^3:Sև!f#88:,g5jHvSS BA d׿L߯@6|OUY|5%|WKo&}ޡ׷ A}Dňѿ4M%A H@?$y-_FJe2qz)(ijLzLd~^9Z?qH"')M9MNr9K'zY%rƽ\.urnVUnuz*rܮA]E{^Od\_ɯi*ȳ#[ / _Oo2Fv'3^'>e>(D'2^)o2f2ӌ6cd)7ƌ73TJ62L5SeafJmfq̕M/'f,6K29ɜeΓRhhY/vy^>O3k4Y&\۔*3L7̑3 ̅~fYYeQl2TolQٮW̟@yO5Eav]j|Ƙ=f*3-Efk>7q.Ur2DWS\9jk1uxulj"EjU湞q=\|R\b= Fhh??=_?\7zGU6I?Ϋ|hko@L[͂gjK:W/H-A2H dZ/UeV'B—hRZW`=fYr,UWB/PW[}/Koȕ{Va+<7MꇲNnQw6u'G.iT 9J"mGȣ'I&PrX^Mfƕ]FYVGH3̔XSN6 L6VkV_Rԅj9ZP  a%j*Va-T3ԉj.V9׫խꇰw76u z3`w廐7k^@X88O'݅ to}R>Ї>(S#!+UЇj1>Ru"|Q㇆F]E𙬾 R'ӗ'U{@[ӛO*IW}uZGYtdVSt4&֢tltGB.'r045|%joo:Uuj@5HߦoSӠ?R3].؛ֆQ&mTMzl//UkkUMU#c1<G'ж&۱PA7F/߱L@Z[8Y_ޭw~MI;؁:qGXXfW\P7bޣ8dދSC_ls8Mrd@z 7HpWWq^d*2z9'Gz9RF9Bֺ D?GcM˝O]őo}ԁς:֖I`(\QA2v4|% dθPe1dWeՁrX%ʅjzPRb"(q jY9[HM7fUrJzYo!rbbd|OfsTOrxI(:T6LqdxOh'' ˛deG`SAAQV~ ??jxdowyWe?ہUpj ovj??POO3(V_ J|HW+r]y<555LUoVC~[ %U2ֿ2H僯2T %1jxaLTCT8:GYoC+\q+nr%]]:t{]O>%$MO|EoujQSL%QcH ?*6#n*Pç̣X~ L8UDϳ8svdoo㓣A$X }pƃ#ȶQdlGM ۲_ZpI "{:tM˖y,y-݋Iw{Qyv, F!\\.c\: dēKJO},$#R'RIIL$S_DCeԏ)dTTGM&_@3H4hcM-#:,%o4y#j"4R'EIR'@ԑ:NIi&4A˛ y3HkIOAj,O#Wl5zm6 3fX>Kic Hs -cC$|f IӇ9JH$@#ijɘ:e12D(dI.ˈm!)%E"2bƈm!#6)җ%E.df-dܶ,%K1n[Lcܶ,eܶD9DYLK,&K%KjH\Rd1)2q[. IEȹGQ'"nUm)RG~ԑ~Foْ)~XU˒ZFrG0-7J,]ꤏKidLSGUrn$M Է\"Cliew Xd$S_,fOIZ2L B3- $l4κԒRuUda%)gDXIV1"#N 6.c\؟a\X)'2"Ȱ9r*YZF_%ɳ:,Qr9Gòmk5X". pcGSH;S 5mLy|Skoɷ -瓈r=Jl9|%AH2qrg9sJdg14t< c Z)?DO%S:8]Dr?X1*]ƨdT!ԱJ֐5$k.iZCf瓣{1Brl.X.yuK֐$$h Kj 5!5DM ;k\ͦ|H -M3dUǑu|rɗ:|M#_`5"?K4RθG]L֑dm)4(ĭ#q琸$7h*L-w'w琻8WoLZE|4B}$C7,<#͑Ls95-ǓAy|-kI 2S/a~bs1n1 b^`.763W+5Ӭ1kQJs%b+9Zx70Qc>QF'1j=QXFĨ}Iڧ3j¨}ګW3jf^4G3vؽ^K_z-}Ws9H~kcû15Z8̃<\CԫzчUI~S%=&? G>GY*U3ʢgyy)SBK^O;wBsBO'(z:^z:%ttE/f=M*Gh^AxAd_PIOOB?=Jz~z~z'+I~?#3 Ҿi_Ig󕤺&սz y%Ͻ乐$y ]Bz{IKn^r[H.!K% HK#.!KH`/ۏRW[Mz[!i HZ/GDBHQ/9̬ Ǔdm4)TN iRKx/9SN"[z-YdK%ҋT&U*9 'U|DR%<$Oz'䉏<'ȓJǑ%O'E䉗cuyRBT'I ǰ<)!Oq<'OI y%O&'䉗c>@'^K$%d Bz !i:u/]ҖA[>V|PZRZZ2QCKIܗ686x2mp&mpmp_`w L F<68H<6x8mp>mp>mp>mHZbZLZ_/-n>-n -n>-n>-n>-n!-h )^s!gsyWV2ȯW~MM9˲"O?rb?b?m0{3i؛9M{OOٛ؛9|gw ;3 |g`$wdϕQ}:}j\|l-p`k}]}l.Ͼ^^^^ޮ\ WѴA$O.cAO9S~ c,*g Df _~cZ2BF ɨrH$UI$޳X'c>r,Hȱ 9#z3z?4+"ޏ" ɴJ2m0$|dZLM f轔q{))(y)7w)RFUEEG3ҞH{&٘A6\CI YzLm9iYNZV3>,'3jFQ/"?K!EIjR$,&Kjt YZG֑di9YZG3Z>D Q$qr$Wj!ZDU$W}j\$Wj!٤k#]}tI ߅dlcN o}m䭏AO2֝M2֝MfA-"{rrg>FG1D&gdLN&䑌]K2jb:2jGVO%0 bYğ6ڬbYs&#y0gSI R=HOAFH!&s\r~9_N2,!Ij~ ?G=/'srr~ 9?%)|A~1 #I"GkGIt>HH ^Dd{$ۃdlA=lfXJgs2ҫb7Fz Lf,0LFz Ȧ//dWo&!$eX^摗䥋4$|CR%){HQ$e #Ǒn2C"N'Fy_q-B3:ΡEP!;)zAc)fZ,UJ-=u>4FR&M|ZH+i}hd *G:զFԊb+EY4fQ! !)fĚ+Tn.N(FS6MٴV1!!5[?523^ 1"aVFkci18ؘle0''3BrfV1ø˸Ǹ]Gƣ) Izx>hcv0`jiojL13RƍyƕƵƍ-RӓR猻/061~qi9ㅀ34F1:ƆƦۍw;g&4c188;#ԸڸθqƝYc2{6~`xeָ,bj 33Fkf*Qk3gCQ cqqXh\;,3fV6.>RJbb1r1ab1XvTeb1X*+bbȲ:IYd*= {ZCF N9>Gp4W\~X"{XBP{<ԞB٢GqЙ~jSBP.Ԯ ]a o }/~;PԺBVZmTEm=E^Qʅڨj2Q86n݁|i83^\1Vk62.Mr|Y+r%EypT't gTй 㨪vDG^W^}rR>׻uF W值& ySYZ;hO1M1!s[)]얋rIiǘ(yTl'e<%O^5IɨKD)hMʢgE5;F]~Q əC8~<ʟ:!c%S$[I 2QZCoZZ[[kmQ;ivѮMkGj?uѡS>=+dL<*d̐rDaݵޢu?K:RGhM1隡c5S4[i :QsuN)vzXܝ w̒2G d̓@J,ŲD2Y.+drBKk6[6&Tism-Tu|u\u.B]u.e\WJ]]ZOzݠF}@7郺Y->}W#~G;r_IWEZt|?G?G];v*\Ac: |#bM\oZ\->7|+7۸17܌s nmwr{廸wܙ wnܝnɽ7ܟ@ăyNd)< x.r!/ż2^+x%|꽼u7n↺\Rp7thƸ42X\rx7Mtn즸<7Ms 7Yn \7w BWn[ꖹn[Vֺ:mp=6-a7=sq'6ROKi(J#MKi*ͤJn6VrX&ݥ%]tY⥧GJ? e $Ia"#d4/+EyW{^%Wū]Ub ^ErWwON+XO/ԂέͿ%[j.XPGXRgEu YWoV5j$H5k$Y)䨜o|+_ZRKi&Rҳo~_W+!S8iUCfΑh~__\.RY-/bX"Ϸ|N*Ɗ5o쿘G?ʳ!]"j-U j N7y+o>~S|s|"]EWu-]'\ ?v~;쎸q;.Jh*ե&j&Mc$!3_lv%$([vBۏ0jFv^.("-Z]k"m !EV^Lv ͺU{PgGbwy'FEG~U_ӯ7mX?ΏD?9d_5w^UY3|#9<LD7jS6o`-m VWuWe\qE),u-,(KIu4Tp!Aś tpfst,XSS4 plphPǃ:c ɨ'b$pQ?O[+C(>X~hG] vR؃)U(2*Ā`7xh/>t_fp>-P}B1;]a cGXp1G&,81qCl}^Yt O=}x@?_ <*k0/gyK0Jֳ`~ Vs`e-x~^/F/`E)]}[xe-@p\yLOڌB l$>3 i^LD8/fCv!K°Oeb5#؈'E㬿nOcO4N&Xj&} & O0*u * O]tis(xEQ;/FiDmZQąuE|D=0d sDPj-}nZgvtFt.|?Ճ`CI w K=`](Cm+i*4/ ),$q9Le2ՆL ]SLtُ=_QwIh*"_+|/~;D1o`M_p?J0*T 󑅵wF]@oAnNH}\VVuV=V߁yO>S23)CiвȤ0cD?ϐzBO"?*]gH#w.] g+Owºc#8Dw|zQ 4]dttא44p4pw3%t~8SΛ J ZF}xΡF 4+@c|_1~(>oq%_(0W zzՄ>t7'q}z\謜Ui=Do\ vs53zƵ@6Zzy׹ &ԃ- #7L2ZؓB%oQIx\*ȳ v$>^MHT>ç,"1*huBl H**o^"xL--Ҵ6_hv u"C#_om c~b@AC//_6H>LOiSO_<=IEI ްʢht ~J[+di_xc`ff mYY hgJ0@sk2N`bS``cg xwVՕ^. }\E" h'qD E+E~? p]{qf)c_nZ>FD"F;VT1n |Wkcu\o {''.j!n#xk-;*nv]7۸r|KZI{*"r/b$Wȕr\+ߗ[6KFɏqyƭ/1yBgd([2KY*dTV)֨GO͸בLZE[n< 2^&$Q$7yYR Y'{0S{e-y:ܸfc]+ه6HJ?eC]lUv]nmh.:%,dMY_flXø942P[0&ԚYs;^rO|:Z#kʹ-+(;ҮڒygRr('%r)\/?Kvl'նN"V;_ʽR 3@1:AkBpjx- :~l;ğDZ8.NOgsqz|7.4m-g'vuq_H{)jo`6IUIoNGwwϧ3WW7ti&-}`V'k5ZfY1̺fݲوlLv`6pTX,t) ( - +L,l-|TM͋-Ūbwvl[vQiuTU{c<``J7yUhQaeX+֍Mcei8>NOǩqZ|>3x:G[Y;j')ӭ]c8{؞mamU'vVNGwә{.O7[q__fzYE:~ZoFefcq qpޏĽ_{ƽ'6 kz\״wÇ=4o{вCQCiriJҴ5B͖kKJMK᧔N^ks_7w*-/]=:FfRjQzWuD߯^Xz ]\uqMD6TgC׵iBIr-D>z 30()qx58*cu}(qe\7ƭ/v`7b-ĮMv;~ecϰ۾j̿˿s'}[uM6^a+QC߲u2*e_KPmw QJP :utR=&A7D/ڧ'ujr mCy-t ƣbODOT 3+<a9 ;O֠3ޠlD+K'㤉<"rNʣr^Z<)mdN "Y SLN+/o?/ߖ)/3T$/j!o;r̖sd-s\yWΓyr,\(e2Y-dUi*)"e mO d|(ߕO;KOJ~$\nrt۱Fk-Gպr֗h=i$V= =hKށvmOZMID#:I^%$ I!)Eai51Akz:UI] t(F| ށ7so턑9_r,sGFn`M&{(8'g A+0N&3|o՟|B6dן"/g|O%,Z#qَـ<| 2^d,^LRWb|)?(N DoyK5j9wru异5yJzn5c r'œװENrHcIOボ׌e9s^GVy-Xڱ*fuXu9_s{zؘ{0Wn,TCnl =rÜrܽlǎ|ם]Z48|[~yܛؗyOv1e>r'{=_uc !GEu1*! RERmk/1!?0"_~w4!kY$u )Y!/ ehBmHHӢZ1!aIڢM6%YڌED=YJ z&yҳkM/yMɳv'zy hړE5X/&mz y^K/%ڛ֥=)֣lJv3J+)() _yP+/'( KDd⸜xfՀZчS߈<FD;-9=Q缜_\沺e>grx5)2E'yv\ܻ;b>Kk)rLsBfث Џ_IQl_׋)`{8|/!EﰥK𵶜VPVQդ'胶8 I] } ~Y}^r/bc,~|_t?{ #{j`uzc}~Jyd:\sք=_,YF=07ƓSxp(=0y/)TJK Xgk{{1 TEosCH2?MȳK9y*MUlI39XE=3N:Ty*"km+{Q佪mGԶo]DޫO#;yd8kuDkZUW&׫~&B.:s0B=_xڼ`\ŵ0<3wV+i}W"YUjY-ٲʪز{wSlBo1@B $$Gd@A4F CIzF"g ,>a˟yS*>P6}œl e\I)>Oo%@E1}hPbJM5-|s|lK?t!&$øD, 00 AԉP,*6ma?_GƟՑ7=sϤ"N W<]Dzm#*0'3Otp8&/v ũ/!~Bhe4͍:3#Ah:Y2͆'cHx03;Qh FoNﰇ+q oџYV&}Ygr* m d`V]߯T~ԬZO} ߾EaېI~%!lH£0w>Ё /;]n4f|tg|+0BcqJlOkЩ__<%E\/⑾ss`{iRhE*BtHt0#=E4ش\.W+;"緄+iWsa齇o=tiWk^s`1}DJg{'9]8ʰNNKOwn}U{yqhrŅٙ|}l~K/W6;|~_?B">w3{)j6N(#T Sa)@b;P"_)‘5:{@o( sr\()cTLY$*Kڪ2͞l[Xf KkLXqP,ՕGRMt0FXfQwO_?E_3Mih) qgKNY9s/34z277! rѨ~DLd~:x|>5a X#O Ohϒ0Y 5$ ($/Pf!6pHl@ $R~A8 { 9 yk09ѐ"syAو&|-t_.OX |%5Gh fX$9pGB7LdأnRGFY~5<xe{TWl`[J+kF^zދuƯV{s[dPh$Ct/jMd>rKϲpь&5/Qu!-P88*0Ss =6k9.s'oЯ o4:/IOtNH aG PUѶOlOha<2WJ1&0J'*<_n?7*HUJcO Sxw2).X,ǩ"S0;@V4v6KY" .؜89~Wig>ni%[1Ќnx& '3'ߊjn5)\!@b( h{ :Hz g{wz9~S=5 Z覠N7pH*? {JKcY†r=SI؛NZQ 1~PG8hf|˓ m(oϏq×Mҗ 6,nF Y{YI/\D9L VBh΢#IzBK/=%e{=̃'aҴXн 9H3*".U3dcHFdng1mv"]]9_؋H/L4;}7}hQ^`@x3E X*UbSB_jQdL3xckܫ/9W|դD?DŽOY; Q fQoFfAuZU24 ,݊'z~ALޟq~ޭ:۟Y/o rQv-qdr1g 4n"=q9_tZ %h׳#P~ea6khmgr]L vlVڕhqXWv^82Č<ԆW:Us%LO=󤻝vh3œ*q@t?>~Њp5?Lm7/y,+%˪ e>ʰ7.e~]?cOg9U9T-@M]GV deݙ5[:`ZǙm47 sمA=0$#4Μ he2DHQevP3ھ(P0՘{ll~Jzg]?]Խ|8H WrIO@编hJҠEqrvH+-L rLxN}?<Pv ᑹJ&x P$zxdӕMdޣ8IBoUiU_Yuѕ]gDe45lXU=Dʟ˪(Ϟg.mlEu45CݑR6WyjpX$VvE%NB"ҁ ďrd 7)R5&{=gb*:[VMq&ۛDnlVZ)hKd\9 Gdz9pjo_2/ 0ٯ\anrJ"a>{L'o&K~NIO?x+Zf ]q&307ϩeG}?Iᦶal&&8M4!y\3dCI @ B:O06SFuOR:דa\dk(Gܑ$1 <_s9!'lC 3卺':ߖG2\v::vw}9_%٘ovA!~DDtɌV៣_jtJ@)> hϒq' QlNi6i8eC:TҰda4L{w:UH<&~KfUW[l4~5yh:>Vʬ@}?N2=v~4`R `| bZ܈YO'sF^+.#͓PcŔ7%2g0~M??A|&:]3lЁ3efB&,3#Ć閵xhӥ}Oxϗ& Z <+$!=bV+7_)^0ox'Hw?|Ïp੽H??Y_?;ϓ+>LO S/ՒN9B5hWs\Ex@ҥcI/xuGOy/9EZ%2[z&kU[Rmbn'|_$ٖ2ܗd}u~)/DA]9g u! QG9-`uTe{"b JȧLL+0TSp+yzſa?"r]ОKjꖹV-]\zd8')*Bڗ הg. /`~Melނs1[+kp uVo4kHhh%"AK>saK,Ġ/4(R Q`Me0( 8R.|B³!N1"ʅ;sR3Cu•vzmυmhXWqҢݝZ\U{Q|' ʟw r&:"- h *ޙ&] BC.y7:Du)&Y PƿS"kԦ)dRm0,///@%՘UT3πzC>9//j\luP~yV.?vڭÍۓ?/6 Ƚ鮞_Q)oDze{!y=wx_`qR(qHGia6Ucu Pʜ(C?9܁e -~KJ6pwlm5.2[myy{Z+&)obƱޯ|!k(1w3''=LԽ6XOOEۚs՜N}cvWSU,;Fo 'FyټKh=\%4&dMO:|{ibͥ&[>{4UXQ_` ]v&,Ttumnumo E7h-,4Z6iO) QUD,wFceɜ3̟ V,.RUmQQ?]j)]t^쾆̌u%XzMC׮YK/[WB"j Q"H{vVM%Vf28srrr ! !P` % `>)EuZԕ 7פhZXUo_mj"]$tOKYe}Zu7niYд*ԒЕfYX~UB bDڠt;tl2 XEy4|)Y B`II 2A+G Bjيh4B65跤'SSɄ=J#;Ok[cIuʮᶅT;pv-4OǕFs;`$ =D>؀5b˾ʉqɠ raf튟wᯮ-*>-ܭH#~_ OM{Kx{Im >>pΊq|ͫc ZVv M:mt{ D :&N:I42Q;ϝ?WѭVoaHW: W÷K9h E;سD6ҳf>A}aC-` u"_lI@sV6(u"pnٱ=Z_VIa0]Ow-=HYjtS\Hpi90 F2nF:=lI$ 'c0yNN?1XlJS.uDEE`ebZ/F.lkv|sU:YDVFȦ@NJZޒ{Okk*fgU6..JUVn NNv۪s9sڢr=\ l־ʪڝegN~FVo«9DG7#RߧgLoD M,~kN#-(Kj/6(ш( ,\w7-͙͌GK\SZ^,]L,=\O` 6٬DuFSI΢"jS%>62h:Ϡ%(LR{5|L6e‹nH>2m֣mm]cxc'ǩ\JoYYy%e,F̓$K/y_sUNBF3Ì68'9-1>'~qρf͸oaZtMVC>Z}_n[d!=H wV1(]&>f))nۛIq80Hr M\tz59'@Vf H@k7R^y@u,l4`y W&FeONf2?릒gPc.jt\6TSSN0e.,>ueq/juM|~Ԣ$,G b;Fr(9JЫQG!{ N R)dcT0z}c7AY(J7܁U3tT[|~:l-3~ߤ&1Yo+QD)Iv! >Il> ;?)1lRi;SL:*>s=>Z}MI~fv焫b3M9eO1ooDBe qiX!= "H }=-%$%ҠMqP@q-Mxo) k%MP J*WErbm9k([ӹx$h~ty#9z^3]dքj1@A9ܡ#DL?çn9Ay[a2W3ό d=9[_[XP[T?2՜& $X[R.]Is`EIܸ"_|6&8EآF08lH,gCV*:E\`-SrjqRf_FHIʾO.(s$g4;Ffm{׽V=Olmr?p 5{6v` p _v2a9 2y9p@(b+S ]^5\VqN% C]^]{kT[a @ V 6pA^IW3Ɯjx; [lXxE]Hל{}[ԓO=9?rXwp[VokH@3U8^Ry֞:XNUz] ~RgɨQ ~}FziFUe&TS t{ _Wn~}6Uq_{jY;iSH > ֫~oLY8DXtnlЧ&n Is H8`qo 7Muk >a < %^chhH`}Vł~|͌֯㻜֘a-fPE-.g ̵kL}{qAs7%wL&zq3=v+5Qy?JhMņޒ]Q!@W݆/L")STOlMt-H)-C%ĩSg&ʌ3eO'f&`fjw_Wa+NK mZL{2+Lqe*Hr.)'%~z2C`1ߢjU=2˘7kY԰8 1GV{Tl75|))"`>8o>l :8w[ι bxCEҟyc}w3On#k 9|)ȰLCajxP8hZ.!>D˩fL %с{V^n\ntVV.뚻xy\܀Źh~Ook]e?IsvΙTz_]:͍iuբbK47rq[R v78:H}eŞ-[6#ZQOm-s@!+r'FI4k*TS<AʺL).A Ғɜ$#kjXJPFEe ijYߜpUN$wn> .[6WcZRdL (^ciáʹ'ᬄ. &2J~ҞD| M1l!x^ }sDffKObM1+bxZ[$4h=C p[^Zx 4ܿW?TxVZLJ񩊆Lf:< y'dҺ<.8k]u|r)guFPwυjVJ]sҬPEzV+hNB^|_5<7b/3KIwk8[=e;ˎG^ TY{v@W|fʓPOG8萏>H ;ji|/S'Na>2xMVO0̵z2Ք,V'y,@cT[}vܺђ5O8Pn`UnǦ-gw.SyC*`3|ëd8К|?lTۻs*<ֵHeqAT9I%ESW8bF MONS}~mUFk=aFObUU0zt{GQ}PG6Z1fJx(ڊz ~Lvr1/^ms8ղp>+opϟx؟~qxF8wRt1~6[^NjB{/}~Ib5Fs>f95_π:H65۴<568km3rFw,[IcˡX֖#C:7n;sil&զ> d8HVh L8MBGsවdF!OO/Oflu=lqqھí:ۦ_=_/H:5:2>Yܳi;Ιdjd1w;;nMON֌w2;xR}Yn|R~g׾iK{L晾{Ǘ-io]bj 6|Ais{'7yY?2n;nWî;3dp?GswrHq#3,&SQ(PT7j}ixP89TZ種:kuPyQY^1PC3ܡ+;#ݳsH~ț5{54~&jCx kr8uPE]-:ws]slXjse8whxlbOφK@`̐46'"#YZT8pjr1c>y߯U wx~S0ZKݡ*r-QSKԈߔ\Ebtjvux8*+ha>5ELV^SoqwpRSG<}M5u4kPlLԡaep> H$qk kGbE ֬۽'6q>(]t -ӡO7RxuHx=΍3}~>'ucjLtoCNmEux(sPA4hKL#@(zf(Sa&BKTSٹ|hEhaX׫> u9覙#UnH[x2%=&/1_?#J)1%8Ԙ= 1}qjLgl8k֕[ǖ+B.|BrM>cQƋ-˟g+T֎R*#7S~_45`u1}Q 4/CL{g5qgK'Ij Op9Zkv鯔y#ҕ=GqCF~:.Ƒ^Zx Tk1|1|Q[kBKAsթ(5]p{^(é?j#{AK=z*K̲AZiuIM45q͘$wfe)6@?ha\nE[D-ncڂQgOǍh.h7(]w`~1;\N<73;{Au嗕K:rv ;ّ͋ցD@UbJRN^:sn3Şb^<3Ɠ\ !bñWOL̝t9uؙVzeuLݭy/CS^VEêMu3J%F3T4ģ(z}^~+ExxIogú37=?߸daT:!a6tR#&y||ep>ïidlrW;}Hk>un r= uKחͣH44gAon  q] ԏ ӤpE &@gq~rjó,mYF=4˃suNdmzz+8X-p#. i#]fi̛pzhP㢣G䂻8^2/qoDw 'Ɲ ;ulioQ(8(F1e=͆Qb$[mntNO6t8^? owO޿ޛVӯ-%˾ym> ŗn&,\iI'Z5^@↑A5wbS7u4 5mko>sNMtk~jUC{]}_Wd|/yhiqa`QXeYiՂy{UGkݩG(> .np}H5 8l'R7]x' ǥcmad`1.P: ݯeRk/chtL*MrzCk|Ȃ-ݱlk11-WM;1)cTV +G 浤 V/)r6){֗6BA_BVJWk_V<T ^O\tzߵ.]7ݧhS[wR4Ίm5Pn4'71ƈE6@UUjbcòhZ[?qvOarkZuXj)9Yb:AUpE!f.Ztށqx؈cJ/x<9n[L,Uaj`즶[;t6yp[w?_ye"Cz1;vv׸.N&,XX r/r7&B\y4A0~ 6m^~=6:i?6K\Ϫk.likfsƙî rʙV'Lӹ@Z6n8k#TnSftL:=~c}&j~};6$>T3ؚ@3`բ:&՘|P6g2N^rk7& ;ˮX~xlLN/hHe{L+%:4 Y.B),/㝂@Vm+Q,iQCEΠ` 'xkEG2NJB2Yl2lVa& RU;W'pڬ\~c8kl}wF&6 =鮆C'jmR&7Fݼ, K^;-EVXR&|4q 9 R.T-^g:Kݺ^\t}Mnˮsr7an^:х]⎚3EyK0j{\ͣ91`2G,|a<|DGyM?K'q$?h|yz o7ݛݙ8!}Hyw{h:c7+nՠUjLIQSd 쐦cs;XG;뭖`Pz~#ES3“}T40@}@l>\IeEk3Jaŝnئzn[ _F8M ^цJ fYB H) pPECID]JVy9H򴤧6 Jʌ'e.j~љP%~)3Ch00'-x%1N!94h-\@O{0HwjH!?4]Z;FZk ]lgfV0T`ffmmy*1;#ޭ/OXcVW٤a5*e h*8\ c%]Q f-Lx[W1R㶩"hԔ #0HAGؐ`E(;zp ~4UUYV{՜9UkWop=H_ a6!;hy*2 ؔ<$ q)_qT)?Ŏ/W{D5L.ST%\e^.*\Ei\׾`" {\hWlɕgjR7jߪ-60(]gb >AFBg PQi&JZYۚP;G@+_7}Z&+604Vå>afwOw,0Qg:LMӟFt&*}JDHC;Vvl߽ ZI\mV<'bF;`C&@Ѥ8HŸj^b8~ s60_Êu(#\=?1!~2dېȜ3b&mQTMeJ 2Z&ҝ`6~!/љ(Nh@hbU͔[Yao&{V}c ~J>Ik*=̷^G PF2>yqGV y>rcS[;\H3d' .ыh-Ϣݗi06FwF;C'|A>TA lZ +Sy4'rsג8}G]CʻbӿK}݇I}2]C>V u-wHU71h*(o=r23<Vaȷ/2?韻J7}-vw哇>ך't =H=}"39oӾ8 &a9?ޫo_dy 5ћg8Y-)rՕ;6VL(djVUS2۷& kX|tvgY{ۓ8O_~Tsp?'}k)oͪm*P'Z*s861޾OEsd\%Ԋ'怬 _S~ʴg*6Ɠ:O vvҼᅟ/Vk%Pʧ/Z"3R vv7ˡs8iUziƾ-tƃ6_=T55K2ڛ;o;pמiu-miiKVDG0co/:@o؂`/Jg}O(##==#/×M#y >!E =kYrܺ#uh>/&IEui(*Hg"jۉ,A H8n0h w#E<=HQM|F:N[dqeû' #z1"}Ytg ixa$՗ѣG_JvR=޿uduwj׫Fk\[k53hH#H*ȋP0:l 96zpǷ o;O?2c [/$-1QOM_ãhʾ3ۊpsVZVo/=M_URW^ИSR#^_SqiPӻ|~=Z((;*~ha%<цGgiF8OQ-I߷'g۰" Y}ַ ޥ0n8;^s)ެeRw2=>)>/g.FGb/Xkev7 ʺlhdzt {}حfΕ}c7Z;SB=+- 0tD}p>AURMXeTanٽb6X{?.Ļέ{XY?"GQoj;&X؉8] zmvj Q` <.Q1[_U1J?iLIЋ&jٯq|HOk݃6ё]]kj(}ɡm-Q!#wamw[s@tYiIvNѫNa$@-Wߑl0ZVmػ|C//.AYL˷:AeSK!y$o|yW逳%ґeՎhiyE L4qT ] 2\уé&)ϟ] k֬YvS[>%&IFmpԘJ#yJX6gs'cYKxsvUzޖmf7>n#u uω9F=8fJ.R/XJ8cC?WC:Q0O!񲄸q߈v'b#Y[lڸqō| C7'`1{8UOet԰[88mOckk+W<ԋ=}EeJtgGbzMfî{cmjZ5|JE]uċgGxqwѱy?apd::9l3Ŋ#obf'|Q,&=so#{ѵ24VKc#Ƒ#b+nJ ͚%jW6מbőXՊcu>ѼŊ)/`ڽ'bέm׎o _Jrx,&^Wy{~Πq4yfp>b 8l A+pAuNQZs*u{xje[jk]־hםF&>!>\?S|Ƈet5й&>\mZCu3 مJZ#uN{`[_Q{ aW8 e s9-SE.yrDVc7GQߛoԠﻵ ЫNT7ePkىK /Qu[3>|m8stqH<\?{9Ǒgek+h?+i43-h?h#9yfq_z|ݓy.B1[_{3*,řuwY#{u& :g=S&g/uȏ$O 3`?t!Wцث]]z19OAꎪ-`oX*זqq_7֣3@i{W-[5^bpxmiw镲3oZ׎Hp.[ov ˤ 2p_E8=ExYڑ 0'(kyܞnR\A6[[U`Ҁ3׶9AqPsnaϚf$6ЇėXp_Rw^3ԥy&44{=Qk}Xx\O~}oqGJ @wb7=H9[agc'ım6ꀶl4k%nh/q#z 6=,|0!Hs\{\Wwp f: ት£*_[QʗX,޾I,9S:2k CT#)D̋}#1>N}\ NS΂(.bqb,={l^dzenzdD~KzegռZ|J6^|Zߋ}47$t+Kg^7˺i=5b >w?'$wBRs >+^gSx=Փeh?=U7*NWӐPo=<~%#?XOjN.,K7z% gtk^{ }=w,OM0ozeu% 29+h~P~z7ovp0jz)VK?9ئ[S%Bء_?ib4-߉-cg5!6=8ZմfYyJ=<5C2sw1#u<XF) FӦVI?WwS> ת5r?ެN >۾my55;[9)r,nLO8-@=3@YbL)؆1Ƨ+/1ܙtA~c]pJ t#& PGe/a)@=@@=|ϖhktm2}Y{{#ڂfL) 7М;ם ̜ aY0ŷi]ryAKe~-Z _;ukάF;blb M{X_`ˆCo^yLvCڷ,]LlzWV,ʾ w,k/[-ɐ̮|c|Q4col4G]4qЩj`\ x5A+ߺ$B?_ֵ5}eW\Xt<1by}Zg\MXMm~Gt#kX<Y8KAsْD,K5o6ϩwǮGgx͙ޓ*ؓ$Ҋ2 qK&nK̺apa<|5593T"i{Wq-߹wVۥUپҪnUת˲nY `04f@4f - /Bb33ޕVxɮ3N9s9s*'YdȡGh]ܲΓwwZyP[Oo+[%|sgߺy =s!Yy2?ZAf0KxiLuF7+qNm i^Z}̇֐ЇV:-ͧv78 47wP!`?/ŋd,]u/Tz?ҮY貃ےUc0dيζ2Qr'3Ƈı'cxV/d|h#@݂l EzF=3s|gåtfXg :4W`}j sU H&DRAΕs]B\PX4zÌBaƌ9tH0txz0nAfF-u}sb8@c\emseAx:gd:KD:K_6MWE <4zK=[-q~ޒS#fVGGN^w]z)߉+s&\Hm8a:ɩ>GKJ2 90ޗ {aɛ0/y.&%aF:1 , HOF ?W߶ t=Uz'âC\n*:.~>+惋{` #?=T?`=/*۳ׄyb4mtb?Zިr3cHspC}N6/kX}2?Y $1bP?UFWq7\6~赛xhC}褹Y>D4M*,@ĞxuƘg`VtZ^6Er,Y ={7n?CHzB= w4w"a^s5uډ!?4[l܆E9Vp ^8kCIV7.F}Z^*ߧNFuۮUD,jddHS+&ĹF ~ǒf/"~om4ڴhn4/Zn(O0fqZޗ=G /}$>\̓Ø~Ό>b;6>CMrxRm!T5>".awn{WG_=.6h]7&qji𛃿\AD膚 aҊ%HdMи<9-wr+UMbDu].ũ~Osnfn߭VfNX$S$ Q^$ )B7](=46;BX)x2KВh~]6hDLCw M tV#&0'q r(J oEJrD.ˤdN*O3iݘY"7[ٱȯ-tx1Tmv UCAg͍*/x[=8,rrİm!OK4$:]T&UTIatΧW=, .$E== b ^Ċ']X y?.@ t; 2_r`EKm )N!\ko|Mڶett->(ii_yq+vLϮX1JY$s+^Eb86g e6RVKP 5Dy~?NpK"@wn8-i^FGn{zoGpЄ6`)" r]kb ZRi 2,4n根PuWBF[^Y\ h_pwYwoXw@ OEXE' ~J--]ˁ<%;\Ayh!c#(gJ9ylB)?/I91w&*/?Q\aZcg3ظ|\L埿?a< Ls$ ]T!gfN@̅| \s8 4nKX!9:ә׋IômSa mۃ ?$Xzc̬'IA 0awϣ'a)>Ba!x7"pN1ꑠxՙu&URXEb-M&I SFm}9lZ["fRŶUK+^WXj9FOWt +߷p+eK*bK5SfԀvE8n*cuZ͐ٺqxMi5VB\xs\z0dͺ,䒺5MT.7 x+|M -Mh}e>؏m*^oHGGKς`cuL׌ݗ̡WVq$H8`C,~̐ܞS3T1x}U$V^u7f>PRl)өK'/fsS큄 h+ŕk­k GY~\Vv{[s[fZk|+TiI1:g\~XHMu CYmG=Jaq1ZCBFYj{eOxԁ$MߓhǑjܼ=4DQN(dnƮ8N(f_bJ N߱q;657JP$TRE>?{>׹wM+csR0o;XQ>ӮCЙlSg֕25e]0NW6 `N+^2r5fRfx {G@MɣJwwޕ 4{Ѝ9L<]zChIv^Fc4XwQLkzZB.ƜO;ӒNJYPyG}jk1azȑTK7c岁GW}kqiYiy-)$XpaJEz8#P*p@Y'cwTKYfSӌzifȁ$>%){*RRc-]¾ldJ%kfuP +aZyޔSO ;RW>'_{oqÞ}pC3M 1@kP^.B{!3b=~s#m 1"jOC6RBXB<&YSPW~l ?;3eG^f7;jDNuQECRNbt㨑Ƣbj"b+pyMmxEQA}abt;w}υn53V3jb&1otd|xHn|k1*{: 5*%X֗>uT(B`,Q7gsL zz Q,{輰u_问|p-y%g*3$5!*qcG]M'o> y"wTm-JO9úCJ򌓣Rut@,U&O/հ?s821<< {qL HyFl KR.|g#2۲\è,%&h<ƒUmiV";fP# r *{|>Uf^_NJ NrnwQ܂q]5Tܽ𒦁= S#ֻR>ֽW DF7'"1yʽܡL欄r`b^3|93)~P.5|;or?\qr"~?˨ӤIRNGR!_gQ;&ɻ؟mN9+yϩNeB'.Ds?62qqwOO\_Eo< f\Ɨ8vd<]wOw$ߠqg^H;t-OAy>/}MjCoP&/-uL$<^hٰ̋E>x%5?TUI9C I33%%3'3[dHҝr|W ̧L;EQ6͗ ydL&ûՔԃ[{Mz\Loqu)|vVl~Ffo'<bp!o <]'7j7<M7޴\ߖqDD_h{g|_4<)8=ۆ KU(wmZ$ZEV>MOK̼T_z^?NoL+7khƴ3> (-7 psvGeQowTI+կ2bUi~Ƕ_Za~43_RwߑѯEUl4_}r듧_x!~}/8_| ,ɏyUf&'1ۃCda&eiq'̯[5ӆϧ[Ϋ)Gub7ݴY 6o]Wիb;A5R^8 Ec"WSAW7# niTzzU}Lj_*zUϤꆴ/ymEo~M[89Z.8*Y"%+]W cJV{{zOe Zo/yٹ0PH7o5hu>kN^j M.8K_"\s|ey͵͍; őԾWZ%sQ;<\%b`E=Tb{=lŗ}q?әxhF7ayV\9o6bc/E yO.Ϣ3Ay5i]n?}O{>vCw<ϗ xO8D$J6iш=c1?/W%NH$X]U%RO?O~1&b91FG>%q~,oU2Ul艑+:=̯]"T0S9~0%81z,*ϊ+wrAgm$Nj~1aQ&l)عdGGY$.UI&V2wp&}[lƆ vϑ#{=ܳ=G@׫~;pQ /&\6H%s$La5j#Qul͒f 9eke6䆣LIC G4B;:iN`K&WϊC[i6׺ MSm u~ɞɀ(}zdV36&Pf0͛d8J̈-XiJX5u#˓S^;}yPHYyruKJ{emCѝbzEkJ{Jze _I9TW^旎m'~* bYC1D3GjS8OdC i$xL7t뭷6' "Y]ldD`;`R);>\K*a]%}ihh Jp~8Bk`XK":C| ?$"/AsEπ󪫂WǎH_'as`9ɹ/eVR'9}0s7\c?>\X7^VcCi}=VkkXTi䂭sa-\8} nUB83y# }Ё3l !I(W%wk\)&s*[u2˻,ؼҞJXljLw'<)Gqf_T8Gx^{޽]h=L2%é=0q{%WȔ$]&'lS;X)ߧ aw"0&_7/T95D V7=4~`]w+++1i:<'\'@-'Wq4kxP][^\1԰^k\j뽴:-EeeҸ.wdm7VjLt؀Gf?D;m koZQLRiI,e.a-k)L? CwVV7 `\D0ߓlW"gn{ܾAwڶ[|ׁ`?^[tƊx|@+ج^~Kg'niinmm{om_[2<[>'0b7}<:E@8!$og7.wAe)-l^ ۋү:=_ώRxIͺ5ͮ^$ƬRgmqapp4ejI0f}?cy>W ~ S~yn]ik"͉U@ z KQ(,[-9VL:GJ"l<[Spp0: XNI0Ü8jtn<:ҫq5j/H42X|}^.SK,4a=~? z[hf' |#n`> {zcXvՎlZf5k6Մ ÃVi,/s2󥭨ʷ0$hNr`C6d|(k,iju:l"ityfގOm/maւ$R3'!'c$ LS6j5Y-Ebw%Y\(<-{mB9IW4.tNxKEv5Ҕ*x W-ryM[[|~ex͚ZP|`#9S+ߎʏ bs1]G~\:,yac^?ѪƾjUؓj5ǭ1-kJ',DZpeI#'yX$]Z:<5f. uN sM.6Pff [fWvEV8X%͵0n/&cAaУ [u%U͘u29fFc.1kLB щ?8]M55 ,&kj֔o-Eևڽ-^WiVVk,WdH3M:C^q1ה!Ϯ3ur MU u4k5FįX>-Ok@o Y><gT{d7-yPǢJMNmvrRR@؇;a-L= RR"LP&6 L~hJL2 ~ * h,z~u>h ApExD@>omx1`9 K8&P$>DBFb+o6Ͷ7{XcZ\ 1(>6vݡJLN'WC L Z?Gg+PP&ߝ 폓 7a}/!ߗp3<zN\v/A ͻf}@#hk'꺜3F3.7^n=_wp?kfC7q%*V۫sN%zC8:ǖa9'85Ng^әv:i[?d[/AO`\؇|oh3$13gقf9=&RzMcR5W?f ]qf5792:s?9/ Afv-DJo('c$>a_J')"E+C N3pNliڛ29 ߪ8I?~1A yoXhFsqTlQ9 _e|3#5#=-*0WSyT`y0wt()@7;3X SpfF'+SYb8urr3My>hb/?823xNAqʤ\\||Gc@sGs>ccG}>]onJu_+P="% 7/A OS΍,;㌑q;lͰaF!&Ip1,ss&י반p:9 W~)aE@EC9\n4 cc…Y[QXvZugAaIb1.Dw|t#ؚ*n60t%#x%TTw:f.z%2f`NċG ﬓY^F#*3mє0a΀Yk,sU[B:~Jkh!W ; BILwÜ)5g{ChӫҦ9D#݃oVqggs@9}6#x&J/)0 CEz{<G){` ރI_C/$2ѼQVJ8l;m[=imwac۹szwῶia,.-r/#/ ]9s8V?=ؠ@$ʠOFz3@ w$cqYAQjF̩g40VÓp?[NKg?bmxJҋ<C}(2wY׊%O0-vw6 !xsep,?#GC# K>ȸ)8bׂ`2!"L8*> yuu&RzHv"Ɖwqt.$K@ 97ŷypz {ǎwq)'ƅ|f28> R:[5U>𪕇wv9'μ}ӦϜpiG)'qy vGڀ6~ ޵U1St% 1+%䢾TC}%ʻ->s׬ml\~AY_M[}؜U06tURXQ0T=?am#̀r)YN#  HFK&Dpr0#QبGȐ\*O&|'${b6(ffJOMC ,>I *:V{xpFۢEK/ڃΝVf&7_:ثUe>n~Qy泙bZ3ARw&ԓHG8hv|d!RB,d\uc`Is*Ã*d ~oݝ|>QVrK[lPd}8`lβ4]p´f5.YF|9L䣌 _6wv}eCfҾ>O,жV}C]p{ڴ{km]KL5<'mu"vh> Ow_.xr$.XCU>peǹ܁ J$vǂcb{tsR;y)dX\3;/^}5έݹp;vnʿal$?M}n/u_j;\NϭΥωPiTʄwB+*9_НKKu\k͏sbh¾vxI]jj񒮾N7Ӭ[لSI,Ӣ։+ kqjV{x"2v|<;O@UN<J(awfo+ZK@ W Yud|h}*‰6#wS%8'qc5)I9 ny%^J E>ǩ/Y~[ћ @ 2Sr݆ tt**#Z³MyS8rxZV̻fiu|{bBys9YT]^z]P~iN6EQBUYi)1*5%7'), ςgLf n' "WM&}㎉;ֳ 3UMN/y%HJIeaG<[ݿ#{HS²VS>mvX.zyB[3G5{M=L t] 0*<_U;wVq3u@%2Yi؈+&8zeJ$ɝKRJH:HDx>OCY]>)/]۽Uݼ'p~hiV>Cy3ZEQUNgw],C%]-o^Zd74oܾ[+ڂ}.S+[:Ț̼Υ;lR*YNa*IYf2IS'㽂A/`>ʒo YQ(/qslL_-H@:\|Lſ ccXf+^`-AoP/b-JǟڲҺuu % 5rr +"X:ţQx)fëLy<*S2$4i2,,xk0&j%Pw =~PKS[p `u,-=Rp FD5޿ƆIiI$L<W\1t]sMסC]B@kҢzX(a(G+tفnU 29&v:B4Lu8V% '9$R! >>[GS8?WW#E?3MuR {[0B(Ԅ#@hӰ߭m/}O|ˉdva`0svX:m4x"x|Y fo|AlOUlEY#'zt1N:D!' Ώ S jQM<)g6ʌw= $bH\%A61RyqhqdWuu_$3uNqwV=Iyt /cKYLR94Ȥ$CVE"NKV <'x_RʝO@zst|v/Q Kwx)D=XƯʤD s#PWRIWf&]aDITZY{4rѫ}ZDwq6| ,#IQdd<,u\SC֫@Try^0{{E,룂f+K'ʥ'H'7"ce*J#[&))#/҃HIH3Tdן|uu}Cv {l{X_k}]2Jy <+99I>OT0$ 5ds 9"k w*kIT sbR$wq{ٝQܝ<f1Ϗ֟ןh85{,9J?9Mf^}?{o'K 9Yпr5"D \MnR]Ry .Ql *E*\$D$zHB?:'@F;Ł/;BG^#5Hڥv~5 %MsuU'M쿸)}S\:~1#!q&Pb1O=W@~#C-3WsA8yTeaK\!lg 'Cx;*Fŷ辋2|ݑT E~ǧZVwpQ}}}?/ JB 1^Fs{˷ߍ ždF~t9?Zhi2 iY j4HJ:{V -\-((7L|$(LaQAO9>½-9bhbSIЬyO@4p[@Lp)ذZb)$* = r/`?+0Y_2aݱc뎽w饗>?1 Y|@|8o%^CҤyeȋ%{t8@qGO|%xy8E3u< 4"yi:WXLHpy01aF[t/) WrFfډ9 iđ.D㹶͸cp>f48A 2qB$e!x DspLSX,Gbfa$Hİig ZVjKMKqq,a*yyڍWU$xO,ZZِ XVQh6z[ݲuRC}?۶.(4痗ݿ#U;v]L{^_zwݛ6ݱR6^:s2 `_g\0`k3d&'e,ñt"11a"h!OW [u$+LK;hjaigQzܴԵiEU[l@auw7uYe;\5_0lؾ`tQe򖡖rʑ_g+*Gkqj}ҁљB<)jr SIŪ"/ܺp˦[|W4lҵg*˂f$ZX w P 8]d6 d!'&b`U<<> ] p‚ʂn ) `T`Ir``CۛBۚ~]"C]m-(a߃g޶mmg;BjuEn!Y^:5Z;XLr538Y1e~I K97z4"vHưP11)F#ī .πN7RD#Asi [u:XQ.l~?lÂ9hhоӚO nonZ=UWz<usrrZ[魑DUՒ%UU8]EEq z)g*hg#aYE,!c#y$eREP ?,XqVj"ص(ZZ?4do,,T{| Å&a])tyU^@gPJJ,*텵ͽzʜVڬrSSZJcSVՖ,T i" "hɅSO}Tmg,OE Rљ8hrV3׌-\n`rQ)W&a$%b TYX*ZvttmHMd2L>r6G<߱b3W4NUN7-cю^̮nkn>fO0AsAHьvA]yޥ#"_&6] n ֤sca@_JuԎ }A_of#xUW1~UpHr0 *@qHH3qr9d|ab xHn9 U욹R:sU*Z.% vf'y/[dtp3TWSu:f^IfGK"ku>C$vG.X/xF+,ϢYEc $:MX:hT.!m%s_ݳswD-k H\?*tu6 JrƠ܍F{o'v3Lh2rEOpT]"gl.v$wG^jp?ov9 ݻgF1Qý[fTvKEjU@Kf*o߮a]#iq#?\׫w,+5Pl\|Dsf*]T?9F55) =(TIG0i>H&,H5"YVKwȟQOFi,z45d;4J"jXX58&ue͇݉nb([jp3OM~/AM~Pm={Bp'#{c}KIi<i_Ըy 4w O[N!rm ~-S>Sv&\ss78+̧H"NX 2"Hr;9GQVf s9̬`&E C>0&{wAs}z<$ p.kn/Ka 6] -sRS />׾7|闟MUKz/ͮO`췅g׭_O%M[3$NɆ=kЦbޏ{<{UJ {Q{@!WDe%3g+.(u݅dnzM7.DYɳ|Gr"g:LE{06ĿInϵZ [=9_=hΆ7W}3_?P9`\~?QdkaMNW櫴͡7ZrLevc5jט ̹l9MUV{*90iY:*VcI<u0MTKpKo8>%o뎔KܯwO(gd҉YOuD\W}xH u?Dy `58{cvI+>$ D&1)2IL x"s-%%|. prO rk@\A7<ɇjY]4$ryo,0t9Ee5eroZ7ShcǍ%6V𸕎 c^s ߖ ̲1fIYɳyVv8 .ReCɚtziV#jɱXrr̼˅ϰ2<\B} w,s: WwVhPo252vN CyVԶR[eҘ@@}SkF'c >!,){Xsx#[,9-Rn]w2ǜd1V/MUŁ\0̀-z)Ug]NҘE]!8)ē h \K]IIA1f[0UaY`9 %y, KLEYn<D`x!l:{5>Ӽ:eŬI%5,hU)JUUkR2=i)@3aǻm̅s} y Y'qź(.&ۦ$)!CHсe)u:Ӗ@Z%?-BheF>X`& zetخл,2VGS6Eu:e?+Υ{;TmF˥]Kecl׎؏{ {@5O8*&}P3X"UD+O Ðv,fiǓ.h^`vKϥ'Y6޵qufvfvgggf^{;vٛ뤾MԪ ڐޤJ TP$*V"j%**  B⁧DxCU"b33vέ⡊2c9g},56wg>{3Yji7!G77/u"rB$,)~8yQr-凍 i'jx|5<5m> Ź᝔N!OҾv Rb;'PWCO${z Fӫ\v.`S.V(nF~x ڮ?hRilI&LRNL(UjnfD. F6f#lh4۩ljf{b{Ͷ-N[Uw2!=\vlm"lW٢uͦE|ꏗ"P'5{W'8 @3i7v*ƫqqR؈W"׹N{f{CPc;1{B?4jvηJ=}n-~pC_nM<'˳~ ?jjU~ZV%?GRZUwm֪ukk?ZKh_k};jUѭ>@Wח+c}4B:EJFhK7}ptusx+ƅQ_.`_mg>{5O{/ PݽE%ĸU%H@*/귵ߑ>{kմ)ki91 Ӵ^Ns3-'zLZNVZN4vZNtNe؋_`> _ǴʊC a?~ݨi3E$T.p혘Dt Zr8KQNQo?9P-e%~Ae*[!Av=$yS%zH.D*u&]4T}̒=(̓ I3I,@eVxFq^jUf9[xLjm"}P~{N7V{'@Jm0 ]Q؛M漅bV3kR1fhDֲCVV՛NQ17P̴FI!}b|WctyفQҙ;) β td5⍲Y<4>jV[꠿B yBX+Ts,1{n=7hԅNĘԹ!{{JGYxC p,(ܭ\zy<~OLT]u1g R3 Y[w^+8i,.ԳN^ٲ=V0&%\0Jseor:#@Z;M*of=?m>}\yݨLUuJG{yV_. a֘O>1r*25CZBX&Pjx :'5ukeNF63[x6,lB˕.,aQ€ʕ #~9J@mN5?/l@` rܷ\b>cGF]:` r Dp<ǵ{UyMt`]{C{ *r9K3y"zkq1GP:oF„5$(Ar&HU}lJ1@( HWR|H%_LJ]EɎ݄ᨇ=)3dA,A4aP T->apT7*P˖ш ĢO _Zu4ݼ) hT;\7ihf5sUZkSMju,ʊMm#hb2 Xn{e TKũbAuϩAJeĂ?vKB 2&!d.eEyT% 3M\iN-k73,tvǫ eWX\=ZH-/hn(H]Fkv;HMabh YeLBfiz,k;kc2͐X}8#7Y2ˣP(37A؇MpMEa;IgJlFx՜S,</[x][Z ^_/Y8?i>|+ۨcUQ oD~A*k:%Ti2^%Jh6ZY3v$G!˛k&o"o(c9ةiV|G- k.)Ob88MyOXe*蚉{"%S8~6+".u Mq7%r+-[Dhx k)ojsF_<#$YPxc`d``u @djwxŘl^Uǟsn۱6mԱvklZҽ묝lcl@W~@N(&J4\SY$MXbDFLJĩ  åy.wM>{=<=|CssRW Qo|J7ˤi*˰U^~,U$F?j3'Uͺº՘MUCt] T_N@U>%"ɳQ&Hb]l %~^WC}l e3p- pBÂ`?F=rI6?^6Ʀsg HIW.c,5R_5okldbJ59gDMYg><=up\dϣ6)O svmrq~9Ҏ6mMt4sʐj {"fLNr;@[y{ԃ՗cy=b:٪|_j;X5&ħƶ̞PTՇ\dth91ڽ3tnk9GА^3wd5/0 C5$M#5Ni֜5SZg 8PΟlSl^g9Rswm,GܟYl<ʞQ¾ORۗGrC*3I;I7'{f'~/pf7zhE>d?@a1_ R XUlKrsC p9u P<[|w[vb\/l_]r}e#=]k["!3@?mBiSfw *iYuedM?l ܇߀x|;-?8\<ҽn٦F՗w_L˴iokY~$,u]1%~/9h%XT[*pC@s~k{N:ܮ1J^xl%bv/>="9xD?WYL缿att}t'{]:$kD[1@R5Od]R) ~q&l ÊFnD3jpvaO~V_f;w o TPvqx^)ZL6?`Ed\JIZ@mt,75G?xMaL҉pBD3DxJhD!)!"g3#S#$N ͈83)19s9s͹s9s@ @6P7 @s 9}T0Q(n6erEQQ3QB&!UftR(ZmEwGcыPCEP3 Bǡ3%f *FcǸb11XV,/<&VG_)qSq ''''N|;%X0fa˰Q|\|R_÷ t>LN%Dɴē'WOnC#jJ8?, S$ BG$h" DM&֒cɘdRrn2'9EQ %C9P4jT)))Y4 CSy"ݏ^GoSbS)RJn+şLERNONv?}O Y>p1eƏ b13%:f;.J[S #X+`G9 v{t6lYp(G8-Έ\8?.;<{ysG8# D+QQg3 B"!@ ,M1F&)37SYYlʴd:3{23G372wH"%&"AZVjVqVE4K e}%Ip'QILTF%M2O%Md 9B?8;o:~>t= ¥h(&⣄(SeJ$d7fcًT@QYTjڨT iqbf9h 9Q,Gӓ3|z9]Bѻ^"bEoeKKKK  1h c2L*ϒ J2˘VT>&<ߙĊc YeYXNVkT+H+  MXʦl1Ȟ *:v5ru9 rfAQ1Yu& y¹׷E"[ 7<7Dv3&ȓ\---׭bp1P2̷ci<߼MM--( `C[ *%JRo@i4\)6 QН2rln]]cw'!`{ ~b>~"'2&EJfT^]0H,kF]ųeZ ]U UUG#Hx&,JV 08IR*eJ2T&i-헆S7iD_ NVՅՂjqZ_mޭjdi6QF&>l|8pK+3&d̏m4rPz 80lu:XWG˫+IuT9E'/ \z]kt|*D~ńbFo50t KC{dlÎ* YrMSUpU*MWUt[%PUQePTݪ~UHU5ZUm6OP+z1ǫ7h 5Ϲ?^l,l4n?>q>yrڵk~u~]WM"}))`x}FxlYkiᴔH[-@ז؀0` T 0 Cߊ~6iDƈysM1՘4&a򚂦 ӂiݴ iEZiVakMh['Zqf,3;^i>aXk Y,sViְuƺlݰAm6VMfh&m`Fٸr&)mzm//k_64 knK?hch t:,=}!vt9"br"jipv9kNH'S9ѹҹyt vMt-wƾf~FFf͑.+ZqE\o5oo=r4wpK݀[ֹ npo߁i5}w{(Lzf=U?i{D== =GQ+yI^Wjkvy=^w;Nyzz^zoaoYW]> V|[>T3Y}?oM{_>=}t0P30I!c= U -cÚaq:^ E{k`!Q01 78X(.P2B*MnRQ%6i&V'3#ϴQk l,`ͱǡi {"pNɡ3ʱK=wg8 J-~DŋT// aq6ٰxeҮm;!vw%:-v艻iKk+ mY\,кGgi~x]P/SJYkT+mʁDdZ0+;|Od܊/#peq+BqGWZRQz,av<NLŦNJ'L9L}1 wi*)kJŠOcώy"J).Q̿YVڲ"6$"B_mWps8=%,RzT;65I3Ü>1fuԣ&eXCDn0i||W,Q.GPM1K K*B40 :7q=ʍƢS6.&exE WeLeBKl*'.Ll&6_]r R|*[n=2%"% D^̗6 rHG QbRNRl#vr(&bV.gșr,g9b;IΕ;]vrW*w{i2+ɜb{i>/#bD{b@rP#n3ŷb- (rOKdY!ȊʅrX(^roI̕E|-wʃĮ`, |J>-|I,_ |K-ߑH~,? J~-I,K-AuP (4B4C ( c`) K2,,'+ʰ  kڰ l066hA0:atl[l ݐHA20I-L)Li00fl̅ava\Ep1\ep9\WUp5\up=7Mp3mp;w]p7}p?<C0<c8<OS4<s</K2k:o[6{>|G1|g9|_W5|w=?O37?/QD@DB 0Ql&llQ8R8epY\pE\ WUpU\ W5pM\ up]\ pC7q a;v` ؉ 7psĭpka{0iD'8v8tgLq;N8w]pW w=00hc:4 0/A,`pO,a+XŅ8p^7~?Ax0ax8GQx4qx<'Ix2ix:gYx6yx>^Ex1^ex9^WUx5^ux=ހ7Mx3ނmx;ށw]x7ރ}x?>C0>c8>OS4>s~ cbXN,/V+C?OS ?/K o[G _W'?/ n"3YT'~R=5PHML-Jh4h,-Mв-O+ЊLЪNkКMкOІmLhQDmN8%DJ<$+Vq qBaXEIO4hSڌ6=Ėbk(h ڒC)JS&64I$nm(LSh;Jh:mO3JqO<"n[maIh6@shGډδ J8X#)+.Si娗l~  HC'LqRUN ih)8Q-V'ioCqt -8gKtHtJtIGt KtH't JtIgtKt]Ht ]Jt]IWt ]KtH7t JtIwtK=H=J=IO =KH/ JIo;mzޥsQHq8ZD[(#>O3/+o;~'Y#wX7B7?/-aI ,Ȳ:j"VjfjFY1RXkikkYk9kykkEk%kekkUk5kuk kMk-kmkk]k=k}kkCk#kckf[V̊[ kiMMPv}`6W*껋ł;U+vcO)媃}y{QXrvRe]mJ]Zcԥ`4է5mP'hMsQNmhyR6"Lڶn rM2pu*Nn6:9Z7EP7E4%4ES\uST3?54B4y@П-Uj~qZ9-L Όp3MgBcm,m0+g:|Zp:ڻff00]rXgR1"szdrݜRvݸS0Y͢sUh 7ǰb.?}䖩Zqf8F˚rYmbυ:.t\8۴mZZf8[?Tp6Zm TGG_0MJ7wav8?hAQӂmB˛ɛɇ[ Z`/dJ84`n!ܖEŚ(r[CmYbbpJ,wPpLՖӖڶLUM՚\*eUeմq7ja-M[aĴbӖkvVV,֊Zqq};h,缙х3R%G( 71}r- UWlzZ:uѬ)6dU].YYn{h)%?HN/˹̬$WIsBQgWygrW"bfj2m"myS ]3C*>G*tI m uM*zu(ˁKR.y+>:76K'eu{D/I˙sT?Ո,)) ji֫GksC>h Kz‡+٦7 ~E%'RkVY1^FrLEbrK(Ծ KʌTj#%ߢ'TMthvUiU}U~U.YjqPzJ_cX m0\s8sIKG؈o9Rk9-+?G#4/W=oocδi6]^iyͳϴ]bL=K3:_gtp|WQ<}/~?68g?iG.b.qvs]~˿==d$'?np)O1NSb)biOsiK]lf ˰}3l1 bcl~Ix3^5 ox]co"^\'{zW./ƽxY?q98W܋(8_'~/8q37Q8]쿋q_7ף;}ݬq~IO2~񓌟L2]+')Iy'i3viK]laϰ}3]ߞ?^T"}-DvttDHw|v섀 Rhw0&642KlgAd xqA;d, $A d2p(`=T "KE*7tH!H(Đ gL88VO,%J֚udCe'_,\wI#59;4כ0 ;: 1Pf:Y6P8}^+BĞzLD92k!AxJypi]ɬ)S)ϰa9iO|L%_/A^`Z5YK%X oPEN} KhY)dfCa]*a8:,j2]B6EEA9S6XxsKcLLlN^YC+ju }v)TA}ڙ'5)m\vKڷo\ ۗ4o=^spa\.[GY˪]{+}aIm,,mRLӣ%|cH)PZ{hW ~BiOn tIC[ҰΎ:=X ?N?_5yR2MNqG8uvfi)/y-Ig2 uSZrQBg9Bb5jsTkJ_o힭l잶֝MݣyDcRgPct8m`e^-`v^ju:ew;be0[h*#emlY~8\TԺ% d:i(_-N腊jHs :jxʀC=-3-=}|+-7l9M^71F|uKQM~xF<=Ӡ.t#+&/;lP1Q*j ;훨Wq^OVV lW{թ*ݬ{9mXrs\ufN9Tq6ꋞ^eYShvJ)((TDqy p=(rP5\gJK^%\Q5uPUWBo ~,՚zڊb51W.A((wBꣿ Fͯ I7RCf^K)*l=*C3e}ЄR_m=fͼb;Z%TrR7q꧕+YhwXmb6˕1APD劃7L/b|I˂_4ƗdxNx+N`ёLL#:PĠ&h<-1 @w]F{㨇g"|S,.q  w ^w __5P[:=_oPM%|IIcQGo`/?67bR&o6svLcE xc` $'' ԿELvhdqH* ^ ^L`0IᡨyzC;TӘy=AxڭViwFyI,% -jaiF&l Ac ];_ds7~Z/$pweZ 둔/&< MQ|(;{!eQڷDD"PDYd|QF˶WM-=.[AU~:ʱ;f3th=%UUH=RҦe+I+WPˆN"iHgh5(l(R$Ay ͐ʧأVK/yw9?)[-9#;8;]V7d; U[6;տ٣L/4#X*_!O(HV SѨlDzO8bJ\3FtwtBu =w„DzQ 'DJM6XI٢Jj+&Ny_v38ԝCVNTrF2l 쓘Log($mlgyN0urp0.QQC"yRSq"{T4F썰w!8R?T h)7T/l6!caYVcJɶ B>ROk/Q'Un?3N߂`o8H]d[ʩk͡Cuq5M7ib.X6i) R(2w w(U}l>ϕ8o's0쿣1t{͉O7pLWыS,]nhVG\S8=\ `bZF)|s0h2sl3g `9 v`9 `:a2AOx8j;cpC3FƎ S\6x8Y:C"@J"'ÅÈ]UGk ,\+#rustup-1.26.0/www/fonts/OFL.txt000066400000000000000000000104661441327105200163060ustar00rootroot00000000000000Copyright (c) 2011, Raph Levien (firstname.lastname@gmail.com), Copyright (c) 2012, Cyreal (cyreal.org) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. rustup-1.26.0/www/fonts/WorkSans-Medium.ttf000066400000000000000000004272741441327105200206420ustar00rootroot00000000000000 DSIG.GDEFH u,.GPOS \uZGSUB tv*OS/2hЂ|`cmap|cvt B!lfpgmo"( gasp!dglyf֡FheadhU`6hhea1l$hmtxə loca maxp  name[6postTprep(- 56CDt DFLTgrek$latn4 "CAT .MOL :NLD FROM RTRK ^    cpspcpspcpspcpspcpspcpspcpspcpspkernkernkernkernkernkernkern kernmark"mark(mark.mark4mark:mark@markFmarkL.<DPXRT#/6KLNTN\PRT&T6UUUUX.X>X< X:b  .4<~ZT 0RtDf     v D V \    & ` & 0 z ,NR\ DZdN|"(.<$P*06<^ ,WXY!_`345 WXYW%4DFHLNP3 ,%Wxj4D HL N PL N Pk    %&+69>)*.4HP\auW ,WY%Wx4DFHLNP WXYW%4DFHLNP)*.\a   , %Wx4 , %Wx/4 ,%$&/6K  K  K     Y/4D % + / D F H ]!^"_!`"np,W %!4DFH $/12](^2_(`2$ !"#$?12]$^(_$`( ]^_`1O   ! " # $ 6>%()*+ .1 2 3D F H\]^_`auFH4DFH   + 12H^ `    ,WY%%4DFH ,W%%DFH%4DFH     W%4DFHWx4D HMNOPQRSTUVW)*.4H\a&?KL! Wx4DFH W%4DFH)*.\a Wx "4fWx4fjW^x "#$fj WY%4DFHLNP&FHNPYDFHLNP WXY%DFHLNP2 Y%DFHLNP ,2&-4u WY%DFHLNP WY%4DFHLNP!DFH~!!%DFH Wx~ "&4!%DFH ,YWxWX% !#%^ Y!#W ,WXY%/0 ,2%W^x~ "4^YWXY & ,?%Wq x "#G ,B%Wx "#CG &,I%Wqx "#CG & ' ,O & ' ,KO & ',KOWXZy[y\yH?WXZy[y\y))H)345>)?j 4 WXY! ,2WXYCFGP }zYyqhu66=>=D6-1(-6-  ! ,$# *p56) zm}Y}#j121Zu}    ay| yF# !&!l  j  RFT^dHv $ W!%4DFH!&FH !%DFH W!%4DFH XY/FHDFHNPLNPWWx~! T-D,6@F\r*<Nd& 2 ^~2 ^~2 ^~22 2~2~2~2~2~2~2~2 ^~2B02F22B02F22 ^~&  "(..44:::::::::""""@(((...44FFFFQrblc?9wPJ.OF`PEJ:"1VHhr &'+,02BCDLMWXYZ[\]gi   !"#%69=@BXjqxz|~ !"$%&,04569CEGKMO\afhkrx 02i/rrgw}horstuv(/#h%&p39r=@yBB}Dtv|1~8$&(*,,..0699;;CCEEGGKKMMOOQfhh-kq.  !"#/DHJLPjy()*./123QRSTUVW\aj(    #(*..13QW\a.6&zf ! -2 47EJLMaqsv.x~29BGYbhlr t 2|DLaikly~.z&6f/   !",--.022378@AABBCCDDEGHJ KKLM NV WWXXYYZ\ ]` aabbccddeeff gg hh ii rrwwx{|}  , @ 3 !,,78ADDHJLMNVWWXXYYZ\]`Aaaffgghhii|}@9: ! F:BB:G"; ;: ;:GF:G ;        .0DCB  $:%%#&&;++;//3566 7899==>>F??B@@BBDD6EH IJKL>MVHWWIXX Y\6]j kl6mt vw xx=y 6 6  >/1E 6 %2$ON<+*LM$$%%&&(((4)* .. //)1233444566?DDFFHH&LLNN PP'QW XXYY ZZ[[ \\ ]]^^__``aa bbcc ddee uu J8-K            / 1 (#( !#"+(,,2-7(8A#BC(DD#EG(HJ0KK(LMNVWW XXYY%Z\]`$aa#be(ff0gghh0iijj(ss(w{(|}#(((/#*6**+!<<*<,*+ 7*+ <  ,    ,  7& - $*%%"&&<++<// 35 667899,==>>@@ BB$$%%&&9((:)* ++... //12 33:44'553;;)DDFFHHQWXX4YY;ZZ4[[;\\ ]]^^__``aa bb4cc;dd4ee;mmnn oopp uu588&DDEEFFGGHHIJ KL WWXXY\]`aijjklmtvxyyz|~~     <DDIJ KL MV#WWY\klxx    $('   !!""!##&$$ %%&&"(()*..3344DDFFHHQWXXYYZZ[[\\aabbccddeeffjjllmmnnooppqq%$N       W8W !8"+W,,-7W8A8BCWDD8EGWHJ#KKWLMNV9WWXX YY Z\]` aa8beWff#gghh#iijjWssWw{W|}8WWW 89$Y<@<:YVVYJK<KY<KLYJ>:YJ K  L    L  > !V< $Y%%&&K++K//3566<7899L==>>:??V@@BBDD=EHXIJ'KLMV WWXXXY\=]jXkl=mtXvwXxxyX=X=X'X?X=XCDG,52AE/*-63)0H.74B  F!!1##+$$(%%O)*".."//00M1244N99PQW;XXIZZI\\"]]%^^__%``aa"bbIddIhhUjjkkQllmm&nnoo&ppqquu<SRT$  LMWWYYZ\]`ggii      // 3578 == ?? @@ BB KL12 ]]^^ __`` llqq      !!""/  ! 8A DD LMZ\aa ggii|}      66 99DDMVY\kl(()*..1233QW \\]]^^__``aauu  LMZ\ggii    //3578==@@BB)*..//12QW\\aajj7 Z\     %%667899==>>??BBMV !!%%)*,,..//004499XXZZ\\aabbddjj uu  "             ## 1KLMV        )* .. QW\\ ]] ^^ __ `` aa llmm nn oo pp qq KLMVllmmnnooppqq DFLTgrek^latn  (08@HPXiqy  !)19AIQYjrz: AZE CAT CRT KAZ MOL NLD (ROM pTAT TRK   "*2:BJRZks{`! #+3;CKS[alt|bc! $,4<DLT\dmu}! %-5=EMU]env~!&.6>FNV^fowg!'/7?GOW_hpx aalt8aalt@aaltHaaltPaaltXaalt`aalthaaltpc2scxc2sc~c2scc2scc2scc2scc2scc2sccaltcaltcaltcaltcaltcaltcaltcaltcasecasecasecasecasecasecasecasecswhcswhcswhcswh cswh(cswh0cswh8cswh@dligHdligNdligTdligZdlig`dligfdligldligrdnomxdnom~dnomdnomdnomdnomdnomdnomfracfracfracfracfracfracfracfrachisthisthisthist histhisthisthist"hlig(hlig.hlig4hlig:hlig@hligFhligLhligRligaXliga^ligadligajligapligavliga|ligalnumlnumlnumlnumlnumlnumlnumlnumloclloclloclloclloclloclloclloclloclnaltnaltnaltnalt nalt nalt nalt nalt numr numr $numr *numr 0numr 6numr FNV^fnx $,4<DL z N&$h Xbhp*42 . H L \ j t z,|vpjjdj",$,w,@F@58286**7MNOPQRSTUVWXYZ[\]^_`abcdefghijklmnuoqrstvwxyz{|}~DEFGHIKJL !"#fghjklqimnopLwMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnoqrstuvwxyz{|}~p !"#fghjklqimnop%          ABT%          ABR(IJP:J0N>:HN7F<7>747*( "#()-.3uvwtxz{y|$s<=>?@s}~ 6798KLMNOPUVWbcde8 9T L"(.4:@jklmnopqr stuv&'(*+,-/0).12345 |}xyz~{r !"%C99$>69:;<$JRZbjrz "*2:BJRZbHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH $,4<DLTPPPPPPPPP :;<   !"$%#BC; xyz{ |}~  *4>H78=>?@A NUWXYZ[\]^_`inoqrstvwxy}ErNOPQRSTUVWXYZ[\]^_`abcdefghijmnoqrstuvwxy#).12pBCg>jlq@-3KLbcde}~uvwtxz{y| 6<DJPV^djpv~ *4>DJPV\bhntz &8J\n  &,28>DJPV\`dhlptx| $,4<DLRX^djntzsMNOPQRSTVajbkcldmenfogphqjk|l}muz{|~rsxtyuzD~FGHv{IKJLMklz {!|"}$~%&'(*+,-/0345     ! "#     f6 <=h7?k9i8;().MNAIOBJPUVWmnopsHi,jklmnopqstuv&'(*+,-/0).12345fg  #%&.zih&&r+,s/2u::y]`z~ "JS_`&&j+,k/2m::q]`rv%"8 !"#().EFGHQRSr("8 !"#().EFGHQRSr4888<@ss}()-.33rrt| &,0:CDEFGHQRSXYZ["%% +<!C3 3EFGHLMXegi.022 47 EJcfhh" $  &  '( ./02c34567dEFGeHIJLMXfghi   ']^_`;Q !$EFGaew;DHklGO 8;:EFGeH !aDkl$wpu3  ## %- 119:<DKK"NP#RW&Y`,bb4ww567TV`amo w  z {88;;DDHHklppuu++--//14>>@@CDX[t&  "$$.028;;!EJ"LM(QQ*XX+aa,ci-457<AEFHK#s&)},,..00::<=??EHQS]`rs8&,./024567EFGHIJcdefh   EXKX^2,UKWN@     +1-w2GH$)S.4(3/  "#,-.348BDEHLNWXYZ]E%F5CD 7;Quy' ?|0,xXY*r @ARQ]^_`\tZ[78&a\%&')9:<OPR/9UYuy~#+37HM[eks&    " & 0 : D p y ~ !!"!&!.!""""""""+"H"`"e%%%%'^'a' 0:VZvz "*.69LP^jnx&    & 0 9 D p t z !!"!&!.!""""""""+"H"`"d%%%%'['a'Gj!}pd>7 ca} 624JXZdpvvzz~v|v|z+1-w2GH$)S.4(3/  "#,-.348BDEHLN]E%F5CDr,uyv|XT&Y0  )%&'7<9:@;?ROPQ[C !a*+($b/c0125d6>=AFe GIf Jg MTVSU\^`_h i RQ]^\'IJAB2%@"YMKYNL +!!%#2p@lP  +@( JbMKNL   +%!#3#''#?nq^LNl_]x 5@2JabMKNL   +#73!#3#''#lR8r?nq^LN5l_]s&S3+=@:JrrbMKNL +#73#'!#3#''#YqpqYP?nq^LNۧkgl_]\&P3+x ?@<JabMKNL    +'3!#3#''#"Xr8c?nq^LNᗗ̭l_]:&S3+)B@?JIbMKNK[ZL'! +#"&547'!#3#327''#:9DX;?nE5!#LN83N(l/ _]W$=@:!JcbUKNL$$%& +%!#&&54632#32654&#''#?nJ<8>I~t~~>@@>^W6P  T9Vaxaa3003=(@% J[UK[VL$%&! +$#"&&546632&&#"3267gb_LL^ceL=]oj^@WfV`RhhRRP-;7tu@>"=x<@9Ja[UK[VL  +#7#"&&546632&&#"3267XR8b_LL^ceL=]oj^@Wfx`RhhRRP-;7tu@>"=":@7"!Jrr[UK[VL$%&" +#'373#"&&546632&&#"3267pqYPPYNb_LL^ceL=]oj^@Wfݧkk`RhhRRP-;7tu@>"=).:@7.-!   Jp[UK\ZL$%,%' +$#"&'732654&'.546632&&#"3267loMD8'7! $ *T|BL^ceL=]oj^@Wfd\ 40>> %WahRRP-;7tu@>"b,@)[MK[NL   $ +##3654&##3第hpph~~}rr}"bG@DJrr[MK [NL  +'373##3654&##3 qYPPYqjhpph~~ݧkkI}rr}" <@9a[MK [NL   $ +###533654&##3#3^^ippi~~&L"}rr}LbN /@,aYMKYNL  +%!!!!!N"\\\ZbNx9@6aaYMKYNL +#73!!!!!nR8r"\\ZbNN@KJr raYMK YNL  +'373!!!!!qYPPYq"ݧkk\\ZbNC@@JrraYMK YNL +#73#'!!!!!YqpqYP"ۧk\\ZbN\&R3+bNn&T3+bNxD@AaaYMK YNL  +'3!!!!!5Xr8"ᗗ{\\ZbN:&T3+b)Y@@=JIaYMKYNK[ZL'! +#"&5467!!!!!!3327Y;9D++"F5!#60'7\Z\/ b? )@&aYMKNL  +!!#!"k8]\=!l@ JKPX@a[UK[NL@#a[UKNK[VLY@!!$%&# +#'#"&&546632&&#"32655#5OfG\LN^f&bZ?^nieObUT-1RihSLP/68uwNJX=s& y3+b '@$aMKNL  +#!#3!lkkFl b@MKNL +3#3kkb&#,/bx%@"aMKNL +#7#3XR80kkx@ /@,JrrMKNL  +'#73#3PPYqpqskkkk%3\ 0@-cMKNL    $ +#"&54632#"&5463#3Z####$$$$%kk\"""""##"Rn&#3+x@aMKNL +#'3#3RXrGkk! :@aMKNL +#53#3 @kkN)!@ JMK\ZL%! +#"&5473327#88CRk=/9"60J*l.3'@ JMK[VL)" +%#"&547732653qhnf69IsHzb9@MKZNL +%!39)k\\b9x )@&aMKZNL   +#73!3R8r*)k\b9 OKPX@MKYOKZNL@aMKZNLY@    +#73!3mF`)k\b\&.. 3+Z ,@) JMKZNL  +%!55737Z)jjl\\8`8A]`\b!@JMKNL +!#7####3373bYc./~4~b@ JMKNL +!#'##33'3@b~AczpVa{pbx2@/JaMKNL  +#7#'##33'3XR8@b~AcxzpVa{pb0@- JrrMKNL +#'373#'##33'3pqYPPYd@b~Acݧkk|zpVa{pbm&4y3+=,@)[UK[VL& +#"&&5466332654&#ӍLL__LL_]jj]^jj^RhhRRhhR]uuuu=x=@:a[UK[VL  +#7#"&&5466332654&#XR8YLL__LL_]jj]^jj^xRhhRRhhR]uuuu="G@DJrr [UK[VL"!  +'#73#"&&5466332654&#PPYqpqJLL__LL_]jj]^jj^kk=RhhRRhhR]uuuu=\&8x3+=x6@3a[UK[VL' +#'3#"&&5466332654&#RXrpLL__LL_]jj]^jj^RhhRRhhR]uuuu=x#H@E a [ UK[VL#" +#7!#7#"&&5466332654&#XR81XR8LL__LL_]jj]^jj^xRhhRRhhR]uuuu=:&8z3+='@%$ JKPX@s[UK[VLKPX@!sMK[UK[VL@!rs[UK[VLYY@  ' &'% +#"'#7&&54663273&#654'3~.L_aG0KJ-1L_fI*KEj-/E^j)-?PhR+Ci-RhR/;bulA#uh>=m&8z3+=&KPX@ JIKPX@ JI@ JIYYKPX@#a[UK [NLKPX@9a[UKYMK YNK [VL@3a[UKYMK YNK  [VLYY@&%!&# +%!5#"&&5466325!!!!654&#"3A%lB^JJ`Aj% ggW`hh`\\\15RhhR40Z\Z ttvvbH 0@-c[MKNL   $ +###3654##3}}kk5Dn``m99sbM 4@1ccMKNL   $ +###336'6&##3{kkFFFG#lfflq;<<;=:&e@  JK(PX@c[UK[RL@c_[ULY@$"(% +67#"&'&&546632332654&#":>U,Xm}L__L~=5j]^jj^]jeZ]ahRRh 6':uuub} +@( Ja[MKNL$!! +##!2#32654&##Zj k|IC{ʚA@@Ai]G_a5775b}x5@2Jaa[MKNL$!! +#73##!2#32654&##dR8rbj k|IC{ʚA@@Ai]G_a5775b}I@FJr ra[MKNL  +'373##!2#32654&## qYPPYq!j k|IC{ʚA@@Aݧkk+i]G_a5775+N(XD@+J[UK[VL('$,% +@ )*0dD&&#"#"'732654&'.54663,QTFELJ_Yi0BwNSK eKGQI[[l1>uNBDI<67+(/1I6:X/K<81-$,2J58\6+Nx,A@>Ja[UK[VL,+  +#7&&#"#"'732654&'.54663XR8Q,QTFELJ_Yi0BwNSK eKGQI[[l1>uNxBDI<67+(/1I6:X/K<81-$,2J58\6+N/@@= Jrr[UK[VL/.$,& +#'373&&#"#"'732654&'.54663~pqYPPYQ,QTFELJ_Yi0BwNSK eKGQI[[l1>uNݧkkBDI<67+(/1I6:X/K<81-$,2J58\6b%K.PX@  J#I@  J#IYK.PX@c[MK[NL@#c[MKNK[NLY@%$$"$%% +#"&'732654&##57#"#4663!3(q5_TCf7[M@YMKNL +###5!Mk/88\M9@6JrrYMKNL  +'373###5!qYPPYqk/ݧkk8\Yu@MK[VL#" +%#"&5332653ulOSSOlv^YY^Yux/@,aMK[VL  +#7#"&5332653XR8 lOSSOlxv^YY^Yu9@6JrrMK[VL  +'#73#"&5332653PPYqpqelOSSOlkk"v^YY^Yu\&Nj3+Yux%@"aMK[VL## +#'3#"&5332653RXrlOSSOlv^YY^Yuz:@7 aMK[VL  +#7#7#"&5332653}XR8-XR8lOSSOlzv^YY^Yu:&Nm3+Y)u 7@4  JMK[VK[ZL ##( +327#"&547&&533265u][[!# 88E8u{lOSSOik+3 =8/?({v^YY^Yu~&NmXX3+}@JMKNL +!#373}r((qI!@ JMKNL +!#333#'#]tnn~i!!?@lS &@# JMKNL  +!#373Ԣ{tOE L @JMKNL +%#53773`kwv44uxcc Lx0@- JaMKNL  +#7#53773XR80kwv44uxxcc L\&Z.3+2G /@,JYMKYNL  +%!5!5!Gzq[[W[W2Gx 9@6 JaYMKYNL   +#73!5!5!VR8rzq[W[W2GJ@G JrrYMKYNL  +'373!5!5!qYPPYqzqݧkk~[W[W2Gn&]<3+=& xb&-[b9&.Kb&4qb}&EU+)N:=@:-,  J[UK[NK[ZL%,$%' +$#"&'732654&'&'732654&'.546632&&#"Nk\D8'7! $ )MK eKGQI[[l1>uN[,QTFELJ_Yi0kc40>> % {K<81-$,2J58\6BDI<67+(/1I6)M;@8 JYMKNK[ZL#% +#"'732654&'##5!kAE8K)#) #, /8)B0?%?%8\\+N&HLM&L(bbJKPX@cYMK[NL@%paYMK[NLY@ !$ +#!!!3#!2654&'7Lqc<qFC@(#'Y[IX]\Z,/*.MbxtJKPX@&acYMK[NL@-paaYMK[NLY@ !% +#73#!!!3#!2654&'7R8rLqc<qFC@(#'[IX]\Z,/*.Mb@ JKPX@*r r cYMK[NL@1r r  p aYMK[NLY@  +'373#!!!3#!2654&'7+qYPPYqLqc<qFC@(#'ݧkk|[IX]\Z,/*.Mb@ JKPX@)rr cYMK[NL@0rr  p aYMK[NLY@!& +#73#'#!!!3#!2654&'7YqpqYPJLqc<qFC@(#'ۧk[IX]\Z,/*.Mb\&jg3+bn&jj3+bxJKPX@' acYMK[NL@.p aaYMK[NLY@  +'3#!!!3#!2654&'79Xr8#Lqc<qFC@(#'ᗗx[IX]\Z,/*.Mb:&jj3+ !/@,J[MKNL!!+! +#7#####"&&5463!37cZcR=F9/%Hauf//l~4A64)9O eMXc~b.@+JGa[MKNL$'! +$&&''##!2%32654&##BX$:JI"qj k|[P{A@@A8]A64)9O eMXc\b)@&cMK\NL!$ +#!332654&'7*[shkAD71%jaN\_-4/1Mbx3@0acMK\NL!% +#73#!332654&'7R8rhkAD71%aN\_-4/1M=@:JrrcMK\NL!% +#'373#!332654&'7pqYPPY[shkAD71%ݧkkaN\_-4/1Mb&_<@9   JcMK\NL%$ +#!5573732654&'7K\sijjlAD71%jaN\_8`8A]`\-4/1M ,8@5) J_[MKNL,,+#$& +#7####"'73265#"&&5463!37cZk`< 424:R=F9/%Hauf//l~4Tiz>4.B664)9O eMXc~bR@  JK,PX@MK[OKNL@cMKNLY@ % +&&#"#'##33'4634X= 324;@b~Al`?9=3,D9zpVa{p5Tibxd@ JK,PX@aMK[OKNL@acMKNLY@& +#73&&#"#'##33'463R8rIX= 324;@b~Al`ᗜ?9=3,D9zpVa{p5Tib"z@  JK,PX@#rrMK[ OKNL@!rr cMKNLY@"!  +'373&&#"#'##33'463:qYPPYqX= 324;@b~Al`ݧkk?9=3,D9zpVa{p5Tib&qbm&y3+ a'0KPX@% J@% JYKPX@ a_[MKNL@&ha_\MKNLY@ $!;#%# +##"&'73265#"&&5463!2#32654&##>obMZ> 422:R=F9/%Haufnj}JC{ʙB@@BUj@<<4.C764)9O eMXcj\F`a5775 ax+4KPX@) J@ ) JYKPX@(aa_ [MKNL@. haa_ \MKNLY@42!;#%# +#73##"&'73265#"&&5463!2#32654&##GR8raobMZ> 422:R=F9/%Haufnj}JC{ʙB@@BUj@<<4.C764)9O eMXcj\F`a5775 a.7KPX@ , J@ , JYKPX@,r r  a_ [MKNL@2r r h  a_ \MKNLY@751/.-'$  +'373##"&'73265#"&&5463!2#32654&##qYPPYq!obMZ> 422:R=F9/%Haufnj}JC{ʙB@@Bݧkk+Uj@<<4.C764)9O eMXcj\F`a5775 a&9:Q-*@'J_[UL-,%+) +&547732654&'&&546632&&#"#ĊbUXNPLbo?wOs~eFLGN!NDWd-C{Qn`W =;?<-9dTCe7`_(HB@;!/%7L7Cd6:Qx1=@: Ja_[UL10#!  +#7&547732654&'&&546632&&#"#XR8bUXNPLbo?wOs~eFLGN!NDWd-C{Qx`W =;?<-9dTCe7`_(HB@;!/%7L7Cd6:Q4<@9"! Jrr_[UL43%+* +#'373&547732654&'&&546632&&#"#~pqYPPYՊbUXNPLbo?wOs~eFLGN!NDWd-C{Qݧkk`W =;?<-9dTCe7`_(HB@;!/%7L7Cd6:Q?6@310  Jp`[UL%+/%' +$#"&'732654&'&&547732654&'&&546632&&#"QygD8'7! $ !+dlbUXNPLbo?wOs~eFLGN!NDWd-u 30>> % ]M =;?<-9dTCe7`_(HB@;!/%7L7:Q&3 3+ "@ J[MKNL*! +###"&&5463!l}80%Hauf88a*8O fLXc >@;Jrr[MKNL  +'373###"&&5463!oqYPPYql}80%Haufݧkk8a*8O fLXc )"=@: J[MKNK[ZL""*!#% +#"'732654&'##"&&5463!@E8K(#( #, }80%Hauf8)B0?%?%8a*8O fLXc\ & /@,JYMKNL,! +#'###".546333~i!!hK>F90&.N-ufnltA64*8O1R3Xc?@ %H@E"J b  [MKNL % %   $ +&54632# &54632#!#3#''#z W .>op^MN&l_]'3KPX@+ [  UK[  UK[VL@% [UK[  MK[VLY@"(((3(2.,'&" & +#"&&54663#"&5463 #"&546332654&#ӍKK__KK_  ] jj^]jj]RhhRRhhRUuuuuYu )8@5 [MK[VL )(%#    $ +&54632#2&54632##"&5332653  }  ÊlOSSOl&  ׂv^YY^1("-[D@:&J[XK[VL###-#,"!$)$# +@)*0dD$7#"&'#"&5467754&#"'66323&6553#+3Z8IZba60cSoQarRm42'$FH "(#'D?;Q.5Z,-3=P@M'! 7 Ja [XK [VL44..4=4<.3.2$%)$%" +$!3267#"&'#"&5467754&#"'66326632$3&#6553>J708 XbQBapNKX^\94.ASpS;UV7krD kRo91OBI""%::6767G<;N .5-,)>F.-,/hEAA7" "9S=h JKPX@OK[XK[VL@!OK[XKNK[VLY@$ +#"&'#3663654&#"3vvg;Y^kU6)IIA=II=zz8/]*0N\RR[RH&IR6 .@+ J[XK[VL%$# +&#"3267#"&54635d[CJJC3> bpUvu(_\TTZ2/!FOzz6 &&6 &-6) *:@7*)  Jp[XK\ZL$#*%' +$#"&'732654&'&&54632&#"3267S?D8'7! $ !+blu5d[CJJC3> bQK 40>> % nz(_\TTZ2/!8"hJKPX@OK[XK[NL@!OK[XKNK[VLY@$# +#'#"&546326554&#"3"^Y9gwwg7TJJJ?AIIA+Y-6zz1*2wZN NY[RR\8(&a@JK(PX@OK[PK[VL@dOK[VLY@ &%)%$ +#"&546632&''7&'37654&#"3^o=kEn39 2]}=+pNJCDJIA`uLq>./==/TR(/--ONND<3J(F:"3 /!IF-2M*79SB)M\ e5)*55**4*"%)  =IVD@9 1 + JKPX@C  c  cOK[MK [XK \ VK [ZL@A c  c  cOK [XK \ VK [ZLY@,KJ>>QOJVKU>I>HDB==<;86&$ " +@>HIJKUV )*0dD&5332653##"'332#"&5467&&5467&&54632663654&#"3"32654&#TO*!!*OTF 06c@/$"LTCuIww/* &*$&'wbC3N7==./==/9,-ONIR(0:TE"00"ET.F-D2M* D<3J(F:"3 /!%2 F-M\5AS5**44**5"%*S-@*JOK[XKNL" +#4#"#3663ikk8PkkS6`Z"NP.,A '@$[OKPKNL $ +&54632##3g&&""%%"5kkH# "" #S@PKNL +3#3kkS &؏1&܏ TKPX@[OKPKNL@cPKNLY@    $ +&54632#2&54632##3####"### kk>"##"""#"A&ߏA)&@YMKPKNL +#53#3BkkLNf) 6@3J[OKPK\ZL $ +&54632##"&547#3327g&&""%%"S"98BQk8-"H# "" #60D- . ) 8@5J[OKPK\ZL $ +#"&5463#"'732653'' !''!5]GA-"+"%k$%%$YR!V*0S7 #@ JOKPKNL +%#373#!ckk~|f*P1@JOK\VL$" +73267#"&53 "1C?k%VDIRP12@/ JaOK\VL  +#73267#"&53BR)9 "1C?k%VDIRP^'@$ JYOK\VL$# +#733267#"&532D\ "1C?k%VDIRP&:8 ^-@*  JOK\VL$ +$67#"&5557373. 3B?]]kssPVDI<_7iJ_D%Sf$V!JKPX@[PKNL@PK[XKNLY@$#### +#4&#"#4&#"#36632663T2k8/7Kk8/ <&k_V38[_6)T=3;8PF3;8!F5S./3113SLJKPX@[PKNL@PK[XKNLY@ " +#4#"#3663ikk8Pk_X8`Z"MPS/.S&:S&AS&B6( ,@)[XK[VL   $ +#"&546332654&#uuuDIIDDJJDzzzzT[UU[\TT\6(&-6(&56(&36(&56(&K6(&56* 'KPX@%" J@%" JYKPX@s[XK[VLKPX@!sPK[XK[VL@!rs[XK[VLYY@'&% +73#"'#7&546377&#"654'#3;'CB@uO8+CCGuo "3DIJ-&2TEwz 8XEz-[UF,>\T=*(6(&46!(4J@G Ja [XK [VL))"")4)3/-"("'$$$$" +$!3267#"&'#"&546326632$!&&#654&#"3M>2A X8DdfEttEeaBIi6F  A8JJCDJJDDJ#!%t6336zz5335>kCDB@F[TT[[TT[S.=h JKPX@[PK[VKRL@!PK[XK[VKRLY@$ +#"&'#3663654&#"3vvg6Uk^Y;)IIA=II=zz0*]/8O[RR\RI&HRS.=B@? JOK[XK[VKRL% +#"&'#3663654&#"3x9cA6SkkT7(IIA?KK?{Tv;+&'-O[RR\ZN NY9.#hJKPX@[XK[VKRL@!PK[XK[VKRLY@$# +##"&5463276554&#"3#kT6gwvg;YXKK?AIIA:#*1zz7/\YYN NZ\RR[SdKPX@ JH@ JIYKPX@[PKNL@PK[XKNLY@ # +&#"#363x( 9%k[ *t b "D0ZdS&7&)&XD@+J[XK[VL&%%+% +@ ! ! ! ! ! ! !)*0dD&'732654&'&&546632&&#"#uHP8384@oZ2_ASdLA2-;5FjXtf 45>+(! F@)G+7:8,)% F?CT)& )&S@.KPX@  J@  JYKPX@-p fc[OK[VLK#PX@1p fc[OKNK[VL@2p nc[OKNK[VLYY@.-#$!$$& +#"&'732654&##732654&#"#4632#S'2dG"<4;AOK*%;D>;K@Y?255T[/t7V0%H/P; =@ JK,PX@[OKNL@cNLY#" +4632&#"#PMK9Fj>./==/TR(/--ONDFD<3J(F:"3 /!IF-2M*79SB)M\ e5)*55**4*"%S7&(P1&S&84&))9@@=-,  J[XK[VK[ZL%+%%' +$#"&'732654&'&&'732654&'&&546632&&#"OGD8'7! $ )BfHP8384@oZ2_ASdLA2-;5FjXVM 40>> %40>+(! F@)G+7:8,)% F?)%A@>"%#JHpYPK\ZL#$# +#"'732654&'&'#53573#327tD9I)! $ ".Z__k'".#%4090>$? 'jTuT$# N )&&O)!$6@3  $JPK\VK[ZL%#(" +#"&5467'#"&53326653327!.7;23 _6Qck30'B'k=55# 4.);V06Y\IB7&H1 .. O&&7 !@JPKNL +#3 uv cK '@$ JPKNL  +##33Ku~xutq}j|r zpp  &@# JPKNL  +!'#7'373~yxw{ )'@$ JPK\ZL$" +#"&'732677'373:!TD'94)4xn"ho;VFR$)6|{^g1 )& )&3 )@&JYPKYNL#" +77#5!73!3TJ>T3&3&3&bKP =@ JK,PX@[OKNL@cNLY#" +4632&#"#PDJ(7jNS5gwwg:X [JJ?AIIA4?C$+*RCC$*ww1+RgVK JVXNNX7)"&,7)",@ JKPX@*a[ XK [NK\ZL@.a PK[XK [NK\ZLY@  , +'%$%%# +#73!"&'732655#"&5463276554&#"3phFKVr f<9>NS5gwwg:X [JJ?AIIAD4?C$+*RCC$*ww1+RgVK JVXNNXU@OKNL +3#3kkU%@"aOKNL +#7#3CQ)$?$D@TuA$0&/ )M!:@7 JcPK\ZL! %&$ +&'732677'376632&&#"#Z94)4xn"aH5 ) % UER$)6|{^g C> D%VF )&3 )&36;KPX@(;JKPX@(;J@(;JYYKPX@#[OK[XK [VLKPX@-[OK[XKYPK [VL@+[OK[XKYPK [VLYY@97%$%$'%" +%#"&54&#"&&#"3267#"&54632&546323#3267E(ET)$$'4d :,CJJC0> `pUvu1' _LKe&"'!EI97-6Ld(.1\TTZ2/ FPzz(SSSS?T$#`&&(\IKPX@7I%$JKPX@7I%$JK!PX@I%$J7IK"PX@I%$ J7IK#PX@I%$J7I@I%$ J7IYYYYYKPX@#[OK[XK [VLKPX@-[OK[XK[XK [VLK!PX@+[OK[XKYPK [VLK"PX@5[OK[XKYPK [VK[VLK#PX@+[OK[XKYPK [VL@5[OK[XKYPK [VK[VLYYYYY@HF%+%+(%" +%#"&54&#"&&#"#"&'732654&'&&546632&546323#327\E(ES,#$& HF/-<5FiY4bDLvHS6386Ci[2_A.%aNLb&".#!EI97/*%F0.&%& E?-F'55;)*$ G>)G* UTST>T$# P& PI ,K!PX@ JK"PX@ JK#PX@ J@ JYYYKPX@[OK\NLK!PX@[OKNK\VLK"PX@OK[OKNK\VLK#PX@[OKNK\VLK,PX@OK[OKNK\VL@cOKNK\VLYYYYY@ $##" +4632&#"#%3267#"&53PMK9Fj "1C>k<98 7 76520! $ +@FPQRS]^ )*0dD#"&5463#"'332#"&5467&5467&&54632663#"'73265654&#"3"32654&#''! '' 8;sf,$9XOWqwz*'=+$$&ve90 M9lkTA'!;;54<<49/1OPJP*2$%%$/NOZ )3D@;*JHYPK [VL(&$#" +%#"&'!327#"&'#5357!573#3267E(ER%!.!E'DR__kk&"'!EI$#!OEITuuT$#7)F '4AKPX@'  0/J@'  0 /JYKPX@/[ OK YPK  [NK \ZLKPX@9[ OK YPK  [NK \ZK\ZL@=[ OKPK [XK  [NK \ZK\ZLYY@"555A5@<:31.,)(%# $ +#"&54633!"&'732655#"&54632%3#"'7325$6554&#"3''! '' [Vr f<9>NS5gwwg:X kaE8*)AJJ?AIIA$%%$4?C$+*RCC$*ww1+RZOYUVK JVXNNXP& P K!PX@ JK"PX@ JK#PX@ J@ JYYYK!PX@[OKNLK"PX@OK[OKNLK#PX@[OKNLK,PX@OK[OKNL@cOKNLYYYY#" +4632&#"#!#3PMK9Fjkk> % aL20%$" C;1O-57I3++" C:#8&#)08@5 J_YKL#%+#"'732654&'##5!R@E8K(")!#, )(B0?%?%WW0& K0 +@( JbKL  +%##3#/#7kmW8 !80ggK 5@2JabKL  +#73##3#/#NR8r7kmW8 !8}z0ggK&M4<<3+K=@:JrrbKL +#73#'##3#/#YqpqYPy7kmW8 !8wk0ggK&M2<<3+K ?@<JabKL    +'3##3#/#Xr8R7kmW8 !8}0ggK&M4<<3+)V0?@<JIb_KL&!+#"&547'##3327''#V:9DZ37kE5!#8 !883N)0/ fggK$=@:!JcbKL$$%& +%##&&54632#32654&#''#7lI<> % {YERM%94j__j57WT0,@)[K[L   $+##3654&##3]^^Ypp00&e^]d|g0`WT G@DJrr[K [L  +'373##3654&##3qYPPYq\]^^YppykkI0&e^]d|g0 <@9a[K [L   $ +##5#5353654&##3#3͚VV]^^Ypp0?&e^]d?W0 /@,aYKYL +%!!!!!BTT0TRW9@6aaYKYL +#73!!!!!QR8rlB}@T0TRW N@KJr raYK YL  +'373!!!!!qYPPYqBykkT0TRWC@@JrraYK YL +#73#'!!!!!YqpqYPBwkrT0TRW&a.<<3+W &a1<<3+WD@AaaYK YL  +'3!!!!! Xr8B}T0TRW&a1<<3+W) 0=@:JIa_YKYL'!+#"&5467!!!!!!3327 ;9D++SF5!#60'70TRT/ W 0 )@&aYKL +!!#!kܚT0T6H8l@ JKPX@a[K[L@#a[KK[LY@$#&#+#'#"&&546632&#"32655#5HKYCR{CEV=]#rQ_\WDM&S*1EYYE,ck_biB< O6H&kX<<3+WD0 '@$aKL +#5!#3!5Dkkk00W0@KL+3#3kk0W%@"aKL+#7#3XR8-kk0W& qJKPX@  a1K[3L@"  a1K3K[9LY@  +#7!#7#3#"&547732653WS8XS9kkri`ge.1>3k0flYL40CFX5 /@,JrrKL +'#73#3PPYqpqskkwkk0$ 0@-cKL    $+#"&54632#"&5463#3R$$##$$##!kk"#"""#""0G&nޒ3+ @aKL+#'3#3RXrGkk}0W0&nx@aKL+#53#3=kkN*0)0@ J`L%!+#"&5473327"98CRk=/9"60J*-.3$0@ JK[L)"+%#"&547732653ri`ge.1>3kflXL4/CFXWO0 @ JKL+#33# ^kk {^0W0@KZL+%!3TkWW0'W )@&aKZL  +#73!3R8rTk}CW0'W0 )@&YKZL +%!3#73TkF`WW0'W/0&z.[3+0 ,@) JKZL +%!55737TbbkWW2T2OTNW0!@ JKL+3373#7####Wy##ydZc0nnO#2ΫWG0@ JKL+!#'##33'3G/d/ejVV0UVjWG2@/JaKL +#7#'##33'3XR8/d/ejVV0UVjWG 0@- JrrKL+#'373#'##33'3pqYPPYL/d/eykkjVV0UVjWG &X<<3+6h8,@)[K[L&+#"&&5466332654&#DDVVDDVR\\RR\\R8EYYEEYYEWi``ii``i6h=@:a[K[L  +#7#"&&5466332654&#XR8QDDVVDDVR\\RR\\REYYEEYYEWi``ii``i6h"G@DJrr [K[L"!  +'#73#"&&5466332654&#PPYqpqSDDVVDDVR\\RR\\Rwkk?EYYEEYYEWi``ii``i6h&R<<3+6h6@3a[K[L'+#'3#"&&5466332654&#vRXrgDDVVDDVR\\RR\\R}EYYEEYYEWi``ii``i6h#H@E a [ K[L#" +#7!#7#"&&5466332654&#eXR8&XS8DDVVDDVR\\RR\\REYYEEYYEWi``ii``i6h&U<<3+6hA'@%$ JKPX@s[K[LKPX@!sK[K[L@!rs[K[LYY@  ' &'%+#"'#7&&54663273&#654'3>*DV]?*?A*+DV^C&?>\))}jaq@;wpp0[PJa[K[L,+ +#7&&#"#"'732654&'.54663XR8Mr)CN?!I7R&W<<3+N{K PX@'h  a a\VL@(p  a a\VLY@  +#7!#7#"&547332653#3?XS86XR8btdegi3057kkkdpPL  )(DB[9N0NK PX@ha\VL@pa\VLY@ $" +%#"&547332653#3tdegi3057kkkdpPL  )(DB[9{"-1F@C&J c][ML##10/.#-#,"!%)$# +7#"&'#"&5467754&#"'66323&6553!!t&A+4:?EY#* A O@IJ4>'"Nz3 +(*4    ,2>="& @b 5@2c][ML    $ +&54632#654#"3!!nUUOPUUPNNMM3AZQRZZRQZ?lnnl@+@(YPK[NL +7&&5###5!728JU&k' R;3^RR<9 ,@)[UK[VL   $ +&54632#6654&#"3}}}HJJH [}||}"D !@ JMKNL  +#47'>7Di"W-SJl"!0 ]&41*@' J[UKYNLF%& +7>54&#"'6632633!F?C:>I] ~gGh62;5O[i85BRI+_n7^;_i\. +@ ('JKPX@,hnc[UK[VL@-pnc[UK[VLY@+*#!$$&  +#2#"'732654&##53254&#"'663ia2%9&?&^(Y)0b5^>kvHI767B<>C9w\5A1(A@> Jc[UK[VL('#!%'$ +&#"36632#"&5466332654&#j^c0J)b>54?>5AKL@@MLA`Q3LT=VeeV Jc[UK[VL'&" &%$ +#"&'732655##"&&546332654&#Zqe854&#"'4663ut>fJ" 84~KD28I_3eG:e\?bJ& SREDF=Ap6A ]xS:/Q2GN PB9X0/=?A460N83n73'HN%?0 3+/0 ?@< JacW[O $%$ +#"&'732654&#"'!!3663n~jZv_C,EX)L1Ds_jvJK38=G?@F!wV 7%)A@> Jc[UK[VL)($"%&% +&&#"36632#"&&5466332654&#h\:4NZf;@b6}kSv=EyN@IH;>DD>DL(03v9:3`@ctTfmNH:;IF?=D 0 +@(JsUYM  +#47!5}jjsuq0Y蘒"zX6"%1D@AJc[UK[VL&&&1&0,*%$ + +#"&54675&&546332654&#654&#"3u1-:ApqA:-2vd6>?55>>5AKKAALLA_R2MU>55=?.:(D@A JccW[O('#!'%$ +#"&'732654'##"&&546332654&#|[r_ @@LX `BAd7l@EF?:HG;:IN+69t:<4aActUE>=EH:;H2* ,@)[UK[VL   $ +&54632#654#"3||| [Y7)@& JMKZNL +%!5347'>737,&c0ZRZYYY*#2 [%47<+@ ('JKPX@,hnc[UK[VL@-pnc[UK[VLY@+*#!$$&  +#2#"'732654&##53254&#"'663w`2%9&?& Jc[UK[VL'&" $'$ +&#"36632#"&5466332654&#j^d/I(d;bw}k~ExL?FF:=CD<CJ-]BtH69s`ctmNC68DA;9@05 %@"JYMKNL  +#47!55~smzwhYy]>%1D@AJc[UK[VL&&&1&0,*%$ + +#"&54675&&546332654&#654&#"3t2,8@nn@9,3tb3;<22<;3>HH>=IH>`Q2MU;WffW:VM3Q`Z3.-33-.3 :33;;33:3!'A@> Jc[UK[VL'&" &%$ +#"&'732655##"&&546332654&#[qf9=WKb@@c6~l=CD<:GF;IK/43 <54`Adu^A;9@C68D:": *@'c[VL   $ +#"&54633254#||xx||x:Sc80)@& JrZNL +%!53477'>738+$^0WOWWWW# "1 U%4'=:;:/,=0 :@7JrsUZN   +%!533##7547#jmmf(AAdSM@$;K"0>,6(0 +@(JsUYM  +#47!5(zlkstx0Y}ꨡ!zX6",:<9 :@7J[UK[VL    $ +#"&5463&#654'3}}}#%HJ"%[z>}|tA`7$: 8@5Jc[VL    $ +#"&5463&#654'3||zz}}zEG $EG :Shgb5VbhgZ3 2* :@7J[UK[VL    $ +#"&5463&#54'3|||%%"![>{=\ :": 8@5Jc[VL    $ +#"&5463&#54'3||xx||x$):Si4XbT0 b@MKNL +3#?[#[l Y&' &'&'o,r 3+  3+Z(@% JcYNLG%' +7676654&#"'6632633!$6%F3#!#(PVFLO?C--~5#5@ $/)>FK5*S. EP 3+ ^ 1@.JrbNL   +%##5#533#547#^CJǯbCY[__+g)% }S 3+ e 3+> 3+!i 3+f 3+r 3+   !@JYML  +#5475667V7:Ls'0I* Z 3+P%t@ "!JK PX@$h_[UK[PL@%p_[UK[PLY@%$$!##& +#"'732654##532654&#"'663L0 $5UE Q E %E < N Q;;2*1/)4?a"@ 26912  ^ 3+S 3+ e 3+ > 3+!i 3+f 3+O?&8; ;3+O?& E E3+O?&8 83+O?&; ;3+O?&2 23+O?&7 73+O?&-+ +3+O?&8 83+O?&8 83+=r 3+ C 3+CZ 3+=P 3+ C^ 3+>S 3+ =e 3+C> 3+!=i 3+=f 3+Gr ,@)[EK[FL   " +5432#6654&#"3TUUT+((++))+GgeegBDFFDDFFD M !@JAKBL  +#5475667V98Ls'0I*MZ*@' J[EKYBLG%' +676654&#"'6632633!$6%F3#!#(PVFLO?C--~#5@ $/)>FK5*S. EGP%v@ "!JK PX@%hc[EK[FL@&pc[EK[FLY@%$$!##& +#"'732654##532654&#"'663L0 $5UE Q E %E < N Q;;2*1/)4?a"@ 26912 M^ 1@.JbAKBL   +##5#533#547#^CJǯbCY__+g)% }HSD@A JpcYAK[FL#$$ +#"&'732654#"'7!#3663 GWE;PNB")H$ D+&MF6BG'-(9%BDN  Ge%A@> Jc[EK[FL%$ &$$ +&54632&#"36632#6654&#"3xXcP5EE8.32%>JRI$&'" ('!Ghdci(+#3K6 C;;DC "%!M> %@"JYAKBL  +#467#5>F#/81@. J[KYL'(+633!5>54&#"'&63H_0~'5VoA866?\ng8.M.^LUMCf[.,6E6%Rk(8+L@I('Jpnc[K\L+*$!$%$  +#"&'732654&##532654&#"'663IX.!34Hw`Unc 973<8: 452..6`pN8(D*)= >9M]CE,0/0))%J*)(**'#?D(0 1@. JbKL  +%!533##7#Meed>mQ &)*0!<@9 JcYK[L! $%$+#"&'732654&#"'!!3663tdrbRk]754>;5%?T$`=3gaOZe;>1*+6.17?T .8'A@> Jc[K[L'&" '$%+&546632&#"36632#6654&#"3|>nFK]WU+A$Z3Vjo_/9943==3\B9>)K5^<)2_RScT3--36*,4&0 %@"JYKL +#667!5j^icb0Sd팄aW67%1D@AJc[K[L&&&1&0,*%$ + +&54675&&54632#654&#"3654&#"3{;6-2q`_q2-6;zh099019:08BC78BA9VI1IB*GQQG*BI1IVP+$%**%%*0))00))0/8&A@> Jc[K[L&%!$&%+&'732655##"&54632#654&#"3h_ 65LC V2]nsdo{zs3>=25<<5@;Jp[UK[VL! & +#'6654&#"'663#"&5463hUfbT?=@;Jp[K[L! &+#'6654&#"'663#"&5463Xt^Le\I756@bsa&& '' 8NFGMMt .+$)41)EMC$$$$: !?@<Jp[K\L !  $+&54632#&5467733267#&& '' lu_Le[J756@bsa$$$$CMGGMLs .+%)50(EMLPgP'qq:?]&oo<8V&pp:?])@& JHW[O++&5467632#g-;;4*8'+ ?A66#G:Nca5+') U(*r@  JK(PX@a_[OL@#caW[OY@#$# +3##"&'7327#5376632&#U / SD!(&B . RE ('STNP RTTNQ S-R:CK!PX@"! <3  JK"PX@"! <3  JK#PX@"! <3  J@"! <3  JYYYK!PX@5a   c[UK \VK  [VLK"PX@<   pa  c[UK \VK  [VLK#PX@5a   c[UK \VK  [VL@<   pa  c[UK \VK  [VLYYY@;;;C;B?=:975%&$#! +$#"&'#"&54632654&'#53&&546632&&#"3#32657&#"3RHD*G*7V3>B7:0l6dAUhY:46A.)$L.#,Q[:0('3 0K%79\4PR-=:9/3- K*-+$-1q#7p@@= JpMK\NL)" +##5755753773265p~zzzzhDVU#;#X#<#9;9X9;9^c/j=@:  ca [MKNL! +3##5#535#5332#'32654&##g\\\\l||lGCCGDMwwMDT8j]\iT6996`>@; Jba  MKNL +3#3##5#535#5333773b}1v{//{v+;I8oo8I;mde?Xu ;<Vv*1")/.@+/.&%"! JUYM +#5&'75.546753&&'&554&'W9q_MA65I*5+U'*K-I@F- Ja   a[UK [VL+)'&%$%"" +%#"&'#53&547#536632&&#"3#3#3267K|[bWMLSfXoXA3;VM7;K T_ujD  ErNK)58RJE  DBH?=@r@  JK(PX@a_[OL@#caW[OY@#$# +3##"&'7327#5376632&#4YI"+)L2XH"+)STNP RTTNQ S8@@= JpMK\NL)" +##57557537732658zyyyye$3&"2*:!9)8%"-%;!>$3&"2*:!9)YYYY+&@#Wc\P$!$! +#"&'&&#"#6323273F@"2/ ] !/. ]7654&#"3667&&#"3O.dM8`E7B3%0N//O/9eD8F1"#..""424I!c"22:@!#//!/_Cdl?L>:/^CC_/=M?:@67@49<4"2:@/?77?7@4JcW[O$%% +&&#"#"&'73265463}*  "+YJ%* '"+ZJ R ))JV R)).JVD%@0+%5%%n֫kmC& @HYNL +%5%!!&`f~Y;y!lHK PX@gUYM@sUYMY@  +#5!5!a{lY;!m@UYM +!5!!\=' 3+=1@UYM +!! 1>I[% 0+''7'77n@AA@@AAAA=53rK PX@*fg bUYM@(rs bUYMY@ +3!#7#537#5!733y?4W4~? 4X4}tTjjTSllSA4%E@BJc[UK[VL%$ $&$ +#"&&546632&&#"'6637&&#"3Ci:;f@8[\X,=!2+_;O??G%K '3KPX@+ c    d [ UK[NL@3 c    dMK [ UKNK[VLY@&(((3(2.,'&"  $ +#"&54633#32654&##"&546332654&#[[LL[[L\Q] ))&'))'$[[LL[[L&))&'))'eSSeeSSe lP73277237eSSeeSSeO72377327% '3?KKPX@1 c    d[UK  [NL@9 c    dMK[UKNK  [VLY@6@@44((@K@JFD4?4>:8(3(2.,'&"  $ +#"&54633#32654&##"&5463 #"&546332654&# 32654&#[[LL[[L\Q] ))&'))'$[[LL[[L[[LL[[LM))&'))'g))&'))'eSSeeSSe lP73277237eSSeeSSeeSSeeSSeO72377327723773278B$< &@#UaYM +##5#53533$]]Yu@ 3+8$ ^KPX@ aYMKYNL@aaYNLY@  +##5#5353!!$YYXXW@ &@#UaYM +##5#53533@qCrrCquu?t?Q k Q?l_oa3@0 JHGrW[O#% +&#"'632&''7&547[rf8Hr_EEHH5dsd6HGGCakL9(@ GML +#&&'7&5367t>?t?Q k Q!FVWFRdoPe?^Wod_a3@0JH GrW[O#  +$32&#"'654'7677urH8frd5HHEEdse9LkaCGGH6!1@.J HGW[O +"'&&'566763!|ePodRFWVFRdoW^? Q?t?>t?Q kxSzU+@(JH GW[O#/ +%'&''654'7327#"'zGJ4dre8Iq_?KGI@_qI8erd2L! #1@.  J#"HGW[O?: +'67#!"'&&'566763!2&'7/VWFXhoFMLFohXFWVFXioHKzMFohX9>?t7P P7t?>t6P P6&SA#0+$7#&&'7&547'6673&'6t>?t7P P7t?>t6P iiXFVWFXioFMzMFoiXFWVFXioFMMF(0@XKVL& +&&546632#wDDwIIwDDwI DwIIwDDwIIwD%3,@)[XK[VL& +&&546632#>54&&#"3yEEyIIyEEyI0M,,M00M,,M0 DwIIwDDwIIwDW-P00P--P00P-#5 0+ #   #5 0+ '#    00 @ JMKNL +#30wwUJJJ2&@PKNL +!&  0(%@"YPKYNL +!!%!0 QR6@UKVL& +&&546632#*YY``YY` Y``YY``Y4,@)[UK[VL& +&&546632#>54&&#"3*YY``YY`Gq@@qGGq@@qG Y``YY``YWAtHHtAAsIIsA50+ 5UUJU50+ '5UUJUU@@MKNL +!llA%@"YMKYNL +!!%!An6&lRA%@"J[MK[VL$% +"6632# 46!t4&SD LVU}ps~"Cy(@%JcW[O$% +267'#"&5463 !]4&SD3:VU}ps~޵A%6@3"J[MK[VL%$%#$ +#"'# 46!"6632663"663 D_ 14&SDKt#14&SD}ps~>A"MVU-+MVUCy%:@7" JcW[O%$$%% +!'267'#"&'#'267'#"&5463266304'SCKt$14&SDF_޵MVU-+MVU}ps~~>@:)X&8@5 !Jp[MK[ZL&&&#)" +%#"&547732655#"&&54663!665X]tE9,5..En?@wOCAI}UI18C@7eCCf8P%ClJ VX@sOL +#3^^}SX@]YOL +#3#3^^^^epp=@<K`@   10JKPX@6   c[ MK [NK\NK[RLK!PX@3   c_[ MK [NK\NLK"PX@:  p   c_[ MK [NK\NLK#PX@3   c_[ MK [NK\NL@:  p   c_[ MK [NK\NLYYYY@===K=JCA<;%&%$&$& +#"&'#"&&5466327332654&&#"3267#"&&5466332677654&#{\1W83< V90L,Ai=k I)=27NmqZ[`M]*%5mXzhnɃHQ833N36guRw>2'),1YEA<; 07)x(4@=@:=9.%"! J[UK[VL))86)4)3(',$ +"&'#"&&5467&&54663265'76654&#327&/e-D/%c;?c7GQ* 'L6J\:L)7$O "$4$&&6"'%H4P=.&?=* +"&,O3;a'5D%'G+IE:V(3BAS=r-# ) .0 :% !2:.1.K%:$'H #@ pq[ML$ +###&&5463!HZ{\qd^Robeo<7T@Q.-"!Jc [UK[ XK[VL  7 620,*&$& +#"&&54663326654&&#&&#"327#"&5463UUggUUgPyBByPPyAAyP}*F ,#/66/JBP@TaaTTffTTffTGBzQQzBBzQQzBTd":44:=66dVWd<-4"JK PX@1ha[ UK [XK [VL@2pa[ UK [XK [VLY@420.+)('&%$#& +#"&&546636654&&#"3#'##323254##UUggUUgPyAAyPPyBByP.)_ZQ8M=LJCCJTffTTffTBzQQzBBzQQzB$: pA6><<C1?+@(=6%$ J_[UL)'" %' +$#"&'732654&'&&5467&546632&&#"654&'&'C/'5bAOi$DH8;@H]}j/(5eCQc#BE7?X<[(([J@@8G )@&_[UL   $ +&54632#6654&#"3cdOPddP)55)(54)G`LL__LM_O4))44))4?C@@ Ja[UK[VL%# +!3267#"&&54663!5&&#4'rF[5+`pVTfK%f@),OURGFTgfT;T(*,p%7@4 J[UK[VL%$*$ +$67#"&55567543236654&#1%'6OI$$%#{?7ME%"6&&FOCJE H BEJ7r * #)e7"c%!@JsOL +#3f`c&u& !@aYML +###53533hhn.X&5@2 aaYML +3##5#535#53533AhhnXXXXAasKPX@C_^]GEDjka`r s  JZYXWVUTSRQPONMLKJIHFH@F_^]GEDjka`r s    JZYXWVUTSRQPONMLKJIHFHYKPX@Lpp  nW c aYPK [ NLK!PX@Mpp  n c  caYPK [ NL@Kpp  n c a  ca [ NLYY@(tttt~}{ywvnlhf\[>=975431/,#2"5"4 +##"54335##"54335###"546335###"54335&&'#"&5463266575'7777777777654&#"67665##"&5#39?)E=. &. . X, C}'&988CAC911%2?F LI(B69;."  " ##3 = Q'0. .%,"d+SE>B!A$A2@26;7@,EE@8&'#G##+  %""V@UYM +#3^^aQ=``3+%98&1=B@?73,#!  J[K[L22''2=2<'1'0&%+#+"&'#"&5467&&5466326'76654#7'&''3((;)EmZk=J&#G2DS3F4  #F9.! /A/4F"2&@-$> (R;8JKPX@ sOL@ riY@  +'#73JPPXoro;hhV> FKPX@[OL@W[OY@   $ +#"&54632#"&5463$$$$$$##""#"""""J@ @[OL $ +#"&5463&& %% ####vB+@UYM +#'3+RclBLB@UYM +#73#73QJhYPJgBHL@YML +!5!dLN)^'@JH[ZL#$ +327#"&546751@UYM +!5!Q>51@UYM +#53>=*(@%aUYM +3#3NO>->*"@aUYM +7#53#53NN>>-G&3+($@! JG[FL+ +'667'#"&5463 **%)*")C *-G @[FL $ +#"&5463|3M '@$aAKBL  +#5##335XWWs G ,@)[EK[FL   $ +#"&546332654&#Fjj^]kk]6;;66<<6l``ll``lBHBBHHBBHO?OKPX@_[OL@cW[OY@& +&&546632#>54&&#"3X^^oo^^oYLLYYLLY(]mm]]mm]BLZZLLZZL+,3[@X!Jp c  ca W [O31/-*('&%$#"& +#"&&546636654&&#"36#'##323254##pyBByOOxBBxO;Z22Z;Yl1Z:z DG:%?x.80000AwNNwAAwNNwA32Z;;[2nZ;Z2+gaa1*+*)5@2 aaYAL +3##5#535#53533UU]EE]EE !@aYAL +###53533UUI-='3+3+( 3+-= 3+=  3+  3+5f' 3+5' 3+5' 3+3C 3+ = 3+ff+i_<u{ c2Eb====bb|b|b|b|b|b|b|b|b|bcb==b/bub/b///R//!/F'bWbWbWbrbygbbbbb=========.=sbb=bbb+++bkkYYYYYYYYYlU U U w2w2w2w2=bWbbb+k+kbbbbbbbbK bbbbubbbbb===byYoooooooood d d d d d d d d F bbbK ububububub    :::::     Y:1:1:1:1:1:1:1:1:1:1s1uS66666666u8g88u8E6E6E6E6E6E6E6E6E6*'kSASSA#AGS8P@PRPP{ SkSkSkSkS^6^6^6^6^6^6^6_6^66uSuSv9SS7)))tSPkOkOkOkOkOkOkO'GS8PkS4))kOkOg&" " " 3333b P#Su7u7u7UU5U1UK"tStStStStSSSB47 7 7 6m(PPPPF"t7(P6bWJWW`W=#=#88]]]]]]]]]]:[Wd6d6d6d6WW?W?W?W?W?W?W?W?W?W+W66WWW-WG -W$bWJWJWJWYW0WWWWW6666666666=WGW6`W`W`W=#=#=#~W88OOOOOOOOOZz1 # # # <,<,9,<,\W\W\W\W777ANAN{t<"K1J.n)F5iAA)k=m=[7 + j%4/U7% W6e?\2\Y\7\<\%\A\A\0\>\3\:\c\=\;\,\K\>\6\6\,t<[7\2\:bt 1  uoq p E! uoq p E!OOOOOOOOO uoq p E! uoq p E!A1~/(5( *. &16/A1-Q*I3mCB?=2%.-LL*A-?n.-z-.K/C\.K.B@t t$iVij?j@t t$iVij?jQQWWQQW ..D.D/114 1 41..K.K/-::NL:<:<LE.3:=<n)q-7/}\?\ \*\\\\#\$\\;-+\8\@66\7\6=\D\C\;\;YY\I\=xAp%%\8Y\8YT9eNC(b-&t3l(!x1!&X(X%X#X#_0X2X06455@AACAC: VS=v)'&<&<]C,8?,@&&j V=9%H`RRVvLHz@5595=-(-3 OC-(-=55953 ((\J\`B.66*<0\r~,Np  D $ d L  d v   B8^Ff PbDTv0Bp(4@hJPbH"r8DD&"4   !$!6!""B"""###$@$$$%d&&&'''(b(t(()D)P))**+V+b+n+z+++,*,6,B,-*-h-t--.@./ ////0000&020012223 33$3z33334 4P4z4455"5^5666 6,6h6t666667L7X78288989D9P999:|::;6;|;;;;;;>4>`>>>>>>???F?@@ @@@A AA"ALAAAAAB,B8BDBPBBC^CjCCCDDDFFFFGlHI2IJxJKKK&K2K>KJKKL LLHLLLLM0M@MMMN>NNOOrOPPP^PPQQZQQQRRR\RRRS&Saab bXbcc@cxccdfddeNexef>fzffgNg\ghh>hii?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnDiokljpqnmEFoGrHpsrstquvIJKLtvwwuxyzMNO{|}PQ~xRy{|z}STUVWX~YZ[\]      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ?" B^`>@ a !_# AC  AbreveAmacronAogonekDcaronDcroatEcaron EdotaccentEmacronEogonekIJImacronIogonekLacuteLcaronLdotNacuteNcaron OhungarumlautOmacronRacuteRcaronSacuteuni1E9ETcaron UhungarumlautUmacronUogonekUringZacute Zdotaccentuni0122uni0136uni013Buni0145uni0156uni015Euni0162uni0218uni021A E.swsh.001Eacute.swsh.001Ecaron.swsh.001Ecircumflex.swsh.001Edieresis.swsh.001Edotaccent.swsh.001Egrave.swsh.001Emacron.swsh.001 M.swsh.001 R.swsh.001Racute.swsh.001Rcaron.swsh.001Rcommaaccent.swsh.001Iacute_J.loclNLDR.ss03 Racute.ss03 Rcaron.ss03Rcommaaccent.ss03G.ss04 Gbreve.ss04Gcommaaccent.ss04IJ.ss05Iacute_J.loclNLD.ss05A.swsh Aacute.swsh Abreve.swshAcircumflex.swshAdieresis.swsh Agrave.swsh Amacron.swsh Aogonek.swsh Atilde.swshE.swsh Eacute.swsh Ecaron.swshEcircumflex.swshEdieresis.swshEdotaccent.swsh Egrave.swsh Emacron.swsh Eogonek.swshF.swshL.swsh Lacute.swsh Lcaron.swshLcommaaccent.swsh Lslash.swshM.swshN.swsh Nacute.swsh Ncaron.swshNcommaaccent.swsh Ntilde.swshR.swsh Racute.swsh Rcaron.swshRcommaaccent.swshS.swsh Sacute.swsh Scaron.swsh Scedilla.swshScommaaccent.swshT.swsh Tcaron.swsh uni0162.swsh uni021A.swshW.swshAdieresis.titlOdieresis.titlUdieresis.titlabreveamacronaogonekdcaronecaron edotaccentemacroneogonek i.loclTRKijimacroniogoneklacutelcaronldotnacutencaron ohungarumlautomacronracutercaronsacutelongstcaron uhungarumlautumacronuni0123uni0137uni013Cuni0146uni0157uni015Funi0163uni0219uni021Buogonekuringzacute zdotaccentgermandbls.caltlongs.componentiacute_j.loclNLDg.ss01 gbreve.ss01gcommaaccent.ss01l.ss02 lacute.ss02 lcaron.ss02lcommaaccent.ss02 ldot.ss02 lslash.ss02n.swsh nacute.swsh ncaron.swshncommaaccent.swsh ntilde.swshr.swsh racute.swsh rcaron.swshrcommaaccent.swsht.swsh tcaron.swsh uni0163.swsh uni021B.swshy.swsh yacute.swshydieresis.swshc_ts_tlongs_ilongs_llongs_sf_t.ligag_j.ligaj_j.ligat_t.ligag.ss01_jfl.ss02 longs_l.ss02Gcommaaccent.scKcommaaccent.scLcommaaccent.scNcommaaccent.scRcommaaccent.sc Scedilla.scScommaaccent.sc uni0162.sc uni021A.sca.sc aacute.sc abreve.scacircumflex.sc adieresis.sc agrave.sc amacron.sc aogonek.scaring.sc atilde.scae.scb.scc.sc cacute.sc ccaron.sc ccedilla.scd.sceth.sc dcaron.sc dcroat.sce.sc eacute.sc ecaron.scecircumflex.sc edieresis.sc edotaccent.sc egrave.sc emacron.sc eogonek.scf.scg.sc gbreve.sch.sci.sc iacute.sciacute_j.loclNLD.scicircumflex.sc idieresis.sc i.sc.loclTRK igrave.scij.sc imacron.sc iogonek.scj.sck.scl.sc lacute.sc lcaron.scldot.sc lslash.scm.scn.sc nacute.sc ncaron.sc ntilde.sco.sc oacute.scocircumflex.sc odieresis.sc ograve.scohungarumlaut.sc omacron.sc oslash.sc otilde.scoe.scp.scthorn.scq.scr.sc racute.sc rcaron.scs.sc sacute.sc scaron.sc germandbls.sct.sc tcaron.scu.sc uacute.scucircumflex.sc udieresis.sc ugrave.scuhungarumlaut.sc umacron.sc uogonek.scuring.scv.scw.scx.scy.sc yacute.sc ydieresis.scz.sc zacute.sc zcaron.sc zdotaccent.scRcommaaccent.sc.ss03 r.sc.ss03racute.sc.ss03rcaron.sc.ss03Gcommaaccent.sc.ss04 g.sc.ss04gbreve.sc.ss04iacute_j.loclNLD.sc.ss05 ij.sc.ss05zero.osfone.osftwo.osf three.osffour.osffive.osfsix.osf seven.osf eight.osfnine.osfzero.tfone.tftwo.tfthree.tffour.tffive.tfsix.tfseven.tfeight.tfnine.tf zero.tosfone.tosftwo.tosf three.tosf four.tosf five.tosfsix.tosf seven.tosf eight.tosf nine.tosf zero.zero zero.osf.zero zero.tf.zerozero.tosf.zero zero.dnomone.dnomtwo.dnom three.dnom four.dnom five.dnomsix.dnom seven.dnom eight.dnom nine.dnom zero.numrone.numrtwo.numr three.numr four.numr five.numrsix.numr seven.numr eight.numr nine.numruni2780uni2781uni2782uni2783uni2784uni2785uni2786uni2787uni2788uni2080uni2081uni2082uni2083uni2084uni2085uni2086uni2087uni2088uni2089uni2070uni00B9uni00B2uni00B3uni2074uni2075uni2076uni2077uni2078uni2079zero.scone.sctwo.scthree.scfour.scfive.scsix.scseven.sceight.scnine.scperiodcentered.caseexclamdown.caseperiodcentered.loclCAT.casequestiondown.caseperiodcentered.loclCATperiodcentered.loclCAT.ss02colon.tfcomma.tf numbersign.tf period.tf semicolon.tfuni208Duni208Euni207Duni207Ebraceleft.casebraceright.casebracketleft.casebracketright.caseparenleft.caseparenright.caseuni00AD emdash.case endash.case hyphen.caseguillemotleft.caseguillemotright.caseguilsinglleft.caseguilsinglright.caseperiodcentered.sc exclam.sc exclamdown.scperiodcentered.loclCAT.sc question.scquestiondown.sc quotedbl.scquotedblleft.scquotedblright.sc quoteleft.sc quoteright.scquotesingle.scuni00A0space.tfEurouni20BAuni20BDcent.tf currency.tf dollar.tfEuro.tf florin.tf uni20BA.tf uni20BD.tf sterling.tfyen.tfuni208Cuni207Cuni208Buni207Buni208Auni207Auni00B5uni2126uni2206uni2215uni2219arrowupuni2197 arrowrightuni2198 arrowdownuni2199 arrowleftuni2196 arrowboth arrowupdnuni25CFcircleuni25C6uni25C7uni25AAuni25AB uni25CF.case circle.case uni25C6.case uni25C7.case uni25AA.case uni25AB.caseuni275Buni275Cuni275Duni275Euni2761 estimateduni2113uniF8FFbar.caseat.case ampersand.scuni0326uni02C9 spacesuperioremdashsuperiorendashsuperiorhyphensuperiorbracketleftsuperiorbracketrightsuperior colonsuperior commasuperiorperiodsuperior Hsuperior Osuperior indexringregisteredsuperiordaggerdblsuperiordaggersuperior coloninferior commainferiorperiodinferiorbracketleftinferiorbracketrightinferioremdashinferiorendashinferiorhypheninferior Hinferior OinferiorkkWW0 8 VVBBC = kkWW00 08 VVBBM G kkTT. ) , UXEY (`f UX%acc#b!!YC#DC`B-, `f-, d P&Z( CEcER[X!#!X PPX!@Y 8PX!8YY  CEcEad(PX! CEcE 0PX!0Y PX f a PX` PX! ` 6PX!6``YYY+YY#PXeYY-, E %ad CPX#B#B!!Y`-,#!#! dbB #B CEc C`Ec*! C +0%&QX`PaRYX#Y! @SX+!@Y#PXeY-,C+C`B-,#B# #Babfc`*-, E Ccb PX@`Yfc`D`-, CEB*!C`B- ,C#DC`B- , E +#C%` E#a d PX!0PX @YY#PXeY%#aDD`- , E +#C%` E#a d$PX@Y#PXeY%#aDD`- , #B EX!#!Y*!- ,EdaD-,` CJPX #BY CJRX #BY-, bfc c#aC` ` #B#-,KTXdDY$ e#x-,KQXKSXdDY!Y$e#x-,CUXCaB+YC%B %B %B# %PXC`%B #a*!#a #a*!C`%B%a*!Y CG CG`b PX@`Yfc Ccb PX@`Yfc`#DC>C`B-,ETX#B E #B #`B `aBB`+u+"Y-,+-,+-,+-,+-,+-,+-,+-,+-,+-, +-),# bfc`KTX# .]!!Y-*,# bfc`KTX# .q!!Y-+,# bfc&`KTX# .r!!Y-, +ETX#B E #B #`B `aBB`+u+"Y-,+- ,+-!,+-",+-#,+-$,+-%,+-&,+-',+-(, +-,, <`--, `` C#`C%a`,*!-.,-+-*-/, G Ccb PX@`Yfc`#a8# UX G Ccb PX@`Yfc`#a8!Y-0,ETX/*EX0Y"Y-1, +ETX/*EX0Y"Y-2, 5`-3,Ecb PX@`Yfc+ Ccb PX@`Yfc+D>#82*-4, < G Ccb PX@`Yfc`Ca8-5,.<-6, < G Ccb PX@`Yfc`CaCc8-7,% . G#B%IG#G#a Xb!Y#B6*-8,%%G#G#a C+e.# <8-9,%% .G#G#a #B C+ `PX @QX  &YBB# C #G#G#a#F`Cb PX@`Yfc` + a C`d#CadPXCaC`Y%b PX@`Yfca# &#Fa8#CF%CG#G#a` Cb PX@`Yfc`# +#C`+%a%b PX@`Yfc&a %`d#%`dPX!#!Y# &#Fa8Y-:, & .G#G#a#<8-;, #B F#G+#a8-<,%%G#G#aTX. <#!%%G#G#a %%G#G#a%%I%acc# Xb!Ycb PX@`Yfc`#.# <8#!Y-=, C .G#G#a ` `fb PX@`Yfc# <8->,# .F%FRX +-g,:+?+-h,:+@+-i,;+..+-j,;+>+-k,;+?+-l,;+@+-m,<+..+-n,<+>+-o,<+?+-p,<+@+-q,=+..+-r,=+>+-s,=+?+-t,=+@+-u, EX!#!YB+e$PxEX0Y-KRXYcpBX8*B@ K?/#*B@ UE7)* B  *B@@@@@ *D$QX@XdD&QX@cTXDYYYY@ MA1% *D^dDrustup-1.26.0/www/index.html000066400000000000000000000247371441327105200157770ustar00rootroot00000000000000 rustup.rs - The Rust toolchain installer

rustup is an installer for
the systems programming language Rust

To install Rust, if you are running Unix,
run the following in your terminal, then follow the onscreen instructions.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

If you are running Windows 64-bit,
download and run rustup‑init.exe then follow the onscreen instructions.


If you are running Windows 32-bit,
download and run rustup‑init.exe then follow the onscreen instructions.

Need help?
Ask on #beginners in the Rust Discord
or in the Rust Users Forum.

rustup is an official Rust project.
other installation options  ·  component availability  ·  about rustup

rustup-1.26.0/www/normalize.css000066400000000000000000000137721441327105200165110ustar00rootroot00000000000000/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /* Document ========================================================================== */ /** * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in iOS. */ html { line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ } /* Sections ========================================================================== */ /** * Remove the margin in all browsers. */ body { margin: 0; } /** * Render the `main` element consistently in IE. */ main { display: block; } /** * Correct the font size and margin on `h1` elements within `section` and * `article` contexts in Chrome, Firefox, and Safari. */ h1 { font-size: 2em; margin: 0.67em 0; } /* Grouping content ========================================================================== */ /** * 1. Add the correct box sizing in Firefox. * 2. Show the overflow in Edge and IE. */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ pre { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /* Text-level semantics ========================================================================== */ /** * Remove the gray background on active links in IE 10. */ a { background-color: transparent; } /** * 1. Remove the bottom border in Chrome 57- * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ text-decoration: underline dotted; /* 2 */ } /** * Add the correct font weight in Chrome, Edge, and Safari. */ b, strong { font-weight: bolder; } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ code, kbd, samp { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /** * Add the correct font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` elements from affecting the line height in * all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* Embedded content ========================================================================== */ /** * Remove the border on images inside links in IE 10. */ img { border-style: none; } /* Forms ========================================================================== */ /** * 1. Change the font styles in all browsers. * 2. Remove the margin in Firefox and Safari. */ button, input, optgroup, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 1 */ line-height: 1.15; /* 1 */ margin: 0; /* 2 */ } /** * Show the overflow in IE. * 1. Show the overflow in Edge. */ button, input { /* 1 */ overflow: visible; } /** * Remove the inheritance of text transform in Edge, Firefox, and IE. * 1. Remove the inheritance of text transform in Firefox. */ button, select { /* 1 */ text-transform: none; } /** * Correct the inability to style clickable types in iOS and Safari. */ button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } /** * Remove the inner border and padding in Firefox. */ button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } /** * Restore the focus styles unset by the previous rule. */ button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } /** * Correct the padding in Firefox. */ fieldset { padding: 0.35em 0.75em 0.625em; } /** * 1. Correct the text wrapping in Edge and IE. * 2. Correct the color inheritance from `fieldset` elements in IE. * 3. Remove the padding so developers are not caught out when they zero out * `fieldset` elements in all browsers. */ legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } /** * Add the correct vertical alignment in Chrome, Firefox, and Opera. */ progress { vertical-align: baseline; } /** * Remove the default vertical scrollbar in IE 10+. */ textarea { overflow: auto; } /** * 1. Add the correct box sizing in IE 10. * 2. Remove the padding in IE 10. */ [type="checkbox"], [type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Correct the cursor style of increment and decrement buttons in Chrome. */ [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Correct the odd appearance in Chrome and Safari. * 2. Correct the outline style in Safari. */ [type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /** * Remove the inner padding in Chrome and Safari on macOS. */ [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * 1. Correct the inability to style clickable types in iOS and Safari. * 2. Change font properties to `inherit` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Interactive ========================================================================== */ /* * Add the correct display in Edge, IE 10+, and Firefox. */ details { display: block; } /* * Add the correct display in all browsers. */ summary { display: list-item; } /* Misc ========================================================================== */ /** * Add the correct display in IE 10+. */ template { display: none; } /** * Add the correct display in IE 10. */ [hidden] { display: none; } rustup-1.26.0/www/rustup.css000066400000000000000000000121731441327105200160450ustar00rootroot00000000000000@font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 300; src: local('Fira Sans Light'), url("fonts/FiraSans-Light.woff") format('woff'); } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 400; src: local('Fira Sans'), url("fonts/FiraSans-Regular.woff") format('woff'); } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 500; src: local('Fira Sans Medium'), url("fonts/FiraSans-Medium.woff") format('woff'); } @font-face { font-family: 'Fira Mono'; font-style: normal; font-weight: 400; src: local('Fira Mono'), url("fonts/FiraMono-Regular.ttf") format('truetype'); } @font-face { font-family: 'Work Sans'; font-style: normal; font-weight: 500; src: local('Work Sans Medium'), url("fonts/WorkSans-Medium.ttf") format('truetype'); } body { margin-top: 2em; background-color: white; color: #515151; font-family: "Fira Sans","Helvetica Neue",Helvetica,Arial,sans-serif; font-weight: 300; font-size: 25px; } pre { font-family: "Fira Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-weight: 400; } body#idx #pitch > a { font-weight: 500; line-height: 2em; } a { color: #428bca; text-decoration: none; } a:hover { color: rgb(42, 100, 150); } body#idx > * { margin-left: auto; margin-right: auto; text-align: center; width: 35em; } body#idx > #pitch { width: 35rem; } #pitch em { font-style: normal; font-weight: 400; } body#idx p { margin-top: 2em; margin-bottom: 2em; } body#idx p.other-platforms-help { font-size: 0.6em; } .instructions { background-color: rgb(250, 250, 250); margin-left: auto; margin-right: auto; text-align: center; border-radius: 3px; border: 1px solid rgb(204, 204, 204); box-shadow: 0px 1px 4px 0px rgb(204, 204, 204); } .instructions > * { width: 45rem; margin-left: auto; margin-right: auto; } hr { margin-top: 2em; margin-bottom: 2em; } #platform-instructions-unix > p, #platform-instructions-win32 > p, #platform-instructions-win64 > p, #platform-instructions-default > p, #platform-instructions-unknown > p { width: 40rem; } .rustup-command::before { color: #999; content: " $ "; margin-left: 15px; } .rustup-command { background-color: #515151; color: white; padding: 1rem 1rem 1rem 0; width: 45rem; height: auto; text-align: center; border-radius: 3px 0 0 3px; box-shadow: inset 0px 0px 20px 0px #333333; overflow: hidden; font-size: 0.6em; white-space: nowrap; height: 26px; line-height: 26px; } #platform-instructions-unix div.copy-container, #platform-instructions-win32 div.copy-container, #platform-instructions-win64 div.copy-container, #platform-instructions-default div.copy-container, #platform-instructions-unknown div.copy-container { display: flex; align-items: center; } #platform-instructions-unix button.copy-button, #platform-instructions-win32 button.copy-button, #platform-instructions-win64 button.copy-button, #platform-instructions-default button.copy-button, #platform-instructions-unknown button.copy-button { height: 58px; margin: 0; padding: 0 5px 0 5px; border-radius: 0 3px 3px 0; border-left-width: 0px; border-left-style: solid; } #platform-instructions-unix div.copy-icon, #platform-instructions-win32 div.copy-icon, #platform-instructions-win64 div.copy-icon, #platform-instructions-default div.copy-icon, #platform-instructions-unknown div.copy-icon { position: relative; width: fit-content; height: fit-content; top: 50%; left: 50%; transform: translate(-50%, -50%); } #platform-instructions-unix div.copy-button-text, #platform-instructions-win32 div.copy-button-text, #platform-instructions-win64 div.copy-button-text, #platform-instructions-default div.copy-button-text, #platform-instructions-unknown div.copy-button-text { transition: opacity 0.2s ease-in-out; opacity: 0; font-size: 10px; color: green; width: 41px; height: 15px; position: relative; top: 5px; } #platform-instructions-win32 a.windows-download, #platform-instructions-win64 a.windows-download, #platform-instructions-default a.windows-download, #platform-instructions-unknown a.windows-download { display: block; padding-top: 0.4rem; padding-bottom: 0.6rem; font-family: "Work Sans", "Fira Sans","Helvetica Neue",Helvetica,Arial,sans-serif; font-weight: 500; letter-spacing: 0.1rem; } /* This is the box that prints navigator.platform, navigator.appVersion values */ #platform-instructions-unknown > div:first-of-type { font-size: 16px; line-height: 2rem; } #about { font-size: 16px; line-height: 2em; } #about > img { width: 30px; height: 30px; transform: translateY(11px); } #platform-button { background-color: #515151; color: white; margin-left: auto; margin-right: auto; padding: 1em; } .display-none { display: none; } .display-block { display: block; } .display-inline { display: inline; } rustup-1.26.0/www/rustup.js000066400000000000000000000160011441327105200156630ustar00rootroot00000000000000// IF YOU CHANGE THIS FILE IT MUST BE CHANGED ON BOTH rust-www and rustup.rs var platforms = ["default", "unknown", "win32", "win64", "unix"]; var platform_override = null; var rustup_install_command = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"; function detect_platform() { "use strict"; if (platform_override !== null) { return platforms[platform_override]; } var os = "unknown"; if (navigator.platform == "Linux x86_64") {os = "unix";} if (navigator.platform == "Linux i686") {os = "unix";} if (navigator.platform == "Linux i686 on x86_64") {os = "unix";} if (navigator.platform == "Linux aarch64") {os = "unix";} if (navigator.platform == "Linux armv6l") {os = "unix";} if (navigator.platform == "Linux armv7l") {os = "unix";} if (navigator.platform == "Linux armv8l") {os = "unix";} if (navigator.platform == "Linux ppc64") {os = "unix";} if (navigator.platform == "Linux mips") {os = "unix";} if (navigator.platform == "Linux mips64") {os = "unix";} if (navigator.platform == "Mac") {os = "unix";} if (navigator.platform == "Win32") {os = "win32";} if (navigator.platform == "Win64" || navigator.userAgent.indexOf("WOW64") != -1 || navigator.userAgent.indexOf("Win64") != -1) { os = "win64"; } if (navigator.platform == "FreeBSD x86_64") {os = "unix";} if (navigator.platform == "FreeBSD amd64") {os = "unix";} if (navigator.platform == "NetBSD x86_64") {os = "unix";} if (navigator.platform == "NetBSD amd64") {os = "unix";} if (navigator.platform == "SunOS i86pc") {os = "unix";} // I wish I knew by now, but I don't. Try harder. if (os == "unknown") { if (navigator.appVersion.indexOf("Win")!=-1) {os = "win32";} if (navigator.appVersion.indexOf("Mac")!=-1) {os = "unix";} // rust-www/#692 - FreeBSD epiphany! if (navigator.appVersion.indexOf("FreeBSD")!=-1) {os = "unix";} } // Firefox Quantum likes to hide platform and appVersion but oscpu works if (navigator.oscpu) { if (navigator.oscpu.indexOf("Win32")!=-1) {os = "win32";} if (navigator.oscpu.indexOf("Win64")!=-1) {os = "win64";} if (navigator.oscpu.indexOf("Mac")!=-1) {os = "unix";} if (navigator.oscpu.indexOf("Linux")!=-1) {os = "unix";} if (navigator.oscpu.indexOf("FreeBSD")!=-1) {os = "unix";} if (navigator.oscpu.indexOf("NetBSD")!=-1) {os = "unix";} if (navigator.oscpu.indexOf("SunOS")!=-1) {os = "unix";} } return os; } function vis(elem, value) { var possible = ["block", "inline", "none"]; for (var i = 0; i < possible.length; i++) { if (possible[i] === value) { elem.classList.add("display-" + possible[i]); } else { elem.classList.remove("display-" + possible[i]); } } } function adjust_for_platform() { "use strict"; var platform = detect_platform(); platforms.forEach(function (platform_elem) { var platform_div = document.getElementById("platform-instructions-" + platform_elem); vis(platform_div, "none"); if (platform == platform_elem) { vis(platform_div, "block"); } }); adjust_platform_specific_instrs(platform); } // NB: This has no effect on rustup.rs function adjust_platform_specific_instrs(platform) { var platform_specific = document.getElementsByClassName("platform-specific"); for (var el of platform_specific) { var el_is_not_win = el.className.indexOf("not-win") !== -1; var el_is_inline = el.tagName.toLowerCase() == "span"; var el_visible_style = "block"; if (el_is_inline) { el_visible_style = "inline"; } if (platform == "win64" || platform == "win32") { if (el_is_not_win) { vis(el, "none"); } else { vis(el, el_visible_style); } } else { if (el_is_not_win) { vis(el, el_visible_style); } else { vis(el, "none"); } } } } function cycle_platform() { if (platform_override == null) { platform_override = 0; } else { platform_override = (platform_override + 1) % platforms.length; } adjust_for_platform(); } function set_up_cycle_button() { var cycle_button = document.getElementById("platform-button"); cycle_button.onclick = cycle_platform; var key="test"; var idx=0; var unlocked=false; document.onkeypress = function(event) { if (event.key == "n" && unlocked) { cycle_platform(); } if (event.key == key[idx]) { idx += 1; if (idx == key.length) { vis(cycle_button, "block"); unlocked = true; cycle_platform(); } } else if (event.key == key[0]) { idx = 1; } else { idx = 0; } }; } function go_to_default_platform() { platform_override = 0; adjust_for_platform(); } // NB: This has no effect on rust-lang.org/install.html function set_up_default_platform_buttons() { var defaults_buttons = document.getElementsByClassName('default-platform-button'); for (var i = 0; i < defaults_buttons.length; i++) { defaults_buttons[i].onclick = go_to_default_platform; } } function fill_in_bug_report_values() { var nav_plat = document.getElementById("nav-plat"); var nav_app = document.getElementById("nav-app"); nav_plat.textContent = navigator.platform; nav_app.textContent = navigator.appVersion; } function process_copy_button_click(id) { try { navigator.clipboard.writeText(rustup_install_command).then(() => document.getElementById(id).style.opacity = '1'); setTimeout(() => document.getElementById(id).style.opacity = '0', 3000); } catch (e) { console.log('Hit a snag when copying to clipboard: ', e); } } function handle_copy_button_click(e) { switch (e.id) { case 'copy-button-unix': process_copy_button_click('copy-status-message-unix'); break; case 'copy-button-win32': process_copy_button_click('copy-status-message-win32'); break; case 'copy-button-win64': process_copy_button_click('copy-status-message-win64'); break; case 'copy-button-unknown': process_copy_button_click('copy-status-message-unknown'); break; case 'copy-button-default': process_copy_button_click('copy-status-message-default'); break; } } function set_up_copy_button_clicks() { var buttons = document.querySelectorAll(".copy-button"); buttons.forEach(function (element) { element.addEventListener('click', function() { handle_copy_button_click(element); }); }) } (function () { adjust_for_platform(); set_up_cycle_button(); set_up_default_platform_buttons(); set_up_copy_button_clicks(); fill_in_bug_report_values(); }()); rustup-1.26.0/www/website_config.json000066400000000000000000000007201441327105200176460ustar00rootroot00000000000000{ "headers": { "Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "X-XSS-Protection": "1; mode=block", "Referrer-Policy": "no-referrer, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' https://www.rust-lang.org; font-src 'self'" } }