├── .github ├── actions │ └── check-clean-git-working-tree │ │ └── action.yaml ├── dependabot.yml └── workflows │ ├── main.yml │ ├── publish.yml │ ├── regenerate-target-info.yml │ ├── regenerate-windows-sys.yml │ ├── release-pr.yml │ └── test-rustc-targets.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── clippy.toml ├── dev-tools ├── cc-test │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── NMakefile │ │ ├── aarch64.S │ │ ├── aarch64.asm │ │ ├── armv7.S │ │ ├── bar1.c │ │ ├── bar2.c │ │ ├── baz.cpp │ │ ├── compile_error.c │ │ ├── cuda.cu │ │ ├── dummy.c │ │ ├── expand.c │ │ ├── foo.c │ │ ├── i686.S │ │ ├── i686.asm │ │ ├── include │ │ │ └── foo.h │ │ ├── lib.rs │ │ ├── msvc.c │ │ ├── opt_linkage.c │ │ ├── riscv64gc.S │ │ ├── windows.c │ │ ├── x86_64.S │ │ └── x86_64.asm │ └── tests │ │ ├── all.rs │ │ └── output.rs ├── gen-target-info │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── read.rs │ │ └── target_specs.rs ├── gen-windows-sys-binding │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── windows_sys.list └── wasm32-wasip1-threads-test │ ├── Cargo.toml │ └── src │ └── main.rs ├── src ├── bin │ └── cc-shim.rs ├── command_helpers.rs ├── detect_compiler_family.c ├── flags.rs ├── lib.rs ├── parallel │ ├── async_executor.rs │ ├── job_token.rs │ ├── mod.rs │ └── stderr.rs ├── target.rs ├── target │ ├── apple.rs │ ├── generated.rs │ ├── llvm.rs │ └── parser.rs ├── tempfile.rs ├── tool.rs ├── utilities.rs └── windows │ ├── com.rs │ ├── find_tools.rs │ ├── mod.rs │ ├── registry.rs │ ├── setup_config.rs │ ├── vs_instances.rs │ ├── winapi.rs │ ├── windows_link.rs │ └── windows_sys.rs └── tests ├── archiver.rs ├── cc_env.rs ├── cflags.rs ├── cflags_shell_escaped.rs ├── cxxflags.rs ├── rustflags.rs ├── support └── mod.rs └── test.rs /.github/actions/check-clean-git-working-tree/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Check Clean Git Working Tree" 2 | description: "Check that the git working tree is clean" 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - name: Check clean Git working tree 8 | shell: bash 9 | run: | 10 | status_output=$(git status --porcelain=v1) 11 | if [ -z "$status_output" ]; then 12 | echo "Git working tree is clean." 13 | exit 0 14 | else 15 | echo "dirty Git working tree detected!" 16 | echo "$status_output" 17 | exit 1 18 | fi 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | # Workflow files stored in the 5 | # default location of `.github/workflows` 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | - package-ecosystem: cargo 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | time: "08:00" 14 | open-pull-requests-limit: 10 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | env: 5 | CARGO_INCREMENTAL: 0 6 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 7 | CC_ENABLE_DEBUG_OUTPUT: true 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref || github.event.pull_request.number || github.sha }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | build: 20 | [ 21 | stable, 22 | beta, 23 | nightly, 24 | linux32, 25 | aarch64-macos, 26 | x86_64-macos, 27 | aarch64-ios, 28 | aarch64-ios-sim, 29 | x86_64-ios-sim, 30 | aarch64-ios-macabi, 31 | x86_64-ios-macabi, 32 | win32, 33 | win64, 34 | mingw32, 35 | mingw64, 36 | windows-2022, 37 | ] 38 | cargo_flags: ['', '--release', '--features parallel'] 39 | include: 40 | - build: stable 41 | os: ubuntu-latest 42 | rust: stable 43 | target: x86_64-unknown-linux-gnu 44 | - build: beta 45 | os: ubuntu-latest 46 | rust: beta 47 | target: x86_64-unknown-linux-gnu 48 | - build: nightly 49 | os: ubuntu-latest 50 | rust: nightly 51 | target: x86_64-unknown-linux-gnu 52 | - build: linux32 53 | os: ubuntu-latest 54 | rust: stable 55 | target: i686-unknown-linux-gnu 56 | - build: aarch64-macos 57 | os: macos-14 58 | rust: stable 59 | target: aarch64-apple-darwin 60 | - build: x86_64-macos 61 | os: macos-13 # x86 62 | rust: stable 63 | target: x86_64-apple-darwin 64 | - build: aarch64-ios 65 | os: macos-latest 66 | rust: stable 67 | target: aarch64-apple-ios 68 | no_run: --no-run 69 | - build: aarch64-ios-sim 70 | os: macos-latest 71 | rust: stable 72 | target: aarch64-apple-ios-sim 73 | no_run: --no-run 74 | - build: x86_64-ios-sim 75 | os: macos-13 # x86 76 | rust: stable 77 | target: x86_64-apple-ios # Simulator 78 | no_run: --no-run 79 | - build: aarch64-ios-macabi 80 | os: macos-latest 81 | rust: stable 82 | target: aarch64-apple-ios-macabi 83 | no_run: --no-run # FIXME(madsmtm): Fix running tests 84 | - build: x86_64-ios-macabi 85 | os: macos-13 # x86 86 | rust: stable 87 | target: x86_64-apple-ios-macabi 88 | no_run: --no-run # FIXME(madsmtm): Fix running tests 89 | - build: cross-macos-aarch64 90 | os: ubuntu-latest 91 | rust: stable 92 | target: aarch64-apple-darwin 93 | no_run: --no-run 94 | - build: cross-ios-aarch64 95 | os: ubuntu-latest 96 | rust: stable 97 | target: aarch64-apple-ios 98 | no_run: --no-run 99 | - build: windows-aarch64 100 | os: windows-latest 101 | rust: stable 102 | target: aarch64-pc-windows-msvc 103 | no_run: --no-run 104 | - build: win32 105 | os: windows-2022 106 | rust: stable-i686-msvc 107 | target: i686-pc-windows-msvc 108 | - build: win64 109 | os: windows-latest 110 | rust: stable 111 | target: x86_64-pc-windows-msvc 112 | - build: mingw32 113 | os: windows-2022 114 | rust: stable-i686-gnu 115 | target: i686-pc-windows-gnu 116 | - build: mingw64 117 | os: windows-latest 118 | rust: stable-x86_64-gnu 119 | target: x86_64-pc-windows-gnu 120 | - build: windows-2022 121 | os: windows-2022 122 | rust: stable-x86_64 123 | target: x86_64-pc-windows-msvc 124 | - build: windows-clang 125 | os: windows-2022 126 | rust: stable 127 | target: x86_64-pc-windows-msvc 128 | CC: clang 129 | CXX: clang++ 130 | - build: windows-clang-cl 131 | os: windows-2022 132 | rust: stable 133 | target: x86_64-pc-windows-msvc 134 | CC: clang-cl 135 | CXX: clang-cl 136 | steps: 137 | - uses: actions/checkout@v4 138 | - name: Install Rust (rustup) 139 | run: | 140 | set -euxo pipefail 141 | rustup toolchain install ${{ matrix.rust }} --no-self-update --profile minimal --target ${{ matrix.target }} 142 | rustup default ${{ matrix.rust }} 143 | shell: bash 144 | - name: Install g++-multilib 145 | run: | 146 | set -e 147 | # Remove the ubuntu-toolchain-r/test PPA, which is added by default. 148 | # Some packages were removed, and this is causing the g++multilib 149 | # install to fail. Similar issue: 150 | # https://github.com/scikit-learn/scikit-learn/issues/13928. 151 | sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test 152 | sudo apt-get update 153 | sudo apt-get install g++-multilib 154 | if: matrix.build == 'linux32' 155 | - name: add clang to path 156 | if: startsWith(matrix.build, 'windows-clang') 157 | run: | 158 | echo "C:\msys64\mingw64\bin" >> "$GITHUB_PATH" 159 | echo -e "AR=llvm-ar\nRUSTFLAGS=-Clinker=lld-link\nCC=${CC}\nCXX=${CXX}" >> "$GITHUB_ENV" 160 | shell: bash 161 | env: 162 | CC: ${{ matrix.CC }} 163 | CXX: ${{ matrix.CXX }} 164 | - name: Install llvm tools (for llvm-ar) 165 | if: startsWith(matrix.build, 'cross-macos') || startsWith(matrix.build, 'cross-ios') 166 | run: sudo apt-get install llvm 167 | - name: Download macOS SDK 168 | working-directory: ${{ runner.temp }} 169 | if: startsWith(matrix.build, 'cross-macos') 170 | run: | 171 | wget https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz 172 | tar -xf MacOSX11.3.sdk.tar.xz 173 | echo "SDKROOT=$(pwd)/MacOSX11.3.sdk" >> $GITHUB_ENV 174 | - name: Download iOS SDK 175 | working-directory: ${{ runner.temp }} 176 | if: startsWith(matrix.build, 'cross-ios') 177 | run: | 178 | wget https://github.com/xybp888/iOS-SDKs/releases/download/iOS18.1-SDKs/iPhoneOS18.1.sdk.zip 179 | unzip iPhoneOS18.1.sdk.zip 180 | echo "SDKROOT=$(pwd)/iPhoneOS18.1.sdk" >> $GITHUB_ENV 181 | - name: Set up Apple cross-compilation 182 | if: startsWith(matrix.build, 'cross-macos') || startsWith(matrix.build, 'cross-ios') 183 | run: | 184 | # Test with clang/llvm for now, has better cross-compilation support (GCC requires downloading a different toolchain) 185 | echo "CC=clang" >> $GITHUB_ENV 186 | echo "CXX=clang++" >> $GITHUB_ENV 187 | echo "AR=llvm-ar" >> $GITHUB_ENV 188 | # Link with rust-lld 189 | UPPERCASE_TARGET_NAME=$(echo "${{ matrix.target }}" | tr '[:lower:]-' '[:upper:]_') 190 | echo "CARGO_TARGET_${UPPERCASE_TARGET_NAME}_LINKER=rust-lld" >> $GITHUB_ENV 191 | - name: setup dev environment 192 | uses: ilammy/msvc-dev-cmd@v1 193 | if: startsWith(matrix.build, 'windows-clang') 194 | - run: cargo update 195 | - uses: Swatinem/rust-cache@v2 196 | - run: cargo test ${{ matrix.no_run }} --workspace --target ${{ matrix.target }} ${{ matrix.cargo_flags }} 197 | # check that there are no uncommitted changes to prevent bugs like https://github.com/rust-lang/cc-rs/issues/1411 198 | - name: check clean Git workting tree 199 | uses: ./.github/actions/check-clean-git-working-tree 200 | 201 | # This is separate from the matrix above because there is no prebuilt rust-std component for these targets. 202 | check-build-std: 203 | name: Test build-std 204 | runs-on: macos-latest 205 | strategy: 206 | matrix: 207 | target: 208 | - x86_64h-apple-darwin 209 | # FIXME(madsmtm): needs deployment target 210 | # - armv7s-apple-ios 211 | # FIXME(madsmtm): needs deployment target 212 | # - i386-apple-ios # Simulator 213 | - aarch64-apple-tvos 214 | - aarch64-apple-tvos-sim 215 | - x86_64-apple-tvos # Simulator 216 | - aarch64-apple-watchos 217 | - aarch64-apple-watchos-sim 218 | - x86_64-apple-watchos-sim 219 | # FIXME(madsmtm): needs deployment target 220 | # - arm64_32-apple-watchos 221 | - armv7k-apple-watchos 222 | - aarch64-apple-visionos 223 | - aarch64-apple-visionos-sim 224 | steps: 225 | - uses: actions/checkout@v4 226 | - name: Install Rust (rustup) 227 | run: | 228 | set -euxo pipefail 229 | rustup toolchain install nightly --no-self-update --profile minimal 230 | rustup component add rust-src --toolchain nightly 231 | rustup default nightly 232 | shell: bash 233 | - run: cargo update 234 | - uses: Swatinem/rust-cache@v2 235 | - run: cargo test -Z build-std=std --no-run --workspace --target ${{ matrix.target }} 236 | - run: cargo test -Z build-std=std --no-run --workspace --target ${{ matrix.target }} --release 237 | - run: cargo test -Z build-std=std --no-run --workspace --target ${{ matrix.target }} --features parallel 238 | # check that there are no uncommitted changes to prevent bugs like https://github.com/rust-lang/cc-rs/issues/1411 239 | - name: check clean Git workting tree 240 | uses: ./.github/actions/check-clean-git-working-tree 241 | 242 | check-wasm: 243 | name: Test wasm 244 | runs-on: ubuntu-latest 245 | strategy: 246 | matrix: 247 | target: [wasm32-unknown-unknown] 248 | steps: 249 | - uses: actions/checkout@v4 250 | - name: Install Rust (rustup) 251 | run: | 252 | rustup target add ${{ matrix.target }} 253 | shell: bash 254 | - run: cargo update 255 | - uses: Swatinem/rust-cache@v2 256 | - run: cargo test --no-run --target ${{ matrix.target }} 257 | - run: cargo test --no-run --target ${{ matrix.target }} --release 258 | - run: cargo test --no-run --target ${{ matrix.target }} --features parallel 259 | # check that there are no uncommitted changes to prevent bugs like https://github.com/rust-lang/cc-rs/issues/1411 260 | - name: check clean Git workting tree 261 | uses: ./.github/actions/check-clean-git-working-tree 262 | 263 | test-wasm32-wasip1-thread: 264 | name: Test wasm32-wasip1-thread 265 | runs-on: ubuntu-latest 266 | env: 267 | TARGET: wasm32-wasip1-threads 268 | steps: 269 | - uses: actions/checkout@v4 270 | - name: Install Rust (rustup) 271 | run: | 272 | rustup toolchain install nightly --no-self-update --profile minimal --target $TARGET 273 | 274 | - name: Get latest version of wasi-sdk 275 | env: 276 | REPO: WebAssembly/wasi-sdk 277 | GH_TOKEN: ${{ github.token }} 278 | run: | 279 | set -euxo pipefail 280 | VERSION="$(gh release list --repo $REPO -L 1 --json tagName --jq '.[]|.tagName')" 281 | echo "WASI_TOOLCHAIN_VERSION=$VERSION" >> "$GITHUB_ENV" 282 | 283 | - name: Install wasi-sdk 284 | working-directory: ${{ runner.temp }} 285 | env: 286 | REPO: WebAssembly/wasi-sdk 287 | run: | 288 | set -euxo pipefail 289 | VERSION="$WASI_TOOLCHAIN_VERSION" 290 | FILE="${VERSION}.0-x86_64-linux.deb" 291 | wget "https://github.com/$REPO/releases/download/${VERSION}/${FILE}" 292 | sudo dpkg -i "${FILE}" 293 | WASI_SDK_PATH="/opt/wasi-sdk" 294 | CC="${WASI_SDK_PATH}/bin/clang" 295 | echo "WASI_SDK_PATH=$WASI_SDK_PATH" >> "$GITHUB_ENV" 296 | echo "CC=$CC" >> "$GITHUB_ENV" 297 | 298 | - run: cargo update 299 | - uses: Swatinem/rust-cache@v2 300 | with: 301 | env-vars: "WASI_TOOLCHAIN_VERSION" 302 | cache-all-crates: "true" 303 | 304 | - name: Run tests 305 | run: cargo +nightly build -p $TARGET-test --target $TARGET 306 | 307 | # check that there are no uncommitted changes to prevent bugs like https://github.com/rust-lang/cc-rs/issues/1411 308 | - name: check clean Git workting tree 309 | uses: ./.github/actions/check-clean-git-working-tree 310 | 311 | cuda: 312 | name: Test CUDA support 313 | runs-on: ubuntu-22.04 314 | steps: 315 | - uses: actions/checkout@v4 316 | - name: Install cuda-minimal-build-11-8 317 | working-directory: ${{ runner.temp }} 318 | shell: bash 319 | run: | 320 | # https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=deb_network 321 | wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.0-1_all.deb 322 | sudo dpkg -i cuda-keyring_1.0-1_all.deb 323 | sudo apt-get update 324 | sudo apt-get -y install cuda-minimal-build-11-8 325 | - run: cargo update 326 | - uses: Swatinem/rust-cache@v2 327 | - name: Test 'cudart' feature 328 | shell: bash 329 | run: | 330 | PATH="/usr/local/cuda/bin:$PATH" cargo test --manifest-path dev-tools/cc-test/Cargo.toml --features test_cuda 331 | PATH="/usr/local/cuda/bin:$PATH" CXX=clang++ cargo test --manifest-path dev-tools/cc-test/Cargo.toml --features test_cuda 332 | # check that there are no uncommitted changes to prevent bugs like https://github.com/rust-lang/cc-rs/issues/1411 333 | - name: check clean Git workting tree 334 | uses: ./.github/actions/check-clean-git-working-tree 335 | 336 | msrv: 337 | name: MSRV 338 | runs-on: ${{ matrix.os }} 339 | strategy: 340 | fail-fast: false 341 | matrix: 342 | os: [ubuntu-latest, windows-latest, macos-latest] 343 | env: 344 | MSRV: 1.63.0 345 | steps: 346 | - uses: actions/checkout@v4 347 | - name: Install Rust 348 | run: | 349 | rustup toolchain install $MSRV --no-self-update --profile minimal 350 | rustup toolchain install nightly --no-self-update --profile minimal 351 | rustup default $MSRV 352 | shell: bash 353 | - name: Create Cargo.lock with minimal version 354 | run: cargo +nightly update -Zminimal-versions 355 | - uses: Swatinem/rust-cache@v2 356 | - run: env -u CARGO_REGISTRIES_CRATES_IO_PROTOCOL cargo check --lib -p cc --locked 357 | - run: env -u CARGO_REGISTRIES_CRATES_IO_PROTOCOL cargo check --lib -p cc --locked --all-features 358 | 359 | clippy: 360 | name: Clippy 361 | runs-on: ubuntu-latest 362 | steps: 363 | - uses: actions/checkout@v4 364 | - name: Install Rust 365 | run: | 366 | rustup toolchain install stable --no-self-update --profile minimal --component rustfmt 367 | rustup default stable 368 | shell: bash 369 | - uses: Swatinem/rust-cache@v2 370 | - run: cargo clippy --no-deps 371 | # check that there are no uncommitted changes to prevent bugs like https://github.com/rust-lang/cc-rs/issues/1411 372 | - name: check clean Git workting tree 373 | uses: ./.github/actions/check-clean-git-working-tree 374 | 375 | rustfmt: 376 | name: Rustfmt 377 | runs-on: ubuntu-latest 378 | steps: 379 | - uses: actions/checkout@v4 380 | - name: Install Rust 381 | run: | 382 | rustup toolchain install stable --no-self-update --profile minimal --component rustfmt 383 | rustup default stable 384 | shell: bash 385 | - uses: Swatinem/rust-cache@v2 386 | - run: cargo fmt -- --check 387 | 388 | semver-checks: 389 | runs-on: ubuntu-latest 390 | steps: 391 | - name: Checkout 392 | uses: actions/checkout@v4 393 | - name: Check semver 394 | uses: obi1kenobi/cargo-semver-checks-action@v2 395 | 396 | # Dummy job to have a stable name for the "all tests pass" requirement 397 | tests-pass: 398 | name: Tests pass 399 | needs: 400 | - test 401 | - check-build-std 402 | - check-wasm 403 | - test-wasm32-wasip1-thread 404 | - cuda 405 | - msrv 406 | - clippy 407 | - rustfmt 408 | - semver-checks 409 | if: always() # always run even if dependencies fail 410 | runs-on: ubuntu-latest 411 | steps: 412 | # fail if ANY dependency has failed or cancelled 413 | - if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')" 414 | run: exit 1 415 | - run: exit 0 416 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz-release: 14 | if: github.repository_owner == 'rust-lang' 15 | name: Release-plz release 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - name: Install Rust toolchain 23 | uses: dtolnay/rust-toolchain@stable 24 | - name: Run release-plz 25 | uses: MarcoIeni/release-plz-action@v0.5 26 | with: 27 | command: release 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/regenerate-target-info.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate target info 2 | 3 | on: 4 | workflow_dispatch: # Allow running on-demand 5 | schedule: 6 | - cron: '0 3 * * 4' 7 | push: 8 | branches: 9 | - 'main' 10 | paths: 11 | - 'dev-tools/gen-target-info/**' 12 | 13 | jobs: 14 | regenerate: 15 | if: github.repository_owner == 'rust-lang' 16 | name: Regenerate target info & Open Pull Request if necessary 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | persist-credentials: true 22 | 23 | - name: Generate branch name 24 | run: | 25 | git checkout -b regenerate-target-info-${{ github.run_id }} 26 | 27 | - name: Install rust 28 | # Install both MSRV and current nightly 29 | run: | 30 | rustup toolchain install 1.63 stable nightly --no-self-update --profile minimal 31 | 32 | - name: Create lockfile 33 | run: cargo update 34 | 35 | - uses: Swatinem/rust-cache@v2 36 | with: 37 | cache-all-crates: 'true' 38 | - name: Regenerate target info 39 | run: cargo run -p gen-target-info 40 | 41 | - name: Detect changes 42 | id: changes 43 | run: 44 | # This output boolean tells us if the dependencies have actually changed 45 | echo "count=$(git status --porcelain=v1 | wc -l)" >> $GITHUB_OUTPUT 46 | 47 | - name: Commit and push changes 48 | # Only push if changes exist 49 | if: steps.changes.outputs.count > 0 50 | run: | 51 | git config user.name github-actions 52 | git config user.email github-actions@github.com 53 | git commit -am "Regenerate target info" 54 | git push origin HEAD 55 | 56 | - name: Open pull request if needed 57 | if: steps.changes.outputs.count > 0 58 | env: 59 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | run: | 61 | gh pr create --base main --title "Update src/target/generated.rs" --body "Automatically regenerated in CI" --head $(git branch --show-current) 62 | -------------------------------------------------------------------------------- /.github/workflows/regenerate-windows-sys.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate windows sys bindings 2 | 3 | on: 4 | workflow_dispatch: # Allow running on-demand 5 | schedule: 6 | - cron: '0 3 * * 4' 7 | push: 8 | branches: 9 | - 'main' 10 | paths: 11 | - 'dev-tools/gen-windows-sys-binding/**' 12 | 13 | jobs: 14 | regenerate: 15 | if: github.repository_owner == 'rust-lang' 16 | name: Regenerate windows sys bindings & Open Pull Request if necessary 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | persist-credentials: true 22 | 23 | - name: Generate branch name 24 | run: | 25 | git checkout -b regenerate-windows-sys-${{ github.run_id }} 26 | 27 | - name: Create lockfile 28 | run: cargo update 29 | 30 | - uses: Swatinem/rust-cache@v2 31 | with: 32 | cache-all-crates: 'true' 33 | 34 | - name: Regenerate windows sys bindings 35 | run: cargo run -p gen-windows-sys-binding 36 | 37 | - name: Detect changes 38 | id: changes 39 | run: 40 | # This output boolean tells us if the dependencies have actually changed 41 | echo "count=$(git status --porcelain=v1 | wc -l)" >> $GITHUB_OUTPUT 42 | 43 | - name: Commit and push changes 44 | # Only push if changes exist 45 | if: steps.changes.outputs.count > 0 46 | run: | 47 | git config user.name github-actions 48 | git config user.email github-actions@github.com 49 | git commit -am "Regenerate windows sys bindings" 50 | git push origin HEAD 51 | 52 | - name: Open pull request if needed 53 | if: steps.changes.outputs.count > 0 54 | env: 55 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | run: | 57 | gh pr create --base main --title "Regenerate windows sys bindings" --body "Automatically regenerated in CI" --head $(git branch --show-current) 58 | -------------------------------------------------------------------------------- /.github/workflows/release-pr.yml: -------------------------------------------------------------------------------- 1 | name: Create release PR 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | workflow_dispatch: # Allow running on-demand 9 | schedule: 10 | - cron: "0 3 * * 5" 11 | 12 | jobs: 13 | release-plz-pr: 14 | name: Release-plz PR 15 | runs-on: ubuntu-latest 16 | concurrency: 17 | group: release-plz-${{ github.ref }} 18 | cancel-in-progress: false 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | - name: Install Rust toolchain 25 | uses: dtolnay/rust-toolchain@stable 26 | - name: Run release-plz 27 | uses: MarcoIeni/release-plz-action@v0.5 28 | with: 29 | command: release-pr 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/test-rustc-targets.yml: -------------------------------------------------------------------------------- 1 | name: Test nightly `rustc` targets and add issue comment if changed 2 | 3 | on: 4 | workflow_dispatch: # Allow running on-demand 5 | schedule: 6 | - cron: '0 3 * * 1,4' # Every Monday and Thursdag 7 | push: 8 | branches: 9 | - 'main' 10 | paths: 11 | - 'src/target/**' 12 | 13 | jobs: 14 | regenerate: 15 | if: github.repository_owner == 'rust-lang' 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | persist-credentials: true 21 | 22 | - name: Install current nightly Rust 23 | run: | 24 | rustup toolchain install nightly --no-self-update --profile minimal 25 | rustup default nightly 26 | - run: cargo update 27 | - uses: Swatinem/rust-cache@v2 28 | 29 | - name: Test with `RUSTFLAGS=--cfg=rustc_target_test cargo test --lib` 30 | id: test 31 | continue-on-error: true # We want to open an issue if it fails 32 | run: | 33 | set -o pipefail 34 | cargo test --lib 2>&1 | tee test_output.txt 35 | env: 36 | RUSTFLAGS: --cfg=rustc_target_test 37 | 38 | # Added to https://github.com/rust-lang/cc-rs/issues/1426 39 | - name: Add issue comment if test failed 40 | if: steps.test.outcome == 'failure' 41 | env: 42 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | run: | 44 | gh issue comment 1426 --body " 45 | Failed parsing \`rustc\` target on \`$(rustc --version)\`. 46 | 47 | Test output: 48 | 49 | \`\`\` 50 | $(cat test_output.txt) 51 | \`\`\` 52 | " 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cc" 3 | version = "1.2.26" 4 | authors = ["Alex Crichton "] 5 | license = "MIT OR Apache-2.0" 6 | repository = "https://github.com/rust-lang/cc-rs" 7 | homepage = "https://github.com/rust-lang/cc-rs" 8 | documentation = "https://docs.rs/cc" 9 | description = """ 10 | A build-time dependency for Cargo build scripts to assist in invoking the native 11 | C compiler to compile native C code into a static archive to be linked into Rust 12 | code. 13 | """ 14 | keywords = ["build-dependencies"] 15 | readme = "README.md" 16 | categories = ["development-tools::build-utils"] 17 | # The binary target is only used by tests. 18 | exclude = ["/.github", "tests", "src/bin"] 19 | edition = "2018" 20 | rust-version = "1.63" 21 | 22 | [dependencies] 23 | jobserver = { version = "0.1.30", default-features = false, optional = true } 24 | shlex = "1.3.0" 25 | 26 | [target.'cfg(unix)'.dependencies] 27 | # Don't turn on the feature "std" for this, see https://github.com/rust-lang/cargo/issues/4866 28 | # which is still an issue with `resolver = "1"`. 29 | libc = { version = "0.2.62", default-features = false, optional = true } 30 | 31 | [features] 32 | parallel = ["dep:libc", "dep:jobserver"] 33 | # This is a placeholder feature for people who incorrectly used `cc` with `features = ["jobserver"]` 34 | # so that they aren't broken. This has never enabled `parallel`, so we won't do that. 35 | jobserver = [] 36 | 37 | [dev-dependencies] 38 | tempfile = "3" 39 | 40 | [workspace] 41 | members = [ 42 | "dev-tools/cc-test", 43 | "dev-tools/gen-target-info", 44 | "dev-tools/gen-windows-sys-binding", 45 | "dev-tools/wasm32-wasip1-threads-test", 46 | ] 47 | 48 | [patch.crates-io] 49 | cc = { path = "." } 50 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Alex Crichton 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cc-rs 2 | 3 | A library for [Cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) 4 | to compile a set of C/C++/assembly/CUDA files into a static archive for Cargo 5 | to link into the crate being built. This crate does not compile code itself; 6 | it calls out to the default compiler for the platform. This crate will 7 | automatically detect situations such as cross compilation and 8 | various environment variables and will build code appropriately. 9 | 10 | Refer to the [documentation](https://docs.rs/cc) for detailed usage instructions. 11 | 12 | ## License 13 | 14 | This project is licensed under either of 15 | 16 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 17 | https://www.apache.org/licenses/LICENSE-2.0) 18 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 19 | https://opensource.org/licenses/MIT) 20 | 21 | at your option. 22 | 23 | ### Contribution 24 | 25 | Unless you explicitly state otherwise, any contribution intentionally submitted 26 | for inclusion in cc-rs by you, as defined in the Apache-2.0 license, shall be 27 | dual licensed as above, without any additional terms or conditions. 28 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-methods = [ 2 | { path = "std::env::var_os", reason = "Please use Build::getenv" }, 3 | { path = "std::env::var", reason = "Please use Build::getenv" }, 4 | ] 5 | doc-valid-idents = ["AppleClang", "OpenBSD", ".."] 6 | -------------------------------------------------------------------------------- /dev-tools/cc-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cc-test" 3 | version = "0.1.0" 4 | authors = ["Alex Crichton "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [lib] 9 | name = "cc_test" 10 | doctest = false 11 | test = false 12 | 13 | [build-dependencies] 14 | cc = { path = "../.." } 15 | which = "^4.0" 16 | 17 | [features] 18 | parallel = ["cc/parallel"] 19 | test_cuda = [] 20 | -------------------------------------------------------------------------------- /dev-tools/cc-test/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::disallowed_methods)] 2 | 3 | use std::env; 4 | use std::fs; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::Command; 7 | 8 | fn main() { 9 | // if we are being executed from a `fork_run_action` call (i.e. this is a 10 | // "fork"), perform the requested action and then return. 11 | if run_action_if_forked() { 12 | return; 13 | } 14 | 15 | let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 16 | fs::remove_dir_all(&out).unwrap(); 17 | fs::create_dir(&out).unwrap(); 18 | 19 | // The following are builds where we want to capture the output (i.e. stdout and 20 | // stderr). We do that by re-running _this_ executable and passing in the 21 | // action as the first argument. 22 | run_forked_capture_output(&out, "metadata-on"); 23 | run_forked_capture_output(&out, "metadata-off"); 24 | 25 | run_forked_capture_output(&out, "warnings-off"); 26 | if cc::Build::new().get_compiler().is_like_msvc() { 27 | // MSVC doesn't output warnings to stderr, so we can't capture them. 28 | // the test will use this env var to know whether to run the test. 29 | println!("cargo:rustc-env=TEST_WARNINGS_ON=0"); 30 | } else { 31 | println!("cargo:rustc-env=TEST_WARNINGS_ON=1"); 32 | run_forked_capture_output(&out, "warnings-on"); 33 | } 34 | 35 | let mut build = cc::Build::new(); 36 | build 37 | .file("src/foo.c") 38 | .flag_if_supported("-Wall") 39 | .flag_if_supported("-Wfoo-bar-this-flag-does-not-exist") 40 | .define("FOO", None) 41 | .define("BAR", "1") 42 | .compile("foo"); 43 | 44 | let compiler = build.get_compiler(); 45 | 46 | cc::Build::new() 47 | .file("src/bar1.c") 48 | .file("src/bar2.c") 49 | .include("src/include") 50 | .compile("bar"); 51 | 52 | let target = std::env::var("TARGET").unwrap(); 53 | let arch = match target.split('-').next().unwrap() { 54 | "arm64_32" => "aarch64", 55 | "armv7k" => "armv7", 56 | "armv7s" => "armv7", 57 | "i386" => "i686", 58 | "x86_64h" => "x86_64", 59 | arch => arch, 60 | }; 61 | let file = format!( 62 | "src/{}.{}", 63 | arch, 64 | if target.contains("msvc") { "asm" } else { "S" } 65 | ); 66 | cc::Build::new().file(file).compile("asm"); 67 | 68 | cc::Build::new() 69 | .file("src/baz.cpp") 70 | .cpp(true) 71 | .compile("baz"); 72 | 73 | if env::var("CARGO_FEATURE_TEST_CUDA").is_ok() { 74 | // Detect if there is CUDA compiler and engage "cuda" feature. 75 | let nvcc = match env::var("NVCC") { 76 | Ok(var) => which::which(var), 77 | Err(_) => which::which("nvcc"), 78 | }; 79 | if nvcc.is_ok() { 80 | cc::Build::new() 81 | .cuda(true) 82 | .cudart("static") 83 | .file("src/cuda.cu") 84 | .compile("libcuda.a"); 85 | 86 | // Communicate [cfg(feature = "cuda")] to test/all.rs. 87 | println!("cargo:rustc-cfg=feature=\"cuda\""); 88 | } 89 | } 90 | 91 | if target.contains("windows") { 92 | cc::Build::new().file("src/windows.c").compile("windows"); 93 | } 94 | 95 | if target.contains("msvc") { 96 | let cc_frontend = if compiler.is_like_msvc() { 97 | "MSVC" 98 | } else if compiler.is_like_clang() { 99 | "CLANG" 100 | } else { 101 | unimplemented!("Unknown compiler that targets msvc but isn't clang-like or msvc-like") 102 | }; 103 | 104 | // Test that the `windows_registry` module will set PATH by looking for 105 | // nmake which runs vanilla cl, and then also test it after we remove all 106 | // the relevant env vars from our own process. 107 | let out = out.join("tmp"); 108 | fs::create_dir(&out).unwrap(); 109 | println!("nmake 1"); 110 | let status = cc::windows_registry::find(&target, "nmake.exe") 111 | .unwrap() 112 | .env_remove("MAKEFLAGS") 113 | .arg("/fsrc/NMakefile") 114 | .env("OUT_DIR", &out) 115 | .env("CC_FRONTEND", cc_frontend) 116 | .status() 117 | .unwrap(); 118 | assert!(status.success()); 119 | 120 | fs::remove_dir_all(&out).unwrap(); 121 | fs::create_dir(&out).unwrap(); 122 | 123 | // windows registry won't find clang in path 124 | if !compiler.path().to_string_lossy().starts_with("clang") { 125 | env::remove_var("PATH"); 126 | } 127 | env::remove_var("VCINSTALLDIR"); 128 | env::remove_var("INCLUDE"); 129 | env::remove_var("LIB"); 130 | println!("nmake 2"); 131 | let status = cc::windows_registry::find(&target, "nmake.exe") 132 | .unwrap() 133 | .env_remove("MAKEFLAGS") 134 | .arg("/fsrc/NMakefile") 135 | .env("OUT_DIR", &out) 136 | .env("CC_FRONTEND", cc_frontend) 137 | .status() 138 | .unwrap(); 139 | assert!(status.success()); 140 | println!("cargo:rustc-link-lib=msvc"); 141 | println!("cargo:rustc-link-search={}", out.display()); 142 | 143 | // Test that the `windows_registry` module detects if we're in a "spectre 144 | // mode" VS environment. 145 | fn has_spectre(target: &str) -> bool { 146 | cc::windows_registry::find_tool(target, "cl.exe") 147 | .unwrap() 148 | .env() 149 | .iter() 150 | .any(|(k, v)| (k == "LIB") && v.to_str().unwrap().contains(r"\lib\spectre\")) 151 | } 152 | 153 | std::env::set_var("VSCMD_ARG_VCVARS_SPECTRE", "spectre"); 154 | assert!( 155 | has_spectre(&target), 156 | "LIB should use spectre-mitigated libs when VSCMD_ARG_VCVARS_SPECTRE is set" 157 | ); 158 | 159 | std::env::remove_var("VSCMD_ARG_VCVARS_SPECTRE"); 160 | assert!( 161 | !has_spectre(&target), 162 | "LIB should not use spectre-mitigated libs when VSCMD_ARG_VCVARS_SPECTRE is not set" 163 | ); 164 | } 165 | 166 | // This tests whether we can build a library but not link it to the main 167 | // crate. The test module will do its own linking. 168 | cc::Build::new() 169 | .cargo_metadata(false) 170 | .file("src/opt_linkage.c") 171 | .compile("OptLinkage"); 172 | 173 | let out = cc::Build::new().file("src/expand.c").expand(); 174 | let out = String::from_utf8(out).unwrap(); 175 | assert!(out.contains("hello world")); 176 | } 177 | 178 | #[track_caller] 179 | fn run_forked_capture_output(out: &Path, action: &str) { 180 | let program = env::current_exe().unwrap(); 181 | let output = Command::new(program).arg(action).output().unwrap(); 182 | assert!(output.status.success(), "output: {:#?}", output); 183 | // we've captured the output and now we write it to a dedicated directory in the 184 | // build output so our tests can access the output. 185 | let action_dir = out.join(action); 186 | fs::create_dir_all(&action_dir).unwrap(); 187 | fs::write(action_dir.join("stdout"), output.stdout).unwrap(); 188 | fs::write(action_dir.join("stderr"), output.stderr).unwrap(); 189 | } 190 | 191 | fn run_action_if_forked() -> bool { 192 | let mut args = env::args(); 193 | let _program = args.next().unwrap(); 194 | let action = args.next(); 195 | match action.as_deref() { 196 | Some("metadata-on") => build_cargo_metadata(true), 197 | Some("metadata-off") => build_cargo_metadata(false), 198 | Some("warnings-on") => build_cargo_warnings(true), 199 | Some("warnings-off") => build_cargo_warnings(false), 200 | // No action requested, we're being called from cargo. Proceed with build. 201 | _ => return false, 202 | } 203 | true 204 | } 205 | 206 | fn disable_debug_output() { 207 | // That env would break tests for warning/debug output, 208 | // and it is set in the CI, to make debugging CI failure easier. 209 | std::env::remove_var("CC_ENABLE_DEBUG_OUTPUT"); 210 | } 211 | 212 | fn build_cargo_warnings(warnings: bool) { 213 | disable_debug_output(); 214 | 215 | cc::Build::new() 216 | .cargo_metadata(false) 217 | .cargo_warnings(warnings) 218 | .file("src/compile_error.c") 219 | .try_compile("compile_error") 220 | .unwrap_err(); 221 | } 222 | 223 | fn build_cargo_metadata(metadata: bool) { 224 | disable_debug_output(); 225 | 226 | cc::Build::new() 227 | .cargo_metadata(metadata) 228 | .file("src/dummy.c") 229 | .try_compile("dummy") 230 | .unwrap(); 231 | } 232 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/NMakefile: -------------------------------------------------------------------------------- 1 | all: $(OUT_DIR)/msvc.lib $(OUT_DIR)/msvc.exe 2 | 3 | !IF "$(CC_FRONTEND)" == "MSVC" 4 | EXTRA_CFLAGS=-nologo 5 | CFLAG_OUTPUT=-Fo 6 | !ELSE 7 | CFLAG_OUTPUT=-o 8 | !ENDIF 9 | 10 | $(OUT_DIR)/msvc.lib: $(OUT_DIR)/msvc.o 11 | lib -nologo -out:$(OUT_DIR)/msvc.lib $(OUT_DIR)/msvc.o 12 | 13 | $(OUT_DIR)/msvc.o: src/msvc.c 14 | $(CC) $(EXTRA_CFLAGS) -c $(CFLAG_OUTPUT)$@ src/msvc.c -MD 15 | 16 | $(OUT_DIR)/msvc.exe: $(OUT_DIR)/msvc2.o 17 | $(CC) $(EXTRA_CFLAGS) $(CFLAG_OUTPUT)$@ $(OUT_DIR)/msvc2.o -Fe:$(OUT_DIR)/msvc.exe 18 | 19 | $(OUT_DIR)/msvc2.o: src/msvc.c 20 | $(CC) $(EXTRA_CFLAGS) -c $(CFLAG_OUTPUT)$@ src/msvc.c -DMAIN -MD 21 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/aarch64.S: -------------------------------------------------------------------------------- 1 | .globl asm 2 | asm: 3 | mov w0, 7 4 | ret 5 | 6 | .globl _asm 7 | _asm: 8 | mov w0, 7 9 | ret 10 | 11 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/aarch64.asm: -------------------------------------------------------------------------------- 1 | AREA |.text|, CODE, READONLY 2 | GLOBAL |asm| 3 | ALIGN 4 4 | |asm| PROC 5 | mov w0, #7 6 | ret 7 | ENDP 8 | END -------------------------------------------------------------------------------- /dev-tools/cc-test/src/armv7.S: -------------------------------------------------------------------------------- 1 | .globl asm 2 | .balign 4 3 | asm: 4 | mov r0, #7 5 | bx lr 6 | 7 | .globl _asm 8 | .balign 4 9 | _asm: 10 | mov r0, #7 11 | bx lr 12 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/bar1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "foo.h" 3 | 4 | int32_t bar1() { 5 | return 5; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/bar2.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int32_t bar2() { 4 | return 6; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/baz.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" int32_t 4 | baz() { 5 | int *a = new int(8); 6 | int b = *a; 7 | delete a; 8 | return b; 9 | } 10 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/compile_error.c: -------------------------------------------------------------------------------- 1 | #error "if you see this, cargo_warnings(false) didn't do its job" 2 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/cuda.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ void kernel() {} 4 | 5 | extern "C" void cuda_kernel() { kernel<<<1, 1>>>(); } 6 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/dummy.c: -------------------------------------------------------------------------------- 1 | /* just an empty file */ 2 | 3 | void dummy(void) {} 4 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/expand.c: -------------------------------------------------------------------------------- 1 | #define HELLO hello 2 | #define WORLD world 3 | 4 | HELLO WORLD 5 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/foo.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef FOO 4 | #if BAR == 1 5 | int32_t foo() { 6 | return 4; 7 | } 8 | #endif 9 | #endif 10 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/i686.S: -------------------------------------------------------------------------------- 1 | .globl asm 2 | asm: 3 | mov $7, %eax 4 | ret 5 | 6 | .globl _asm 7 | _asm: 8 | mov $7, %eax 9 | ret 10 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/i686.asm: -------------------------------------------------------------------------------- 1 | .586 2 | .MODEL FLAT, C 3 | .CODE 4 | 5 | asm PROC 6 | MOV EAX, 7 7 | RET 8 | asm ENDP 9 | END 10 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/include/foo.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-lang/cc-rs/43ccae940d17ae4f635ce2d20bc91e1f760e02b8/dev-tools/cc-test/src/include/foo.h -------------------------------------------------------------------------------- /dev-tools/cc-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | pub fn foo() -> i32; 3 | 4 | pub fn bar1() -> i32; 5 | pub fn bar2() -> i32; 6 | 7 | pub fn asm() -> i32; 8 | 9 | pub fn baz() -> i32; 10 | 11 | #[cfg(windows)] 12 | pub fn windows(); 13 | 14 | #[cfg(target_env = "msvc")] 15 | pub fn msvc(); 16 | } 17 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/msvc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void msvc() {} 4 | 5 | #ifdef MAIN 6 | int main() {} 7 | #endif 8 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/opt_linkage.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int32_t answer() { 4 | return 42; 5 | } 6 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/riscv64gc.S: -------------------------------------------------------------------------------- 1 | .globl asm 2 | asm: 3 | li a0, 7 4 | ret 5 | 6 | .globl _asm 7 | _asm: 8 | li a0, 7 9 | ret 10 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/windows.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void windows() {} 4 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/x86_64.S: -------------------------------------------------------------------------------- 1 | .globl asm 2 | asm: 3 | mov $7, %eax 4 | ret 5 | 6 | .globl _asm 7 | _asm: 8 | mov $7, %eax 9 | ret 10 | -------------------------------------------------------------------------------- /dev-tools/cc-test/src/x86_64.asm: -------------------------------------------------------------------------------- 1 | _TEXT SEGMENT 2 | 3 | asm PROC 4 | MOV EAX, 7 5 | RET 6 | asm ENDP 7 | 8 | END 9 | -------------------------------------------------------------------------------- /dev-tools/cc-test/tests/all.rs: -------------------------------------------------------------------------------- 1 | use cc_test::*; 2 | 3 | #[link(name = "OptLinkage", kind = "static")] 4 | extern "C" { 5 | fn answer() -> i32; 6 | } 7 | 8 | #[test] 9 | fn foo_here() { 10 | unsafe { 11 | assert_eq!(foo(), 4); 12 | } 13 | } 14 | 15 | #[test] 16 | fn bar_here() { 17 | unsafe { 18 | assert_eq!(bar1(), 5); 19 | assert_eq!(bar2(), 6); 20 | } 21 | } 22 | 23 | #[test] 24 | fn asm_here() { 25 | unsafe { 26 | assert_eq!(asm(), 7); 27 | } 28 | } 29 | 30 | #[test] 31 | fn baz_here() { 32 | unsafe { 33 | assert_eq!(baz(), 8); 34 | } 35 | } 36 | 37 | #[test] 38 | #[cfg(windows)] 39 | fn windows_here() { 40 | unsafe { 41 | windows(); 42 | } 43 | } 44 | 45 | #[test] 46 | #[cfg(target_env = "msvc")] 47 | fn msvc_here() { 48 | unsafe { 49 | msvc(); 50 | } 51 | } 52 | 53 | #[test] 54 | fn opt_linkage() { 55 | unsafe { 56 | assert_eq!(answer(), 42); 57 | } 58 | } 59 | 60 | #[cfg(feature = "test_cuda")] 61 | #[test] 62 | fn cuda_here() { 63 | extern "C" { 64 | fn cuda_kernel(); 65 | } 66 | unsafe { 67 | cuda_kernel(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /dev-tools/cc-test/tests/output.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | #[test] 5 | fn cargo_warnings_on() { 6 | if env!("TEST_WARNINGS_ON") == "0" { 7 | // in some cases we don't catch compiler warnings and turn them into cargo 8 | // instructions. 9 | return; 10 | } 11 | let (stdout, stderr) = load_output("warnings-on"); 12 | assert!(stderr.is_empty()); 13 | assert!(stdout.contains("cargo:warning=")); 14 | } 15 | 16 | #[test] 17 | fn cargo_warnings_off() { 18 | let (stdout, stderr) = load_output("warnings-off"); 19 | assert!(stderr.is_empty()); 20 | assert!(!stdout.contains("cargo:warning=")); 21 | } 22 | 23 | #[test] 24 | fn cargo_metadata_on() { 25 | let (stdout, stderr) = load_output("metadata-on"); 26 | assert!(stderr.is_empty()); 27 | assert!(stdout.contains("cargo:rustc-link-lib=")); 28 | assert!(stdout.contains("cargo:rustc-link-search=")); 29 | } 30 | 31 | #[test] 32 | fn cargo_metadata_off() { 33 | let (stdout, stderr) = load_output("metadata-off"); 34 | assert!(stderr.is_empty()); 35 | 36 | // most of the instructions aren't currently used 37 | const INSTRUCTIONS: &[&str] = &[ 38 | "cargo:rerun-if-changed=", 39 | "cargo:rerun-if-env-changed=", 40 | "cargo:rustc-cdylib-link-arg=", 41 | "cargo:rustc-cfg=", 42 | "cargo:rustc-env=", 43 | "cargo:rustc-flags=", 44 | "cargo:rustc-link-arg-benches=", 45 | "cargo:rustc-link-arg-bin=", 46 | "cargo:rustc-link-arg-bins=", 47 | "cargo:rustc-link-arg-examples=", 48 | "cargo:rustc-link-arg-tests=", 49 | "cargo:rustc-link-arg=", 50 | "cargo:rustc-link-lib=", 51 | "cargo:rustc-link-search=", 52 | ]; 53 | for instr in INSTRUCTIONS { 54 | assert!(!stdout.contains(instr), "instruction present: {}", instr); 55 | } 56 | } 57 | 58 | #[track_caller] 59 | fn load_output(action: &str) -> (String, String) { 60 | // these files are written by the `run_forked_capture_output` function in the 61 | // build script. 62 | let action_dir = PathBuf::from(env!("OUT_DIR")).join(action); 63 | let stdout = fs::read_to_string(action_dir.join("stdout")).unwrap(); 64 | let stderr = fs::read_to_string(action_dir.join("stderr")).unwrap(); 65 | println!("compile stdout: {:?}", stdout); 66 | println!("compile stderr: {:?}", stderr); 67 | (stdout, stderr) 68 | } 69 | -------------------------------------------------------------------------------- /dev-tools/gen-target-info/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gen-target-info" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | serde = { version = "1.0.163", features = ["derive"] } 9 | serde_json = "1.0.107" 10 | -------------------------------------------------------------------------------- /dev-tools/gen-target-info/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod target_specs; 2 | pub use target_specs::*; 3 | 4 | mod read; 5 | pub use read::*; 6 | -------------------------------------------------------------------------------- /dev-tools/gen-target-info/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write as _; 2 | use std::{fs::File, io::BufRead}; 3 | 4 | use gen_target_info::{ 5 | get_target_spec_from_msrv, get_target_specs_from_json, get_targets_msrv, RustcTargetSpecs, 6 | }; 7 | 8 | const PRELUDE: &str = r#"//! This file is generated code. Please edit the generator in 9 | //! dev-tools/gen-target-info if you need to make changes, or see 10 | //! src/target/llvm.rs if you need to configure a specific LLVM triple. 11 | 12 | "#; 13 | 14 | fn generate_target_mapping(f: &mut File, target_specs: &RustcTargetSpecs) -> std::io::Result<()> { 15 | writeln!(f, "#[rustfmt::skip]")?; 16 | writeln!(f, "pub(crate) const LLVM_TARGETS: &[(&str, &str)] = &[")?; 17 | 18 | for (target_name, spec) in &target_specs.0 { 19 | writeln!(f, " ({target_name:?}, {:?}),", spec.llvm_target)?; 20 | } 21 | 22 | writeln!(f, "];")?; 23 | 24 | Ok(()) 25 | } 26 | 27 | fn main() { 28 | // Primarily use information from nightly. 29 | let mut target_specs = get_target_specs_from_json(std::env::var("RUSTC").ok()); 30 | // Next, read from MSRV to support old, removed targets. 31 | if std::env::var("CC_RS_MSRV") 32 | .unwrap_or("1".to_string()) 33 | .parse::() 34 | .unwrap() 35 | != 0 36 | { 37 | for target_name in get_targets_msrv().lines() { 38 | let target_name = target_name.unwrap(); 39 | let target_name = target_name.trim(); 40 | target_specs 41 | .0 42 | .entry(target_name.to_string()) 43 | .or_insert_with(|| get_target_spec_from_msrv(target_name)); 44 | } 45 | } 46 | 47 | // Open file to write to 48 | let manifest_dir = env!("CARGO_MANIFEST_DIR"); 49 | 50 | let path = format!("{manifest_dir}/../../src/target/generated.rs"); 51 | let mut f = File::create(path).expect("failed to create src/target/generated.rs"); 52 | 53 | f.write_all(PRELUDE.as_bytes()).unwrap(); 54 | 55 | // Start generating 56 | generate_target_mapping(&mut f, &target_specs).unwrap(); 57 | 58 | // Flush the data onto disk 59 | f.flush().unwrap(); 60 | } 61 | -------------------------------------------------------------------------------- /dev-tools/gen-target-info/src/read.rs: -------------------------------------------------------------------------------- 1 | use std::process; 2 | 3 | use crate::{RustcTargetSpecs, TargetSpec}; 4 | 5 | pub fn get_targets_msrv() -> Vec { 6 | let mut cmd = process::Command::new("rustc"); 7 | cmd.args(["+1.63", "--print", "target-list"]); 8 | cmd.stdout(process::Stdio::piped()); 9 | cmd.stderr(process::Stdio::inherit()); 10 | 11 | let process::Output { status, stdout, .. } = cmd.output().unwrap(); 12 | 13 | if !status.success() { 14 | panic!("{:?} failed with non-zero exit status: {}", cmd, status) 15 | } 16 | 17 | stdout 18 | } 19 | 20 | pub fn get_target_spec_from_msrv(target: &str) -> TargetSpec { 21 | let mut cmd = process::Command::new("rustc"); 22 | cmd.args([ 23 | "+1.63", 24 | "-Zunstable-options", 25 | "--print", 26 | "target-spec-json", 27 | "--target", 28 | target, 29 | ]); 30 | cmd.env("RUSTC_BOOTSTRAP", "1"); 31 | cmd.stdout(process::Stdio::piped()); 32 | cmd.stderr(process::Stdio::inherit()); 33 | 34 | let process::Output { status, stdout, .. } = cmd.output().unwrap(); 35 | 36 | if !status.success() { 37 | panic!("{:?} failed with non-zero exit status: {}", cmd, status) 38 | } 39 | 40 | serde_json::from_slice(&stdout).unwrap() 41 | } 42 | 43 | pub fn get_target_specs_from_json(rustc: Option) -> RustcTargetSpecs { 44 | let mut cmd = process::Command::new(rustc.clone().unwrap_or("rustc".into())); 45 | 46 | if rustc.is_none() { 47 | cmd.arg("+nightly"); 48 | } 49 | 50 | cmd.args(["-Zunstable-options", "--print", "all-target-specs-json"]); 51 | cmd.stdout(process::Stdio::piped()); 52 | cmd.stderr(process::Stdio::inherit()); 53 | 54 | let process::Output { status, stdout, .. } = cmd.output().unwrap(); 55 | 56 | if !status.success() { 57 | panic!("{:?} failed with non-zero exit status: {}", cmd, status) 58 | } 59 | 60 | serde_json::from_slice(&stdout).unwrap() 61 | } 62 | -------------------------------------------------------------------------------- /dev-tools/gen-target-info/src/target_specs.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::collections::BTreeMap; 3 | 4 | #[derive(Debug, Deserialize)] 5 | #[serde(transparent)] 6 | pub struct PreLinkArgs( 7 | /// First field in the linker name, 8 | /// second field is the args. 9 | pub BTreeMap>, 10 | ); 11 | 12 | #[derive(Debug, Deserialize)] 13 | #[serde(rename_all(deserialize = "kebab-case"))] 14 | pub struct TargetSpec { 15 | pub arch: String, 16 | pub llvm_target: String, 17 | /// link env to remove, mostly for apple 18 | pub link_env_remove: Option>, 19 | /// link env to set, mostly for apple, e.g. `ZERO_AR_DATE=1` 20 | pub link_env: Option>, 21 | pub os: Option, 22 | /// `apple`, `pc` 23 | pub vendor: Option, 24 | pub env: Option, 25 | pub abi: Option, 26 | pub pre_link_args: Option, 27 | } 28 | 29 | #[derive(Debug, Deserialize)] 30 | #[serde(transparent)] 31 | pub struct RustcTargetSpecs( 32 | /// First field in the tuple is the rustc target 33 | pub BTreeMap, 34 | ); 35 | -------------------------------------------------------------------------------- /dev-tools/gen-windows-sys-binding/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gen-windows-sys-binding" 3 | version = "0.0.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | windows-bindgen = "0.61" 9 | tempfile = "3" 10 | regex = "1" 11 | -------------------------------------------------------------------------------- /dev-tools/gen-windows-sys-binding/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Adapted from 2 | //! https://github.com/rust-lang/rust/blob/master/src/tools/generate-windows-sys/src/main.rs 3 | 4 | use std::{ 5 | fs, 6 | io::{BufWriter, Write as _}, 7 | }; 8 | 9 | use regex::Regex; 10 | 11 | /// This is printed to the file before the rest of the contents. 12 | const PRELUDE: &str = r#"// This file is autogenerated. 13 | // 14 | // To add bindings, edit windows_sys.lst then run: 15 | // 16 | // ``` 17 | // cd generate-windows-sys/ 18 | // cargo run 19 | // ```"#; 20 | 21 | fn main() { 22 | let manifest_dir = env!("CARGO_MANIFEST_DIR"); 23 | let filter = format!("{manifest_dir}/windows_sys.list"); 24 | let temp_file = tempfile::Builder::new() 25 | .suffix(".rs") 26 | .tempfile() 27 | .expect("failed to create temp file"); 28 | 29 | // Generate bindings. 30 | windows_bindgen::bindgen([ 31 | "--flat", 32 | "--sys", 33 | "--no-deps", 34 | "--out", 35 | temp_file.path().to_str().unwrap(), 36 | "--filter", 37 | "--etc", 38 | &filter, 39 | ]); 40 | 41 | let bindings = 42 | fs::read_to_string(temp_file.path()).expect("failed to read temp windows_sys.rs"); 43 | 44 | let mut f = fs::File::create(format!("{manifest_dir}/../../src/windows/windows_sys.rs")) 45 | .map(BufWriter::new) 46 | .expect("failed to create windows_sys.rs"); 47 | 48 | write!(&mut f, "{PRELUDE}\n{bindings}\n").unwrap(); 49 | 50 | let mut dll_names: Vec<&str> = Regex::new(r#"link!\("(.*)\.dll""#) 51 | .unwrap() 52 | .captures_iter(&bindings) 53 | .map(|caps| caps.extract().1) 54 | .map(|[dll_name]| dll_name) 55 | .filter(|dll_name| *dll_name != "kernel32") 56 | .collect(); 57 | 58 | if !dll_names.is_empty() { 59 | dll_names.sort_unstable(); 60 | dll_names.dedup(); 61 | 62 | for dll_name in dll_names { 63 | write!(&mut f, r#"#[link(name = "{dll_name}")]"#).unwrap(); 64 | f.write_all("\n".as_bytes()).unwrap(); 65 | } 66 | 67 | f.write_all(r#"extern "C" {}"#.as_bytes()).unwrap(); 68 | f.write_all("\n".as_bytes()).unwrap(); 69 | } 70 | 71 | f.write_all(r#"use super::windows_link;"#.as_bytes()) 72 | .unwrap(); 73 | f.write_all("\n".as_bytes()).unwrap(); 74 | 75 | f.into_inner().unwrap().sync_all().unwrap(); 76 | } 77 | -------------------------------------------------------------------------------- /dev-tools/gen-windows-sys-binding/windows_sys.list: -------------------------------------------------------------------------------- 1 | FILETIME 2 | ERROR_NO_MORE_ITEMS 3 | ERROR_SUCCESS 4 | SysFreeString 5 | SysStringLen 6 | S_FALSE 7 | S_OK 8 | FALSE 9 | HANDLE 10 | WAIT_OBJECT_0 11 | WAIT_TIMEOUT 12 | WAIT_FAILED 13 | WAIT_ABANDONED 14 | FreeLibrary 15 | 16 | SAFEARRAY 17 | SAFEARRAYBOUND 18 | CLSCTX_ALL 19 | COINIT_MULTITHREADED 20 | CoCreateInstance 21 | CoInitializeEx 22 | 23 | GetProcAddress 24 | LoadLibraryA 25 | 26 | PeekNamedPipe 27 | 28 | RegCloseKey 29 | RegEnumKeyExW 30 | RegOpenKeyExW 31 | RegQueryValueExW 32 | HKEY 33 | HKEY_LOCAL_MACHINE 34 | KEY_READ 35 | KEY_WOW64_32KEY 36 | REG_SZ 37 | 38 | IMAGE_FILE_MACHINE_AMD64 39 | 40 | FILE_ATTRIBUTE_TEMPORARY 41 | 42 | GetMachineTypeAttributes 43 | ReleaseSemaphore 44 | WaitForSingleObject 45 | SEMAPHORE_MODIFY_STATE 46 | THREAD_SYNCHRONIZE 47 | UserEnabled 48 | 49 | OpenSemaphoreA 50 | -------------------------------------------------------------------------------- /dev-tools/wasm32-wasip1-threads-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm32-wasip1-threads-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | rusqlite = { version = "0.36.0", features = ["bundled"] } 9 | -------------------------------------------------------------------------------- /dev-tools/wasm32-wasip1-threads-test/src/main.rs: -------------------------------------------------------------------------------- 1 | use rusqlite::{Connection, Result}; 2 | 3 | #[derive(Debug)] 4 | struct Person { 5 | #[allow(unused)] 6 | pub id: i32, 7 | pub name: String, 8 | pub data: Option>, 9 | } 10 | 11 | fn main() -> Result<()> { 12 | let conn = Connection::open_in_memory()?; 13 | 14 | conn.execute( 15 | "CREATE TABLE person ( 16 | id INTEGER PRIMARY KEY, 17 | name TEXT NOT NULL, 18 | data BLOB 19 | )", 20 | (), // empty list of parameters. 21 | )?; 22 | let me = Person { 23 | id: 0, 24 | name: "Steven".to_string(), 25 | data: None, 26 | }; 27 | conn.execute( 28 | "INSERT INTO person (name, data) VALUES (?1, ?2)", 29 | (&me.name, &me.data), 30 | )?; 31 | 32 | let mut stmt = conn.prepare("SELECT id, name, data FROM person")?; 33 | let person_iter = stmt.query_map([], |row| { 34 | Ok(Person { 35 | id: row.get(0)?, 36 | name: row.get(1)?, 37 | data: row.get(2)?, 38 | }) 39 | })?; 40 | 41 | for person in person_iter { 42 | println!("Found person {:?}", person.unwrap()); 43 | } 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/bin/cc-shim.rs: -------------------------------------------------------------------------------- 1 | //! This bin target is only used for this crate's tests. 2 | //! It is not intended for users and is not published with the library code to crates.io. 3 | 4 | #![cfg_attr(test, allow(dead_code))] 5 | #![allow(clippy::disallowed_methods)] 6 | 7 | use std::env; 8 | use std::fs::File; 9 | use std::io::{self, prelude::*}; 10 | use std::path::PathBuf; 11 | use std::process::ExitCode; 12 | 13 | fn main() -> ExitCode { 14 | let args = env::args().collect::>(); 15 | let mut args = args.iter(); 16 | let program = args.next().expect("Unexpected empty args"); 17 | 18 | let out_dir = PathBuf::from( 19 | env::var_os("CC_SHIM_OUT_DIR") 20 | .unwrap_or_else(|| panic!("{}: CC_SHIM_OUT_DIR not found", program)), 21 | ); 22 | 23 | // Find the first nonexistent candidate file to which the program's args can be written. 24 | let candidate = (0..).find_map(|i| { 25 | let candidate = out_dir.join(format!("out{}", i)); 26 | 27 | if candidate.exists() { 28 | // If the file exists, commands have already run. Try again. 29 | None 30 | } else { 31 | Some(candidate) 32 | } 33 | }).unwrap_or_else(|| panic!("Cannot find the first nonexistent candidate file to which the program's args can be written under out_dir '{}'", out_dir.display())); 34 | 35 | // Create a file and record the args passed to the command. 36 | let f = File::create(&candidate).unwrap_or_else(|e| { 37 | panic!( 38 | "{}: can't create candidate: {}, error: {}", 39 | program, 40 | candidate.display(), 41 | e 42 | ) 43 | }); 44 | let mut f = io::BufWriter::new(f); 45 | 46 | (|| { 47 | for arg in args.clone() { 48 | writeln!(f, "{}", arg)?; 49 | } 50 | 51 | f.flush()?; 52 | 53 | let mut f = f.into_inner()?; 54 | f.flush()?; 55 | f.sync_all() 56 | })() 57 | .unwrap_or_else(|e| { 58 | panic!( 59 | "{}: can't write to candidate: {}, error: {}", 60 | program, 61 | candidate.display(), 62 | e 63 | ) 64 | }); 65 | 66 | if program.starts_with("clang") { 67 | // Validate that we got no `-?` without a preceding `--driver-mode=cl`. Compiler family 68 | // detection depends on this. 69 | if let Some(cl_like_help_option_idx) = args.clone().position(|a| a == "-?") { 70 | let has_cl_clang_driver_before_cl_like_help_option = args 71 | .clone() 72 | .take(cl_like_help_option_idx) 73 | .rev() 74 | .find_map(|a| a.strip_prefix("--driver-mode=")) 75 | .is_some_and(|a| a == "cl"); 76 | if has_cl_clang_driver_before_cl_like_help_option { 77 | return ExitCode::SUCCESS; 78 | } else { 79 | eprintln!( 80 | "Found `-?` argument, but it was not preceded by a `--driver-mode=cl` argument." 81 | ); 82 | return ExitCode::FAILURE; 83 | } 84 | } 85 | } 86 | 87 | // Create a file used by some tests. 88 | let path = &out_dir.join("libfoo.a"); 89 | File::create(path).unwrap_or_else(|e| { 90 | panic!( 91 | "{}: can't create libfoo.a: {}, error: {}", 92 | program, 93 | path.display(), 94 | e 95 | ) 96 | }); 97 | 98 | ExitCode::SUCCESS 99 | } 100 | -------------------------------------------------------------------------------- /src/command_helpers.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous helpers for running commands 2 | 3 | use std::{ 4 | borrow::Cow, 5 | collections::hash_map, 6 | ffi::OsString, 7 | fmt::Display, 8 | fs, 9 | hash::Hasher, 10 | io::{self, Read, Write}, 11 | path::Path, 12 | process::{Child, ChildStderr, Command, Stdio}, 13 | sync::{ 14 | atomic::{AtomicBool, Ordering}, 15 | Arc, 16 | }, 17 | }; 18 | 19 | use crate::{Error, ErrorKind, Object}; 20 | 21 | #[derive(Clone, Debug)] 22 | pub(crate) struct CargoOutput { 23 | pub(crate) metadata: bool, 24 | pub(crate) warnings: bool, 25 | pub(crate) debug: bool, 26 | pub(crate) output: OutputKind, 27 | checked_dbg_var: Arc, 28 | } 29 | 30 | /// Different strategies for handling compiler output (to stdout) 31 | #[derive(Clone, Debug)] 32 | pub(crate) enum OutputKind { 33 | /// Forward the output to this process' stdout ([`Stdio::inherit()`]) 34 | Forward, 35 | /// Discard the output ([`Stdio::null()`]) 36 | Discard, 37 | /// Capture the result (`[Stdio::piped()`]) 38 | Capture, 39 | } 40 | 41 | impl CargoOutput { 42 | pub(crate) fn new() -> Self { 43 | #[allow(clippy::disallowed_methods)] 44 | Self { 45 | metadata: true, 46 | warnings: true, 47 | output: OutputKind::Forward, 48 | debug: match std::env::var_os("CC_ENABLE_DEBUG_OUTPUT") { 49 | Some(v) => v != "0" && v != "false" && !v.is_empty(), 50 | None => false, 51 | }, 52 | checked_dbg_var: Arc::new(AtomicBool::new(false)), 53 | } 54 | } 55 | 56 | pub(crate) fn print_metadata(&self, s: &dyn Display) { 57 | if self.metadata { 58 | println!("{}", s); 59 | } 60 | } 61 | 62 | pub(crate) fn print_warning(&self, arg: &dyn Display) { 63 | if self.warnings { 64 | println!("cargo:warning={}", arg); 65 | } 66 | } 67 | 68 | pub(crate) fn print_debug(&self, arg: &dyn Display) { 69 | if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) { 70 | self.checked_dbg_var.store(true, Ordering::Relaxed); 71 | println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT"); 72 | } 73 | if self.debug { 74 | println!("{}", arg); 75 | } 76 | } 77 | 78 | fn stdio_for_warnings(&self) -> Stdio { 79 | if self.warnings { 80 | Stdio::piped() 81 | } else { 82 | Stdio::null() 83 | } 84 | } 85 | 86 | fn stdio_for_output(&self) -> Stdio { 87 | match self.output { 88 | OutputKind::Capture => Stdio::piped(), 89 | OutputKind::Forward => Stdio::inherit(), 90 | OutputKind::Discard => Stdio::null(), 91 | } 92 | } 93 | } 94 | 95 | pub(crate) struct StderrForwarder { 96 | inner: Option<(ChildStderr, Vec)>, 97 | #[cfg(feature = "parallel")] 98 | is_non_blocking: bool, 99 | #[cfg(feature = "parallel")] 100 | bytes_available_failed: bool, 101 | /// number of bytes buffered in inner 102 | bytes_buffered: usize, 103 | } 104 | 105 | const MIN_BUFFER_CAPACITY: usize = 100; 106 | 107 | impl StderrForwarder { 108 | pub(crate) fn new(child: &mut Child) -> Self { 109 | Self { 110 | inner: child 111 | .stderr 112 | .take() 113 | .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))), 114 | bytes_buffered: 0, 115 | #[cfg(feature = "parallel")] 116 | is_non_blocking: false, 117 | #[cfg(feature = "parallel")] 118 | bytes_available_failed: false, 119 | } 120 | } 121 | 122 | fn forward_available(&mut self) -> bool { 123 | if let Some((stderr, buffer)) = self.inner.as_mut() { 124 | loop { 125 | // For non-blocking we check to see if there is data available, so we should try to 126 | // read at least that much. For blocking, always read at least the minimum amount. 127 | #[cfg(not(feature = "parallel"))] 128 | let to_reserve = MIN_BUFFER_CAPACITY; 129 | #[cfg(feature = "parallel")] 130 | let to_reserve = if self.is_non_blocking && !self.bytes_available_failed { 131 | match crate::parallel::stderr::bytes_available(stderr) { 132 | #[cfg(windows)] 133 | Ok(0) => break false, 134 | #[cfg(unix)] 135 | Ok(0) => { 136 | // On Unix, depending on the implementation, we may sometimes get 0 in a 137 | // loop (either there is data available or the pipe is broken), so 138 | // continue with the non-blocking read anyway. 139 | MIN_BUFFER_CAPACITY 140 | } 141 | #[cfg(windows)] 142 | Err(_) => { 143 | // On Windows, if we get an error then the pipe is broken, so flush 144 | // the buffer and bail. 145 | if !buffer.is_empty() { 146 | write_warning(&buffer[..]); 147 | } 148 | self.inner = None; 149 | break true; 150 | } 151 | #[cfg(unix)] 152 | Err(_) => { 153 | // On Unix, depending on the implementation, we may get spurious 154 | // errors so make a note not to use bytes_available again and try 155 | // the non-blocking read anyway. 156 | self.bytes_available_failed = true; 157 | MIN_BUFFER_CAPACITY 158 | } 159 | #[cfg(target_family = "wasm")] 160 | Err(_) => panic!("bytes_available should always succeed on wasm"), 161 | Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available), 162 | } 163 | } else { 164 | MIN_BUFFER_CAPACITY 165 | }; 166 | if self.bytes_buffered + to_reserve > buffer.len() { 167 | buffer.resize(self.bytes_buffered + to_reserve, 0); 168 | } 169 | 170 | match stderr.read(&mut buffer[self.bytes_buffered..]) { 171 | Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { 172 | // No data currently, yield back. 173 | break false; 174 | } 175 | Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { 176 | // Interrupted, try again. 177 | continue; 178 | } 179 | Ok(bytes_read) if bytes_read != 0 => { 180 | self.bytes_buffered += bytes_read; 181 | let mut consumed = 0; 182 | for line in buffer[..self.bytes_buffered].split_inclusive(|&b| b == b'\n') { 183 | // Only forward complete lines, leave the rest in the buffer. 184 | if let Some((b'\n', line)) = line.split_last() { 185 | consumed += line.len() + 1; 186 | write_warning(line); 187 | } 188 | } 189 | if consumed > 0 && consumed < self.bytes_buffered { 190 | // Remove the consumed bytes from buffer 191 | buffer.copy_within(consumed.., 0); 192 | } 193 | self.bytes_buffered -= consumed; 194 | } 195 | res => { 196 | // End of stream: flush remaining data and bail. 197 | if self.bytes_buffered > 0 { 198 | write_warning(&buffer[..self.bytes_buffered]); 199 | } 200 | if let Err(err) = res { 201 | write_warning( 202 | format!("Failed to read from child stderr: {err}").as_bytes(), 203 | ); 204 | } 205 | self.inner.take(); 206 | break true; 207 | } 208 | } 209 | } 210 | } else { 211 | true 212 | } 213 | } 214 | 215 | #[cfg(feature = "parallel")] 216 | pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> { 217 | assert!(!self.is_non_blocking); 218 | 219 | #[cfg(unix)] 220 | if let Some((stderr, _)) = self.inner.as_ref() { 221 | crate::parallel::stderr::set_non_blocking(stderr)?; 222 | } 223 | 224 | self.is_non_blocking = true; 225 | Ok(()) 226 | } 227 | 228 | #[cfg(feature = "parallel")] 229 | fn forward_all(&mut self) { 230 | while !self.forward_available() {} 231 | } 232 | 233 | #[cfg(not(feature = "parallel"))] 234 | fn forward_all(&mut self) { 235 | let forward_result = self.forward_available(); 236 | assert!(forward_result, "Should have consumed all data"); 237 | } 238 | } 239 | 240 | fn write_warning(line: &[u8]) { 241 | let stdout = io::stdout(); 242 | let mut stdout = stdout.lock(); 243 | stdout.write_all(b"cargo:warning=").unwrap(); 244 | stdout.write_all(line).unwrap(); 245 | stdout.write_all(b"\n").unwrap(); 246 | } 247 | 248 | fn wait_on_child( 249 | cmd: &Command, 250 | child: &mut Child, 251 | cargo_output: &CargoOutput, 252 | ) -> Result<(), Error> { 253 | StderrForwarder::new(child).forward_all(); 254 | 255 | let status = match child.wait() { 256 | Ok(s) => s, 257 | Err(e) => { 258 | return Err(Error::new( 259 | ErrorKind::ToolExecError, 260 | format!("failed to wait on spawned child process `{cmd:?}`: {e}"), 261 | )); 262 | } 263 | }; 264 | 265 | cargo_output.print_debug(&status); 266 | 267 | if status.success() { 268 | Ok(()) 269 | } else { 270 | Err(Error::new( 271 | ErrorKind::ToolExecError, 272 | format!("command did not execute successfully (status code {status}): {cmd:?}"), 273 | )) 274 | } 275 | } 276 | 277 | /// Find the destination object path for each file in the input source files, 278 | /// and store them in the output Object. 279 | pub(crate) fn objects_from_files(files: &[Arc], dst: &Path) -> Result, Error> { 280 | let mut objects = Vec::with_capacity(files.len()); 281 | for file in files { 282 | let basename = file 283 | .file_name() 284 | .ok_or_else(|| { 285 | Error::new( 286 | ErrorKind::InvalidArgument, 287 | "No file_name for object file path!", 288 | ) 289 | })? 290 | .to_string_lossy(); 291 | let dirname = file 292 | .parent() 293 | .ok_or_else(|| { 294 | Error::new( 295 | ErrorKind::InvalidArgument, 296 | "No parent for object file path!", 297 | ) 298 | })? 299 | .to_string_lossy(); 300 | 301 | // Hash the dirname. This should prevent conflicts if we have multiple 302 | // object files with the same filename in different subfolders. 303 | let mut hasher = hash_map::DefaultHasher::new(); 304 | 305 | // Make the dirname relative (if possible) to avoid full system paths influencing the sha 306 | // and making the output system-dependent 307 | // 308 | // NOTE: Here we allow using std::env::var (instead of Build::getenv) because 309 | // CARGO_* variables always trigger a rebuild when changed 310 | #[allow(clippy::disallowed_methods)] 311 | let dirname = if let Some(root) = std::env::var_os("CARGO_MANIFEST_DIR") { 312 | let root = root.to_string_lossy(); 313 | Cow::Borrowed(dirname.strip_prefix(&*root).unwrap_or(&dirname)) 314 | } else { 315 | dirname 316 | }; 317 | 318 | hasher.write(dirname.as_bytes()); 319 | if let Some(extension) = file.extension() { 320 | hasher.write(extension.to_string_lossy().as_bytes()); 321 | } 322 | let obj = dst 323 | .join(format!("{:016x}-{}", hasher.finish(), basename)) 324 | .with_extension("o"); 325 | 326 | match obj.parent() { 327 | Some(s) => fs::create_dir_all(s)?, 328 | None => { 329 | return Err(Error::new( 330 | ErrorKind::InvalidArgument, 331 | "dst is an invalid path with no parent", 332 | )); 333 | } 334 | }; 335 | 336 | objects.push(Object::new(file.to_path_buf(), obj)); 337 | } 338 | 339 | Ok(objects) 340 | } 341 | 342 | pub(crate) fn run(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<(), Error> { 343 | let mut child = spawn(cmd, cargo_output)?; 344 | wait_on_child(cmd, &mut child, cargo_output) 345 | } 346 | 347 | pub(crate) fn run_output(cmd: &mut Command, cargo_output: &CargoOutput) -> Result, Error> { 348 | // We specifically need the output to be captured, so override default 349 | let mut captured_cargo_output = cargo_output.clone(); 350 | captured_cargo_output.output = OutputKind::Capture; 351 | let mut child = spawn(cmd, &captured_cargo_output)?; 352 | 353 | let mut stdout = vec![]; 354 | child 355 | .stdout 356 | .take() 357 | .unwrap() 358 | .read_to_end(&mut stdout) 359 | .unwrap(); 360 | 361 | // Don't care about this output, use the normal settings 362 | wait_on_child(cmd, &mut child, cargo_output)?; 363 | 364 | Ok(stdout) 365 | } 366 | 367 | pub(crate) fn spawn(cmd: &mut Command, cargo_output: &CargoOutput) -> Result { 368 | struct ResetStderr<'cmd>(&'cmd mut Command); 369 | 370 | impl Drop for ResetStderr<'_> { 371 | fn drop(&mut self) { 372 | // Reset stderr to default to release pipe_writer so that print thread will 373 | // not block forever. 374 | self.0.stderr(Stdio::inherit()); 375 | } 376 | } 377 | 378 | cargo_output.print_debug(&format_args!("running: {:?}", cmd)); 379 | 380 | let cmd = ResetStderr(cmd); 381 | let child = cmd 382 | .0 383 | .stderr(cargo_output.stdio_for_warnings()) 384 | .stdout(cargo_output.stdio_for_output()) 385 | .spawn(); 386 | match child { 387 | Ok(child) => Ok(child), 388 | Err(ref e) if e.kind() == io::ErrorKind::NotFound => { 389 | let extra = if cfg!(windows) { 390 | " (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help)" 391 | } else { 392 | "" 393 | }; 394 | Err(Error::new( 395 | ErrorKind::ToolNotFound, 396 | format!("failed to find tool {:?}: {e}{extra}", cmd.0.get_program()), 397 | )) 398 | } 399 | Err(e) => Err(Error::new( 400 | ErrorKind::ToolExecError, 401 | format!("command `{:?}` failed to start: {e}", cmd.0), 402 | )), 403 | } 404 | } 405 | 406 | pub(crate) struct CmdAddOutputFileArgs { 407 | pub(crate) cuda: bool, 408 | pub(crate) is_assembler_msvc: bool, 409 | pub(crate) msvc: bool, 410 | pub(crate) clang: bool, 411 | pub(crate) gnu: bool, 412 | pub(crate) is_asm: bool, 413 | pub(crate) is_arm: bool, 414 | } 415 | 416 | pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) { 417 | if args.is_assembler_msvc 418 | || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm)) 419 | { 420 | let mut s = OsString::from("-Fo"); 421 | s.push(dst); 422 | cmd.arg(s); 423 | } else { 424 | cmd.arg("-o").arg(dst); 425 | } 426 | } 427 | 428 | #[cfg(feature = "parallel")] 429 | pub(crate) fn try_wait_on_child( 430 | cmd: &Command, 431 | child: &mut Child, 432 | stdout: &mut dyn io::Write, 433 | stderr_forwarder: &mut StderrForwarder, 434 | ) -> Result, Error> { 435 | stderr_forwarder.forward_available(); 436 | 437 | match child.try_wait() { 438 | Ok(Some(status)) => { 439 | stderr_forwarder.forward_all(); 440 | 441 | let _ = writeln!(stdout, "{}", status); 442 | 443 | if status.success() { 444 | Ok(Some(())) 445 | } else { 446 | Err(Error::new( 447 | ErrorKind::ToolExecError, 448 | format!("command did not execute successfully (status code {status}): {cmd:?}"), 449 | )) 450 | } 451 | } 452 | Ok(None) => Ok(None), 453 | Err(e) => { 454 | stderr_forwarder.forward_all(); 455 | Err(Error::new( 456 | ErrorKind::ToolExecError, 457 | format!("failed to wait on spawned child process `{cmd:?}`: {e}"), 458 | )) 459 | } 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/detect_compiler_family.c: -------------------------------------------------------------------------------- 1 | #ifdef __clang__ 2 | #pragma message "clang" 3 | #endif 4 | 5 | #ifdef __GNUC__ 6 | #pragma message "gcc" 7 | #endif 8 | 9 | #ifdef __EMSCRIPTEN__ 10 | #pragma message "emscripten" 11 | #endif 12 | 13 | #ifdef __VXWORKS__ 14 | #pragma message "VxWorks" 15 | #endif 16 | -------------------------------------------------------------------------------- /src/parallel/async_executor.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::Cell, 3 | future::Future, 4 | pin::Pin, 5 | ptr, 6 | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, 7 | thread, 8 | time::Duration, 9 | }; 10 | 11 | use crate::Error; 12 | 13 | const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new( 14 | // Cloning just returns a new no-op raw waker 15 | |_| NOOP_RAW_WAKER, 16 | // `wake` does nothing 17 | |_| {}, 18 | // `wake_by_ref` does nothing 19 | |_| {}, 20 | // Dropping does nothing as we don't allocate anything 21 | |_| {}, 22 | ); 23 | const NOOP_RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE); 24 | 25 | #[derive(Default)] 26 | pub(crate) struct YieldOnce(bool); 27 | 28 | impl Future for YieldOnce { 29 | type Output = (); 30 | 31 | fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { 32 | let flag = &mut std::pin::Pin::into_inner(self).0; 33 | if !*flag { 34 | *flag = true; 35 | Poll::Pending 36 | } else { 37 | Poll::Ready(()) 38 | } 39 | } 40 | } 41 | 42 | /// Execute the futures and return when they are all done. 43 | /// 44 | /// Here we use our own homebrew async executor since cc is used in the build 45 | /// script of many popular projects, pulling in additional dependencies would 46 | /// significantly slow down its compilation. 47 | pub(crate) fn block_on( 48 | mut fut1: Fut1, 49 | mut fut2: Fut2, 50 | has_made_progress: &Cell, 51 | ) -> Result<(), Error> 52 | where 53 | Fut1: Future>, 54 | Fut2: Future>, 55 | { 56 | // Shadows the future so that it can never be moved and is guaranteed 57 | // to be pinned. 58 | // 59 | // The same trick used in `pin!` macro. 60 | // 61 | // TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!` 62 | let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) }); 63 | let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) }); 64 | 65 | // TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version 66 | // which it is stablised, replace this with `Waker::noop`. 67 | let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) }; 68 | let mut context = Context::from_waker(&waker); 69 | 70 | let mut backoff_cnt = 0; 71 | 72 | loop { 73 | has_made_progress.set(false); 74 | 75 | if let Some(fut) = fut2.as_mut() { 76 | if let Poll::Ready(res) = fut.as_mut().poll(&mut context) { 77 | fut2 = None; 78 | res?; 79 | } 80 | } 81 | 82 | if let Some(fut) = fut1.as_mut() { 83 | if let Poll::Ready(res) = fut.as_mut().poll(&mut context) { 84 | fut1 = None; 85 | res?; 86 | } 87 | } 88 | 89 | if fut1.is_none() && fut2.is_none() { 90 | return Ok(()); 91 | } 92 | 93 | if !has_made_progress.get() { 94 | if backoff_cnt > 3 { 95 | // We have yielded at least three times without making' 96 | // any progress, so we will sleep for a while. 97 | let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10)); 98 | thread::sleep(duration); 99 | } else { 100 | // Given that we spawned a lot of compilation tasks, it is unlikely 101 | // that OS cannot find other ready task to execute. 102 | // 103 | // If all of them are done, then we will yield them and spawn more, 104 | // or simply return. 105 | // 106 | // Thus this will not be turned into a busy-wait loop and it will not 107 | // waste CPU resource. 108 | thread::yield_now(); 109 | } 110 | } 111 | 112 | backoff_cnt = if has_made_progress.get() { 113 | 0 114 | } else { 115 | backoff_cnt + 1 116 | }; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/parallel/job_token.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{utilities::OnceLock, Error}; 4 | 5 | pub(crate) struct JobToken(PhantomData<()>); 6 | 7 | impl JobToken { 8 | fn new() -> Self { 9 | Self(PhantomData) 10 | } 11 | } 12 | 13 | impl Drop for JobToken { 14 | fn drop(&mut self) { 15 | match JobTokenServer::new() { 16 | JobTokenServer::Inherited(jobserver) => jobserver.release_token_raw(), 17 | JobTokenServer::InProcess(jobserver) => jobserver.release_token_raw(), 18 | } 19 | } 20 | } 21 | 22 | enum JobTokenServer { 23 | Inherited(inherited_jobserver::JobServer), 24 | InProcess(inprocess_jobserver::JobServer), 25 | } 26 | 27 | impl JobTokenServer { 28 | /// This function returns a static reference to the jobserver because 29 | /// - creating a jobserver from env is a bit fd-unsafe (e.g. the fd might 30 | /// be closed by other jobserver users in the process) and better do it 31 | /// at the start of the program. 32 | /// - in case a jobserver cannot be created from env (e.g. it's not 33 | /// present), we will create a global in-process only jobserver 34 | /// that has to be static so that it will be shared by all cc 35 | /// compilation. 36 | fn new() -> &'static Self { 37 | // TODO: Replace with a OnceLock once MSRV is 1.70 38 | static JOBSERVER: OnceLock = OnceLock::new(); 39 | 40 | JOBSERVER.get_or_init(|| { 41 | unsafe { inherited_jobserver::JobServer::from_env() } 42 | .map(Self::Inherited) 43 | .unwrap_or_else(|| Self::InProcess(inprocess_jobserver::JobServer::new())) 44 | }) 45 | } 46 | } 47 | 48 | pub(crate) enum ActiveJobTokenServer { 49 | Inherited(inherited_jobserver::ActiveJobServer<'static>), 50 | InProcess(&'static inprocess_jobserver::JobServer), 51 | } 52 | 53 | impl ActiveJobTokenServer { 54 | pub(crate) fn new() -> Self { 55 | match JobTokenServer::new() { 56 | JobTokenServer::Inherited(inherited_jobserver) => { 57 | Self::Inherited(inherited_jobserver.enter_active()) 58 | } 59 | JobTokenServer::InProcess(inprocess_jobserver) => Self::InProcess(inprocess_jobserver), 60 | } 61 | } 62 | 63 | pub(crate) async fn acquire(&mut self) -> Result { 64 | match self { 65 | Self::Inherited(jobserver) => jobserver.acquire().await, 66 | Self::InProcess(jobserver) => Ok(jobserver.acquire().await), 67 | } 68 | } 69 | } 70 | 71 | mod inherited_jobserver { 72 | use super::JobToken; 73 | 74 | use crate::{parallel::async_executor::YieldOnce, Error, ErrorKind}; 75 | 76 | use std::{ 77 | io, mem, 78 | sync::{mpsc, Mutex, MutexGuard, PoisonError}, 79 | }; 80 | 81 | pub(super) struct JobServer { 82 | /// Implicit token for this process which is obtained and will be 83 | /// released in parent. Since `JobTokens` only give back what they got, 84 | /// there should be at most one global implicit token in the wild. 85 | /// 86 | /// Since Rust does not execute any `Drop` for global variables, 87 | /// we can't just put it back to jobserver and then re-acquire it at 88 | /// the end of the process. 89 | /// 90 | /// Use `Mutex` to avoid race between acquire and release. 91 | /// If an `AtomicBool` is used, then it's possible for: 92 | /// - `release_token_raw`: Tries to set `global_implicit_token` to true, but it is already 93 | /// set to `true`, continue to release it to jobserver 94 | /// - `acquire` takes the global implicit token, set `global_implicit_token` to false 95 | /// - `release_token_raw` now writes the token back into the jobserver, while 96 | /// `global_implicit_token` is `false` 97 | /// 98 | /// If the program exits here, then cc effectively increases parallelism by one, which is 99 | /// incorrect, hence we use a `Mutex` here. 100 | global_implicit_token: Mutex, 101 | inner: jobserver::Client, 102 | } 103 | 104 | impl JobServer { 105 | pub(super) unsafe fn from_env() -> Option { 106 | jobserver::Client::from_env().map(|inner| Self { 107 | inner, 108 | global_implicit_token: Mutex::new(true), 109 | }) 110 | } 111 | 112 | fn get_global_implicit_token(&self) -> MutexGuard<'_, bool> { 113 | self.global_implicit_token 114 | .lock() 115 | .unwrap_or_else(PoisonError::into_inner) 116 | } 117 | 118 | /// All tokens except for the global implicit token will be put back into the jobserver 119 | /// immediately and they cannot be cached, since Rust does not call `Drop::drop` on 120 | /// global variables. 121 | pub(super) fn release_token_raw(&self) { 122 | let mut global_implicit_token = self.get_global_implicit_token(); 123 | 124 | if *global_implicit_token { 125 | // There's already a global implicit token, so this token must 126 | // be released back into jobserver. 127 | // 128 | // `release_raw` should not block 129 | let _ = self.inner.release_raw(); 130 | } else { 131 | *global_implicit_token = true; 132 | } 133 | } 134 | 135 | pub(super) fn enter_active(&self) -> ActiveJobServer<'_> { 136 | ActiveJobServer { 137 | jobserver: self, 138 | helper_thread: None, 139 | } 140 | } 141 | } 142 | 143 | struct HelperThread { 144 | inner: jobserver::HelperThread, 145 | /// When rx is dropped, all the token stored within it will be dropped. 146 | rx: mpsc::Receiver>, 147 | } 148 | 149 | impl HelperThread { 150 | fn new(jobserver: &JobServer) -> Result { 151 | let (tx, rx) = mpsc::channel(); 152 | 153 | Ok(Self { 154 | rx, 155 | inner: jobserver.inner.clone().into_helper_thread(move |res| { 156 | let _ = tx.send(res); 157 | })?, 158 | }) 159 | } 160 | } 161 | 162 | pub(crate) struct ActiveJobServer<'a> { 163 | jobserver: &'a JobServer, 164 | helper_thread: Option, 165 | } 166 | 167 | impl ActiveJobServer<'_> { 168 | pub(super) async fn acquire(&mut self) -> Result { 169 | let mut has_requested_token = false; 170 | 171 | loop { 172 | // Fast path 173 | if mem::replace(&mut *self.jobserver.get_global_implicit_token(), false) { 174 | break Ok(JobToken::new()); 175 | } 176 | 177 | match self.jobserver.inner.try_acquire() { 178 | Ok(Some(acquired)) => { 179 | acquired.drop_without_releasing(); 180 | break Ok(JobToken::new()); 181 | } 182 | Ok(None) => YieldOnce::default().await, 183 | Err(err) if err.kind() == io::ErrorKind::Unsupported => { 184 | // Fallback to creating a help thread with blocking acquire 185 | let helper_thread = if let Some(thread) = self.helper_thread.as_ref() { 186 | thread 187 | } else { 188 | self.helper_thread 189 | .insert(HelperThread::new(self.jobserver)?) 190 | }; 191 | 192 | match helper_thread.rx.try_recv() { 193 | Ok(res) => { 194 | let acquired = res?; 195 | acquired.drop_without_releasing(); 196 | break Ok(JobToken::new()); 197 | } 198 | Err(mpsc::TryRecvError::Disconnected) => break Err(Error::new( 199 | ErrorKind::JobserverHelpThreadError, 200 | "jobserver help thread has returned before ActiveJobServer is dropped", 201 | )), 202 | Err(mpsc::TryRecvError::Empty) => { 203 | if !has_requested_token { 204 | helper_thread.inner.request_token(); 205 | has_requested_token = true; 206 | } 207 | YieldOnce::default().await 208 | } 209 | } 210 | } 211 | Err(err) => break Err(err.into()), 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | mod inprocess_jobserver { 219 | use super::JobToken; 220 | 221 | use crate::parallel::async_executor::YieldOnce; 222 | 223 | use std::{ 224 | env::var, 225 | sync::atomic::{ 226 | AtomicU32, 227 | Ordering::{AcqRel, Acquire}, 228 | }, 229 | }; 230 | 231 | pub(crate) struct JobServer(AtomicU32); 232 | 233 | impl JobServer { 234 | pub(super) fn new() -> Self { 235 | // Use `NUM_JOBS` if set (it's configured by Cargo) and otherwise 236 | // just fall back to the number of cores on the local machine, or a reasonable 237 | // default if that cannot be determined. 238 | 239 | let parallelism = var("NUM_JOBS") 240 | .ok() 241 | .and_then(|j| j.parse::().ok()) 242 | .or_else(|| Some(std::thread::available_parallelism().ok()?.get() as u32)) 243 | .unwrap_or(4); 244 | 245 | Self(AtomicU32::new(parallelism)) 246 | } 247 | 248 | pub(super) async fn acquire(&self) -> JobToken { 249 | loop { 250 | let res = self 251 | .0 252 | .fetch_update(AcqRel, Acquire, |tokens| tokens.checked_sub(1)); 253 | 254 | if res.is_ok() { 255 | break JobToken::new(); 256 | } 257 | 258 | YieldOnce::default().await 259 | } 260 | } 261 | 262 | pub(super) fn release_token_raw(&self) { 263 | self.0.fetch_add(1, AcqRel); 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/parallel/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod async_executor; 2 | pub(crate) mod job_token; 3 | pub(crate) mod stderr; 4 | -------------------------------------------------------------------------------- /src/parallel/stderr.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(target_family = "wasm", allow(unused))] 2 | /// Helpers functions for [`ChildStderr`]. 3 | use std::{convert::TryInto, process::ChildStderr}; 4 | 5 | use crate::{Error, ErrorKind}; 6 | 7 | #[cfg(all(not(unix), not(windows), not(target_family = "wasm")))] 8 | compile_error!("Only unix and windows support non-blocking pipes! For other OSes, disable the parallel feature."); 9 | 10 | #[cfg(unix)] 11 | fn get_flags(fd: std::os::unix::io::RawFd) -> Result { 12 | let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) }; 13 | if flags == -1 { 14 | Err(Error::new( 15 | ErrorKind::IOError, 16 | format!( 17 | "Failed to get flags for pipe {}: {}", 18 | fd, 19 | std::io::Error::last_os_error() 20 | ), 21 | )) 22 | } else { 23 | Ok(flags) 24 | } 25 | } 26 | 27 | #[cfg(unix)] 28 | fn set_flags(fd: std::os::unix::io::RawFd, flags: std::os::raw::c_int) -> Result<(), Error> { 29 | if unsafe { libc::fcntl(fd, libc::F_SETFL, flags) } == -1 { 30 | Err(Error::new( 31 | ErrorKind::IOError, 32 | format!( 33 | "Failed to set flags for pipe {}: {}", 34 | fd, 35 | std::io::Error::last_os_error() 36 | ), 37 | )) 38 | } else { 39 | Ok(()) 40 | } 41 | } 42 | 43 | #[cfg(unix)] 44 | pub fn set_non_blocking(pipe: &impl std::os::unix::io::AsRawFd) -> Result<(), Error> { 45 | // On Unix, switch the pipe to non-blocking mode. 46 | // On Windows, we have a different way to be non-blocking. 47 | let fd = pipe.as_raw_fd(); 48 | 49 | let flags = get_flags(fd)?; 50 | set_flags(fd, flags | libc::O_NONBLOCK) 51 | } 52 | 53 | pub fn bytes_available(stderr: &mut ChildStderr) -> Result { 54 | let mut bytes_available = 0; 55 | #[cfg(windows)] 56 | { 57 | use crate::windows::windows_sys::PeekNamedPipe; 58 | use std::os::windows::io::AsRawHandle; 59 | use std::ptr::null_mut; 60 | if unsafe { 61 | PeekNamedPipe( 62 | stderr.as_raw_handle(), 63 | null_mut(), 64 | 0, 65 | null_mut(), 66 | &mut bytes_available, 67 | null_mut(), 68 | ) 69 | } == 0 70 | { 71 | return Err(Error::new( 72 | ErrorKind::IOError, 73 | format!( 74 | "PeekNamedPipe failed with {}", 75 | std::io::Error::last_os_error() 76 | ), 77 | )); 78 | } 79 | } 80 | #[cfg(unix)] 81 | { 82 | use std::os::unix::io::AsRawFd; 83 | if unsafe { libc::ioctl(stderr.as_raw_fd(), libc::FIONREAD, &mut bytes_available) } != 0 { 84 | return Err(Error::new( 85 | ErrorKind::IOError, 86 | format!("ioctl failed with {}", std::io::Error::last_os_error()), 87 | )); 88 | } 89 | } 90 | Ok(bytes_available.try_into().unwrap()) 91 | } 92 | -------------------------------------------------------------------------------- /src/target.rs: -------------------------------------------------------------------------------- 1 | //! Parsing of `rustc` target names to match the values exposed to Cargo 2 | //! build scripts (`CARGO_CFG_*`). 3 | 4 | mod apple; 5 | mod generated; 6 | mod llvm; 7 | mod parser; 8 | 9 | pub(crate) use parser::TargetInfoParser; 10 | 11 | /// Information specific to a `rustc` target. 12 | /// 13 | /// See . 14 | #[derive(Debug, PartialEq, Clone)] 15 | pub(crate) struct TargetInfo<'a> { 16 | /// The full architecture, including the subarchitecture. 17 | /// 18 | /// This differs from `cfg!(target_arch)`, which only specifies the 19 | /// overall architecture, which is too coarse for certain cases. 20 | pub full_arch: &'a str, 21 | /// The overall target architecture. 22 | /// 23 | /// This is the same as the value of `cfg!(target_arch)`. 24 | pub arch: &'a str, 25 | /// The target vendor. 26 | /// 27 | /// This is the same as the value of `cfg!(target_vendor)`. 28 | pub vendor: &'a str, 29 | /// The operating system, or `none` on bare-metal targets. 30 | /// 31 | /// This is the same as the value of `cfg!(target_os)`. 32 | pub os: &'a str, 33 | /// The environment on top of the operating system. 34 | /// 35 | /// This is the same as the value of `cfg!(target_env)`. 36 | pub env: &'a str, 37 | /// The ABI on top of the operating system. 38 | /// 39 | /// This is the same as the value of `cfg!(target_abi)`. 40 | pub abi: &'a str, 41 | } 42 | -------------------------------------------------------------------------------- /src/target/apple.rs: -------------------------------------------------------------------------------- 1 | use super::TargetInfo; 2 | 3 | impl TargetInfo<'_> { 4 | pub(crate) fn apple_sdk_name(&self) -> &'static str { 5 | match (self.os, self.abi) { 6 | ("macos", "") => "macosx", 7 | ("ios", "") => "iphoneos", 8 | ("ios", "sim") => "iphonesimulator", 9 | ("ios", "macabi") => "macosx", 10 | ("tvos", "") => "appletvos", 11 | ("tvos", "sim") => "appletvsimulator", 12 | ("watchos", "") => "watchos", 13 | ("watchos", "sim") => "watchsimulator", 14 | ("visionos", "") => "xros", 15 | ("visionos", "sim") => "xrsimulator", 16 | (os, _) => panic!("invalid Apple target OS {}", os), 17 | } 18 | } 19 | 20 | pub(crate) fn apple_version_flag(&self, min_version: &str) -> String { 21 | // There are many aliases for these, and `-mtargetos=` is preferred on Clang nowadays, but 22 | // for compatibility with older Clang, we use the earliest supported name here. 23 | // 24 | // NOTE: GCC does not support `-miphoneos-version-min=` etc. (because it does not support 25 | // iOS in general), but we specify them anyhow in case we actually have a Clang-like 26 | // compiler disguised as a GNU-like compiler, or in case GCC adds support for these in the 27 | // future. 28 | // 29 | // See also: 30 | // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mmacos-version-min 31 | // https://clang.llvm.org/docs/AttributeReference.html#availability 32 | // https://gcc.gnu.org/onlinedocs/gcc/Darwin-Options.html#index-mmacosx-version-min 33 | match (self.os, self.abi) { 34 | ("macos", "") => format!("-mmacosx-version-min={min_version}"), 35 | ("ios", "") => format!("-miphoneos-version-min={min_version}"), 36 | ("ios", "sim") => format!("-mios-simulator-version-min={min_version}"), 37 | ("ios", "macabi") => format!("-mtargetos=ios{min_version}-macabi"), 38 | ("tvos", "") => format!("-mappletvos-version-min={min_version}"), 39 | ("tvos", "sim") => format!("-mappletvsimulator-version-min={min_version}"), 40 | ("watchos", "") => format!("-mwatchos-version-min={min_version}"), 41 | ("watchos", "sim") => format!("-mwatchsimulator-version-min={min_version}"), 42 | // `-mxros-version-min` does not exist 43 | // https://github.com/llvm/llvm-project/issues/88271 44 | ("visionos", "") => format!("-mtargetos=xros{min_version}"), 45 | ("visionos", "sim") => format!("-mtargetos=xros{min_version}-simulator"), 46 | (os, _) => panic!("invalid Apple target OS {}", os), 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/target/generated.rs: -------------------------------------------------------------------------------- 1 | //! This file is generated code. Please edit the generator in 2 | //! dev-tools/gen-target-info if you need to make changes, or see 3 | //! src/target/llvm.rs if you need to configure a specific LLVM triple. 4 | 5 | #[rustfmt::skip] 6 | pub(crate) const LLVM_TARGETS: &[(&str, &str)] = &[ 7 | ("aarch64-apple-darwin", "arm64-apple-macosx"), 8 | ("aarch64-apple-ios", "arm64-apple-ios"), 9 | ("aarch64-apple-ios-macabi", "arm64-apple-ios-macabi"), 10 | ("aarch64-apple-ios-sim", "arm64-apple-ios-simulator"), 11 | ("aarch64-apple-tvos", "arm64-apple-tvos"), 12 | ("aarch64-apple-tvos-sim", "arm64-apple-tvos-simulator"), 13 | ("aarch64-apple-visionos", "arm64-apple-xros"), 14 | ("aarch64-apple-visionos-sim", "arm64-apple-xros-simulator"), 15 | ("aarch64-apple-watchos", "arm64-apple-watchos"), 16 | ("aarch64-apple-watchos-sim", "arm64-apple-watchos-simulator"), 17 | ("aarch64-fuchsia", "aarch64-fuchsia"), 18 | ("aarch64-kmc-solid_asp3", "aarch64-unknown-none"), 19 | ("aarch64-linux-android", "aarch64-linux-android"), 20 | ("aarch64-nintendo-switch-freestanding", "aarch64-unknown-none"), 21 | ("aarch64-pc-windows-gnullvm", "aarch64-pc-windows-gnu"), 22 | ("aarch64-pc-windows-msvc", "aarch64-pc-windows-msvc"), 23 | ("aarch64-unknown-freebsd", "aarch64-unknown-freebsd"), 24 | ("aarch64-unknown-fuchsia", "aarch64-unknown-fuchsia"), 25 | ("aarch64-unknown-hermit", "aarch64-unknown-hermit"), 26 | ("aarch64-unknown-illumos", "aarch64-unknown-solaris2.11"), 27 | ("aarch64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"), 28 | ("aarch64-unknown-linux-gnu_ilp32", "aarch64-unknown-linux-gnu_ilp32"), 29 | ("aarch64-unknown-linux-musl", "aarch64-unknown-linux-musl"), 30 | ("aarch64-unknown-linux-ohos", "aarch64-unknown-linux-ohos"), 31 | ("aarch64-unknown-netbsd", "aarch64-unknown-netbsd"), 32 | ("aarch64-unknown-none", "aarch64-unknown-none"), 33 | ("aarch64-unknown-none-softfloat", "aarch64-unknown-none"), 34 | ("aarch64-unknown-nto-qnx700", "aarch64-unknown-unknown"), 35 | ("aarch64-unknown-nto-qnx710", "aarch64-unknown-unknown"), 36 | ("aarch64-unknown-nto-qnx710_iosock", "aarch64-unknown-unknown"), 37 | ("aarch64-unknown-nto-qnx800", "aarch64-unknown-unknown"), 38 | ("aarch64-unknown-nuttx", "aarch64-unknown-none"), 39 | ("aarch64-unknown-openbsd", "aarch64-unknown-openbsd"), 40 | ("aarch64-unknown-redox", "aarch64-unknown-redox"), 41 | ("aarch64-unknown-teeos", "aarch64-unknown-none"), 42 | ("aarch64-unknown-trusty", "aarch64-unknown-unknown-musl"), 43 | ("aarch64-unknown-uefi", "aarch64-unknown-windows"), 44 | ("aarch64-uwp-windows-msvc", "aarch64-pc-windows-msvc"), 45 | ("aarch64-wrs-vxworks", "aarch64-unknown-linux-gnu"), 46 | ("aarch64_be-unknown-linux-gnu", "aarch64_be-unknown-linux-gnu"), 47 | ("aarch64_be-unknown-linux-gnu_ilp32", "aarch64_be-unknown-linux-gnu_ilp32"), 48 | ("aarch64_be-unknown-netbsd", "aarch64_be-unknown-netbsd"), 49 | ("amdgcn-amd-amdhsa", "amdgcn-amd-amdhsa"), 50 | ("arm-linux-androideabi", "arm-linux-androideabi"), 51 | ("arm-unknown-linux-gnueabi", "arm-unknown-linux-gnueabi"), 52 | ("arm-unknown-linux-gnueabihf", "arm-unknown-linux-gnueabihf"), 53 | ("arm-unknown-linux-musleabi", "arm-unknown-linux-musleabi"), 54 | ("arm-unknown-linux-musleabihf", "arm-unknown-linux-musleabihf"), 55 | ("arm64_32-apple-watchos", "arm64_32-apple-watchos"), 56 | ("arm64e-apple-darwin", "arm64e-apple-macosx"), 57 | ("arm64e-apple-ios", "arm64e-apple-ios"), 58 | ("arm64e-apple-tvos", "arm64e-apple-tvos"), 59 | ("arm64ec-pc-windows-msvc", "arm64ec-pc-windows-msvc"), 60 | ("armeb-unknown-linux-gnueabi", "armeb-unknown-linux-gnueabi"), 61 | ("armebv7r-none-eabi", "armebv7r-none-eabi"), 62 | ("armebv7r-none-eabihf", "armebv7r-none-eabihf"), 63 | ("armv4t-none-eabi", "armv4t-none-eabi"), 64 | ("armv4t-unknown-linux-gnueabi", "armv4t-unknown-linux-gnueabi"), 65 | ("armv5te-none-eabi", "armv5te-none-eabi"), 66 | ("armv5te-unknown-linux-gnueabi", "armv5te-unknown-linux-gnueabi"), 67 | ("armv5te-unknown-linux-musleabi", "armv5te-unknown-linux-musleabi"), 68 | ("armv5te-unknown-linux-uclibceabi", "armv5te-unknown-linux-gnueabi"), 69 | ("armv6-unknown-freebsd", "armv6-unknown-freebsd-gnueabihf"), 70 | ("armv6-unknown-netbsd-eabihf", "armv6-unknown-netbsdelf-eabihf"), 71 | ("armv6k-nintendo-3ds", "armv6k-none-eabihf"), 72 | ("armv7-apple-ios", "armv7-apple-ios7.0.0"), 73 | ("armv7-linux-androideabi", "armv7-none-linux-android"), 74 | ("armv7-rtems-eabihf", "armv7-unknown-none-eabihf"), 75 | ("armv7-sony-vita-newlibeabihf", "thumbv7a-sony-vita-eabihf"), 76 | ("armv7-unknown-freebsd", "armv7-unknown-freebsd-gnueabihf"), 77 | ("armv7-unknown-linux-gnueabi", "armv7-unknown-linux-gnueabi"), 78 | ("armv7-unknown-linux-gnueabihf", "armv7-unknown-linux-gnueabihf"), 79 | ("armv7-unknown-linux-musleabi", "armv7-unknown-linux-musleabi"), 80 | ("armv7-unknown-linux-musleabihf", "armv7-unknown-linux-musleabihf"), 81 | ("armv7-unknown-linux-ohos", "armv7-unknown-linux-ohos"), 82 | ("armv7-unknown-linux-uclibceabi", "armv7-unknown-linux-gnueabi"), 83 | ("armv7-unknown-linux-uclibceabihf", "armv7-unknown-linux-gnueabihf"), 84 | ("armv7-unknown-netbsd-eabihf", "armv7-unknown-netbsdelf-eabihf"), 85 | ("armv7-unknown-trusty", "armv7-unknown-unknown-gnueabi"), 86 | ("armv7-wrs-vxworks-eabihf", "armv7-unknown-linux-gnueabihf"), 87 | ("armv7a-kmc-solid_asp3-eabi", "armv7a-none-eabi"), 88 | ("armv7a-kmc-solid_asp3-eabihf", "armv7a-none-eabihf"), 89 | ("armv7a-none-eabi", "armv7a-none-eabi"), 90 | ("armv7a-none-eabihf", "armv7a-none-eabihf"), 91 | ("armv7a-nuttx-eabi", "armv7a-none-eabi"), 92 | ("armv7a-nuttx-eabihf", "armv7a-none-eabihf"), 93 | ("armv7k-apple-watchos", "armv7k-apple-watchos"), 94 | ("armv7r-none-eabi", "armv7r-none-eabi"), 95 | ("armv7r-none-eabihf", "armv7r-none-eabihf"), 96 | ("armv7s-apple-ios", "armv7s-apple-ios"), 97 | ("armv8r-none-eabihf", "armv8r-none-eabihf"), 98 | ("asmjs-unknown-emscripten", "wasm32-unknown-emscripten"), 99 | ("avr-none", "avr-unknown-unknown"), 100 | ("avr-unknown-gnu-atmega328", "avr-unknown-unknown"), 101 | ("bpfeb-unknown-none", "bpfeb"), 102 | ("bpfel-unknown-none", "bpfel"), 103 | ("csky-unknown-linux-gnuabiv2", "csky-unknown-linux-gnuabiv2"), 104 | ("csky-unknown-linux-gnuabiv2hf", "csky-unknown-linux-gnuabiv2"), 105 | ("hexagon-unknown-linux-musl", "hexagon-unknown-linux-musl"), 106 | ("hexagon-unknown-none-elf", "hexagon-unknown-none-elf"), 107 | ("i386-apple-ios", "i386-apple-ios-simulator"), 108 | ("i586-pc-windows-msvc", "i586-pc-windows-msvc"), 109 | ("i586-unknown-linux-gnu", "i586-unknown-linux-gnu"), 110 | ("i586-unknown-linux-musl", "i586-unknown-linux-musl"), 111 | ("i586-unknown-netbsd", "i586-unknown-netbsdelf"), 112 | ("i586-unknown-redox", "i586-unknown-redox"), 113 | ("i686-apple-darwin", "i686-apple-macosx"), 114 | ("i686-linux-android", "i686-linux-android"), 115 | ("i686-pc-nto-qnx700", "i586-pc-unknown"), 116 | ("i686-pc-windows-gnu", "i686-pc-windows-gnu"), 117 | ("i686-pc-windows-gnullvm", "i686-pc-windows-gnu"), 118 | ("i686-pc-windows-msvc", "i686-pc-windows-msvc"), 119 | ("i686-unknown-freebsd", "i686-unknown-freebsd"), 120 | ("i686-unknown-haiku", "i686-unknown-haiku"), 121 | ("i686-unknown-hurd-gnu", "i686-unknown-hurd-gnu"), 122 | ("i686-unknown-linux-gnu", "i686-unknown-linux-gnu"), 123 | ("i686-unknown-linux-musl", "i686-unknown-linux-musl"), 124 | ("i686-unknown-netbsd", "i686-unknown-netbsdelf"), 125 | ("i686-unknown-openbsd", "i686-unknown-openbsd"), 126 | ("i686-unknown-uefi", "i686-unknown-windows-gnu"), 127 | ("i686-uwp-windows-gnu", "i686-pc-windows-gnu"), 128 | ("i686-uwp-windows-msvc", "i686-pc-windows-msvc"), 129 | ("i686-win7-windows-gnu", "i686-pc-windows-gnu"), 130 | ("i686-win7-windows-msvc", "i686-pc-windows-msvc"), 131 | ("i686-wrs-vxworks", "i686-unknown-linux-gnu"), 132 | ("loongarch64-unknown-linux-gnu", "loongarch64-unknown-linux-gnu"), 133 | ("loongarch64-unknown-linux-musl", "loongarch64-unknown-linux-musl"), 134 | ("loongarch64-unknown-linux-ohos", "loongarch64-unknown-linux-ohos"), 135 | ("loongarch64-unknown-none", "loongarch64-unknown-none"), 136 | ("loongarch64-unknown-none-softfloat", "loongarch64-unknown-none"), 137 | ("m68k-unknown-linux-gnu", "m68k-unknown-linux-gnu"), 138 | ("m68k-unknown-none-elf", "m68k"), 139 | ("mips-mti-none-elf", "mips"), 140 | ("mips-unknown-linux-gnu", "mips-unknown-linux-gnu"), 141 | ("mips-unknown-linux-musl", "mips-unknown-linux-musl"), 142 | ("mips-unknown-linux-uclibc", "mips-unknown-linux-gnu"), 143 | ("mips64-openwrt-linux-musl", "mips64-unknown-linux-musl"), 144 | ("mips64-unknown-linux-gnuabi64", "mips64-unknown-linux-gnuabi64"), 145 | ("mips64-unknown-linux-muslabi64", "mips64-unknown-linux-musl"), 146 | ("mips64el-unknown-linux-gnuabi64", "mips64el-unknown-linux-gnuabi64"), 147 | ("mips64el-unknown-linux-muslabi64", "mips64el-unknown-linux-musl"), 148 | ("mipsel-mti-none-elf", "mipsel"), 149 | ("mipsel-sony-psp", "mipsel-sony-psp"), 150 | ("mipsel-sony-psx", "mipsel-sony-psx"), 151 | ("mipsel-unknown-linux-gnu", "mipsel-unknown-linux-gnu"), 152 | ("mipsel-unknown-linux-musl", "mipsel-unknown-linux-musl"), 153 | ("mipsel-unknown-linux-uclibc", "mipsel-unknown-linux-gnu"), 154 | ("mipsel-unknown-netbsd", "mipsel-unknown-netbsd"), 155 | ("mipsel-unknown-none", "mipsel-unknown-none"), 156 | ("mipsisa32r6-unknown-linux-gnu", "mipsisa32r6-unknown-linux-gnu"), 157 | ("mipsisa32r6el-unknown-linux-gnu", "mipsisa32r6el-unknown-linux-gnu"), 158 | ("mipsisa64r6-unknown-linux-gnuabi64", "mipsisa64r6-unknown-linux-gnuabi64"), 159 | ("mipsisa64r6el-unknown-linux-gnuabi64", "mipsisa64r6el-unknown-linux-gnuabi64"), 160 | ("msp430-none-elf", "msp430-none-elf"), 161 | ("nvptx64-nvidia-cuda", "nvptx64-nvidia-cuda"), 162 | ("powerpc-unknown-freebsd", "powerpc-unknown-freebsd13.0"), 163 | ("powerpc-unknown-linux-gnu", "powerpc-unknown-linux-gnu"), 164 | ("powerpc-unknown-linux-gnuspe", "powerpc-unknown-linux-gnuspe"), 165 | ("powerpc-unknown-linux-musl", "powerpc-unknown-linux-musl"), 166 | ("powerpc-unknown-linux-muslspe", "powerpc-unknown-linux-muslspe"), 167 | ("powerpc-unknown-netbsd", "powerpc-unknown-netbsd"), 168 | ("powerpc-unknown-openbsd", "powerpc-unknown-openbsd"), 169 | ("powerpc-wrs-vxworks", "powerpc-unknown-linux-gnu"), 170 | ("powerpc-wrs-vxworks-spe", "powerpc-unknown-linux-gnuspe"), 171 | ("powerpc64-ibm-aix", "powerpc64-ibm-aix"), 172 | ("powerpc64-unknown-freebsd", "powerpc64-unknown-freebsd"), 173 | ("powerpc64-unknown-linux-gnu", "powerpc64-unknown-linux-gnu"), 174 | ("powerpc64-unknown-linux-musl", "powerpc64-unknown-linux-musl"), 175 | ("powerpc64-unknown-openbsd", "powerpc64-unknown-openbsd"), 176 | ("powerpc64-wrs-vxworks", "powerpc64-unknown-linux-gnu"), 177 | ("powerpc64le-unknown-freebsd", "powerpc64le-unknown-freebsd"), 178 | ("powerpc64le-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu"), 179 | ("powerpc64le-unknown-linux-musl", "powerpc64le-unknown-linux-musl"), 180 | ("riscv32-wrs-vxworks", "riscv32-unknown-linux-gnu"), 181 | ("riscv32e-unknown-none-elf", "riscv32"), 182 | ("riscv32em-unknown-none-elf", "riscv32"), 183 | ("riscv32emc-unknown-none-elf", "riscv32"), 184 | ("riscv32gc-unknown-linux-gnu", "riscv32-unknown-linux-gnu"), 185 | ("riscv32gc-unknown-linux-musl", "riscv32-unknown-linux-musl"), 186 | ("riscv32i-unknown-none-elf", "riscv32"), 187 | ("riscv32im-risc0-zkvm-elf", "riscv32"), 188 | ("riscv32im-unknown-none-elf", "riscv32"), 189 | ("riscv32ima-unknown-none-elf", "riscv32"), 190 | ("riscv32imac-esp-espidf", "riscv32"), 191 | ("riscv32imac-unknown-none-elf", "riscv32"), 192 | ("riscv32imac-unknown-nuttx-elf", "riscv32"), 193 | ("riscv32imac-unknown-xous-elf", "riscv32"), 194 | ("riscv32imafc-esp-espidf", "riscv32"), 195 | ("riscv32imafc-unknown-none-elf", "riscv32"), 196 | ("riscv32imafc-unknown-nuttx-elf", "riscv32"), 197 | ("riscv32imc-esp-espidf", "riscv32"), 198 | ("riscv32imc-unknown-none-elf", "riscv32"), 199 | ("riscv32imc-unknown-nuttx-elf", "riscv32"), 200 | ("riscv64-linux-android", "riscv64-linux-android"), 201 | ("riscv64-wrs-vxworks", "riscv64-unknown-linux-gnu"), 202 | ("riscv64gc-unknown-freebsd", "riscv64-unknown-freebsd"), 203 | ("riscv64gc-unknown-fuchsia", "riscv64-unknown-fuchsia"), 204 | ("riscv64gc-unknown-hermit", "riscv64-unknown-hermit"), 205 | ("riscv64gc-unknown-linux-gnu", "riscv64-unknown-linux-gnu"), 206 | ("riscv64gc-unknown-linux-musl", "riscv64-unknown-linux-musl"), 207 | ("riscv64gc-unknown-netbsd", "riscv64-unknown-netbsd"), 208 | ("riscv64gc-unknown-none-elf", "riscv64"), 209 | ("riscv64gc-unknown-nuttx-elf", "riscv64"), 210 | ("riscv64gc-unknown-openbsd", "riscv64-unknown-openbsd"), 211 | ("riscv64imac-unknown-none-elf", "riscv64"), 212 | ("riscv64imac-unknown-nuttx-elf", "riscv64"), 213 | ("s390x-unknown-linux-gnu", "s390x-unknown-linux-gnu"), 214 | ("s390x-unknown-linux-musl", "s390x-unknown-linux-musl"), 215 | ("sparc-unknown-linux-gnu", "sparc-unknown-linux-gnu"), 216 | ("sparc-unknown-none-elf", "sparc-unknown-none-elf"), 217 | ("sparc64-unknown-linux-gnu", "sparc64-unknown-linux-gnu"), 218 | ("sparc64-unknown-netbsd", "sparc64-unknown-netbsd"), 219 | ("sparc64-unknown-openbsd", "sparc64-unknown-openbsd"), 220 | ("sparcv9-sun-solaris", "sparcv9-sun-solaris"), 221 | ("thumbv4t-none-eabi", "thumbv4t-none-eabi"), 222 | ("thumbv5te-none-eabi", "thumbv5te-none-eabi"), 223 | ("thumbv6m-none-eabi", "thumbv6m-none-eabi"), 224 | ("thumbv6m-nuttx-eabi", "thumbv6m-none-eabi"), 225 | ("thumbv7a-nuttx-eabi", "thumbv7a-none-eabi"), 226 | ("thumbv7a-nuttx-eabihf", "thumbv7a-none-eabihf"), 227 | ("thumbv7a-pc-windows-msvc", "thumbv7a-pc-windows-msvc"), 228 | ("thumbv7a-uwp-windows-msvc", "thumbv7a-pc-windows-msvc"), 229 | ("thumbv7em-none-eabi", "thumbv7em-none-eabi"), 230 | ("thumbv7em-none-eabihf", "thumbv7em-none-eabihf"), 231 | ("thumbv7em-nuttx-eabi", "thumbv7em-none-eabi"), 232 | ("thumbv7em-nuttx-eabihf", "thumbv7em-none-eabihf"), 233 | ("thumbv7m-none-eabi", "thumbv7m-none-eabi"), 234 | ("thumbv7m-nuttx-eabi", "thumbv7m-none-eabi"), 235 | ("thumbv7neon-linux-androideabi", "armv7-none-linux-android"), 236 | ("thumbv7neon-unknown-linux-gnueabihf", "armv7-unknown-linux-gnueabihf"), 237 | ("thumbv7neon-unknown-linux-musleabihf", "armv7-unknown-linux-musleabihf"), 238 | ("thumbv8m.base-none-eabi", "thumbv8m.base-none-eabi"), 239 | ("thumbv8m.base-nuttx-eabi", "thumbv8m.base-none-eabi"), 240 | ("thumbv8m.main-none-eabi", "thumbv8m.main-none-eabi"), 241 | ("thumbv8m.main-none-eabihf", "thumbv8m.main-none-eabihf"), 242 | ("thumbv8m.main-nuttx-eabi", "thumbv8m.main-none-eabi"), 243 | ("thumbv8m.main-nuttx-eabihf", "thumbv8m.main-none-eabihf"), 244 | ("wasm32-unknown-emscripten", "wasm32-unknown-emscripten"), 245 | ("wasm32-unknown-unknown", "wasm32-unknown-unknown"), 246 | ("wasm32-wali-linux-musl", "wasm32-wasi"), 247 | ("wasm32-wasi", "wasm32-wasi"), 248 | ("wasm32-wasip1", "wasm32-wasip1"), 249 | ("wasm32-wasip1-threads", "wasm32-wasi"), 250 | ("wasm32-wasip2", "wasm32-wasip2"), 251 | ("wasm32v1-none", "wasm32-unknown-unknown"), 252 | ("wasm64-unknown-unknown", "wasm64-unknown-unknown"), 253 | ("x86_64-apple-darwin", "x86_64-apple-macosx"), 254 | ("x86_64-apple-ios", "x86_64-apple-ios-simulator"), 255 | ("x86_64-apple-ios-macabi", "x86_64-apple-ios-macabi"), 256 | ("x86_64-apple-tvos", "x86_64-apple-tvos-simulator"), 257 | ("x86_64-apple-watchos-sim", "x86_64-apple-watchos-simulator"), 258 | ("x86_64-fortanix-unknown-sgx", "x86_64-elf"), 259 | ("x86_64-fuchsia", "x86_64-fuchsia"), 260 | ("x86_64-linux-android", "x86_64-linux-android"), 261 | ("x86_64-lynx-lynxos178", "x86_64-unknown-unknown-gnu"), 262 | ("x86_64-pc-cygwin", "x86_64-pc-cygwin"), 263 | ("x86_64-pc-nto-qnx710", "x86_64-pc-unknown"), 264 | ("x86_64-pc-nto-qnx710_iosock", "x86_64-pc-unknown"), 265 | ("x86_64-pc-nto-qnx800", "x86_64-pc-unknown"), 266 | ("x86_64-pc-solaris", "x86_64-pc-solaris"), 267 | ("x86_64-pc-windows-gnu", "x86_64-pc-windows-gnu"), 268 | ("x86_64-pc-windows-gnullvm", "x86_64-pc-windows-gnu"), 269 | ("x86_64-pc-windows-msvc", "x86_64-pc-windows-msvc"), 270 | ("x86_64-sun-solaris", "x86_64-pc-solaris"), 271 | ("x86_64-unikraft-linux-musl", "x86_64-unknown-linux-musl"), 272 | ("x86_64-unknown-dragonfly", "x86_64-unknown-dragonfly"), 273 | ("x86_64-unknown-freebsd", "x86_64-unknown-freebsd"), 274 | ("x86_64-unknown-fuchsia", "x86_64-unknown-fuchsia"), 275 | ("x86_64-unknown-haiku", "x86_64-unknown-haiku"), 276 | ("x86_64-unknown-hermit", "x86_64-unknown-hermit"), 277 | ("x86_64-unknown-hurd-gnu", "x86_64-unknown-hurd-gnu"), 278 | ("x86_64-unknown-illumos", "x86_64-pc-solaris"), 279 | ("x86_64-unknown-l4re-uclibc", "x86_64-unknown-l4re-gnu"), 280 | ("x86_64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"), 281 | ("x86_64-unknown-linux-gnux32", "x86_64-unknown-linux-gnux32"), 282 | ("x86_64-unknown-linux-musl", "x86_64-unknown-linux-musl"), 283 | ("x86_64-unknown-linux-none", "x86_64-unknown-linux-none"), 284 | ("x86_64-unknown-linux-ohos", "x86_64-unknown-linux-ohos"), 285 | ("x86_64-unknown-netbsd", "x86_64-unknown-netbsd"), 286 | ("x86_64-unknown-none", "x86_64-unknown-none-elf"), 287 | ("x86_64-unknown-none-linuxkernel", "x86_64-unknown-none-elf"), 288 | ("x86_64-unknown-openbsd", "x86_64-unknown-openbsd"), 289 | ("x86_64-unknown-redox", "x86_64-unknown-redox"), 290 | ("x86_64-unknown-trusty", "x86_64-unknown-unknown-musl"), 291 | ("x86_64-unknown-uefi", "x86_64-unknown-windows"), 292 | ("x86_64-uwp-windows-gnu", "x86_64-pc-windows-gnu"), 293 | ("x86_64-uwp-windows-msvc", "x86_64-pc-windows-msvc"), 294 | ("x86_64-win7-windows-gnu", "x86_64-pc-windows-gnu"), 295 | ("x86_64-win7-windows-msvc", "x86_64-pc-windows-msvc"), 296 | ("x86_64-wrs-vxworks", "x86_64-unknown-linux-gnu"), 297 | ("x86_64h-apple-darwin", "x86_64h-apple-macosx"), 298 | ("xtensa-esp32-espidf", "xtensa-none-elf"), 299 | ("xtensa-esp32-none-elf", "xtensa-none-elf"), 300 | ("xtensa-esp32s2-espidf", "xtensa-none-elf"), 301 | ("xtensa-esp32s2-none-elf", "xtensa-none-elf"), 302 | ("xtensa-esp32s3-espidf", "xtensa-none-elf"), 303 | ("xtensa-esp32s3-none-elf", "xtensa-none-elf"), 304 | ]; 305 | -------------------------------------------------------------------------------- /src/target/llvm.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use super::{generated, TargetInfo}; 4 | 5 | impl TargetInfo<'_> { 6 | /// The LLVM/Clang target triple. 7 | /// 8 | /// See . 9 | /// 10 | /// Rust and Clang don't really agree on target naming, so we first try to 11 | /// find the matching trible based on `rustc`'s output, but if no such 12 | /// triple exists, we attempt to construct the triple from scratch. 13 | /// 14 | /// NOTE: You should never need to match on this explicitly, use the 15 | /// fields on [`TargetInfo`] instead. 16 | pub(crate) fn llvm_target( 17 | &self, 18 | rustc_target: &str, 19 | version: Option<&str>, 20 | ) -> Cow<'static, str> { 21 | if rustc_target == "armv7-apple-ios" { 22 | // FIXME(madsmtm): Unnecessary once we bump MSRV to Rust 1.74 23 | return Cow::Borrowed("armv7-apple-ios"); 24 | } else if self.os == "uefi" { 25 | // Override the UEFI LLVM targets. 26 | // 27 | // The rustc mappings (as of 1.82) for the UEFI targets are: 28 | // * i686-unknown-uefi -> i686-unknown-windows-gnu 29 | // * x86_64-unknown-uefi -> x86_64-unknown-windows 30 | // * aarch64-unknown-uefi -> aarch64-unknown-windows 31 | // 32 | // However, in cc-rs all the UEFI targets use 33 | // -windows-gnu. This has been the case since 2021 [1]. 34 | // * i686-unknown-uefi -> i686-unknown-windows-gnu 35 | // * x86_64-unknown-uefi -> x86_64-unknown-windows-gnu 36 | // * aarch64-unknown-uefi -> aarch64-unknown-windows-gnu 37 | // 38 | // For now, override the UEFI mapping to keep the behavior 39 | // of cc-rs unchanged. 40 | // 41 | // TODO: as discussed in [2], it may be possible to switch 42 | // to new UEFI targets added to clang, and regardless it 43 | // would be good to have consistency between rustc and 44 | // cc-rs. 45 | // 46 | // [1]: https://github.com/rust-lang/cc-rs/pull/623 47 | // [2]: https://github.com/rust-lang/cc-rs/pull/1264 48 | return Cow::Owned(format!("{}-unknown-windows-gnu", self.full_arch)); 49 | } 50 | 51 | // If no version is requested, let's take the triple directly from 52 | // `rustc` (the logic below is not yet good enough for most targets). 53 | // 54 | // FIXME(madsmtm): This should ideally be removed. 55 | if version.is_none() { 56 | if let Ok(index) = generated::LLVM_TARGETS 57 | .binary_search_by_key(&rustc_target, |(rustc_target, _)| rustc_target) 58 | { 59 | let (_, llvm_target) = &generated::LLVM_TARGETS[index]; 60 | return Cow::Borrowed(llvm_target); 61 | } 62 | } 63 | 64 | // Otherwise, attempt to construct the triple from the target info. 65 | 66 | let arch = match self.full_arch { 67 | riscv32 if riscv32.starts_with("riscv32") => "riscv32", 68 | riscv64 if riscv64.starts_with("riscv64") => "riscv64", 69 | "aarch64" if self.vendor == "apple" => "arm64", 70 | "armv7" if self.vendor == "sony" => "thumbv7a", // FIXME 71 | arch => arch, 72 | }; 73 | let vendor = match self.vendor { 74 | "kmc" | "nintendo" => "unknown", 75 | "unknown" if self.os == "android" => "linux", 76 | "uwp" => "pc", 77 | "espressif" => "", 78 | _ if self.arch == "msp430" => "", 79 | vendor => vendor, 80 | }; 81 | let os = match self.os { 82 | "macos" => "macosx", 83 | "visionos" => "xros", 84 | "uefi" => "windows", 85 | "solid_asp3" | "horizon" | "teeos" | "nuttx" | "espidf" => "none", 86 | "nto" => "unknown", // FIXME 87 | "trusty" => "unknown", // FIXME 88 | os => os, 89 | }; 90 | let version = version.unwrap_or(""); 91 | let env = match self.env { 92 | "newlib" | "nto70" | "nto71" | "nto71_iosock" | "p1" | "p2" | "relibc" | "sgx" 93 | | "uclibc" => "", 94 | env => env, 95 | }; 96 | let abi = match self.abi { 97 | "sim" => "simulator", 98 | "llvm" | "softfloat" | "uwp" | "vec-extabi" => "", 99 | "ilp32" => "_ilp32", 100 | "abi64" => "", 101 | abi => abi, 102 | }; 103 | Cow::Owned(match (vendor, env, abi) { 104 | ("", "", "") => format!("{arch}-{os}{version}"), 105 | ("", env, abi) => format!("{arch}-{os}{version}-{env}{abi}"), 106 | (vendor, "", "") => format!("{arch}-{vendor}-{os}{version}"), 107 | (vendor, env, abi) => format!("{arch}-{vendor}-{os}{version}-{env}{abi}"), 108 | }) 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use std::process::Command; 115 | 116 | use crate::TargetInfo; 117 | 118 | #[test] 119 | fn test_old_ios_target() { 120 | assert_eq!( 121 | TargetInfo { 122 | full_arch: "armv7", 123 | arch: "armv7", 124 | vendor: "apple", 125 | os: "ios", 126 | env: "", 127 | abi: "", 128 | } 129 | .llvm_target("armv7-apple-ios", None), 130 | "armv7-apple-ios" 131 | ); 132 | } 133 | 134 | #[test] 135 | fn basic_llvm_triple_guessing() { 136 | assert_eq!( 137 | TargetInfo { 138 | full_arch: "aarch64", 139 | arch: "aarch64", 140 | vendor: "unknown", 141 | os: "linux", 142 | env: "", 143 | abi: "", 144 | } 145 | .llvm_target("invalid", None), 146 | "aarch64-unknown-linux" 147 | ); 148 | assert_eq!( 149 | TargetInfo { 150 | full_arch: "x86_64", 151 | arch: "x86_64", 152 | vendor: "unknown", 153 | os: "linux", 154 | env: "gnu", 155 | abi: "", 156 | } 157 | .llvm_target("invalid", None), 158 | "x86_64-unknown-linux-gnu" 159 | ); 160 | assert_eq!( 161 | TargetInfo { 162 | full_arch: "x86_64", 163 | arch: "x86_64", 164 | vendor: "unknown", 165 | os: "linux", 166 | env: "gnu", 167 | abi: "eabi", 168 | } 169 | .llvm_target("invalid", None), 170 | "x86_64-unknown-linux-gnueabi" 171 | ); 172 | assert_eq!( 173 | TargetInfo { 174 | full_arch: "x86_64", 175 | arch: "x86_64", 176 | vendor: "apple", 177 | os: "macos", 178 | env: "", 179 | abi: "", 180 | } 181 | .llvm_target("invalid", None), 182 | "x86_64-apple-macosx" 183 | ); 184 | } 185 | 186 | #[test] 187 | fn llvm_version() { 188 | assert_eq!( 189 | TargetInfo { 190 | full_arch: "aarch64", 191 | arch: "aarch64", 192 | vendor: "apple", 193 | os: "ios", 194 | env: "", 195 | abi: "sim", 196 | } 197 | .llvm_target("aarch64-apple-ios-sim", Some("14.0")), 198 | "arm64-apple-ios14.0-simulator" 199 | ); 200 | assert_eq!( 201 | TargetInfo { 202 | full_arch: "aarch64", 203 | arch: "aarch64", 204 | vendor: "apple", 205 | os: "visionos", 206 | env: "", 207 | abi: "", 208 | } 209 | .llvm_target("aarch64-apple-visionos", Some("2.0")), 210 | "arm64-apple-xros2.0" 211 | ); 212 | assert_eq!( 213 | TargetInfo { 214 | full_arch: "aarch64", 215 | arch: "aarch64", 216 | vendor: "apple", 217 | os: "ios", 218 | env: "", 219 | abi: "macabi", 220 | } 221 | .llvm_target("aarch64-apple-ios-macabi", Some("13.1")), 222 | "arm64-apple-ios13.1-macabi" 223 | ); 224 | } 225 | 226 | #[test] 227 | fn uefi() { 228 | assert_eq!( 229 | TargetInfo { 230 | full_arch: "i686", 231 | arch: "x86", 232 | vendor: "unknown", 233 | os: "uefi", 234 | env: "", 235 | abi: "", 236 | } 237 | .llvm_target("i686-unknown-uefi", None), 238 | "i686-unknown-windows-gnu" 239 | ); 240 | assert_eq!( 241 | TargetInfo { 242 | full_arch: "x86_64", 243 | arch: "x86_64", 244 | vendor: "unknown", 245 | os: "uefi", 246 | env: "", 247 | abi: "", 248 | } 249 | .llvm_target("x86_64-unknown-uefi", None), 250 | "x86_64-unknown-windows-gnu" 251 | ); 252 | assert_eq!( 253 | TargetInfo { 254 | full_arch: "aarch64", 255 | arch: "aarch64", 256 | vendor: "unknown", 257 | os: "uefi", 258 | env: "", 259 | abi: "", 260 | } 261 | .llvm_target("aarch64-unknown-uefi", None), 262 | "aarch64-unknown-windows-gnu" 263 | ); 264 | } 265 | 266 | #[test] 267 | #[ignore = "not yet done"] 268 | fn llvm_for_all_rustc_targets() { 269 | let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()); 270 | 271 | let target_list = Command::new(&rustc) 272 | .arg("--print=target-list") 273 | .output() 274 | .unwrap() 275 | .stdout; 276 | let target_list = String::from_utf8(target_list).unwrap(); 277 | 278 | let mut has_failure = false; 279 | for target in target_list.lines() { 280 | let spec_json = Command::new(&rustc) 281 | .arg("--target") 282 | .arg(target) 283 | .arg("-Zunstable-options") 284 | .arg("--print=target-spec-json") 285 | .env("RUSTC_BOOTSTRAP", "1") // Crimes 286 | .output() 287 | .unwrap() 288 | .stdout; 289 | let spec_json = String::from_utf8(spec_json).unwrap(); 290 | 291 | // JSON crimes 292 | let expected = spec_json 293 | .split_once("llvm-target\": \"") 294 | .unwrap() 295 | .1 296 | .split_once("\"") 297 | .unwrap() 298 | .0; 299 | let actual = TargetInfo::from_rustc_target(target) 300 | .map(|target| target.llvm_target("invalid", None)); 301 | 302 | if Some(expected) != actual.as_deref().ok() { 303 | eprintln!("failed comparing {target}:"); 304 | eprintln!(" expected: Ok({expected:?})"); 305 | eprintln!(" actual: {actual:?}"); 306 | eprintln!(); 307 | has_failure = true; 308 | } 309 | } 310 | 311 | if has_failure { 312 | panic!("failed comparing targets"); 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/tempfile.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(target_family = "wasm", allow(unused))] 2 | 3 | use std::{ 4 | collections::hash_map::RandomState, 5 | fs::{remove_file, File, OpenOptions}, 6 | hash::{BuildHasher, Hasher}, 7 | io, os, 8 | path::{Path, PathBuf}, 9 | }; 10 | 11 | #[cfg(not(any(unix, target_family = "wasm", windows)))] 12 | compile_error!("Your system is not supported since cc cannot create named tempfile"); 13 | 14 | fn rand() -> u64 { 15 | RandomState::new().build_hasher().finish() 16 | } 17 | 18 | fn tmpname(suffix: &str) -> String { 19 | format!("{}{}", rand(), suffix) 20 | } 21 | 22 | fn create_named(path: &Path) -> io::Result { 23 | let mut open_options = OpenOptions::new(); 24 | 25 | open_options.read(true).write(true).create_new(true); 26 | 27 | #[cfg(all(unix, not(target_os = "wasi")))] 28 | ::mode(&mut open_options, 0o600); 29 | 30 | #[cfg(windows)] 31 | ::custom_flags( 32 | &mut open_options, 33 | crate::windows::windows_sys::FILE_ATTRIBUTE_TEMPORARY, 34 | ); 35 | 36 | open_options.open(path) 37 | } 38 | 39 | pub(super) struct NamedTempfile { 40 | path: PathBuf, 41 | file: Option, 42 | } 43 | 44 | impl NamedTempfile { 45 | pub(super) fn new(base: &Path, suffix: &str) -> io::Result { 46 | for _ in 0..10 { 47 | let path = base.join(tmpname(suffix)); 48 | match create_named(&path) { 49 | Ok(file) => { 50 | return Ok(Self { 51 | file: Some(file), 52 | path, 53 | }) 54 | } 55 | Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, 56 | Err(e) => return Err(e), 57 | }; 58 | } 59 | 60 | Err(io::Error::new( 61 | io::ErrorKind::AlreadyExists, 62 | format!( 63 | "too many temporary files exist in base `{}` with suffix `{}`", 64 | base.display(), 65 | suffix 66 | ), 67 | )) 68 | } 69 | 70 | pub(super) fn path(&self) -> &Path { 71 | &self.path 72 | } 73 | 74 | pub(super) fn take_file(&mut self) -> Option { 75 | self.file.take() 76 | } 77 | } 78 | 79 | impl Drop for NamedTempfile { 80 | fn drop(&mut self) { 81 | // On Windows you have to close all handle to it before 82 | // removing the file. 83 | self.file.take(); 84 | let _ = remove_file(&self.path); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/utilities.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::UnsafeCell, 3 | ffi::OsStr, 4 | fmt::{self, Write}, 5 | marker::PhantomData, 6 | mem::MaybeUninit, 7 | panic::{RefUnwindSafe, UnwindSafe}, 8 | path::Path, 9 | sync::Once, 10 | }; 11 | 12 | pub(super) struct JoinOsStrs<'a, T> { 13 | pub(super) slice: &'a [T], 14 | pub(super) delimiter: char, 15 | } 16 | 17 | impl fmt::Display for JoinOsStrs<'_, T> 18 | where 19 | T: AsRef, 20 | { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | let len = self.slice.len(); 23 | for (index, os_str) in self.slice.iter().enumerate() { 24 | // TODO: Use OsStr::display once it is stablised, 25 | // Path and OsStr has the same `Display` impl 26 | write!(f, "{}", Path::new(os_str).display())?; 27 | if index + 1 < len { 28 | f.write_char(self.delimiter)?; 29 | } 30 | } 31 | Ok(()) 32 | } 33 | } 34 | 35 | pub(super) struct OptionOsStrDisplay(pub(super) Option); 36 | 37 | impl fmt::Display for OptionOsStrDisplay 38 | where 39 | T: AsRef, 40 | { 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 | // TODO: Use OsStr::display once it is stablised 43 | // Path and OsStr has the same `Display` impl 44 | if let Some(os_str) = self.0.as_ref() { 45 | write!(f, "Some({})", Path::new(os_str).display()) 46 | } else { 47 | f.write_str("None") 48 | } 49 | } 50 | } 51 | 52 | pub(crate) struct OnceLock { 53 | once: Once, 54 | value: UnsafeCell>, 55 | _marker: PhantomData, 56 | } 57 | 58 | impl Default for OnceLock { 59 | fn default() -> Self { 60 | Self::new() 61 | } 62 | } 63 | 64 | impl OnceLock { 65 | pub(crate) const fn new() -> Self { 66 | Self { 67 | once: Once::new(), 68 | value: UnsafeCell::new(MaybeUninit::uninit()), 69 | _marker: PhantomData, 70 | } 71 | } 72 | 73 | #[inline] 74 | fn is_initialized(&self) -> bool { 75 | self.once.is_completed() 76 | } 77 | 78 | unsafe fn get_unchecked(&self) -> &T { 79 | debug_assert!(self.is_initialized()); 80 | #[allow(clippy::needless_borrow)] 81 | #[allow(unused_unsafe)] 82 | unsafe { 83 | (&*self.value.get()).assume_init_ref() 84 | } 85 | } 86 | 87 | pub(crate) fn get_or_init(&self, f: impl FnOnce() -> T) -> &T { 88 | self.once.call_once(|| { 89 | unsafe { &mut *self.value.get() }.write(f()); 90 | }); 91 | unsafe { self.get_unchecked() } 92 | } 93 | 94 | pub(crate) fn get(&self) -> Option<&T> { 95 | if self.is_initialized() { 96 | // Safe b/c checked is_initialized 97 | Some(unsafe { self.get_unchecked() }) 98 | } else { 99 | None 100 | } 101 | } 102 | } 103 | 104 | impl fmt::Debug for OnceLock { 105 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 106 | let mut d = f.debug_tuple("OnceLock"); 107 | match self.get() { 108 | Some(v) => d.field(v), 109 | None => d.field(&format_args!("")), 110 | }; 111 | d.finish() 112 | } 113 | } 114 | 115 | unsafe impl Sync for OnceLock {} 116 | unsafe impl Send for OnceLock {} 117 | 118 | impl RefUnwindSafe for OnceLock {} 119 | impl UnwindSafe for OnceLock {} 120 | 121 | impl Drop for OnceLock { 122 | #[inline] 123 | fn drop(&mut self) { 124 | if self.once.is_completed() { 125 | // SAFETY: The cell is initialized and being dropped, so it can't 126 | // be accessed again. 127 | unsafe { self.value.get_mut().assume_init_drop() }; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/windows/com.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 winapi-rs developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT license 4 | // , at your option. 5 | // All files in the project carrying such notice may not be copied, modified, or distributed 6 | // except according to those terms. 7 | 8 | use crate::windows::{ 9 | winapi::{IUnknown, Interface}, 10 | windows_sys::{ 11 | CoInitializeEx, SysFreeString, SysStringLen, BSTR, COINIT_MULTITHREADED, HRESULT, S_FALSE, 12 | S_OK, 13 | }, 14 | }; 15 | use std::{ 16 | convert::TryInto, 17 | ffi::OsString, 18 | ops::Deref, 19 | os::windows::ffi::OsStringExt, 20 | ptr::{null, null_mut}, 21 | slice::from_raw_parts, 22 | }; 23 | 24 | pub fn initialize() -> Result<(), HRESULT> { 25 | let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED.try_into().unwrap()) }; 26 | if err != S_OK && err != S_FALSE { 27 | // S_FALSE just means COM is already initialized 28 | Err(err) 29 | } else { 30 | Ok(()) 31 | } 32 | } 33 | 34 | pub struct ComPtr(*mut T) 35 | where 36 | T: Interface; 37 | impl ComPtr 38 | where 39 | T: Interface, 40 | { 41 | /// Creates a `ComPtr` to wrap a raw pointer. 42 | /// It takes ownership over the pointer which means it does __not__ call `AddRef`. 43 | /// `T` __must__ be a COM interface that inherits from `IUnknown`. 44 | pub unsafe fn from_raw(ptr: *mut T) -> ComPtr { 45 | assert!(!ptr.is_null()); 46 | ComPtr(ptr) 47 | } 48 | /// For internal use only. 49 | fn as_unknown(&self) -> &IUnknown { 50 | unsafe { &*(self.0 as *mut IUnknown) } 51 | } 52 | /// Performs `QueryInterface` fun. 53 | pub fn cast(&self) -> Result, i32> 54 | where 55 | U: Interface, 56 | { 57 | let mut obj = null_mut(); 58 | let err = unsafe { self.as_unknown().QueryInterface(&U::uuidof(), &mut obj) }; 59 | if err < 0 { 60 | return Err(err); 61 | } 62 | Ok(unsafe { ComPtr::from_raw(obj as *mut U) }) 63 | } 64 | } 65 | impl Deref for ComPtr 66 | where 67 | T: Interface, 68 | { 69 | type Target = T; 70 | fn deref(&self) -> &T { 71 | unsafe { &*self.0 } 72 | } 73 | } 74 | impl Clone for ComPtr 75 | where 76 | T: Interface, 77 | { 78 | fn clone(&self) -> Self { 79 | unsafe { 80 | self.as_unknown().AddRef(); 81 | ComPtr::from_raw(self.0) 82 | } 83 | } 84 | } 85 | impl Drop for ComPtr 86 | where 87 | T: Interface, 88 | { 89 | fn drop(&mut self) { 90 | unsafe { 91 | self.as_unknown().Release(); 92 | } 93 | } 94 | } 95 | pub struct BStr(BSTR); 96 | impl BStr { 97 | pub unsafe fn from_raw(s: BSTR) -> BStr { 98 | BStr(s) 99 | } 100 | pub fn to_osstring(&self) -> OsString { 101 | let len = unsafe { SysStringLen(self.0) }; 102 | let slice = unsafe { from_raw_parts(self.0, len as usize) }; 103 | OsStringExt::from_wide(slice) 104 | } 105 | } 106 | impl Drop for BStr { 107 | fn drop(&mut self) { 108 | unsafe { SysFreeString(self.0) }; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | //! These modules are all glue to support reading the MSVC version from 2 | //! the registry and from COM interfaces. 3 | 4 | // This is used in the crate's public API, so don't use #[cfg(windows)] 5 | pub mod find_tools; 6 | 7 | #[cfg(windows)] 8 | mod windows_link; 9 | #[cfg(windows)] 10 | pub(crate) mod windows_sys; 11 | 12 | #[cfg(windows)] 13 | mod registry; 14 | #[cfg(windows)] 15 | #[macro_use] 16 | mod winapi; 17 | #[cfg(windows)] 18 | mod com; 19 | #[cfg(windows)] 20 | mod setup_config; 21 | #[cfg(windows)] 22 | mod vs_instances; 23 | -------------------------------------------------------------------------------- /src/windows/registry.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | use crate::windows::windows_sys::{ 12 | RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, RegQueryValueExW, ERROR_NO_MORE_ITEMS, 13 | ERROR_SUCCESS, HKEY, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, REG_SZ, 14 | }; 15 | use std::{ 16 | ffi::{OsStr, OsString}, 17 | io, 18 | ops::RangeFrom, 19 | os::windows::prelude::*, 20 | ptr::null_mut, 21 | }; 22 | 23 | /// Must never be `HKEY_PERFORMANCE_DATA`. 24 | pub(crate) struct RegistryKey(Repr); 25 | 26 | #[allow(clippy::upper_case_acronyms)] 27 | type DWORD = u32; 28 | 29 | struct OwnedKey(HKEY); 30 | 31 | /// Note: must not encode `HKEY_PERFORMANCE_DATA` or one of its subkeys. 32 | enum Repr { 33 | /// `HKEY_LOCAL_MACHINE`. 34 | LocalMachine, 35 | /// A subkey of `HKEY_LOCAL_MACHINE`. 36 | Owned(OwnedKey), 37 | } 38 | 39 | pub struct Iter<'a> { 40 | idx: RangeFrom, 41 | key: &'a RegistryKey, 42 | } 43 | 44 | unsafe impl Sync for Repr {} 45 | unsafe impl Send for Repr {} 46 | 47 | pub(crate) const LOCAL_MACHINE: RegistryKey = RegistryKey(Repr::LocalMachine); 48 | 49 | impl RegistryKey { 50 | fn raw(&self) -> HKEY { 51 | match self.0 { 52 | Repr::LocalMachine => HKEY_LOCAL_MACHINE, 53 | Repr::Owned(ref val) => val.0, 54 | } 55 | } 56 | 57 | /// Open a sub-key of `self`. 58 | pub fn open(&self, key: &OsStr) -> io::Result { 59 | let key = key.encode_wide().chain(Some(0)).collect::>(); 60 | let mut ret = null_mut(); 61 | let err = unsafe { 62 | RegOpenKeyExW( 63 | self.raw(), 64 | key.as_ptr(), 65 | 0, 66 | KEY_READ | KEY_WOW64_32KEY, 67 | &mut ret, 68 | ) 69 | }; 70 | if err == ERROR_SUCCESS { 71 | Ok(RegistryKey(Repr::Owned(OwnedKey(ret)))) 72 | } else { 73 | Err(io::Error::from_raw_os_error(err as i32)) 74 | } 75 | } 76 | 77 | pub fn iter(&self) -> Iter { 78 | Iter { 79 | idx: 0.., 80 | key: self, 81 | } 82 | } 83 | 84 | pub fn query_str(&self, name: &str) -> io::Result { 85 | let name: &OsStr = name.as_ref(); 86 | let name = name.encode_wide().chain(Some(0)).collect::>(); 87 | let mut len = 0; 88 | let mut kind = 0; 89 | unsafe { 90 | let err = RegQueryValueExW( 91 | self.raw(), 92 | name.as_ptr(), 93 | null_mut(), 94 | &mut kind, 95 | null_mut(), 96 | &mut len, 97 | ); 98 | if err != ERROR_SUCCESS { 99 | return Err(io::Error::from_raw_os_error(err as i32)); 100 | } 101 | if kind != REG_SZ { 102 | return Err(io::Error::new( 103 | io::ErrorKind::Other, 104 | "registry key wasn't a string", 105 | )); 106 | } 107 | 108 | // The length here is the length in bytes, but we're using wide 109 | // characters so we need to be sure to halve it for the length 110 | // passed in. 111 | assert!(len % 2 == 0, "impossible wide string size: {} bytes", len); 112 | let vlen = len as usize / 2; 113 | // Defensively initialized, see comment about 114 | // `HKEY_PERFORMANCE_DATA` below. 115 | let mut v = vec![0u16; vlen]; 116 | let err = RegQueryValueExW( 117 | self.raw(), 118 | name.as_ptr(), 119 | null_mut(), 120 | null_mut(), 121 | v.as_mut_ptr() as *mut _, 122 | &mut len, 123 | ); 124 | // We don't check for `ERROR_MORE_DATA` (which would if the value 125 | // grew between the first and second call to `RegQueryValueExW`), 126 | // both because it's extremely unlikely, and this is a bit more 127 | // defensive more defensive against weird types of registry keys. 128 | if err != ERROR_SUCCESS { 129 | return Err(io::Error::from_raw_os_error(err as i32)); 130 | } 131 | // The length is allowed to change, but should still be even, as 132 | // well as smaller. 133 | assert!(len % 2 == 0, "impossible wide string size: {} bytes", len); 134 | // If the length grew but returned a success code, it *probably* 135 | // indicates we're `HKEY_PERFORMANCE_DATA` or a subkey(?). We 136 | // consider this UB, since those keys write "undefined" or 137 | // "unpredictable" values to len, and need to use a completely 138 | // different loop structure. This should be impossible (and enforce 139 | // it in the API to the best of our ability), but to mitigate the 140 | // damage we do some smoke-checks on the len, and ensure `v` has 141 | // been fully initialized (rather than trusting the result of 142 | // `RegQueryValueExW`). 143 | let actual_len = len as usize / 2; 144 | assert!(actual_len <= v.len()); 145 | v.truncate(actual_len); 146 | // Some registry keys may have a terminating nul character, but 147 | // we're not interested in that, so chop it off if it's there. 148 | if !v.is_empty() && v[v.len() - 1] == 0 { 149 | v.pop(); 150 | } 151 | Ok(OsString::from_wide(&v)) 152 | } 153 | } 154 | } 155 | 156 | impl Drop for OwnedKey { 157 | fn drop(&mut self) { 158 | unsafe { 159 | RegCloseKey(self.0); 160 | } 161 | } 162 | } 163 | 164 | impl<'a> Iterator for Iter<'a> { 165 | type Item = io::Result; 166 | 167 | fn next(&mut self) -> Option> { 168 | self.idx.next().and_then(|i| unsafe { 169 | let mut v = Vec::with_capacity(256); 170 | let mut len = v.capacity() as DWORD; 171 | let ret = RegEnumKeyExW( 172 | self.key.raw(), 173 | i, 174 | v.as_mut_ptr(), 175 | &mut len, 176 | null_mut(), 177 | null_mut(), 178 | null_mut(), 179 | null_mut(), 180 | ); 181 | if ret == ERROR_NO_MORE_ITEMS { 182 | None 183 | } else if ret != ERROR_SUCCESS { 184 | Some(Err(io::Error::from_raw_os_error(ret as i32))) 185 | } else { 186 | v.set_len(len as usize); 187 | Some(Ok(OsString::from_wide(&v))) 188 | } 189 | }) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/windows/setup_config.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 winapi-rs developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT license 4 | // , at your option. 5 | // All files in the project carrying such notice may not be copied, modified, or distributed 6 | // except according to those terms. 7 | 8 | #![allow(bad_style)] 9 | #![allow(unused)] 10 | 11 | use crate::windows::{ 12 | com::{BStr, ComPtr}, 13 | winapi::{ 14 | IUnknown, IUnknownVtbl, Interface, LCID, LPCOLESTR, LPCWSTR, LPFILETIME, LPSAFEARRAY, 15 | PULONGLONG, ULONG, 16 | }, 17 | windows_sys::{CoCreateInstance, BSTR, CLSCTX_ALL, HRESULT, S_FALSE}, 18 | }; 19 | 20 | use std::{ 21 | ffi::OsString, 22 | ptr::{null, null_mut}, 23 | }; 24 | 25 | // Bindings to the Setup.Configuration stuff 26 | pub type InstanceState = u32; 27 | 28 | pub const eNone: InstanceState = 0; 29 | pub const eLocal: InstanceState = 1; 30 | pub const eRegistered: InstanceState = 2; 31 | pub const eNoRebootRequired: InstanceState = 4; 32 | pub const eComplete: InstanceState = -1i32 as u32; 33 | 34 | RIDL! {#[uuid(0xb41463c3, 0x8866, 0x43b5, 0xbc, 0x33, 0x2b, 0x06, 0x76, 0xf7, 0xf4, 0x2e)] 35 | interface ISetupInstance(ISetupInstanceVtbl): IUnknown(IUnknownVtbl) { 36 | fn GetInstanceId( 37 | pbstrInstanceId: *mut BSTR, 38 | ) -> HRESULT, 39 | fn GetInstallDate( 40 | pInstallDate: LPFILETIME, 41 | ) -> HRESULT, 42 | fn GetInstallationName( 43 | pbstrInstallationName: *mut BSTR, 44 | ) -> HRESULT, 45 | fn GetInstallationPath( 46 | pbstrInstallationPath: *mut BSTR, 47 | ) -> HRESULT, 48 | fn GetInstallationVersion( 49 | pbstrInstallationVersion: *mut BSTR, 50 | ) -> HRESULT, 51 | fn GetDisplayName( 52 | lcid: LCID, 53 | pbstrDisplayName: *mut BSTR, 54 | ) -> HRESULT, 55 | fn GetDescription( 56 | lcid: LCID, 57 | pbstrDescription: *mut BSTR, 58 | ) -> HRESULT, 59 | fn ResolvePath( 60 | pwszRelativePath: LPCOLESTR, 61 | pbstrAbsolutePath: *mut BSTR, 62 | ) -> HRESULT, 63 | }} 64 | 65 | RIDL! {#[uuid(0x89143c9a, 0x05af, 0x49b0, 0xb7, 0x17, 0x72, 0xe2, 0x18, 0xa2, 0x18, 0x5c)] 66 | interface ISetupInstance2(ISetupInstance2Vtbl): ISetupInstance(ISetupInstanceVtbl) { 67 | fn GetState( 68 | pState: *mut InstanceState, 69 | ) -> HRESULT, 70 | fn GetPackages( 71 | ppsaPackages: *mut LPSAFEARRAY, 72 | ) -> HRESULT, 73 | fn GetProduct( 74 | ppPackage: *mut *mut ISetupPackageReference, 75 | ) -> HRESULT, 76 | fn GetProductPath( 77 | pbstrProductPath: *mut BSTR, 78 | ) -> HRESULT, 79 | }} 80 | 81 | RIDL! {#[uuid(0x6380bcff, 0x41d3, 0x4b2e, 0x8b, 0x2e, 0xbf, 0x8a, 0x68, 0x10, 0xc8, 0x48)] 82 | interface IEnumSetupInstances(IEnumSetupInstancesVtbl): IUnknown(IUnknownVtbl) { 83 | fn Next( 84 | celt: ULONG, 85 | rgelt: *mut *mut ISetupInstance, 86 | pceltFetched: *mut ULONG, 87 | ) -> HRESULT, 88 | fn Skip( 89 | celt: ULONG, 90 | ) -> HRESULT, 91 | fn Reset() -> HRESULT, 92 | fn Clone( 93 | ppenum: *mut *mut IEnumSetupInstances, 94 | ) -> HRESULT, 95 | }} 96 | 97 | RIDL! {#[uuid(0x42843719, 0xdb4c, 0x46c2, 0x8e, 0x7c, 0x64, 0xf1, 0x81, 0x6e, 0xfd, 0x5b)] 98 | interface ISetupConfiguration(ISetupConfigurationVtbl): IUnknown(IUnknownVtbl) { 99 | fn EnumInstances( 100 | ppEnumInstances: *mut *mut IEnumSetupInstances, 101 | ) -> HRESULT, 102 | fn GetInstanceForCurrentProcess( 103 | ppInstance: *mut *mut ISetupInstance, 104 | ) -> HRESULT, 105 | fn GetInstanceForPath( 106 | wzPath: LPCWSTR, 107 | ppInstance: *mut *mut ISetupInstance, 108 | ) -> HRESULT, 109 | }} 110 | 111 | RIDL! {#[uuid(0x26aab78c, 0x4a60, 0x49d6, 0xaf, 0x3b, 0x3c, 0x35, 0xbc, 0x93, 0x36, 0x5d)] 112 | interface ISetupConfiguration2(ISetupConfiguration2Vtbl): 113 | ISetupConfiguration(ISetupConfigurationVtbl) { 114 | fn EnumAllInstances( 115 | ppEnumInstances: *mut *mut IEnumSetupInstances, 116 | ) -> HRESULT, 117 | }} 118 | 119 | RIDL! {#[uuid(0xda8d8a16, 0xb2b6, 0x4487, 0xa2, 0xf1, 0x59, 0x4c, 0xcc, 0xcd, 0x6b, 0xf5)] 120 | interface ISetupPackageReference(ISetupPackageReferenceVtbl): IUnknown(IUnknownVtbl) { 121 | fn GetId( 122 | pbstrId: *mut BSTR, 123 | ) -> HRESULT, 124 | fn GetVersion( 125 | pbstrVersion: *mut BSTR, 126 | ) -> HRESULT, 127 | fn GetChip( 128 | pbstrChip: *mut BSTR, 129 | ) -> HRESULT, 130 | fn GetLanguage( 131 | pbstrLanguage: *mut BSTR, 132 | ) -> HRESULT, 133 | fn GetBranch( 134 | pbstrBranch: *mut BSTR, 135 | ) -> HRESULT, 136 | fn GetType( 137 | pbstrType: *mut BSTR, 138 | ) -> HRESULT, 139 | fn GetUniqueId( 140 | pbstrUniqueId: *mut BSTR, 141 | ) -> HRESULT, 142 | }} 143 | 144 | RIDL! {#[uuid(0x42b21b78, 0x6192, 0x463e, 0x87, 0xbf, 0xd5, 0x77, 0x83, 0x8f, 0x1d, 0x5c)] 145 | interface ISetupHelper(ISetupHelperVtbl): IUnknown(IUnknownVtbl) { 146 | fn ParseVersion( 147 | pwszVersion: LPCOLESTR, 148 | pullVersion: PULONGLONG, 149 | ) -> HRESULT, 150 | fn ParseVersionRange( 151 | pwszVersionRange: LPCOLESTR, 152 | pullMinVersion: PULONGLONG, 153 | pullMaxVersion: PULONGLONG, 154 | ) -> HRESULT, 155 | }} 156 | 157 | DEFINE_GUID! {CLSID_SetupConfiguration, 158 | 0x177f0c4a, 0x1cd3, 0x4de7, 0xa3, 0x2c, 0x71, 0xdb, 0xbb, 0x9f, 0xa3, 0x6d} 159 | 160 | // Safe wrapper around the COM interfaces 161 | pub struct SetupConfiguration(ComPtr); 162 | 163 | impl SetupConfiguration { 164 | pub fn new() -> Result { 165 | let mut obj = null_mut(); 166 | let err = unsafe { 167 | CoCreateInstance( 168 | &CLSID_SetupConfiguration, 169 | null_mut(), 170 | CLSCTX_ALL, 171 | &ISetupConfiguration::uuidof(), 172 | &mut obj, 173 | ) 174 | }; 175 | if err < 0 { 176 | return Err(err); 177 | } 178 | let obj = unsafe { ComPtr::from_raw(obj as *mut ISetupConfiguration) }; 179 | Ok(SetupConfiguration(obj)) 180 | } 181 | pub fn get_instance_for_current_process(&self) -> Result { 182 | let mut obj = null_mut(); 183 | let err = unsafe { self.0.GetInstanceForCurrentProcess(&mut obj) }; 184 | if err < 0 { 185 | return Err(err); 186 | } 187 | Ok(unsafe { SetupInstance::from_raw(obj) }) 188 | } 189 | pub fn enum_instances(&self) -> Result { 190 | let mut obj = null_mut(); 191 | let err = unsafe { self.0.EnumInstances(&mut obj) }; 192 | if err < 0 { 193 | return Err(err); 194 | } 195 | Ok(unsafe { EnumSetupInstances::from_raw(obj) }) 196 | } 197 | pub fn enum_all_instances(&self) -> Result { 198 | let mut obj = null_mut(); 199 | let this = self.0.cast::()?; 200 | let err = unsafe { this.EnumAllInstances(&mut obj) }; 201 | if err < 0 { 202 | return Err(err); 203 | } 204 | Ok(unsafe { EnumSetupInstances::from_raw(obj) }) 205 | } 206 | } 207 | 208 | pub struct SetupInstance(ComPtr); 209 | 210 | impl SetupInstance { 211 | pub unsafe fn from_raw(obj: *mut ISetupInstance) -> SetupInstance { 212 | SetupInstance(ComPtr::from_raw(obj)) 213 | } 214 | pub fn instance_id(&self) -> Result { 215 | let mut s = null(); 216 | let err = unsafe { self.0.GetInstanceId(&mut s) }; 217 | let bstr = unsafe { BStr::from_raw(s) }; 218 | if err < 0 { 219 | return Err(err); 220 | } 221 | Ok(bstr.to_osstring()) 222 | } 223 | pub fn installation_name(&self) -> Result { 224 | let mut s = null(); 225 | let err = unsafe { self.0.GetInstallationName(&mut s) }; 226 | let bstr = unsafe { BStr::from_raw(s) }; 227 | if err < 0 { 228 | return Err(err); 229 | } 230 | Ok(bstr.to_osstring()) 231 | } 232 | pub fn installation_path(&self) -> Result { 233 | let mut s = null(); 234 | let err = unsafe { self.0.GetInstallationPath(&mut s) }; 235 | let bstr = unsafe { BStr::from_raw(s) }; 236 | if err < 0 { 237 | return Err(err); 238 | } 239 | Ok(bstr.to_osstring()) 240 | } 241 | pub fn installation_version(&self) -> Result { 242 | let mut s = null(); 243 | let err = unsafe { self.0.GetInstallationVersion(&mut s) }; 244 | let bstr = unsafe { BStr::from_raw(s) }; 245 | if err < 0 { 246 | return Err(err); 247 | } 248 | Ok(bstr.to_osstring()) 249 | } 250 | pub fn product_path(&self) -> Result { 251 | let mut s = null(); 252 | let this = self.0.cast::()?; 253 | let err = unsafe { this.GetProductPath(&mut s) }; 254 | let bstr = unsafe { BStr::from_raw(s) }; 255 | if err < 0 { 256 | return Err(err); 257 | } 258 | Ok(bstr.to_osstring()) 259 | } 260 | } 261 | 262 | pub struct EnumSetupInstances(ComPtr); 263 | 264 | impl EnumSetupInstances { 265 | pub unsafe fn from_raw(obj: *mut IEnumSetupInstances) -> EnumSetupInstances { 266 | EnumSetupInstances(ComPtr::from_raw(obj)) 267 | } 268 | } 269 | 270 | impl Iterator for EnumSetupInstances { 271 | type Item = Result; 272 | fn next(&mut self) -> Option> { 273 | let mut obj = null_mut(); 274 | let err = unsafe { self.0.Next(1, &mut obj, null_mut()) }; 275 | if err < 0 { 276 | return Some(Err(err)); 277 | } 278 | if err == S_FALSE { 279 | return None; 280 | } 281 | Some(Ok(unsafe { SetupInstance::from_raw(obj) })) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/windows/vs_instances.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashMap; 3 | use std::convert::TryFrom; 4 | use std::io::BufRead; 5 | use std::path::PathBuf; 6 | 7 | use crate::windows::setup_config::{EnumSetupInstances, SetupInstance}; 8 | 9 | pub enum VsInstance { 10 | Com(SetupInstance), 11 | Vswhere(VswhereInstance), 12 | } 13 | 14 | impl VsInstance { 15 | pub fn installation_name(&self) -> Option> { 16 | match self { 17 | VsInstance::Com(s) => s 18 | .installation_name() 19 | .ok() 20 | .and_then(|s| s.into_string().ok()) 21 | .map(Cow::from), 22 | VsInstance::Vswhere(v) => v.map.get("installationName").map(Cow::from), 23 | } 24 | } 25 | 26 | pub fn installation_path(&self) -> Option { 27 | match self { 28 | VsInstance::Com(s) => s.installation_path().ok().map(PathBuf::from), 29 | VsInstance::Vswhere(v) => v.map.get("installationPath").map(PathBuf::from), 30 | } 31 | } 32 | 33 | pub fn installation_version(&self) -> Option> { 34 | match self { 35 | VsInstance::Com(s) => s 36 | .installation_version() 37 | .ok() 38 | .and_then(|s| s.into_string().ok()) 39 | .map(Cow::from), 40 | VsInstance::Vswhere(v) => v.map.get("installationVersion").map(Cow::from), 41 | } 42 | } 43 | } 44 | 45 | pub enum VsInstances { 46 | ComBased(EnumSetupInstances), 47 | VswhereBased(VswhereInstance), 48 | } 49 | 50 | impl IntoIterator for VsInstances { 51 | type Item = VsInstance; 52 | #[allow(bare_trait_objects)] 53 | type IntoIter = Box>; 54 | 55 | fn into_iter(self) -> Self::IntoIter { 56 | match self { 57 | VsInstances::ComBased(e) => { 58 | Box::new(e.into_iter().filter_map(Result::ok).map(VsInstance::Com)) 59 | } 60 | VsInstances::VswhereBased(v) => Box::new(std::iter::once(VsInstance::Vswhere(v))), 61 | } 62 | } 63 | } 64 | 65 | #[derive(Debug)] 66 | pub struct VswhereInstance { 67 | map: HashMap, 68 | } 69 | 70 | impl TryFrom<&Vec> for VswhereInstance { 71 | type Error = &'static str; 72 | 73 | fn try_from(output: &Vec) -> Result { 74 | let map: HashMap<_, _> = output 75 | .lines() 76 | .map_while(Result::ok) 77 | .filter_map(|s| { 78 | let mut splitn = s.splitn(2, ": "); 79 | Some((splitn.next()?.to_owned(), splitn.next()?.to_owned())) 80 | }) 81 | .collect(); 82 | 83 | if !map.contains_key("installationName") 84 | || !map.contains_key("installationPath") 85 | || !map.contains_key("installationVersion") 86 | { 87 | return Err("required properties not found"); 88 | } 89 | 90 | Ok(Self { map }) 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests_ { 96 | use std::borrow::Cow; 97 | use std::convert::TryFrom; 98 | use std::path::PathBuf; 99 | 100 | #[test] 101 | fn it_parses_vswhere_output_correctly() { 102 | let output = br"instanceId: 58104422 103 | installDate: 21/02/2021 21:50:33 104 | installationName: VisualStudio/16.9.2+31112.23 105 | installationPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools 106 | installationVersion: 16.9.31112.23 107 | productId: Microsoft.VisualStudio.Product.BuildTools 108 | productPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\LaunchDevCmd.bat 109 | state: 4294967295 110 | isComplete: 1 111 | isLaunchable: 1 112 | isPrerelease: 0 113 | isRebootRequired: 0 114 | displayName: Visual Studio Build Tools 2019 115 | description: The Visual Studio Build Tools allows you to build native and managed MSBuild-based applications without requiring the Visual Studio IDE. There are options to install the Visual C++ compilers and libraries, MFC, ATL, and C++/CLI support. 116 | channelId: VisualStudio.16.Release 117 | channelUri: https://aka.ms/vs/16/release/channel 118 | enginePath: C:\Program Files (x86)\Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service 119 | releaseNotes: https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes-v16.9#16.9.2 120 | thirdPartyNotices: https://go.microsoft.com/fwlink/?LinkId=660909 121 | updateDate: 2021-03-17T21:16:46.5963702Z 122 | catalog_buildBranch: d16.9 123 | catalog_buildVersion: 16.9.31112.23 124 | catalog_id: VisualStudio/16.9.2+31112.23 125 | catalog_localBuild: build-lab 126 | catalog_manifestName: VisualStudio 127 | catalog_manifestType: installer 128 | catalog_productDisplayVersion: 16.9.2 129 | catalog_productLine: Dev16 130 | catalog_productLineVersion: 2019 131 | catalog_productMilestone: RTW 132 | catalog_productMilestoneIsPreRelease: False 133 | catalog_productName: Visual Studio 134 | catalog_productPatchVersion: 2 135 | catalog_productPreReleaseMilestoneSuffix: 1.0 136 | catalog_productSemanticVersion: 16.9.2+31112.23 137 | catalog_requiredEngineVersion: 2.9.3365.38425 138 | properties_campaignId: 156063665.1613940062 139 | properties_channelManifestId: VisualStudio.16.Release/16.9.2+31112.23 140 | properties_nickname: 141 | properties_setupEngineFilePath: C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installershell.exe 142 | " 143 | .to_vec(); 144 | 145 | let vswhere_instance = super::VswhereInstance::try_from(&output); 146 | assert!(vswhere_instance.is_ok()); 147 | 148 | let vs_instance = super::VsInstance::Vswhere(vswhere_instance.unwrap()); 149 | assert_eq!( 150 | vs_instance.installation_name(), 151 | Some(Cow::from("VisualStudio/16.9.2+31112.23")) 152 | ); 153 | assert_eq!( 154 | vs_instance.installation_path(), 155 | Some(PathBuf::from( 156 | r"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" 157 | )) 158 | ); 159 | assert_eq!( 160 | vs_instance.installation_version(), 161 | Some(Cow::from("16.9.31112.23")) 162 | ); 163 | } 164 | 165 | #[test] 166 | fn it_returns_an_error_for_empty_output() { 167 | let output = b"".to_vec(); 168 | 169 | let vswhere_instance = super::VswhereInstance::try_from(&output); 170 | 171 | assert!(vswhere_instance.is_err()); 172 | } 173 | 174 | #[test] 175 | fn it_returns_an_error_for_output_consisting_of_empty_lines() { 176 | let output = br" 177 | 178 | " 179 | .to_vec(); 180 | 181 | let vswhere_instance = super::VswhereInstance::try_from(&output); 182 | 183 | assert!(vswhere_instance.is_err()); 184 | } 185 | 186 | #[test] 187 | fn it_returns_an_error_for_output_without_required_properties() { 188 | let output = br"instanceId: 58104422 189 | installDate: 21/02/2021 21:50:33 190 | productId: Microsoft.VisualStudio.Product.BuildTools 191 | productPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\LaunchDevCmd.bat 192 | " 193 | .to_vec(); 194 | 195 | let vswhere_instance = super::VswhereInstance::try_from(&output); 196 | 197 | assert!(vswhere_instance.is_err()); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/windows/winapi.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2015-2017 winapi-rs developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT license 4 | // , at your option. 5 | // All files in the project carrying such notice may not be copied, modified, or distributed 6 | // except according to those terms. 7 | 8 | #![allow(bad_style, clippy::upper_case_acronyms)] 9 | 10 | use std::os::raw; 11 | 12 | pub type wchar_t = u16; 13 | 14 | pub use crate::windows::windows_sys::{FILETIME, GUID, HRESULT, SAFEARRAY}; 15 | 16 | pub type REFIID = *const IID; 17 | pub type IID = GUID; 18 | pub type ULONG = raw::c_ulong; 19 | pub type DWORD = u32; 20 | pub type LPFILETIME = *mut FILETIME; 21 | pub type OLECHAR = WCHAR; 22 | pub type WCHAR = wchar_t; 23 | pub type LPCOLESTR = *const OLECHAR; 24 | pub type LCID = DWORD; 25 | pub type LPCWSTR = *const WCHAR; 26 | pub type PULONGLONG = *mut ULONGLONG; 27 | pub type ULONGLONG = u64; 28 | 29 | pub trait Interface { 30 | fn uuidof() -> GUID; 31 | } 32 | 33 | pub type LPSAFEARRAY = *mut SAFEARRAY; 34 | 35 | macro_rules! DEFINE_GUID { 36 | ( 37 | $name:ident, $l:expr, $w1:expr, $w2:expr, 38 | $b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr 39 | ) => { 40 | pub const $name: $crate::windows::winapi::GUID = $crate::windows::winapi::GUID { 41 | data1: $l, 42 | data2: $w1, 43 | data3: $w2, 44 | data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], 45 | }; 46 | }; 47 | } 48 | 49 | macro_rules! RIDL { 50 | (#[uuid($($uuid:expr),+)] 51 | interface $interface:ident ($vtbl:ident) {$( 52 | fn $method:ident($($p:ident : $t:ty,)*) -> $rtr:ty, 53 | )+}) => ( 54 | #[repr(C)] 55 | pub struct $vtbl { 56 | $(pub $method: unsafe extern "system" fn( 57 | This: *mut $interface, 58 | $($p: $t),* 59 | ) -> $rtr,)+ 60 | } 61 | #[repr(C)] 62 | pub struct $interface { 63 | pub lpVtbl: *const $vtbl, 64 | } 65 | RIDL!{@impl $interface {$(fn $method($($p: $t,)*) -> $rtr,)+}} 66 | RIDL!{@uuid $interface $($uuid),+} 67 | ); 68 | (#[uuid($($uuid:expr),+)] 69 | interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) { 70 | }) => ( 71 | #[repr(C)] 72 | pub struct $vtbl { 73 | pub parent: $pvtbl, 74 | } 75 | #[repr(C)] 76 | pub struct $interface { 77 | pub lpVtbl: *const $vtbl, 78 | } 79 | RIDL!{@deref $interface $pinterface} 80 | RIDL!{@uuid $interface $($uuid),+} 81 | ); 82 | (#[uuid($($uuid:expr),+)] 83 | interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) {$( 84 | fn $method:ident($($p:ident : $t:ty,)*) -> $rtr:ty, 85 | )+}) => ( 86 | #[repr(C)] 87 | pub struct $vtbl { 88 | pub parent: $pvtbl, 89 | $(pub $method: unsafe extern "system" fn( 90 | This: *mut $interface, 91 | $($p: $t,)* 92 | ) -> $rtr,)+ 93 | } 94 | #[repr(C)] 95 | pub struct $interface { 96 | pub lpVtbl: *const $vtbl, 97 | } 98 | RIDL!{@impl $interface {$(fn $method($($p: $t,)*) -> $rtr,)+}} 99 | RIDL!{@deref $interface $pinterface} 100 | RIDL!{@uuid $interface $($uuid),+} 101 | ); 102 | (@deref $interface:ident $pinterface:ident) => ( 103 | impl ::std::ops::Deref for $interface { 104 | type Target = $pinterface; 105 | #[inline] 106 | fn deref(&self) -> &$pinterface { 107 | unsafe { &*(self as *const $interface as *const $pinterface) } 108 | } 109 | } 110 | ); 111 | (@impl $interface:ident {$( 112 | fn $method:ident($($p:ident : $t:ty,)*) -> $rtr:ty, 113 | )+}) => ( 114 | impl $interface { 115 | $(#[inline] pub unsafe fn $method(&self, $($p: $t,)*) -> $rtr { 116 | ((*self.lpVtbl).$method)(self as *const _ as *mut _, $($p,)*) 117 | })+ 118 | } 119 | ); 120 | (@uuid $interface:ident 121 | $l:expr, $w1:expr, $w2:expr, 122 | $b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr 123 | ) => ( 124 | impl $crate::windows::winapi::Interface for $interface { 125 | #[inline] 126 | fn uuidof() -> $crate::windows::winapi::GUID { 127 | $crate::windows::winapi::GUID { 128 | data1: $l, 129 | data2: $w1, 130 | data3: $w2, 131 | data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], 132 | } 133 | } 134 | } 135 | ); 136 | } 137 | 138 | RIDL! {#[uuid(0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46)] 139 | interface IUnknown(IUnknownVtbl) { 140 | fn QueryInterface( 141 | riid: REFIID, 142 | ppvObject: *mut *mut raw::c_void, 143 | ) -> HRESULT, 144 | fn AddRef() -> ULONG, 145 | fn Release() -> ULONG, 146 | }} 147 | -------------------------------------------------------------------------------- /src/windows/windows_link.rs: -------------------------------------------------------------------------------- 1 | //! Provides the `link!` macro used by the generated windows bindings. 2 | //! 3 | //! This is a simple wrapper around an `extern` block with a `#[link]` attribute. 4 | //! It's very roughly equivalent to the windows-targets crate. 5 | 6 | macro_rules! link_macro { 7 | ($library:literal $abi:literal $($link_name:literal)? $(#[$doc:meta])? fn $($function:tt)*) => ( 8 | // Note: the windows-targets crate uses a pre-built Windows.lib import library which we don't 9 | // have in this repo. So instead we always link kernel32.lib and add the rest of the import 10 | // libraries below by using an empty extern block. This works because extern blocks are not 11 | // connected to the library given in the #[link] attribute. 12 | #[link(name = "kernel32")] 13 | extern $abi { 14 | $(#[link_name=$link_name])? 15 | pub fn $($function)*; 16 | } 17 | ) 18 | } 19 | pub(crate) use link_macro as link; 20 | -------------------------------------------------------------------------------- /src/windows/windows_sys.rs: -------------------------------------------------------------------------------- 1 | // This file is autogenerated. 2 | // 3 | // To add bindings, edit windows_sys.lst then run: 4 | // 5 | // ``` 6 | // cd generate-windows-sys/ 7 | // cargo run 8 | // ``` 9 | // Bindings generated by `windows-bindgen` 0.61.1 10 | 11 | #![allow( 12 | non_snake_case, 13 | non_upper_case_globals, 14 | non_camel_case_types, 15 | dead_code, 16 | clippy::all 17 | )] 18 | 19 | windows_link::link!("ole32.dll" "system" fn CoCreateInstance(rclsid : *const GUID, punkouter : * mut core::ffi::c_void, dwclscontext : CLSCTX, riid : *const GUID, ppv : *mut *mut core::ffi::c_void) -> HRESULT); 20 | windows_link::link!("ole32.dll" "system" fn CoInitializeEx(pvreserved : *const core::ffi::c_void, dwcoinit : u32) -> HRESULT); 21 | windows_link::link!("kernel32.dll" "system" fn FreeLibrary(hlibmodule : HMODULE) -> BOOL); 22 | windows_link::link!("kernel32.dll" "system" fn GetMachineTypeAttributes(machine : u16, machinetypeattributes : *mut MACHINE_ATTRIBUTES) -> HRESULT); 23 | windows_link::link!("kernel32.dll" "system" fn GetProcAddress(hmodule : HMODULE, lpprocname : PCSTR) -> FARPROC); 24 | windows_link::link!("kernel32.dll" "system" fn LoadLibraryA(lplibfilename : PCSTR) -> HMODULE); 25 | windows_link::link!("kernel32.dll" "system" fn OpenSemaphoreA(dwdesiredaccess : u32, binherithandle : BOOL, lpname : PCSTR) -> HANDLE); 26 | windows_link::link!("kernel32.dll" "system" fn PeekNamedPipe(hnamedpipe : HANDLE, lpbuffer : *mut core::ffi::c_void, nbuffersize : u32, lpbytesread : *mut u32, lptotalbytesavail : *mut u32, lpbytesleftthismessage : *mut u32) -> BOOL); 27 | windows_link::link!("advapi32.dll" "system" fn RegCloseKey(hkey : HKEY) -> WIN32_ERROR); 28 | windows_link::link!("advapi32.dll" "system" fn RegEnumKeyExW(hkey : HKEY, dwindex : u32, lpname : PWSTR, lpcchname : *mut u32, lpreserved : *const u32, lpclass : PWSTR, lpcchclass : *mut u32, lpftlastwritetime : *mut FILETIME) -> WIN32_ERROR); 29 | windows_link::link!("advapi32.dll" "system" fn RegOpenKeyExW(hkey : HKEY, lpsubkey : PCWSTR, uloptions : u32, samdesired : REG_SAM_FLAGS, phkresult : *mut HKEY) -> WIN32_ERROR); 30 | windows_link::link!("advapi32.dll" "system" fn RegQueryValueExW(hkey : HKEY, lpvaluename : PCWSTR, lpreserved : *const u32, lptype : *mut REG_VALUE_TYPE, lpdata : *mut u8, lpcbdata : *mut u32) -> WIN32_ERROR); 31 | windows_link::link!("kernel32.dll" "system" fn ReleaseSemaphore(hsemaphore : HANDLE, lreleasecount : i32, lppreviouscount : *mut i32) -> BOOL); 32 | windows_link::link!("oleaut32.dll" "system" fn SysFreeString(bstrstring : BSTR)); 33 | windows_link::link!("oleaut32.dll" "system" fn SysStringLen(pbstr : BSTR) -> u32); 34 | windows_link::link!("kernel32.dll" "system" fn WaitForSingleObject(hhandle : HANDLE, dwmilliseconds : u32) -> WAIT_EVENT); 35 | pub type ADVANCED_FEATURE_FLAGS = u16; 36 | pub type BOOL = i32; 37 | pub type BSTR = *const u16; 38 | pub type CLSCTX = u32; 39 | pub const CLSCTX_ALL: CLSCTX = 23u32; 40 | pub type COINIT = i32; 41 | pub const COINIT_MULTITHREADED: COINIT = 0i32; 42 | pub const ERROR_NO_MORE_ITEMS: WIN32_ERROR = 259u32; 43 | pub const ERROR_SUCCESS: WIN32_ERROR = 0u32; 44 | pub const FALSE: BOOL = 0i32; 45 | pub type FARPROC = Option isize>; 46 | #[repr(C)] 47 | #[derive(Clone, Copy, Default)] 48 | pub struct FILETIME { 49 | pub dwLowDateTime: u32, 50 | pub dwHighDateTime: u32, 51 | } 52 | pub const FILE_ATTRIBUTE_TEMPORARY: FILE_FLAGS_AND_ATTRIBUTES = 256u32; 53 | pub type FILE_FLAGS_AND_ATTRIBUTES = u32; 54 | #[repr(C)] 55 | #[derive(Clone, Copy)] 56 | pub struct GUID { 57 | pub data1: u32, 58 | pub data2: u16, 59 | pub data3: u16, 60 | pub data4: [u8; 8], 61 | } 62 | impl GUID { 63 | pub const fn from_u128(uuid: u128) -> Self { 64 | Self { 65 | data1: (uuid >> 96) as u32, 66 | data2: (uuid >> 80 & 0xffff) as u16, 67 | data3: (uuid >> 64 & 0xffff) as u16, 68 | data4: (uuid as u64).to_be_bytes(), 69 | } 70 | } 71 | } 72 | pub type HANDLE = *mut core::ffi::c_void; 73 | pub type HINSTANCE = *mut core::ffi::c_void; 74 | pub type HKEY = *mut core::ffi::c_void; 75 | pub const HKEY_LOCAL_MACHINE: HKEY = -2147483646i32 as _; 76 | pub type HMODULE = *mut core::ffi::c_void; 77 | pub type HRESULT = i32; 78 | pub type IMAGE_FILE_MACHINE = u16; 79 | pub const IMAGE_FILE_MACHINE_AMD64: IMAGE_FILE_MACHINE = 34404u16; 80 | pub const IID_IUnknown: GUID = GUID::from_u128(0x00000000_0000_0000_c000_000000000046); 81 | #[repr(C)] 82 | pub struct IUnknown_Vtbl { 83 | pub QueryInterface: unsafe extern "system" fn( 84 | this: *mut core::ffi::c_void, 85 | iid: *const GUID, 86 | interface: *mut *mut core::ffi::c_void, 87 | ) -> HRESULT, 88 | pub AddRef: unsafe extern "system" fn(this: *mut core::ffi::c_void) -> u32, 89 | pub Release: unsafe extern "system" fn(this: *mut core::ffi::c_void) -> u32, 90 | } 91 | pub const KEY_READ: REG_SAM_FLAGS = 131097u32; 92 | pub const KEY_WOW64_32KEY: REG_SAM_FLAGS = 512u32; 93 | pub type MACHINE_ATTRIBUTES = i32; 94 | pub type PCSTR = *const u8; 95 | pub type PCWSTR = *const u16; 96 | pub type PWSTR = *mut u16; 97 | pub type REG_SAM_FLAGS = u32; 98 | pub const REG_SZ: REG_VALUE_TYPE = 1u32; 99 | pub type REG_VALUE_TYPE = u32; 100 | #[repr(C)] 101 | #[derive(Clone, Copy)] 102 | pub struct SAFEARRAY { 103 | pub cDims: u16, 104 | pub fFeatures: ADVANCED_FEATURE_FLAGS, 105 | pub cbElements: u32, 106 | pub cLocks: u32, 107 | pub pvData: *mut core::ffi::c_void, 108 | pub rgsabound: [SAFEARRAYBOUND; 1], 109 | } 110 | impl Default for SAFEARRAY { 111 | fn default() -> Self { 112 | unsafe { core::mem::zeroed() } 113 | } 114 | } 115 | #[repr(C)] 116 | #[derive(Clone, Copy, Default)] 117 | pub struct SAFEARRAYBOUND { 118 | pub cElements: u32, 119 | pub lLbound: i32, 120 | } 121 | pub const SEMAPHORE_MODIFY_STATE: SYNCHRONIZATION_ACCESS_RIGHTS = 2u32; 122 | pub type SYNCHRONIZATION_ACCESS_RIGHTS = u32; 123 | pub const S_FALSE: HRESULT = 0x1_u32 as _; 124 | pub const S_OK: HRESULT = 0x0_u32 as _; 125 | pub type THREAD_ACCESS_RIGHTS = u32; 126 | pub const THREAD_SYNCHRONIZE: THREAD_ACCESS_RIGHTS = 1048576u32; 127 | pub const UserEnabled: MACHINE_ATTRIBUTES = 1i32; 128 | pub const WAIT_ABANDONED: WAIT_EVENT = 128u32; 129 | pub type WAIT_EVENT = u32; 130 | pub const WAIT_FAILED: WAIT_EVENT = 4294967295u32; 131 | pub const WAIT_OBJECT_0: WAIT_EVENT = 0u32; 132 | pub const WAIT_TIMEOUT: WAIT_EVENT = 258u32; 133 | pub type WIN32_ERROR = u32; 134 | 135 | #[link(name = "advapi32")] 136 | #[link(name = "ole32")] 137 | #[link(name = "oleaut32")] 138 | extern "C" {} 139 | use super::windows_link; 140 | -------------------------------------------------------------------------------- /tests/archiver.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | #[test] 4 | fn main() { 5 | unsafe { env::set_var("AR_i586_pc_nto_qnx700", "custom-ar") }; 6 | let ar = get_ar_for_target("i586-pc-nto-qnx700"); 7 | assert_eq!(ar, "custom-ar"); 8 | unsafe { env::remove_var("AR_i586_pc_nto_qnx700") }; 9 | 10 | unsafe { env::set_var("AR", "custom-ar2") }; 11 | let ar = get_ar_for_target("x86_64-unknown-linux-gnu"); 12 | assert_eq!(ar, "custom-ar2"); 13 | unsafe { env::remove_var("AR") }; 14 | 15 | let ar = get_ar_for_target("x86_64-unknown-linux-gnu"); 16 | assert_eq!(ar, "ar"); 17 | 18 | let ar = get_ar_for_target("x86_64-unknown-linux-musl"); 19 | assert_eq!(ar, "ar"); 20 | 21 | let ar = get_ar_for_target("riscv64gc-unknown-openbsd"); 22 | assert_eq!(ar, "ar"); 23 | 24 | let ar = get_ar_for_target("i686-wrs-vxworks"); 25 | assert_eq!(ar, "wr-ar"); 26 | 27 | let ar = get_ar_for_target("i586-pc-nto-qnx700"); 28 | assert_eq!(ar, "ntox86-ar"); 29 | 30 | let ar = get_ar_for_target("aarch64-unknown-nto-qnx700"); 31 | assert_eq!(ar, "ntoaarch64-ar"); 32 | 33 | let ar = get_ar_for_target("x86_64-pc-nto-qnx710"); 34 | assert_eq!(ar, "ntox86_64-ar"); 35 | 36 | let ar = get_ar_for_target("wasm32-wasip1"); 37 | assert!( 38 | // `llvm-ar` is usually an absolute path for this target, so we check it with `ends_with`. 39 | ar.ends_with(&maybe_exe("llvm-ar")) 40 | // If `llvm-ar` doesn't exist, the logic falls back to `ar` for this target. 41 | || ar == "ar" 42 | ); 43 | 44 | let ar = get_ar_for_target("riscv64-linux-android"); 45 | // If `llvm-ar` is not available on the system, this will fall back to `$target-ar` (e.g., `riscv64-linux-android-ar` in this case) 46 | assert!(ar == "llvm-ar" || ar == "riscv64-linux-android-ar"); 47 | } 48 | 49 | fn get_ar_for_target(target: &'static str) -> String { 50 | let mut cfg = cc::Build::new(); 51 | cfg.host("x86_64-unknown-linux-gnu").target(target); 52 | let ar = cfg.get_archiver(); 53 | let ar = ar.get_program().to_str().unwrap().to_string(); 54 | println!("cc::Build::get_archiver -> target: '{target}': resolved archiver: '{ar}'"); 55 | ar 56 | } 57 | 58 | /// Appends `.exe` to the file name on Windows systems. 59 | fn maybe_exe(file: &'static str) -> String { 60 | if cfg!(windows) { 61 | format!("{file}.exe") 62 | } else { 63 | file.to_owned() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/cc_env.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::path::Path; 4 | 5 | mod support; 6 | use crate::support::Test; 7 | 8 | #[test] 9 | fn main() { 10 | ccache(); 11 | distcc(); 12 | ccache_spaces(); 13 | ccache_env_flags(); 14 | leading_spaces(); 15 | extra_flags(); 16 | path_to_ccache(); 17 | more_spaces(); 18 | clang_cl(); 19 | env_var_alternatives_override(); 20 | } 21 | 22 | fn ccache() { 23 | let test = Test::gnu(); 24 | 25 | env::set_var("CC", "ccache cc"); 26 | let compiler = test.gcc().file("foo.c").get_compiler(); 27 | 28 | assert_eq!(compiler.path(), Path::new("cc")); 29 | } 30 | 31 | fn ccache_spaces() { 32 | let test = Test::gnu(); 33 | test.shim("ccache"); 34 | 35 | env::set_var("CC", "ccache cc"); 36 | let compiler = test.gcc().file("foo.c").get_compiler(); 37 | assert_eq!(compiler.path(), Path::new("cc")); 38 | } 39 | 40 | fn distcc() { 41 | let test = Test::gnu(); 42 | test.shim("distcc"); 43 | 44 | env::set_var("CC", "distcc cc"); 45 | let compiler = test.gcc().file("foo.c").get_compiler(); 46 | assert_eq!(compiler.path(), Path::new("cc")); 47 | } 48 | 49 | fn ccache_env_flags() { 50 | let test = Test::gnu(); 51 | test.shim("ccache"); 52 | 53 | env::set_var("CC", "ccache lol-this-is-not-a-compiler"); 54 | let compiler = test.gcc().file("foo.c").get_compiler(); 55 | assert_eq!(compiler.path(), Path::new("lol-this-is-not-a-compiler")); 56 | assert_eq!( 57 | compiler.cc_env(), 58 | OsString::from("ccache lol-this-is-not-a-compiler") 59 | ); 60 | assert!(!compiler 61 | .cflags_env() 62 | .into_string() 63 | .unwrap() 64 | .contains("ccache")); 65 | assert!(!compiler 66 | .cflags_env() 67 | .into_string() 68 | .unwrap() 69 | .contains(" lol-this-is-not-a-compiler")); 70 | 71 | env::set_var("CC", ""); 72 | } 73 | 74 | fn leading_spaces() { 75 | let test = Test::gnu(); 76 | test.shim("ccache"); 77 | 78 | env::set_var("CC", " test "); 79 | let compiler = test.gcc().file("foo.c").get_compiler(); 80 | assert_eq!(compiler.path(), Path::new("test")); 81 | 82 | env::set_var("CC", ""); 83 | } 84 | 85 | fn extra_flags() { 86 | let test = Test::gnu(); 87 | test.shim("ccache"); 88 | 89 | env::set_var("CC", "ccache cc -m32"); 90 | let compiler = test.gcc().file("foo.c").get_compiler(); 91 | assert_eq!(compiler.path(), Path::new("cc")); 92 | } 93 | 94 | fn path_to_ccache() { 95 | let test = Test::gnu(); 96 | test.shim("ccache"); 97 | 98 | env::set_var("CC", "/path/to/ccache.exe cc -m32"); 99 | let compiler = test.gcc().file("foo.c").get_compiler(); 100 | assert_eq!(compiler.path(), Path::new("cc")); 101 | assert_eq!( 102 | compiler.cc_env(), 103 | OsString::from("/path/to/ccache.exe cc -m32"), 104 | ); 105 | } 106 | 107 | fn more_spaces() { 108 | let test = Test::gnu(); 109 | test.shim("ccache"); 110 | 111 | env::set_var("CC", "cc -m32"); 112 | let compiler = test.gcc().file("foo.c").get_compiler(); 113 | assert_eq!(compiler.path(), Path::new("cc")); 114 | } 115 | 116 | fn clang_cl() { 117 | for exe_suffix in ["", ".exe"] { 118 | let test = Test::clang(); 119 | let bin = format!("clang{exe_suffix}"); 120 | env::set_var("CC", format!("{bin} --driver-mode=cl")); 121 | let test_compiler = |build: cc::Build| { 122 | let compiler = build.get_compiler(); 123 | assert_eq!(compiler.path(), Path::new(&*bin)); 124 | assert!(compiler.is_like_msvc()); 125 | assert!(compiler.is_like_clang_cl()); 126 | }; 127 | test_compiler(test.gcc()); 128 | } 129 | } 130 | 131 | fn env_var_alternatives_override() { 132 | let compiler1 = format!("clang1{}", env::consts::EXE_SUFFIX); 133 | let compiler2 = format!("clang2{}", env::consts::EXE_SUFFIX); 134 | let compiler3 = format!("clang3{}", env::consts::EXE_SUFFIX); 135 | let compiler4 = format!("clang4{}", env::consts::EXE_SUFFIX); 136 | 137 | let test = Test::new(); 138 | test.shim(&compiler1); 139 | test.shim(&compiler2); 140 | test.shim(&compiler3); 141 | test.shim(&compiler4); 142 | 143 | env::set_var("CC", &compiler1); 144 | let compiler = test.gcc().target("x86_64-unknown-none").get_compiler(); 145 | assert_eq!(compiler.path(), Path::new(&compiler1)); 146 | 147 | env::set_var("HOST_CC", &compiler2); 148 | env::set_var("TARGET_CC", &compiler2); 149 | let compiler = test.gcc().target("x86_64-unknown-none").get_compiler(); 150 | assert_eq!(compiler.path(), Path::new(&compiler2)); 151 | 152 | env::set_var("CC_x86_64_unknown_none", &compiler3); 153 | let compiler = test.gcc().target("x86_64-unknown-none").get_compiler(); 154 | assert_eq!(compiler.path(), Path::new(&compiler3)); 155 | 156 | env::set_var("CC_x86_64-unknown-none", &compiler4); 157 | let compiler = test.gcc().target("x86_64-unknown-none").get_compiler(); 158 | assert_eq!(compiler.path(), Path::new(&compiler4)); 159 | } 160 | -------------------------------------------------------------------------------- /tests/cflags.rs: -------------------------------------------------------------------------------- 1 | //! This test is in its own module because it modifies the environment and would affect other tests 2 | //! when run in parallel with them. 3 | mod support; 4 | 5 | use crate::support::Test; 6 | use std::env; 7 | 8 | #[test] 9 | fn cflags() { 10 | gnu_no_warnings_if_cflags(); 11 | cflags_order(); 12 | } 13 | 14 | fn gnu_no_warnings_if_cflags() { 15 | env::set_var("CFLAGS", "-arbitrary"); 16 | let test = Test::gnu(); 17 | test.gcc().file("foo.c").compile("foo"); 18 | 19 | test.cmd(0).must_not_have("-Wall").must_not_have("-Wextra"); 20 | } 21 | 22 | /// Test the ordering of flags. 23 | /// 24 | /// 1. Default flags 25 | /// 2. Rustflags. 26 | /// 3. Builder flags. 27 | /// 4. Environment flags. 28 | fn cflags_order() { 29 | // FIXME(madsmtm): Re-enable once `is_flag_supported` works in CI regardless of `target`. 30 | // unsafe { std::env::set_var("CARGO_ENCODED_RUSTFLAGS", "-Cdwarf-version=5") }; 31 | 32 | unsafe { env::set_var("CFLAGS", "-Larbitrary1") }; 33 | unsafe { env::set_var("HOST_CFLAGS", "-Larbitrary2") }; 34 | unsafe { env::set_var("TARGET_CFLAGS", "-Larbitrary2") }; 35 | unsafe { env::set_var("CFLAGS_x86_64_unknown_none", "-Larbitrary3") }; 36 | unsafe { env::set_var("CFLAGS_x86_64-unknown-none", "-Larbitrary4") }; 37 | 38 | let test = Test::gnu(); 39 | test.gcc() 40 | .target("x86_64-unknown-none") 41 | .static_flag(true) 42 | .flag("-Lbuilder-flag1") 43 | .flag("-Lbuilder-flag2") 44 | .file("foo.c") 45 | .compile("foo"); 46 | 47 | test.cmd(0) 48 | // .must_have_in_order("-static", "-gdwarf-5") 49 | // .must_have_in_order("-gdwarf-5", "-Lbuilder-flag1") 50 | .must_have_in_order("-static", "-Lbuilder-flag1") 51 | .must_have_in_order("-Lbuilder-flag1", "-Lbuilder-flag2") 52 | .must_have_in_order("-Lbuilder-flag2", "-Larbitrary1") 53 | .must_have_in_order("-Larbitrary1", "-Larbitrary2") 54 | .must_have_in_order("-Larbitrary1", "-Larbitrary2") 55 | .must_have_in_order("-Larbitrary2", "-Larbitrary3") 56 | .must_have_in_order("-Larbitrary3", "-Larbitrary4"); 57 | } 58 | -------------------------------------------------------------------------------- /tests/cflags_shell_escaped.rs: -------------------------------------------------------------------------------- 1 | mod support; 2 | 3 | use crate::support::Test; 4 | use std::env; 5 | 6 | /// This test is in its own module because it modifies the environment and would affect other tests 7 | /// when run in parallel with them. 8 | #[test] 9 | fn gnu_test_parse_shell_escaped_flags() { 10 | env::set_var("CFLAGS", "foo \"bar baz\""); 11 | env::set_var("CC_SHELL_ESCAPED_FLAGS", "1"); 12 | let test = Test::gnu(); 13 | test.gcc().file("foo.c").compile("foo"); 14 | 15 | test.cmd(0).must_have("foo").must_have("bar baz"); 16 | 17 | env::remove_var("CC_SHELL_ESCAPED_FLAGS"); 18 | let test = Test::gnu(); 19 | test.gcc().file("foo.c").compile("foo"); 20 | 21 | test.cmd(0) 22 | .must_have("foo") 23 | .must_have_in_order("\"bar", "baz\""); 24 | } 25 | -------------------------------------------------------------------------------- /tests/cxxflags.rs: -------------------------------------------------------------------------------- 1 | mod support; 2 | 3 | use crate::support::Test; 4 | use std::env; 5 | 6 | /// This test is in its own module because it modifies the environment and would affect other tests 7 | /// when run in parallel with them. 8 | #[test] 9 | fn gnu_no_warnings_if_cxxflags() { 10 | env::set_var("CXXFLAGS", "-arbitrary"); 11 | let test = Test::gnu(); 12 | test.gcc().file("foo.cpp").cpp(true).compile("foo"); 13 | 14 | test.cmd(0).must_not_have("-Wall").must_not_have("-Wextra"); 15 | } 16 | -------------------------------------------------------------------------------- /tests/rustflags.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(windows))] 2 | use crate::support::Test; 3 | mod support; 4 | 5 | /// This test is in its own module because it modifies the environment and would affect other tests 6 | /// when run in parallel with them. 7 | #[test] 8 | #[cfg(not(windows))] 9 | fn inherits_rustflags() { 10 | // Sanity check - no flags 11 | std::env::set_var("CARGO_ENCODED_RUSTFLAGS", ""); 12 | let test = Test::gnu(); 13 | test.gcc().file("foo.c").compile("foo"); 14 | test.cmd(0) 15 | .must_not_have("-fno-omit-frame-pointer") 16 | .must_not_have("-mcmodel=small") 17 | .must_not_have("-msoft-float"); 18 | 19 | // Correctly inherits flags from rustc 20 | std::env::set_var( 21 | "CARGO_ENCODED_RUSTFLAGS", 22 | "-Cforce-frame-pointers=true\u{1f}-Ccode-model=small\u{1f}-Csoft-float\u{1f}-Cdwarf-version=5", 23 | ); 24 | let test = Test::gnu(); 25 | test.gcc().file("foo.c").compile("foo"); 26 | test.cmd(0) 27 | .must_have("-fno-omit-frame-pointer") 28 | .must_have("-mcmodel=small") 29 | .must_have("-msoft-float") 30 | .must_have("-gdwarf-5"); 31 | } 32 | -------------------------------------------------------------------------------- /tests/support/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(clippy::disallowed_methods)] 3 | 4 | use std::env; 5 | use std::ffi::{OsStr, OsString}; 6 | use std::fs::{self, File}; 7 | use std::io; 8 | use std::io::prelude::*; 9 | use std::path::{Path, PathBuf}; 10 | 11 | use tempfile::{Builder, TempDir}; 12 | 13 | pub struct Test { 14 | pub td: TempDir, 15 | pub gcc: PathBuf, 16 | pub msvc: bool, 17 | } 18 | 19 | pub struct Execution { 20 | pub args: Vec, 21 | } 22 | 23 | impl Test { 24 | pub fn new() -> Test { 25 | // This is ugly: `sccache` needs to introspect the compiler it is 26 | // executing, as it adjusts its behavior depending on the 27 | // language/compiler. This crate's test driver uses mock compilers that 28 | // are obviously not supported by sccache, so the tests fail if 29 | // RUSTC_WRAPPER is set. rust doesn't build test dependencies with 30 | // the `test` feature enabled, so we can't conditionally disable the 31 | // usage of `sccache` if running in a test environment, at least not 32 | // without setting an environment variable here and testing for it 33 | // there. Explicitly deasserting RUSTC_WRAPPER here seems to be the 34 | // lesser of the two evils. 35 | env::remove_var("RUSTC_WRAPPER"); 36 | 37 | // cc-rs prefers these env vars to the wrappers. We set these in some tests, so unset them so the wrappers get used 38 | env::remove_var("CC"); 39 | env::remove_var("CXX"); 40 | env::remove_var("AR"); 41 | 42 | let mut gcc = env::current_exe().unwrap(); 43 | gcc.pop(); 44 | if gcc.ends_with("deps") { 45 | gcc.pop(); 46 | } 47 | let td = Builder::new() 48 | .prefix("cc-shim-test") 49 | .tempdir_in(&gcc) 50 | .unwrap(); 51 | gcc.push(format!("cc-shim{}", env::consts::EXE_SUFFIX)); 52 | Test { 53 | td, 54 | gcc, 55 | msvc: false, 56 | } 57 | } 58 | 59 | pub fn gnu() -> Test { 60 | let t = Test::new(); 61 | t.shim("cc").shim("c++").shim("ar"); 62 | t 63 | } 64 | 65 | pub fn msvc() -> Test { 66 | let mut t = Test::new(); 67 | t.shim("cl").shim("lib.exe"); 68 | t.msvc = true; 69 | t 70 | } 71 | 72 | pub fn clang() -> Test { 73 | let t = Test::new(); 74 | t.shim("clang").shim("clang++").shim("ar"); 75 | t 76 | } 77 | 78 | pub fn shim(&self, name: &str) -> &Test { 79 | let name = if name.ends_with(env::consts::EXE_SUFFIX) { 80 | name.to_string() 81 | } else { 82 | format!("{}{}", name, env::consts::EXE_SUFFIX) 83 | }; 84 | link_or_copy(&self.gcc, self.td.path().join(name)).unwrap(); 85 | self 86 | } 87 | 88 | pub fn gcc(&self) -> cc::Build { 89 | let mut cfg = cc::Build::new(); 90 | let target = if self.msvc { 91 | "x86_64-pc-windows-msvc" 92 | } else if cfg!(target_os = "macos") { 93 | "x86_64-apple-darwin" 94 | } else { 95 | "x86_64-unknown-linux-gnu" 96 | }; 97 | 98 | cfg.target(target) 99 | .host(target) 100 | .opt_level(2) 101 | .debug(false) 102 | .out_dir(self.td.path()) 103 | .__set_env("PATH", self.path()) 104 | .__set_env("CC_SHIM_OUT_DIR", self.td.path()); 105 | if self.msvc { 106 | cfg.compiler(self.td.path().join("cl")); 107 | cfg.archiver(self.td.path().join("lib.exe")); 108 | } 109 | cfg 110 | } 111 | 112 | fn path(&self) -> OsString { 113 | let mut path = env::split_paths(&env::var_os("PATH").unwrap()).collect::>(); 114 | path.insert(0, self.td.path().to_owned()); 115 | env::join_paths(path).unwrap() 116 | } 117 | 118 | pub fn cmd(&self, i: u32) -> Execution { 119 | let mut s = String::new(); 120 | File::open(self.td.path().join(format!("out{}", i))) 121 | .unwrap() 122 | .read_to_string(&mut s) 123 | .unwrap(); 124 | Execution { 125 | args: s.lines().map(|s| s.to_string()).collect(), 126 | } 127 | } 128 | } 129 | 130 | impl Execution { 131 | pub fn must_have>(&self, p: P) -> &Execution { 132 | if !self.has(p.as_ref()) { 133 | panic!("didn't find {:?} in {:?}", p.as_ref(), self.args); 134 | } else { 135 | self 136 | } 137 | } 138 | 139 | pub fn must_not_have>(&self, p: P) -> &Execution { 140 | if self.has(p.as_ref()) { 141 | panic!("found {:?}", p.as_ref()); 142 | } else { 143 | self 144 | } 145 | } 146 | 147 | pub fn has(&self, p: &OsStr) -> bool { 148 | self.args.iter().any(|arg| OsStr::new(arg) == p) 149 | } 150 | 151 | pub fn must_have_in_order(&self, before: &str, after: &str) -> &Execution { 152 | let before_position = self 153 | .args 154 | .iter() 155 | .rposition(|x| OsStr::new(x) == OsStr::new(before)); 156 | let after_position = self 157 | .args 158 | .iter() 159 | .rposition(|x| OsStr::new(x) == OsStr::new(after)); 160 | match (before_position, after_position) { 161 | (Some(b), Some(a)) if b < a => {} 162 | (b, a) => panic!( 163 | "{:?} (last position: {:?}) did not appear before {:?} (last position: {:?}): {:?}", 164 | before, b, after, a, self.args 165 | ), 166 | }; 167 | self 168 | } 169 | } 170 | 171 | /// Hard link an executable or copy it if that fails. 172 | /// 173 | /// We first try to hard link an executable to save space. If that fails (as on Windows with 174 | /// different mount points, issue #60), we copy. 175 | #[cfg(not(target_os = "macos"))] 176 | fn link_or_copy, Q: AsRef>(from: P, to: Q) -> io::Result<()> { 177 | let from = from.as_ref(); 178 | let to = to.as_ref(); 179 | fs::hard_link(from, to).or_else(|_| fs::copy(from, to).map(|_| ())) 180 | } 181 | 182 | /// Copy an executable. 183 | /// 184 | /// On macOS, hard linking the executable leads to strange failures (issue #419), so we just copy. 185 | #[cfg(target_os = "macos")] 186 | fn link_or_copy, Q: AsRef>(from: P, to: Q) -> io::Result<()> { 187 | fs::copy(from, to).map(|_| ()) 188 | } 189 | --------------------------------------------------------------------------------