├── .github └── workflows │ ├── main.yml │ ├── publish.yml │ └── release-process.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md ├── ci ├── build-tarballs.sh ├── docker │ ├── aarch64-linux │ │ └── Dockerfile │ └── x86_64-linux │ │ └── Dockerfile ├── print-current-version.sh └── publish.rs ├── src ├── argfile.rs ├── lib.rs └── main.rs └── tests └── all.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | merge_group: 5 | 6 | # Cancel any in-flight jobs for the same PR/branch so there's only one active 7 | # at a time 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | jobs: 17 | build: 18 | name: Build wasm-component-ld 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | include: 23 | - build: x86_64-linux 24 | os: ubuntu-latest 25 | - build: x86_64-macos 26 | os: macos-latest 27 | target: x86_64-apple-darwin 28 | - build: aarch64-macos 29 | os: macos-latest 30 | target: aarch64-apple-darwin 31 | - build: x86_64-windows 32 | os: windows-latest 33 | - build: aarch64-linux 34 | os: ubuntu-latest 35 | target: aarch64-unknown-linux-gnu 36 | steps: 37 | - uses: actions/checkout@v4 38 | with: 39 | submodules: true 40 | - run: rustup update stable --no-self-update && rustup default stable 41 | - uses: bytecodealliance/wasmtime/.github/actions/binary-compatible-builds@v17.0.1 42 | with: 43 | name: ${{ matrix.build }} 44 | - run: | 45 | echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV 46 | rustup target add ${{ matrix.target }} 47 | if: matrix.target != '' 48 | - run: $CENTOS cargo build --release 49 | - run: ./ci/build-tarballs.sh "${{ matrix.build }}" "${{ matrix.target }}" 50 | - uses: actions/upload-artifact@v4 51 | with: 52 | name: bins-${{ matrix.build }} 53 | path: dist 54 | 55 | test: 56 | name: Test 57 | runs-on: ${{ matrix.os }} 58 | strategy: 59 | matrix: 60 | include: 61 | - os: ubuntu-latest 62 | rust: stable 63 | - os: ubuntu-latest 64 | rust: beta 65 | - os: ubuntu-latest 66 | rust: nightly 67 | - os: macos-latest 68 | rust: stable 69 | - os: windows-latest 70 | rust: stable 71 | steps: 72 | - uses: actions/checkout@v4 73 | with: 74 | submodules: true 75 | - name: Install Rust (rustup) 76 | run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} 77 | - run: rustup target add wasm32-wasip1 78 | - run: cargo test --locked 79 | 80 | msrv: 81 | name: Build MSRV 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v4 85 | with: 86 | submodules: true 87 | - name: Install Rust (rustup) 88 | run: rustup update 1.76.0 --no-self-update && rustup default 1.76.0 89 | - run: cargo build 90 | 91 | rustfmt: 92 | name: Rustfmt 93 | runs-on: ubuntu-latest 94 | steps: 95 | - uses: actions/checkout@v4 96 | - name: Install Rust 97 | run: rustup update stable && rustup default stable && rustup component add rustfmt 98 | # Note that this doesn't use `cargo fmt` because that doesn't format 99 | # modules-defined-in-macros which is in use in `wast` for example. This is 100 | # the best alternative I can come up with at this time 101 | - run: find . -name '*.rs' | xargs rustfmt --check --edition 2021 102 | 103 | # "Join node" which the merge queue waits on. 104 | ci-status: 105 | name: Record the result of testing and building steps 106 | runs-on: ubuntu-latest 107 | needs: 108 | - test 109 | - rustfmt 110 | - build 111 | - msrv 112 | if: always() 113 | 114 | steps: 115 | - name: Successful test and build 116 | if: ${{ !(contains(needs.*.result, 'failure')) }} 117 | run: exit 0 118 | - name: Failing test and build 119 | if: ${{ contains(needs.*.result, 'failure') }} 120 | run: exit 1 121 | - name: Report failure on cancellation 122 | if: ${{ contains(needs.*.result, 'cancelled') || cancelled() }} 123 | run: exit 1 124 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # Publication half of the release process for this repository. This runs on 2 | # pushes to `main` and will detect a magical string in commit messages. When 3 | # found a tag will be created, pushed, and then everything is published. 4 | 5 | name: Publish Artifacts 6 | on: 7 | push: 8 | branches: [main] 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | create_tag: 15 | name: Publish artifacts of build 16 | runs-on: ubuntu-latest 17 | if: | 18 | github.repository_owner == 'bytecodealliance' 19 | && github.event_name == 'push' 20 | && github.ref == 'refs/heads/main' 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | submodules: true 25 | fetch-depth: 0 26 | 27 | - run: rustup update stable && rustup default stable 28 | 29 | # If this is a push to `main` see if the push has an indicator saying that 30 | # a tag should be made. If so create one and push it. 31 | - name: Test if tag is needed 32 | run: | 33 | git log ${{ github.event.before }}...${{ github.event.after }} | tee main.log 34 | version=$(./ci/print-current-version.sh) 35 | echo "version: $version" 36 | echo "version=$version" >> $GITHUB_OUTPUT 37 | echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 38 | if grep -q "automatically-tag-and-release-this-commit" main.log; then 39 | echo push-tag 40 | echo "push_tag=yes" >> $GITHUB_OUTPUT 41 | else 42 | echo no-push-tag 43 | echo "push_tag=no" >> $GITHUB_OUTPUT 44 | fi 45 | id: tag 46 | 47 | - name: Push the tag 48 | run: | 49 | git_refs_url=$(jq .repository.git_refs_url $GITHUB_EVENT_PATH | tr -d '"' | sed 's/{\/sha}//g') 50 | curl -iX POST $git_refs_url \ 51 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 52 | -d @- << EOF 53 | { 54 | "ref": "refs/tags/v${{ steps.tag.outputs.version }}", 55 | "sha": "${{ steps.tag.outputs.sha }}" 56 | } 57 | EOF 58 | if: steps.tag.outputs.push_tag == 'yes' 59 | 60 | - run: | 61 | sha=${{ github.sha }} 62 | run_id=$( 63 | gh api -H 'Accept: application/vnd.github+json' \ 64 | /repos/${{ github.repository }}/actions/workflows/main.yml/runs\?exclude_pull_requests=true \ 65 | | jq '.workflow_runs' \ 66 | | jq "map(select(.head_commit.id == \"$sha\"))[0].id" \ 67 | ) 68 | gh run download $run_id 69 | ls 70 | find bins-* 71 | mkdir dist 72 | mv bins-*/* dist 73 | env: 74 | GH_TOKEN: ${{ github.token }} 75 | 76 | - uses: softprops/action-gh-release@v1 77 | if: steps.tag.outputs.push_tag == 'yes' 78 | with: 79 | files: "dist/*" 80 | tag_name: v${{ steps.tag.outputs.version }} 81 | 82 | - run: | 83 | rm -rf dist main.log 84 | rustc ci/publish.rs 85 | ./publish publish 86 | env: 87 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 88 | if: steps.tag.outputs.push_tag == 'yes' 89 | -------------------------------------------------------------------------------- /.github/workflows/release-process.yml: -------------------------------------------------------------------------------- 1 | # Initiation half of the release process for this repository. 2 | # 3 | # This is triggered manually through the github actions UI and will execute 4 | # `./publish bump` (or `bump-patch`). Afterwards the result will be pushed to a 5 | # branch in the main repository and a PR will be opened. This PR, when merged, 6 | # will trigger the second half in `publish.yml`. 7 | 8 | name: "Automated Release Process" 9 | on: 10 | # Allow manually triggering this request via the button on the action 11 | # workflow page. 12 | workflow_dispatch: 13 | inputs: 14 | action: 15 | description: 'Publish script argument: "bump", or "bump-patch"' 16 | required: false 17 | default: 'bump' 18 | 19 | permissions: 20 | contents: write 21 | pull-requests: write 22 | 23 | jobs: 24 | release_process: 25 | name: Run the release process 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: true 31 | - name: Setup 32 | run: | 33 | rustc ci/publish.rs 34 | git config user.name 'Auto Release Process' 35 | git config user.email 'auto-release-process@users.noreply.github.com' 36 | git remote set-url origin https://git:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} 37 | 38 | - name: Bump version number 39 | run: ./publish ${{ github.event.inputs.action }} 40 | 41 | - name: Prep PR metadata 42 | run: | 43 | set -ex 44 | git fetch origin 45 | 46 | cur=$(./ci/print-current-version.sh) 47 | 48 | git commit --allow-empty -a -F-<> $GITHUB_ENV 58 | echo "PR_TITLE=Release ${{ github.event.repository.name }} $cur" >> $GITHUB_ENV 59 | echo "PR_BASE=main" >> $GITHUB_ENV 60 | cat > pr-body <<-EOF 61 | This is an automated pull request from CI to release 62 | ${{ github.event.repository.name }} $cur when merged. The commit 63 | message for this PR has a marker that is detected by CI to create 64 | tags and publish crate artifacts. 65 | 66 | When first opened this PR will not have CI run because it is generated 67 | by a bot. A maintainer should close this PR and then reopen it to 68 | trigger CI to execute which will then enable merging this PR. 69 | EOF 70 | 71 | - name: Make a PR 72 | run: gh pr create -B "$PR_BASE" -H "$PR_HEAD" --title "$PR_TITLE" --body "$(cat ./pr-body)" 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | publish 3 | tmp 4 | dist 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell", 52 | "windows-sys", 53 | ] 54 | 55 | [[package]] 56 | name = "anyhow" 57 | version = "1.0.98" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "2.9.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 66 | 67 | [[package]] 68 | name = "bumpalo" 69 | version = "3.17.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "1.0.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 78 | 79 | [[package]] 80 | name = "clap" 81 | version = "4.5.36" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" 84 | dependencies = [ 85 | "clap_builder", 86 | "clap_derive", 87 | ] 88 | 89 | [[package]] 90 | name = "clap_builder" 91 | version = "4.5.36" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" 94 | dependencies = [ 95 | "anstream", 96 | "anstyle", 97 | "clap_lex", 98 | "strsim", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_derive" 103 | version = "4.5.32" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 106 | dependencies = [ 107 | "heck", 108 | "proc-macro2", 109 | "quote", 110 | "syn", 111 | ] 112 | 113 | [[package]] 114 | name = "clap_lex" 115 | version = "0.7.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 118 | 119 | [[package]] 120 | name = "colorchoice" 121 | version = "1.0.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 124 | 125 | [[package]] 126 | name = "equivalent" 127 | version = "1.0.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 130 | 131 | [[package]] 132 | name = "errno" 133 | version = "0.3.11" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 136 | dependencies = [ 137 | "libc", 138 | "windows-sys", 139 | ] 140 | 141 | [[package]] 142 | name = "fastrand" 143 | version = "2.3.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 146 | 147 | [[package]] 148 | name = "foldhash" 149 | version = "0.1.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 152 | 153 | [[package]] 154 | name = "getrandom" 155 | version = "0.3.2" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 158 | dependencies = [ 159 | "cfg-if", 160 | "libc", 161 | "r-efi", 162 | "wasi", 163 | ] 164 | 165 | [[package]] 166 | name = "hashbrown" 167 | version = "0.15.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 170 | dependencies = [ 171 | "foldhash", 172 | "serde", 173 | ] 174 | 175 | [[package]] 176 | name = "heck" 177 | version = "0.5.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 180 | 181 | [[package]] 182 | name = "id-arena" 183 | version = "2.2.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 186 | 187 | [[package]] 188 | name = "indexmap" 189 | version = "2.9.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 192 | dependencies = [ 193 | "equivalent", 194 | "hashbrown", 195 | "serde", 196 | ] 197 | 198 | [[package]] 199 | name = "is_terminal_polyfill" 200 | version = "1.70.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 203 | 204 | [[package]] 205 | name = "itoa" 206 | version = "1.0.15" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 209 | 210 | [[package]] 211 | name = "leb128fmt" 212 | version = "0.1.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 215 | 216 | [[package]] 217 | name = "lexopt" 218 | version = "0.3.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" 221 | 222 | [[package]] 223 | name = "libc" 224 | version = "0.2.172" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 227 | 228 | [[package]] 229 | name = "linux-raw-sys" 230 | version = "0.9.4" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 233 | 234 | [[package]] 235 | name = "log" 236 | version = "0.4.27" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 239 | 240 | [[package]] 241 | name = "memchr" 242 | version = "2.7.4" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 245 | 246 | [[package]] 247 | name = "once_cell" 248 | version = "1.21.3" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 251 | 252 | [[package]] 253 | name = "proc-macro2" 254 | version = "1.0.95" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 257 | dependencies = [ 258 | "unicode-ident", 259 | ] 260 | 261 | [[package]] 262 | name = "quote" 263 | version = "1.0.40" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 266 | dependencies = [ 267 | "proc-macro2", 268 | ] 269 | 270 | [[package]] 271 | name = "r-efi" 272 | version = "5.2.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 275 | 276 | [[package]] 277 | name = "rustix" 278 | version = "1.0.5" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 281 | dependencies = [ 282 | "bitflags", 283 | "errno", 284 | "libc", 285 | "linux-raw-sys", 286 | "windows-sys", 287 | ] 288 | 289 | [[package]] 290 | name = "ryu" 291 | version = "1.0.20" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 294 | 295 | [[package]] 296 | name = "semver" 297 | version = "1.0.26" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 300 | 301 | [[package]] 302 | name = "serde" 303 | version = "1.0.219" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 306 | dependencies = [ 307 | "serde_derive", 308 | ] 309 | 310 | [[package]] 311 | name = "serde_derive" 312 | version = "1.0.219" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 315 | dependencies = [ 316 | "proc-macro2", 317 | "quote", 318 | "syn", 319 | ] 320 | 321 | [[package]] 322 | name = "serde_json" 323 | version = "1.0.140" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 326 | dependencies = [ 327 | "itoa", 328 | "memchr", 329 | "ryu", 330 | "serde", 331 | ] 332 | 333 | [[package]] 334 | name = "strsim" 335 | version = "0.11.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 338 | 339 | [[package]] 340 | name = "syn" 341 | version = "2.0.100" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 344 | dependencies = [ 345 | "proc-macro2", 346 | "quote", 347 | "unicode-ident", 348 | ] 349 | 350 | [[package]] 351 | name = "tempfile" 352 | version = "3.19.1" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 355 | dependencies = [ 356 | "fastrand", 357 | "getrandom", 358 | "once_cell", 359 | "rustix", 360 | "windows-sys", 361 | ] 362 | 363 | [[package]] 364 | name = "unicode-ident" 365 | version = "1.0.18" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 368 | 369 | [[package]] 370 | name = "unicode-width" 371 | version = "0.2.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 374 | 375 | [[package]] 376 | name = "unicode-xid" 377 | version = "0.2.6" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 380 | 381 | [[package]] 382 | name = "utf8parse" 383 | version = "0.2.2" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 386 | 387 | [[package]] 388 | name = "wasi" 389 | version = "0.14.2+wasi-0.2.4" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 392 | dependencies = [ 393 | "wit-bindgen-rt", 394 | ] 395 | 396 | [[package]] 397 | name = "wasi-preview1-component-adapter-provider" 398 | version = "31.0.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "86fabda09a0d89ffd1615b297b4a5d4b4d99df9598aeb24685837e63019e927b" 401 | 402 | [[package]] 403 | name = "wasm-component-ld" 404 | version = "0.5.13" 405 | dependencies = [ 406 | "anyhow", 407 | "clap", 408 | "lexopt", 409 | "libc", 410 | "tempfile", 411 | "wasi-preview1-component-adapter-provider", 412 | "wasmparser", 413 | "wat", 414 | "windows-sys", 415 | "winsplit", 416 | "wit-component", 417 | "wit-parser", 418 | ] 419 | 420 | [[package]] 421 | name = "wasm-encoder" 422 | version = "0.230.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "d4349d0943718e6e434b51b9639e876293093dca4b96384fb136ab5bd5ce6660" 425 | dependencies = [ 426 | "leb128fmt", 427 | "wasmparser", 428 | ] 429 | 430 | [[package]] 431 | name = "wasm-metadata" 432 | version = "0.230.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "1a52e010df5494f4289ccc68ce0c2a8c17555225a5e55cc41b98f5ea28d0844b" 435 | dependencies = [ 436 | "anyhow", 437 | "indexmap", 438 | "wasm-encoder", 439 | "wasmparser", 440 | ] 441 | 442 | [[package]] 443 | name = "wasmparser" 444 | version = "0.230.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed" 447 | dependencies = [ 448 | "bitflags", 449 | "hashbrown", 450 | "indexmap", 451 | "semver", 452 | "serde", 453 | ] 454 | 455 | [[package]] 456 | name = "wast" 457 | version = "230.0.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "b8edac03c5fa691551531533928443faf3dc61a44f814a235c7ec5d17b7b34f1" 460 | dependencies = [ 461 | "bumpalo", 462 | "leb128fmt", 463 | "memchr", 464 | "unicode-width", 465 | "wasm-encoder", 466 | ] 467 | 468 | [[package]] 469 | name = "wat" 470 | version = "1.230.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "0d77d62229e38db83eac32bacb5f61ebb952366ab0dae90cf2b3c07a65eea894" 473 | dependencies = [ 474 | "wast", 475 | ] 476 | 477 | [[package]] 478 | name = "windows-sys" 479 | version = "0.59.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 482 | dependencies = [ 483 | "windows-targets", 484 | ] 485 | 486 | [[package]] 487 | name = "windows-targets" 488 | version = "0.52.6" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 491 | dependencies = [ 492 | "windows_aarch64_gnullvm", 493 | "windows_aarch64_msvc", 494 | "windows_i686_gnu", 495 | "windows_i686_gnullvm", 496 | "windows_i686_msvc", 497 | "windows_x86_64_gnu", 498 | "windows_x86_64_gnullvm", 499 | "windows_x86_64_msvc", 500 | ] 501 | 502 | [[package]] 503 | name = "windows_aarch64_gnullvm" 504 | version = "0.52.6" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 507 | 508 | [[package]] 509 | name = "windows_aarch64_msvc" 510 | version = "0.52.6" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 513 | 514 | [[package]] 515 | name = "windows_i686_gnu" 516 | version = "0.52.6" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 519 | 520 | [[package]] 521 | name = "windows_i686_gnullvm" 522 | version = "0.52.6" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 525 | 526 | [[package]] 527 | name = "windows_i686_msvc" 528 | version = "0.52.6" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 531 | 532 | [[package]] 533 | name = "windows_x86_64_gnu" 534 | version = "0.52.6" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 537 | 538 | [[package]] 539 | name = "windows_x86_64_gnullvm" 540 | version = "0.52.6" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 543 | 544 | [[package]] 545 | name = "windows_x86_64_msvc" 546 | version = "0.52.6" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 549 | 550 | [[package]] 551 | name = "winsplit" 552 | version = "0.1.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" 555 | 556 | [[package]] 557 | name = "wit-bindgen-rt" 558 | version = "0.39.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 561 | dependencies = [ 562 | "bitflags", 563 | ] 564 | 565 | [[package]] 566 | name = "wit-component" 567 | version = "0.230.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "b607b15ead6d0e87f5d1613b4f18c04d4e80ceeada5ffa608d8360e6909881df" 570 | dependencies = [ 571 | "anyhow", 572 | "bitflags", 573 | "indexmap", 574 | "log", 575 | "serde", 576 | "serde_derive", 577 | "serde_json", 578 | "wasm-encoder", 579 | "wasm-metadata", 580 | "wasmparser", 581 | "wit-parser", 582 | ] 583 | 584 | [[package]] 585 | name = "wit-parser" 586 | version = "0.230.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "679fde5556495f98079a8e6b9ef8c887f731addaffa3d48194075c1dd5cd611b" 589 | dependencies = [ 590 | "anyhow", 591 | "id-arena", 592 | "indexmap", 593 | "log", 594 | "semver", 595 | "serde", 596 | "serde_derive", 597 | "serde_json", 598 | "unicode-xid", 599 | "wasmparser", 600 | ] 601 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-component-ld" 3 | version = "0.5.13" 4 | edition = "2021" 5 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 6 | description = "Linker for `wasm32-wasip2`" 7 | repository = "https://github.com/bytecodealliance/wasm-component-ld" 8 | readme = "README.md" 9 | rust-version = "1.76.0" 10 | 11 | [package.metadata.binstall] 12 | pkg-url = "{repo}/releases/download/v{version}/{name}-v{version}-{target-arch}-{target-family}{archive-suffix}" 13 | bin-dir = "{name}-v{version}-{target-arch}-{target-family}/{bin}{binary-ext}" 14 | pkg-fmt = "tgz" 15 | [package.metadata.binstall.overrides.x86_64-apple-darwin] 16 | pkg-url = "{repo}/releases/download/v{version}/{name}-v{version}-{target-arch}-macos{archive-suffix}" 17 | bin-dir = "{name}-v{version}-{target-arch}-macos/{bin}{binary-ext}" 18 | [package.metadata.binstall.overrides.aarch64-apple-darwin] 19 | pkg-url = "{repo}/releases/download/v{version}/{name}-v{version}-{target-arch}-macos{archive-suffix}" 20 | bin-dir = "{name}-v{version}-{target-arch}-macos/{bin}{binary-ext}" 21 | [package.metadata.binstall.overrides.x86_64-pc-windows-msvc] 22 | pkg-fmt = "zip" 23 | [package.metadata.binstall.overrides.x86_64-pc-windows-gnu] 24 | pkg-fmt = "zip" 25 | 26 | [dependencies] 27 | anyhow = "1.0.80" 28 | clap = { version = "4.5.4", features = ['derive'] } 29 | lexopt = "0.3.0" 30 | tempfile = "3.10.0" 31 | wasmparser = "0.230.0" 32 | wat = "1.230.0" 33 | wit-component = "0.230.0" 34 | wit-parser = "0.230.0" 35 | wasi-preview1-component-adapter-provider = "31.0.0" 36 | 37 | [target.'cfg(unix)'.dependencies] 38 | libc = "0.2" 39 | 40 | [target.'cfg(windows)'.dependencies] 41 | winsplit = "0.1" 42 | windows-sys = { version = "0.59", features = ['Win32_Foundation'] } 43 | -------------------------------------------------------------------------------- /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-Apache-2.0_WITH_LLVM-exception: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | 205 | --- LLVM Exceptions to the Apache 2.0 License ---- 206 | 207 | As an exception, if, as a result of your compiling your source code, portions 208 | of this Software are embedded into an Object form of such source code, you 209 | may redistribute such embedded portions in such Object form without complying 210 | with the conditions of Sections 4(a), 4(b) and 4(d) of the License. 211 | 212 | In addition, if you combine or link compiled forms of this Software with 213 | software that is licensed under the GPLv2 ("Combined Software") and if a 214 | court of competent jurisdiction determines that the patent provision (Section 215 | 3), the indemnity provision (Section 9) or other Section of the License 216 | conflicts with the conditions of the GPLv2, you may retroactively and 217 | prospectively choose to deem waived or otherwise exclude such Section(s) of 218 | the License, but only in their entirety and only with respect to the Combined 219 | Software. 220 | 221 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `wasm-component-ld` 2 | 3 | This crate contains a binary named `wasm-component-ld` which is a wrapper around 4 | two pieces of functionality used to produce a [WebAssembly Component] 5 | 6 | 1. The `wasm-ld` linker driver provided by LLVM 7 | 2. The [`wit_component::ComponentEncoder`] type 8 | 9 | This binary will first invoke `wasm-ld` and then run the componentization 10 | process to produce a final component. 11 | 12 | [WebAssembly Component]: https://component-model.bytecodealliance.org/ 13 | [`wit_component::ComponentEncoder`]: https://docs.rs/wit-component/latest/wit_component/struct.ComponentEncoder.html 14 | 15 | ## Installation 16 | 17 | This repository provides [precompiled 18 | binaries](https://github.com/bytecodealliance/wasm-component-ld/releases) of 19 | `wasm-component-ld`. This repository can also be installed with [`cargo binstall`]. 20 | 21 | Installations of [wasi-sdk] have this binary packaged by default in the sysroot 22 | and the Rust `wasm32-wasip2` target, upon reaching tier 2, will also come 23 | packaged with this binary included. 24 | 25 | This means that while a version can be installed manually it should not be 26 | required to do so. 27 | 28 | [`cargo binstall`]: https://github.com/cargo-bins/cargo-binstall 29 | [wasi-sdk]: https://github.com/WebAssembly/wasi-sdk 30 | 31 | ## Options 32 | 33 | The `wasm-component-ld` binary is suitable to use as a linker driver during 34 | compilations. For Clang and Rust the `wasm32-wasip2` target will automatically 35 | invoke this binary as the linker. 36 | 37 | This means that `wasm-component-ld` forwards most of its arguments to `wasm-ld`. 38 | Additionally all flags of `wasm-ld` are supported and forwarded to `wasm-ld`. 39 | For example you can invoke the linker like `wasm-component-ld --max-memory=N 40 | ...`. 41 | 42 | The `wasm-component-ld` binary has a few custom arguments for itself as well 43 | which are not forwarded to `wasm-ld` and can be explored with `-h` or `--help`. 44 | 45 | # License 46 | 47 | This project is triple licenced under the Apache 2/ Apache 2 with LLVM exceptions/ MIT licences. The reasoning for this is: 48 | - Apache 2/ MIT is common in the rust ecosystem. 49 | - Apache 2/ MIT is used in the rust standard library, and some of this code may be migrated there. 50 | - Some of this code may be used in compiler output, and the Apache 2 with LLVM exceptions licence is useful for this. 51 | 52 | For more details see 53 | - [Apache 2 Licence](LICENSE-APACHE) 54 | - [Apache 2 Licence with LLVM exceptions](LICENSE-Apache-2.0_WITH_LLVM-exception) 55 | - [MIT Licence](LICENSE-MIT) 56 | 57 | ### Contribution 58 | 59 | Unless you explicitly state otherwise, any contribution intentionally submitted 60 | for inclusion in this project by you, as defined in the Apache 2/ Apache 2 with LLVM exceptions/ MIT licenses, 61 | shall be licensed as above, without any additional terms or conditions. 62 | -------------------------------------------------------------------------------- /ci/build-tarballs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | platform=$1 6 | target=$2 7 | 8 | rm -rf tmp 9 | mkdir tmp 10 | mkdir -p dist 11 | 12 | tag=v$(./ci/print-current-version.sh) 13 | bin_pkgname=wasm-component-ld-$tag-$platform 14 | 15 | mkdir tmp/$bin_pkgname 16 | cp LICENSE-* README.md tmp/$bin_pkgname 17 | 18 | fmt=tar 19 | if [ "$platform" = "x86_64-windows" ]; then 20 | cp target/release/wasm-component-ld.exe tmp/$bin_pkgname 21 | fmt=zip 22 | elif [ "$target" = "" ]; then 23 | cp target/release/wasm-component-ld tmp/$bin_pkgname 24 | else 25 | cp target/$target/release/wasm-component-ld tmp/$bin_pkgname 26 | fi 27 | 28 | 29 | mktarball() { 30 | dir=$1 31 | if [ "$fmt" = "tar" ]; then 32 | tar czvf dist/$dir.tar.gz -C tmp $dir 33 | else 34 | # Note that this runs on Windows, and it looks like GitHub Actions doesn't 35 | # have a `zip` tool there, so we use something else 36 | (cd tmp && 7z a ../dist/$dir.zip $dir/) 37 | fi 38 | } 39 | 40 | mktarball $bin_pkgname 41 | -------------------------------------------------------------------------------- /ci/docker/aarch64-linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update -y && apt-get install -y gcc gcc-aarch64-linux-gnu ca-certificates git 4 | 5 | ENV PATH=$PATH:/rust/bin 6 | ENV CARGO_BUILD_TARGET=aarch64-unknown-linux-gnu 7 | ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 8 | -------------------------------------------------------------------------------- /ci/docker/x86_64-linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM almalinux:8 2 | 3 | RUN dnf install -y git gcc 4 | 5 | ENV PATH=$PATH:/rust/bin 6 | -------------------------------------------------------------------------------- /ci/print-current-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | grep '^version =' Cargo.toml | head -n 1 | sed 's/.*"\(.*\)"/\1/' 3 | -------------------------------------------------------------------------------- /ci/publish.rs: -------------------------------------------------------------------------------- 1 | //! Helper script to publish this repository's suites of crates 2 | //! 3 | //! In a nutshell 4 | //! 5 | //! * `./publish bump` - bump crate versions as a major release 6 | //! * `./publish bump-patch` - bump crate versions as a patch release 7 | //! * `./publish publish` - actually publish crates to crates.io 8 | //! * `./publish verify` - verify that crates can be published, like a dry run 9 | 10 | use std::collections::HashMap; 11 | use std::env; 12 | use std::fs; 13 | use std::path::{Path, PathBuf}; 14 | use std::process::{Command, Stdio}; 15 | use std::thread; 16 | use std::time::Duration; 17 | 18 | // note that this list must be topologically sorted by dependencies 19 | const CRATES_TO_PUBLISH: &[&str] = &["wasm-component-ld"]; 20 | 21 | struct Workspace { 22 | version: String, 23 | } 24 | 25 | struct Crate { 26 | manifest: PathBuf, 27 | name: String, 28 | version: String, 29 | publish: bool, 30 | workspace_version: Option, 31 | } 32 | 33 | fn main() { 34 | let mut crates = Vec::new(); 35 | let root = read_crate(None, "./Cargo.toml".as_ref()); 36 | crates.push(root); 37 | 38 | let pos = CRATES_TO_PUBLISH 39 | .iter() 40 | .enumerate() 41 | .map(|(i, c)| (*c, i)) 42 | .collect::>(); 43 | crates.sort_by_key(|krate| pos.get(&krate.name[..])); 44 | 45 | match &env::args().nth(1).expect("must have one argument")[..] { 46 | name @ "bump" | name @ "bump-patch" => { 47 | for krate in crates.iter() { 48 | bump_version(&krate, &crates, name == "bump-patch"); 49 | } 50 | // update the lock file 51 | assert!(Command::new("cargo") 52 | .arg("fetch") 53 | .status() 54 | .unwrap() 55 | .success()); 56 | } 57 | 58 | "publish" => { 59 | // We have so many crates to publish we're frequently either 60 | // rate-limited or we run into issues where crates can't publish 61 | // successfully because they're waiting on the index entries of 62 | // previously-published crates to propagate. This means we try to 63 | // publish in a loop and we remove crates once they're successfully 64 | // published. Failed-to-publish crates get enqueued for another try 65 | // later on. 66 | for _ in 0..10 { 67 | crates.retain(|krate| !publish(krate)); 68 | 69 | if crates.is_empty() { 70 | break; 71 | } 72 | 73 | println!( 74 | "{} crates failed to publish, waiting for a bit to retry", 75 | crates.len(), 76 | ); 77 | thread::sleep(Duration::from_secs(40)); 78 | } 79 | 80 | assert!(crates.is_empty(), "failed to publish all crates"); 81 | } 82 | 83 | "verify" => { 84 | verify(&crates); 85 | } 86 | 87 | s => panic!("unknown command: {}", s), 88 | } 89 | } 90 | 91 | fn read_crate(ws: Option<&Workspace>, manifest: &Path) -> Crate { 92 | let mut name = None; 93 | let mut version = None; 94 | let mut workspace_version = None; 95 | let mut publish = true; 96 | let mut in_workspace = false; 97 | for line in fs::read_to_string(manifest).unwrap().lines() { 98 | if line.starts_with("[") { 99 | in_workspace = line.starts_with("[workspace"); 100 | continue; 101 | } 102 | 103 | if name.is_none() && line.starts_with("name = \"") { 104 | name = Some( 105 | line.replace("name = \"", "") 106 | .replace("\"", "") 107 | .trim() 108 | .to_string(), 109 | ); 110 | } 111 | if line.starts_with("version = \"") { 112 | let dst = if in_workspace { 113 | &mut workspace_version 114 | } else { 115 | &mut version 116 | }; 117 | assert!(dst.is_none()); 118 | *dst = Some( 119 | line.replace("version = \"", "") 120 | .replace("\"", "") 121 | .trim() 122 | .to_string(), 123 | ); 124 | } 125 | if let Some(ws) = ws { 126 | if version.is_none() && line.starts_with("version.workspace = true") { 127 | version = Some(ws.version.clone()); 128 | } 129 | } 130 | if line.starts_with("publish = false") { 131 | publish = false; 132 | } 133 | } 134 | let name = name.unwrap(); 135 | let version = if !publish { 136 | "0.0.0".to_string() 137 | } else { 138 | version.unwrap() 139 | }; 140 | Crate { 141 | manifest: manifest.to_path_buf(), 142 | name, 143 | version, 144 | workspace_version, 145 | publish, 146 | } 147 | } 148 | 149 | fn bump_version(krate: &Crate, crates: &[Crate], patch: bool) { 150 | let contents = fs::read_to_string(&krate.manifest).unwrap(); 151 | let next_version = |krate: &Crate| -> String { 152 | if CRATES_TO_PUBLISH.contains(&&krate.name[..]) { 153 | bump( 154 | &krate.version, 155 | if patch { 156 | BumpKind::Patch 157 | } else { 158 | BumpKind::Major 159 | }, 160 | ) 161 | } else { 162 | krate.version.clone() 163 | } 164 | }; 165 | 166 | let mut new_manifest = String::new(); 167 | let mut is_deps = false; 168 | let mut is_workspace = false; 169 | for line in contents.lines() { 170 | let mut rewritten = false; 171 | if !is_deps && line.starts_with("version =") { 172 | if CRATES_TO_PUBLISH.contains(&&krate.name[..]) { 173 | println!( 174 | "bump `{}` {} => {}", 175 | krate.name, 176 | krate.version, 177 | next_version(krate), 178 | ); 179 | let new_line = if is_workspace { 180 | let ws_version = krate.workspace_version.as_ref().unwrap(); 181 | let next_version = bump( 182 | ws_version, 183 | if patch { 184 | BumpKind::Patch 185 | } else { 186 | BumpKind::Major 187 | }, 188 | ); 189 | line.replace(ws_version, &next_version) 190 | } else { 191 | line.replace(&krate.version, &next_version(krate)) 192 | }; 193 | new_manifest.push_str(&new_line); 194 | rewritten = true; 195 | } 196 | } 197 | 198 | if line.starts_with("[") { 199 | is_deps = line.contains("dependencies"); 200 | is_workspace = line.contains("workspace"); 201 | } 202 | 203 | for other in crates { 204 | // If `other` isn't a published crate then it's not going to get a 205 | // bumped version so we don't need to update anything in the 206 | // manifest. 207 | if !other.publish { 208 | continue; 209 | } 210 | if !is_deps || !line.starts_with(&format!("{} ", other.name)) { 211 | continue; 212 | } 213 | if !line.contains(&other.version) { 214 | if !line.contains("version =") || !krate.publish { 215 | continue; 216 | } 217 | panic!( 218 | "{:?} has a dep on {} but doesn't list version {}", 219 | krate.manifest, other.name, other.version 220 | ); 221 | } 222 | rewritten = true; 223 | new_manifest.push_str(&line.replace(&other.version, &next_version(other))); 224 | break; 225 | } 226 | if !rewritten { 227 | new_manifest.push_str(line); 228 | } 229 | new_manifest.push_str("\n"); 230 | } 231 | fs::write(&krate.manifest, new_manifest).unwrap(); 232 | } 233 | 234 | enum BumpKind { 235 | Major, 236 | #[allow(dead_code)] 237 | Minor, 238 | Patch, 239 | } 240 | 241 | /// Performs a major version bump increment on the semver version `version`. 242 | /// 243 | /// This function will perform a semver-major-version bump on the `version` 244 | /// specified. This is used to calculate the next version of a crate in this 245 | /// repository since we're currently making major version bumps for all our 246 | /// releases. This may end up getting tweaked as we stabilize crates and start 247 | /// doing more minor/patch releases, but for now this should do the trick. 248 | fn bump(version: &str, bump: BumpKind) -> String { 249 | let mut iter = version.split('.').map(|s| s.parse::().unwrap()); 250 | let major = iter.next().expect("major version"); 251 | let minor = iter.next().expect("minor version"); 252 | let patch = iter.next().expect("patch version"); 253 | 254 | match bump { 255 | BumpKind::Patch => { 256 | format!("{}.{}.{}", major, minor, patch + 1) 257 | } 258 | BumpKind::Minor => { 259 | format!("{}.{}.0", major, minor + 1) 260 | } 261 | BumpKind::Major if major != 0 => { 262 | format!("{}.0.0", major + 1) 263 | } 264 | BumpKind::Major if minor != 0 => { 265 | format!("0.{}.0", minor + 1) 266 | } 267 | BumpKind::Major => { 268 | format!("0.0.{}", patch + 1) 269 | } 270 | } 271 | } 272 | 273 | fn publish(krate: &Crate) -> bool { 274 | if !CRATES_TO_PUBLISH.iter().any(|s| *s == krate.name) { 275 | return true; 276 | } 277 | 278 | // First make sure the crate isn't already published at this version. This 279 | // script may be re-run and there's no need to re-attempt previous work. 280 | let output = Command::new("curl") 281 | .arg(&format!("https://crates.io/api/v1/crates/{}", krate.name)) 282 | .output() 283 | .expect("failed to invoke `curl`"); 284 | if output.status.success() 285 | && String::from_utf8_lossy(&output.stdout) 286 | .contains(&format!("\"newest_version\":\"{}\"", krate.version)) 287 | { 288 | println!( 289 | "skip publish {} because {} is latest version", 290 | krate.name, krate.version, 291 | ); 292 | return true; 293 | } 294 | 295 | let status = Command::new("cargo") 296 | .arg("publish") 297 | .current_dir(krate.manifest.parent().unwrap()) 298 | .arg("--no-verify") 299 | .status() 300 | .expect("failed to run cargo"); 301 | if !status.success() { 302 | println!("FAIL: failed to publish `{}`: {}", krate.name, status); 303 | return false; 304 | } 305 | 306 | // // After we've published then make sure that the `wasmtime-publish` group is 307 | // // added to this crate for future publications. If it's already present 308 | // // though we can skip the `cargo owner` modification. 309 | // let output = Command::new("curl") 310 | // .arg(&format!( 311 | // "https://crates.io/api/v1/crates/{}/owners", 312 | // krate.name 313 | // )) 314 | // .output() 315 | // .expect("failed to invoke `curl`"); 316 | // if output.status.success() 317 | // && String::from_utf8_lossy(&output.stdout).contains("wasmtime-publish") 318 | // { 319 | // println!( 320 | // "wasmtime-publish already listed as an owner of {}", 321 | // krate.name 322 | // ); 323 | // return true; 324 | // } 325 | 326 | // // Note that the status is ignored here. This fails most of the time because 327 | // // the owner is already set and present, so we only want to add this to 328 | // // crates which haven't previously been published. 329 | // let status = Command::new("cargo") 330 | // .arg("owner") 331 | // .arg("-a") 332 | // .arg("github:bytecodealliance:wasmtime-publish") 333 | // .arg(&krate.name) 334 | // .status() 335 | // .expect("failed to run cargo"); 336 | // if !status.success() { 337 | // panic!( 338 | // "FAIL: failed to add wasmtime-publish as owner `{}`: {}", 339 | // krate.name, status 340 | // ); 341 | // } 342 | 343 | true 344 | } 345 | 346 | // Verify the current tree is publish-able to crates.io. The intention here is 347 | // that we'll run `cargo package` on everything which verifies the build as-if 348 | // it were published to crates.io. This requires using an incrementally-built 349 | // directory registry generated from `cargo vendor` because the versions 350 | // referenced from `Cargo.toml` may not exist on crates.io. 351 | fn verify(crates: &[Crate]) { 352 | drop(fs::remove_dir_all(".cargo")); 353 | drop(fs::remove_dir_all("vendor")); 354 | let vendor = Command::new("cargo") 355 | .arg("vendor") 356 | .stderr(Stdio::inherit()) 357 | .output() 358 | .unwrap(); 359 | assert!(vendor.status.success()); 360 | 361 | fs::create_dir_all(".cargo").unwrap(); 362 | fs::write(".cargo/config.toml", vendor.stdout).unwrap(); 363 | 364 | for krate in crates { 365 | if !krate.publish { 366 | continue; 367 | } 368 | verify_and_vendor(&krate); 369 | } 370 | 371 | fn verify_and_vendor(krate: &Crate) { 372 | let mut cmd = Command::new("cargo"); 373 | cmd.arg("package") 374 | .arg("--allow-dirty") 375 | .arg("--manifest-path") 376 | .arg(&krate.manifest) 377 | .env("CARGO_TARGET_DIR", "./target"); 378 | let status = cmd.status().unwrap(); 379 | assert!(status.success(), "failed to verify {:?}", &krate.manifest); 380 | let tar = Command::new("tar") 381 | .arg("xf") 382 | .arg(format!( 383 | "../target/package/{}-{}.crate", 384 | krate.name, krate.version 385 | )) 386 | .current_dir("./vendor") 387 | .status() 388 | .unwrap(); 389 | assert!(tar.success()); 390 | fs::write( 391 | format!( 392 | "./vendor/{}-{}/.cargo-checksum.json", 393 | krate.name, krate.version 394 | ), 395 | "{\"files\":{}}", 396 | ) 397 | .unwrap(); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/argfile.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::ffi::{OsStr, OsString}; 3 | 4 | pub fn expand() -> Result> { 5 | let mut expander = Expander::default(); 6 | for arg in std::env::args_os() { 7 | expander.push(arg)?; 8 | } 9 | Ok(expander.args) 10 | } 11 | 12 | #[derive(Default)] 13 | struct Expander { 14 | args: Vec, 15 | } 16 | 17 | impl Expander { 18 | fn push(&mut self, arg: OsString) -> Result<()> { 19 | let bytes = arg.as_encoded_bytes(); 20 | match bytes.split_first() { 21 | Some((b'@', rest)) => { 22 | self.push_file(unsafe { OsStr::from_encoded_bytes_unchecked(rest) }) 23 | } 24 | _ => { 25 | self.args.push(arg); 26 | Ok(()) 27 | } 28 | } 29 | } 30 | 31 | fn push_file(&mut self, file: &OsStr) -> Result<()> { 32 | let contents = 33 | std::fs::read_to_string(file).with_context(|| format!("failed to read {file:?}"))?; 34 | 35 | for part in imp::split(&contents) { 36 | self.push(part.into())?; 37 | } 38 | Ok(()) 39 | } 40 | } 41 | 42 | #[cfg(not(windows))] 43 | use gnu as imp; 44 | #[cfg(not(windows))] 45 | mod gnu { 46 | pub fn split(s: &str) -> impl Iterator + '_ { 47 | Split { iter: s.chars() } 48 | } 49 | 50 | struct Split<'a> { 51 | iter: std::str::Chars<'a>, 52 | } 53 | 54 | impl<'a> Iterator for Split<'a> { 55 | type Item = String; 56 | 57 | fn next(&mut self) -> Option { 58 | loop { 59 | match self.iter.next()? { 60 | c if c.is_whitespace() => {} 61 | '"' => break Some(self.quoted('"')), 62 | '\'' => break Some(self.quoted('\'')), 63 | c => { 64 | let mut ret = String::new(); 65 | self.push(&mut ret, c); 66 | while let Some(next) = self.iter.next() { 67 | if next.is_whitespace() { 68 | break; 69 | } 70 | self.push(&mut ret, next); 71 | } 72 | break Some(ret); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | impl Split<'_> { 80 | fn quoted(&mut self, end: char) -> String { 81 | let mut part = String::new(); 82 | while let Some(next) = self.iter.next() { 83 | if next == end { 84 | break; 85 | } 86 | self.push(&mut part, next); 87 | } 88 | part 89 | } 90 | 91 | fn push(&mut self, dst: &mut String, ch: char) { 92 | if ch == '\\' { 93 | if let Some(ch) = self.iter.next() { 94 | dst.push(ch); 95 | return; 96 | } 97 | } 98 | dst.push(ch); 99 | } 100 | } 101 | 102 | #[test] 103 | fn tests() { 104 | assert_eq!(split("x").collect::>(), ["x"]); 105 | assert_eq!(split("\\x").collect::>(), ["x"]); 106 | assert_eq!(split("'x'").collect::>(), ["x"]); 107 | assert_eq!(split("\"x\"").collect::>(), ["x"]); 108 | 109 | assert_eq!(split("x y").collect::>(), ["x", "y"]); 110 | assert_eq!(split("x\ny").collect::>(), ["x", "y"]); 111 | assert_eq!(split("\\x y").collect::>(), ["x", "y"]); 112 | assert_eq!(split("'x y'").collect::>(), ["x y"]); 113 | assert_eq!(split("\"x y\"").collect::>(), ["x y"]); 114 | assert_eq!(split("\"x 'y'\"\n'y'").collect::>(), ["x 'y'", "y"]); 115 | assert_eq!( 116 | split( 117 | r#" 118 | a\ \\b 119 | z 120 | "x y \\z" 121 | "# 122 | ) 123 | .collect::>(), 124 | ["a \\b", "z", "x y \\z"] 125 | ); 126 | } 127 | } 128 | 129 | #[cfg(windows)] 130 | use windows as imp; 131 | #[cfg(windows)] 132 | mod windows { 133 | pub fn split(s: &str) -> impl Iterator { 134 | winsplit::split(s).into_iter() 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use clap::{ArgAction, CommandFactory, FromArgMatches}; 3 | use lexopt::Arg; 4 | use std::env; 5 | use std::ffi::OsString; 6 | use std::path::{Path, PathBuf}; 7 | use std::process::{Command, ExitStatus}; 8 | use std::str::FromStr; 9 | use wasmparser::Payload; 10 | use wit_component::StringEncoding; 11 | use wit_parser::{Resolve, WorldId}; 12 | 13 | mod argfile; 14 | 15 | /// Representation of a flag passed to `wasm-ld` 16 | /// 17 | /// Note that the parsing of flags in `wasm-ld` is not as uniform as parsing 18 | /// arguments via `clap`. For example if `--foo bar` is supported that doesn't 19 | /// mean that `--foo=bar` is supported. Similarly some options such as `--foo` 20 | /// support optional values as `--foo=bar` but can't be specified as 21 | /// `--foo bar`. 22 | /// 23 | /// Finally there's currently only one "weird" flag which is `-shared` which has 24 | /// a single dash but a long name. That's specially handled elsewhere. 25 | /// 26 | /// The general goal here is that we want to inherit `wasm-ld`'s CLI but also 27 | /// want to be able to reserve CLI flags for this linker itself, so `wasm-ld`'s 28 | /// arguments are parsed where our own are intermixed. 29 | struct LldFlag { 30 | clap_name: &'static str, 31 | long: Option<&'static str>, 32 | short: Option, 33 | value: FlagValue, 34 | } 35 | 36 | enum FlagValue { 37 | /// This option has no value, e.g. `-f` or `--foo` 38 | None, 39 | 40 | /// This option's value must be specified with `=`, for example `--foo=bar` 41 | RequiredEqual(&'static str), 42 | 43 | /// This option's value must be specified with ` `, for example `--foo bar`. 44 | /// 45 | /// I think that `wasm-ld` supports both `-f foo` and `-ffoo` for 46 | /// single-character flags, but I haven't tested as putting a space seems to 47 | /// work. 48 | RequiredSpace(&'static str), 49 | 50 | /// This option's value is optional but if specified it must use an `=` for 51 | /// example `--foo=bar` or `--foo`. 52 | Optional(&'static str), 53 | } 54 | 55 | /// This is a large macro which is intended to take CLI-looking syntax and turn 56 | /// each individual flag into a `LldFlag` specified above. 57 | macro_rules! flag { 58 | // Long options specified as: 59 | // 60 | // -f / --foo 61 | // 62 | // or just 63 | // 64 | // --foo 65 | // 66 | // Options can look like `--foo`, `--foo=bar`, `--foo[=bar]`, or 67 | // `--foo bar` to match the kinds of flags that LLD supports. 68 | ($(-$short:ident /)? --$($flag:tt)*) => { 69 | LldFlag { 70 | clap_name: concat!("long_", $(stringify!($flag),)*), 71 | long: Some(flag!(@name [] $($flag)*)), 72 | short: flag!(@short $($short)?), 73 | value: flag!(@value $($flag)*), 74 | } 75 | }; 76 | 77 | // Short options specified as `-f` or `-f foo`. 78 | (-$flag:tt $($val:tt)*) => { 79 | LldFlag { 80 | clap_name: concat!("short_", stringify!($flag)), 81 | long: None, 82 | short: Some(flag!(@char $flag)), 83 | value: flag!(@value $flag $($val)*), 84 | } 85 | }; 86 | 87 | // Generates the long name of a flag, collected within the `[]` argument to 88 | // this macro. This will iterate over the flag given as the rest of the 89 | // macro arguments and collect values into `[...]` and recurse. 90 | // 91 | // The first recursion case handles `foo-bar-baz=..` where Rust tokenizes 92 | // this as `foo` then `-` then `bar` then ... If this is found then `foo-` 93 | // is added to the name and then the macro recurses. 94 | (@name [$($name:tt)*] $n:ident-$($rest:tt)*) => (flag!(@name [$($name)* $n-] $($rest)*)); 95 | // These are the ways options are represented, either `--foo bar`, 96 | // `--foo=bar`, `--foo=bar`, or `--foo`. In all these cases discard the 97 | // value itself and then recurse. 98 | (@name [$($name:tt)*] $n:ident $_value:ident) => (flag!(@name [$($name)* $n])); 99 | (@name [$($name:tt)*] $n:ident=$_value:ident) => (flag!(@name [$($name)* $n])); 100 | (@name [$($name:tt)*] $n:ident[=$_value:ident]) => (flag!(@name [$($name)* $n])); 101 | (@name [$($name:tt)*] $n:ident) => (flag!(@name [$($name)* $n])); 102 | // If there's nothing left then the `$name` has collected everything so 103 | // it's stringifyied and caoncatenated. 104 | (@name [$($name:tt)*]) => (concat!($(stringify!($name),)*)); 105 | 106 | // This parses the value-style of the flag given. The recursion here looks 107 | // similar to `@name` above. except that the four terminal cases all 108 | // correspond to different variants of `FlagValue`. 109 | (@value $n:ident - $($rest:tt)*) => (flag!(@value $($rest)*)); 110 | (@value $_flag:ident = $name:ident) => (FlagValue::RequiredEqual(stringify!($name))); 111 | (@value $_flag:ident $name:ident) => (FlagValue::RequiredSpace(stringify!($name))); 112 | (@value $_flag:ident [= $name:ident]) => (FlagValue::Optional(stringify!($name))); 113 | (@value $_flag:ident) => (FlagValue::None); 114 | 115 | // Helper for flags that have both a long and a short form to parse whether 116 | // a short form was provided. 117 | (@short) => (None); 118 | (@short $name:ident) => (Some(flag!(@char $name))); 119 | 120 | // Helper for getting the `char` of a short flag. 121 | (@char $name:ident) => ({ 122 | let name = stringify!($name); 123 | assert!(name.len() == 1); 124 | name.as_bytes()[0] as char 125 | }); 126 | } 127 | 128 | const LLD_FLAGS: &[LldFlag] = &[ 129 | flag! { --allow-undefined-file=PATH }, 130 | flag! { --allow-undefined }, 131 | flag! { --Bdynamic }, 132 | flag! { --Bstatic }, 133 | flag! { --Bsymbolic }, 134 | flag! { --build-id[=VAL] }, 135 | flag! { --call_shared }, 136 | flag! { --check-features }, 137 | flag! { --color-diagnostics[=VALUE] }, 138 | flag! { --compress-relocations }, 139 | flag! { --demangle }, 140 | flag! { --dn }, 141 | flag! { --dy }, 142 | flag! { --emit-relocs }, 143 | flag! { --end-lib }, 144 | flag! { --entry SYM }, 145 | flag! { --error-limit=N }, 146 | flag! { --error-unresolved-symbols }, 147 | flag! { --experimental-pic }, 148 | flag! { --export-all }, 149 | flag! { -E / --export-dynamic }, 150 | flag! { --export-if-defined=SYM }, 151 | flag! { --export-memory[=NAME] }, 152 | flag! { --export-table }, 153 | flag! { --export=SYM }, 154 | flag! { --extra-features=LIST }, 155 | flag! { --fatal-warnings }, 156 | flag! { --features=LIST }, 157 | flag! { --gc-sections }, 158 | flag! { --global-base=VALUE }, 159 | flag! { --growable-table }, 160 | flag! { --import-memory[=NAME] }, 161 | flag! { --import-table }, 162 | flag! { --import-undefined }, 163 | flag! { --initial-heap=SIZE }, 164 | flag! { --initial-memory=SIZE }, 165 | flag! { --keep-section=NAME }, 166 | flag! { --lto-CGO=LEVEL }, 167 | flag! { --lto-debug-pass-manager }, 168 | flag! { --lto-O=LEVEL }, 169 | flag! { --lto-partitions=NUM }, 170 | flag! { -L PATH }, 171 | flag! { -l LIB }, 172 | flag! { --Map=FILE }, 173 | flag! { --max-memory=SIZE }, 174 | flag! { --merge-data-segments }, 175 | flag! { --mllvm=FLAG }, 176 | flag! { -m ARCH }, 177 | flag! { --no-check-features }, 178 | flag! { --no-color-diagnostics }, 179 | flag! { --no-demangle }, 180 | flag! { --no-entry }, 181 | flag! { --no-export-dynamic }, 182 | flag! { --no-gc-sections }, 183 | flag! { --no-merge-data-segments }, 184 | flag! { --no-pie }, 185 | flag! { --no-print-gc-sections }, 186 | flag! { --no-whole-archive }, 187 | flag! { --non_shared }, 188 | flag! { -O LEVEL }, 189 | flag! { --pie }, 190 | flag! { --print-gc-sections }, 191 | flag! { -M / --print-map }, 192 | flag! { --relocatable }, 193 | flag! { --save-temps }, 194 | flag! { --shared-memory }, 195 | flag! { --shared }, 196 | flag! { --soname=VALUE }, 197 | flag! { --stack-first }, 198 | flag! { --start-lib }, 199 | flag! { --static }, 200 | flag! { -s / --strip-all }, 201 | flag! { -S / --strip-debug }, 202 | flag! { --table-base=VALUE }, 203 | flag! { --thinlto-cache-dir=PATH }, 204 | flag! { --thinlto-cache-policy=VALUE }, 205 | flag! { --thinlto-jobs=N }, 206 | flag! { --threads=N }, 207 | flag! { -y / --trace-symbol=SYM }, 208 | flag! { -t / --trace }, 209 | flag! { --undefined=SYM }, 210 | flag! { --unresolved-symbols=VALUE }, 211 | flag! { --warn-unresolved-symbols }, 212 | flag! { --whole-archive }, 213 | flag! { --why-extract=MEMBER }, 214 | flag! { --wrap=VALUE }, 215 | flag! { -z OPT }, 216 | ]; 217 | 218 | const LLD_LONG_FLAGS_NONSTANDARD: &[&str] = &["-shared"]; 219 | 220 | #[derive(Default)] 221 | struct App { 222 | component: ComponentLdArgs, 223 | lld_args: Vec, 224 | shared: bool, 225 | } 226 | 227 | /// A linker to create a Component from input object files and libraries. 228 | /// 229 | /// This application is an equivalent of `wasm-ld` except that it produces a 230 | /// component instead of a core wasm module. This application behaves very 231 | /// similarly to `wasm-ld` in that it takes the same inputs and flags, and it 232 | /// will internally invoke `wasm-ld`. After `wasm-ld` has been invoked the core 233 | /// wasm module will be turned into a component using component tooling and 234 | /// embedded information in the core wasm module. 235 | #[derive(clap::Parser, Default)] 236 | #[command(version)] 237 | struct ComponentLdArgs { 238 | /// Which default WASI adapter, if any, to use when creating the output 239 | /// component. 240 | #[clap(long, name = "command|reactor|proxy|none")] 241 | wasi_adapter: Option, 242 | 243 | /// Location of where to find `wasm-ld`. 244 | /// 245 | /// If not specified this is automatically detected. 246 | #[clap(long, name = "PATH")] 247 | wasm_ld_path: Option, 248 | 249 | /// Quoting syntax for response files. 250 | #[clap(long, name = "STYLE")] 251 | rsp_quoting: Option, 252 | 253 | /// Where to place the component output. 254 | #[clap(short, long)] 255 | output: PathBuf, 256 | 257 | /// Print verbose output. 258 | #[clap(short, long)] 259 | verbose: bool, 260 | 261 | /// Whether or not the output component is validated. 262 | /// 263 | /// This defaults to `true`. 264 | #[clap(long)] 265 | validate_component: Option, 266 | 267 | /// Whether or not imports are deduplicated based on semver in the final 268 | /// component. 269 | /// 270 | /// This defaults to `true`. 271 | #[clap(long)] 272 | merge_imports_based_on_semver: Option, 273 | 274 | /// Adapters to use when creating the final component. 275 | #[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)] 276 | adapters: Vec<(String, Vec)>, 277 | 278 | /// Whether or not "legacy" names are rejected during componentization. 279 | /// 280 | /// This option can be used to require the naming scheme outlined in 281 | /// to be used 282 | /// and rejects all modules using the previous ad-hoc naming scheme. 283 | /// 284 | /// This defaults to `false`. 285 | #[clap(long)] 286 | reject_legacy_names: bool, 287 | 288 | /// Whether or not the `cabi_realloc` function used by the adapter is backed 289 | /// by `memory.grow`. 290 | /// 291 | /// By default the adapter will import `cabi_realloc` from the main module 292 | /// and use that, but this can be used to instead back memory allocation 293 | /// requests with `memory.grow` instead. 294 | /// 295 | /// This defaults to `false`. 296 | #[clap(long)] 297 | realloc_via_memory_grow: bool, 298 | 299 | /// WIT file representing additional component type information to use. 300 | /// 301 | /// May be specified more than once. 302 | /// 303 | /// See also the `--string-encoding` option. 304 | #[clap(long = "component-type", value_name = "WIT_FILE")] 305 | component_types: Vec, 306 | 307 | /// String encoding to use when creating the final component. 308 | /// 309 | /// This may be either "utf8", "utf16", or "compact-utf16". This value is 310 | /// only used when one or more `--component-type` options are specified. 311 | #[clap(long, value_parser = parse_encoding, default_value = "utf8")] 312 | string_encoding: StringEncoding, 313 | 314 | /// Skip the `wit-component`-based process to generate a component. 315 | #[clap(long)] 316 | skip_wit_component: bool, 317 | } 318 | 319 | fn parse_adapter(s: &str) -> Result<(String, Vec)> { 320 | let (name, path) = parse_optionally_name_file(s); 321 | let wasm = wat::parse_file(path)?; 322 | Ok((name.to_string(), wasm)) 323 | } 324 | 325 | fn parse_encoding(s: &str) -> Result { 326 | Ok(match s { 327 | "utf8" => StringEncoding::UTF8, 328 | "utf16" => StringEncoding::UTF16, 329 | "compact-utf16" => StringEncoding::CompactUTF16, 330 | _ => bail!("unknown string encoding: {s:?}"), 331 | }) 332 | } 333 | 334 | fn parse_optionally_name_file(s: &str) -> (&str, &str) { 335 | let mut parts = s.splitn(2, '='); 336 | let name_or_path = parts.next().unwrap(); 337 | match parts.next() { 338 | Some(path) => (name_or_path, path), 339 | None => { 340 | let name = Path::new(name_or_path) 341 | .file_name() 342 | .unwrap() 343 | .to_str() 344 | .unwrap(); 345 | let name = match name.find('.') { 346 | Some(i) => &name[..i], 347 | None => name, 348 | }; 349 | (name, name_or_path) 350 | } 351 | } 352 | } 353 | 354 | #[derive(Debug, Copy, Clone)] 355 | enum WasiAdapter { 356 | Command, 357 | Reactor, 358 | Proxy, 359 | None, 360 | } 361 | 362 | impl FromStr for WasiAdapter { 363 | type Err = anyhow::Error; 364 | 365 | fn from_str(s: &str) -> Result { 366 | match s { 367 | "none" => Ok(WasiAdapter::None), 368 | "command" => Ok(WasiAdapter::Command), 369 | "reactor" => Ok(WasiAdapter::Reactor), 370 | "proxy" => Ok(WasiAdapter::Proxy), 371 | _ => bail!("unknown wasi adapter {s}, must be one of: none, command, reactor, proxy"), 372 | } 373 | } 374 | } 375 | 376 | pub fn main() { 377 | let err = match run() { 378 | Ok(()) => return, 379 | Err(e) => e, 380 | }; 381 | eprintln!("error: {err}"); 382 | if err.chain().len() > 1 { 383 | eprintln!("\nCaused by:"); 384 | for (i, err) in err.chain().skip(1).enumerate() { 385 | eprintln!("{i:>5}: {}", err.to_string().replace("\n", "\n ")); 386 | } 387 | } 388 | 389 | std::process::exit(1); 390 | } 391 | 392 | fn run() -> Result<()> { 393 | App::parse()?.run() 394 | } 395 | 396 | impl App { 397 | /// Parse the CLI arguments into an `App` to run the linker. 398 | /// 399 | /// This is unfortunately nontrivial because the way `wasm-ld` takes 400 | /// arguments is not compatible with `clap`. Namely flags like 401 | /// `--whole-archive` are positional are processed in a stateful manner. 402 | /// This means that the relative ordering of flags to `wasm-ld` needs to be 403 | /// preserved. Additionally there are flags like `-shared` which clap does 404 | /// not support. 405 | /// 406 | /// To handle this the `lexopt` crate is used to perform low-level argument 407 | /// parsing. That's then used to determine whether the argument is intended 408 | /// for `wasm-component-ld` or `wasm-ld`, so arguments are filtered into two 409 | /// lists. Using these lists the arguments to `wasm-component-ld` are then 410 | /// parsed. On failure a help message is presented with all `wasm-ld` 411 | /// arguments added as well. 412 | /// 413 | /// This means that functionally it looks like `clap` parses everything when 414 | /// in fact `lexopt` is used to filter out `wasm-ld` arguments and `clap` 415 | /// only parses arguments specific to `wasm-component-ld`. 416 | fn parse() -> Result { 417 | let mut args = argfile::expand().context("failed to expand @-response files")?; 418 | 419 | // First remove `-flavor wasm` in case this is invoked as a generic LLD 420 | // driver. We can safely ignore that going forward. 421 | if let Some([flavor, wasm]) = args.get(1..3) { 422 | if flavor == "-flavor" && wasm == "wasm" { 423 | args.remove(1); 424 | args.remove(1); 425 | } 426 | } 427 | 428 | let mut command = ComponentLdArgs::command(); 429 | let mut lld_args = Vec::new(); 430 | let mut component_ld_args = vec![std::env::args_os().nth(0).unwrap()]; 431 | let mut shared = false; 432 | let mut parser = lexopt::Parser::from_iter(args); 433 | 434 | fn handle_lld_arg( 435 | lld: &LldFlag, 436 | parser: &mut lexopt::Parser, 437 | lld_args: &mut Vec, 438 | ) -> Result<()> { 439 | let mut arg = OsString::new(); 440 | match (lld.short, lld.long) { 441 | (_, Some(long)) => { 442 | arg.push("--"); 443 | arg.push(long); 444 | } 445 | (Some(short), _) => { 446 | arg.push("-"); 447 | arg.push(short.encode_utf8(&mut [0; 5])); 448 | } 449 | (None, None) => unreachable!(), 450 | } 451 | match lld.value { 452 | FlagValue::None => { 453 | lld_args.push(arg); 454 | } 455 | 456 | FlagValue::RequiredSpace(_) => { 457 | lld_args.push(arg); 458 | lld_args.push(parser.value()?); 459 | } 460 | 461 | FlagValue::RequiredEqual(_) => { 462 | arg.push("="); 463 | arg.push(&parser.value()?); 464 | lld_args.push(arg); 465 | } 466 | 467 | // If the value is optional then the argument must have an `=` 468 | // in the argument itself. 469 | FlagValue::Optional(_) => { 470 | match parser.optional_value() { 471 | Some(val) => { 472 | arg.push("="); 473 | arg.push(&val); 474 | } 475 | None => {} 476 | } 477 | lld_args.push(arg); 478 | } 479 | } 480 | Ok(()) 481 | } 482 | 483 | loop { 484 | if let Some(mut args) = parser.try_raw_args() { 485 | if let Some(arg) = args.peek() { 486 | let for_lld = LLD_LONG_FLAGS_NONSTANDARD.iter().any(|s| arg == *s); 487 | if for_lld { 488 | lld_args.push(arg.to_owned()); 489 | if arg == "-shared" { 490 | shared = true; 491 | } 492 | args.next(); 493 | continue; 494 | } 495 | } 496 | } 497 | 498 | match parser.next()? { 499 | Some(Arg::Value(obj)) => { 500 | lld_args.push(obj); 501 | } 502 | Some(Arg::Short(c)) => match LLD_FLAGS.iter().find(|f| f.short == Some(c)) { 503 | Some(lld) => { 504 | handle_lld_arg(lld, &mut parser, &mut lld_args)?; 505 | } 506 | None => { 507 | component_ld_args.push(format!("-{c}").into()); 508 | if let Some(arg) = 509 | command.get_arguments().find(|a| a.get_short() == Some(c)) 510 | { 511 | if let ArgAction::Set = arg.get_action() { 512 | component_ld_args.push(parser.value()?); 513 | } 514 | } 515 | } 516 | }, 517 | Some(Arg::Long(c)) => match LLD_FLAGS.iter().find(|f| f.long == Some(c)) { 518 | Some(lld) => { 519 | handle_lld_arg(lld, &mut parser, &mut lld_args)?; 520 | } 521 | None => { 522 | component_ld_args.push(format!("--{c}").into()); 523 | if let Some(arg) = command.get_arguments().find(|a| a.get_long() == Some(c)) 524 | { 525 | match arg.get_action() { 526 | ArgAction::Set | ArgAction::Append => { 527 | component_ld_args.push(parser.value()?) 528 | } 529 | _ => (), 530 | } 531 | } 532 | } 533 | }, 534 | None => break, 535 | } 536 | } 537 | 538 | match command.try_get_matches_from_mut(component_ld_args.clone()) { 539 | Ok(matches) => Ok(App { 540 | component: ComponentLdArgs::from_arg_matches(&matches)?, 541 | lld_args, 542 | shared, 543 | }), 544 | Err(_) => { 545 | add_wasm_ld_options(ComponentLdArgs::command()).get_matches_from(component_ld_args); 546 | unreachable!(); 547 | } 548 | } 549 | } 550 | 551 | fn run(&mut self) -> Result<()> { 552 | let mut lld = self.lld(); 553 | 554 | // If a temporary output is needed make sure it has the same file name 555 | // as the output of our command itself since LLD will embed this file 556 | // name in the name section of the output. 557 | let temp_dir = match self.component.output.parent() { 558 | Some(parent) => tempfile::TempDir::new_in(parent)?, 559 | None => tempfile::TempDir::new()?, 560 | }; 561 | let temp_output = match self.component.output.file_name() { 562 | Some(name) => temp_dir.path().join(name), 563 | None => bail!( 564 | "output of {:?} does not have a file name", 565 | self.component.output 566 | ), 567 | }; 568 | 569 | // Shared libraries don't get wit-component run below so place the 570 | // output directly at the desired output location. Otherwise output to a 571 | // temporary location for wit-component to read and then the real output 572 | // is created after wit-component runs. 573 | if self.skip_wit_component() { 574 | lld.output(&self.component.output); 575 | } else { 576 | lld.output(&temp_output); 577 | } 578 | 579 | let linker = &lld.exe; 580 | let status = lld 581 | .status(&temp_dir, &self.lld_args) 582 | .with_context(|| format!("failed to spawn {linker:?}"))?; 583 | if !status.success() { 584 | bail!("failed to invoke LLD: {status}"); 585 | } 586 | 587 | if self.skip_wit_component() { 588 | return Ok(()); 589 | } 590 | 591 | let reactor_adapter = 592 | wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER; 593 | let command_adapter = 594 | wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_COMMAND_ADAPTER; 595 | let proxy_adapter = 596 | wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_PROXY_ADAPTER; 597 | let mut core_module = std::fs::read(&temp_output) 598 | .with_context(|| format!("failed to read {linker:?} output: {temp_output:?}"))?; 599 | 600 | // Inspect the output module to see if it's a command or reactor. 601 | let mut exports_start = false; 602 | for payload in wasmparser::Parser::new(0).parse_all(&core_module) { 603 | match payload { 604 | Ok(Payload::ExportSection(e)) => { 605 | for export in e { 606 | if let Ok(e) = export { 607 | if e.name == "_start" { 608 | exports_start = true; 609 | break; 610 | } 611 | } 612 | } 613 | } 614 | _ => {} 615 | } 616 | } 617 | 618 | if !self.component.component_types.is_empty() { 619 | let mut merged = None::<(Resolve, WorldId)>; 620 | for wit_file in &self.component.component_types { 621 | let mut resolve = Resolve::default(); 622 | let (package, _) = resolve 623 | .push_path(wit_file) 624 | .with_context(|| format!("unable to add component type {wit_file:?}"))?; 625 | 626 | let world = resolve.select_world(package, None)?; 627 | 628 | if let Some((merged_resolve, merged_world)) = &mut merged { 629 | let world = merged_resolve.merge(resolve)?.map_world(world, None)?; 630 | merged_resolve.merge_worlds(world, *merged_world)?; 631 | } else { 632 | merged = Some((resolve, world)); 633 | } 634 | } 635 | 636 | let Some((resolve, world)) = merged else { 637 | unreachable!() 638 | }; 639 | 640 | wit_component::embed_component_metadata( 641 | &mut core_module, 642 | &resolve, 643 | world, 644 | self.component.string_encoding, 645 | )?; 646 | } 647 | 648 | let mut encoder = wit_component::ComponentEncoder::default() 649 | .reject_legacy_names(self.component.reject_legacy_names) 650 | .realloc_via_memory_grow(self.component.realloc_via_memory_grow); 651 | if let Some(validate) = self.component.validate_component { 652 | encoder = encoder.validate(validate); 653 | } 654 | if let Some(merge) = self.component.merge_imports_based_on_semver { 655 | encoder = encoder.merge_imports_based_on_semver(merge); 656 | } 657 | encoder = encoder 658 | .module(&core_module) 659 | .context("failed to parse core wasm for componentization")?; 660 | let adapter = self.component.wasi_adapter.unwrap_or(if exports_start { 661 | WasiAdapter::Command 662 | } else { 663 | WasiAdapter::Reactor 664 | }); 665 | let adapter = match adapter { 666 | WasiAdapter::Command => Some(&command_adapter[..]), 667 | WasiAdapter::Reactor => Some(&reactor_adapter[..]), 668 | WasiAdapter::Proxy => Some(&proxy_adapter[..]), 669 | WasiAdapter::None => None, 670 | }; 671 | 672 | if let Some(adapter) = adapter { 673 | encoder = encoder 674 | .adapter("wasi_snapshot_preview1", adapter) 675 | .context("failed to inject adapter")?; 676 | } 677 | 678 | for (name, adapter) in self.component.adapters.iter() { 679 | encoder = encoder 680 | .adapter(name, adapter) 681 | .with_context(|| format!("failed to inject adapter {name:?}"))?; 682 | } 683 | 684 | let component = encoder.encode().context("failed to encode component")?; 685 | 686 | std::fs::write(&self.component.output, &component).context(format!( 687 | "failed to write output file: {:?}", 688 | self.component.output 689 | ))?; 690 | 691 | Ok(()) 692 | } 693 | 694 | fn skip_wit_component(&self) -> bool { 695 | self.component.skip_wit_component 696 | // Skip componentization with `--shared` since that's creating a 697 | // shared library that's not a component yet. 698 | || self.shared 699 | } 700 | 701 | fn lld(&self) -> Lld { 702 | let mut lld = self.find_lld(); 703 | if self.component.verbose { 704 | lld.verbose = true 705 | } 706 | lld 707 | } 708 | 709 | fn find_lld(&self) -> Lld { 710 | if let Some(path) = &self.component.wasm_ld_path { 711 | return Lld::new(path); 712 | } 713 | 714 | // Search for the first of `wasm-ld` or `rust-lld` in `$PATH` 715 | let wasm_ld = format!("wasm-ld{}", env::consts::EXE_SUFFIX); 716 | let rust_lld = format!("rust-lld{}", env::consts::EXE_SUFFIX); 717 | for entry in env::split_paths(&env::var_os("PATH").unwrap_or_default()) { 718 | if entry.join(&wasm_ld).is_file() { 719 | return Lld::new(wasm_ld); 720 | } 721 | if entry.join(&rust_lld).is_file() { 722 | let mut lld = Lld::new(rust_lld); 723 | lld.needs_flavor = true; 724 | return lld; 725 | } 726 | } 727 | 728 | // Fall back to `wasm-ld` if the search failed to get an error message 729 | // that indicates that `wasm-ld` was attempted to be found but couldn't 730 | // be found. 731 | Lld::new("wasm-ld") 732 | } 733 | } 734 | 735 | /// Helper structure representing an `lld` invocation. 736 | struct Lld { 737 | exe: PathBuf, 738 | needs_flavor: bool, 739 | verbose: bool, 740 | output: Option, 741 | } 742 | 743 | impl Lld { 744 | fn new(exe: impl Into) -> Lld { 745 | Lld { 746 | exe: exe.into(), 747 | needs_flavor: false, 748 | verbose: false, 749 | output: None, 750 | } 751 | } 752 | 753 | fn output(&mut self, dst: impl Into) { 754 | self.output = Some(dst.into()); 755 | } 756 | 757 | fn status(&self, tmpdir: &tempfile::TempDir, args: &[OsString]) -> Result { 758 | // If we can probably pass `args` natively, try to do so. In some cases 759 | // though just skip this entirely and go straight to below. 760 | if !self.probably_too_big(args) { 761 | match self.run(args) { 762 | // If this subprocess failed to spawn because the arguments 763 | // were too large, fall through to below. 764 | Err(ref e) if self.command_line_too_big(e) => { 765 | if self.verbose { 766 | eprintln!("command line was too large, trying again..."); 767 | } 768 | } 769 | other => return Ok(other?), 770 | } 771 | } else if self.verbose { 772 | eprintln!("arguments probably too large {args:?}"); 773 | } 774 | 775 | // The `args` are too big to be passed via the command line itself so 776 | // encode the mall using "posix quoting" into an "argfile". This gets 777 | // passed as `@foo` to lld and we also pass `--rsp-quoting=posix` to 778 | // ensure that LLD always uses posix quoting. That means that we don't 779 | // have to implement the dual nature of both posix and windows encoding 780 | // here. 781 | let mut argfile = Vec::new(); 782 | for arg in args { 783 | for byte in arg.as_encoded_bytes() { 784 | if *byte == b'\\' || *byte == b' ' { 785 | argfile.push(b'\\'); 786 | } 787 | argfile.push(*byte); 788 | } 789 | argfile.push(b'\n'); 790 | } 791 | let path = tmpdir.path().join("argfile_tmp"); 792 | std::fs::write(&path, &argfile).with_context(|| format!("failed to write {path:?}"))?; 793 | let mut argfile_arg = OsString::from("@"); 794 | argfile_arg.push(&path); 795 | let status = self.run(&["--rsp-quoting=posix".into(), argfile_arg.into()])?; 796 | Ok(status) 797 | } 798 | 799 | /// Tests whether the `args` array is too large to execute natively. 800 | /// 801 | /// Windows `cmd.exe` has a very small limit of around 8k so perform a 802 | /// guess up to 6k. This isn't 100% accurate. 803 | fn probably_too_big(&self, args: &[OsString]) -> bool { 804 | let args_size = args 805 | .iter() 806 | .map(|s| s.as_encoded_bytes().len()) 807 | .sum::(); 808 | cfg!(windows) && args_size > 6 * 1024 809 | } 810 | 811 | /// Test if the OS failed to spawn a process because the arguments were too 812 | /// long. 813 | fn command_line_too_big(&self, err: &std::io::Error) -> bool { 814 | #[cfg(unix)] 815 | return err.raw_os_error() == Some(libc::E2BIG); 816 | #[cfg(windows)] 817 | return err.raw_os_error() 818 | == Some(windows_sys::Win32::Foundation::ERROR_FILENAME_EXCED_RANGE as i32); 819 | #[cfg(not(any(unix, windows)))] 820 | { 821 | let _ = err; 822 | return false; 823 | } 824 | } 825 | 826 | fn run(&self, args: &[OsString]) -> std::io::Result { 827 | let mut cmd = Command::new(&self.exe); 828 | if self.needs_flavor { 829 | cmd.arg("-flavor").arg("wasm"); 830 | } 831 | cmd.args(args); 832 | if self.verbose { 833 | cmd.arg("--verbose"); 834 | } 835 | if let Some(output) = &self.output { 836 | cmd.arg("-o").arg(output); 837 | } 838 | if self.verbose { 839 | eprintln!("running {cmd:?}"); 840 | } 841 | cmd.status() 842 | } 843 | } 844 | 845 | fn add_wasm_ld_options(mut command: clap::Command) -> clap::Command { 846 | use clap::Arg; 847 | 848 | command = command.arg( 849 | Arg::new("objects") 850 | .action(ArgAction::Append) 851 | .help("objects to pass to `wasm-ld`"), 852 | ); 853 | 854 | for flag in LLD_FLAGS { 855 | let mut arg = Arg::new(flag.clap_name).help("forwarded to `wasm-ld`"); 856 | if let Some(short) = flag.short { 857 | arg = arg.short(short); 858 | } 859 | if let Some(long) = flag.long { 860 | arg = arg.long(long); 861 | } 862 | arg = match flag.value { 863 | FlagValue::RequiredEqual(name) | FlagValue::RequiredSpace(name) => { 864 | arg.action(ArgAction::Set).value_name(name) 865 | } 866 | FlagValue::Optional(name) => arg 867 | .action(ArgAction::Set) 868 | .value_name(name) 869 | .num_args(0..=1) 870 | .require_equals(true), 871 | FlagValue::None => arg.action(ArgAction::SetTrue), 872 | }; 873 | arg = arg.help_heading("Options forwarded to `wasm-ld`"); 874 | command = command.arg(arg); 875 | } 876 | 877 | command 878 | } 879 | 880 | #[test] 881 | fn verify_app() { 882 | ComponentLdArgs::command().debug_assert(); 883 | add_wasm_ld_options(ComponentLdArgs::command()).debug_assert(); 884 | } 885 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | wasm_component_ld::main(); 3 | } 4 | -------------------------------------------------------------------------------- /tests/all.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use std::env; 3 | use std::fs; 4 | use std::io::Write; 5 | use std::process::{Command, Stdio}; 6 | 7 | fn compile(args: &[&str], src: &str) -> Vec { 8 | Project::new().compile(args, src) 9 | } 10 | 11 | struct Project { 12 | tempdir: tempfile::TempDir, 13 | } 14 | 15 | impl Project { 16 | fn new() -> Project { 17 | Project { 18 | tempdir: tempfile::TempDir::new().unwrap(), 19 | } 20 | } 21 | fn file(&self, name: &str, contents: &str) { 22 | fs::write(self.tempdir.path().join(name), contents.as_bytes()).unwrap(); 23 | } 24 | 25 | fn compile(&self, args: &[&str], src: &str) -> Vec { 26 | self.try_compile(args, src, true).unwrap() 27 | } 28 | 29 | fn try_compile(&self, args: &[&str], src: &str, inherit_stderr: bool) -> Result> { 30 | let mut myself = env::current_exe().unwrap(); 31 | myself.pop(); // exe name 32 | myself.pop(); // 'deps' 33 | myself.push("wasm-component-ld"); 34 | let mut rustc = Command::new("rustc"); 35 | rustc 36 | .arg("--target") 37 | .arg("wasm32-wasip1") 38 | .arg("-") 39 | .arg("-o") 40 | .arg("-") 41 | .arg("-C") 42 | .arg(&format!("linker={}", myself.to_str().unwrap())) 43 | .args(args) 44 | .stdin(Stdio::piped()) 45 | .stdout(Stdio::piped()) 46 | .current_dir(self.tempdir.path()); 47 | if !inherit_stderr { 48 | rustc.stderr(Stdio::piped()); 49 | } 50 | let mut rustc = rustc.spawn().context("failed to spawn rustc")?; 51 | 52 | rustc 53 | .stdin 54 | .take() 55 | .context("stdin should be present")? 56 | .write_all(src.as_bytes()) 57 | .context("failed to write stdin")?; 58 | let output = rustc 59 | .wait_with_output() 60 | .context("failed to wait for subprocess")?; 61 | if !output.status.success() { 62 | let mut error = format!("subprocess failed: {:?}", output.status); 63 | if !output.stdout.is_empty() { 64 | error.push_str(&format!( 65 | "\nstdout: {}", 66 | String::from_utf8_lossy(&output.stdout) 67 | )); 68 | } 69 | if !output.stderr.is_empty() { 70 | error.push_str(&format!( 71 | "\nstderr: {}", 72 | String::from_utf8_lossy(&output.stderr) 73 | )); 74 | } 75 | bail!("{error}") 76 | } 77 | Ok(output.stdout) 78 | } 79 | } 80 | 81 | fn assert_component(bytes: &[u8]) { 82 | assert!(wasmparser::Parser::is_component(&bytes)); 83 | wasmparser::Validator::new().validate_all(&bytes).unwrap(); 84 | } 85 | 86 | fn assert_module(bytes: &[u8]) { 87 | assert!(wasmparser::Parser::is_core_wasm(&bytes)); 88 | wasmparser::Validator::new().validate_all(&bytes).unwrap(); 89 | } 90 | 91 | #[test] 92 | fn empty() { 93 | let output = compile(&["--crate-type", "cdylib"], ""); 94 | assert_component(&output); 95 | } 96 | 97 | #[test] 98 | fn empty_main() { 99 | let output = compile(&[], "fn main() {}"); 100 | assert_component(&output); 101 | } 102 | 103 | #[test] 104 | fn hello_world() { 105 | let output = compile( 106 | &[], 107 | r#" 108 | fn main() { 109 | println!("hello!"); 110 | } 111 | "#, 112 | ); 113 | assert_component(&output); 114 | } 115 | 116 | #[test] 117 | fn cdylib_arbitrary_export() { 118 | let output = compile( 119 | &["--crate-type", "cdylib"], 120 | r#" 121 | #[no_mangle] 122 | pub extern "C" fn foo() { 123 | println!("x"); 124 | } 125 | "#, 126 | ); 127 | assert_component(&output); 128 | } 129 | 130 | #[test] 131 | fn can_access_badfd() { 132 | let output = compile( 133 | &[], 134 | r#" 135 | #[link(wasm_import_module = "wasi_snapshot_preview1")] 136 | extern "C" { 137 | fn adapter_open_badfd(fd: &mut u32) -> u32; 138 | } 139 | 140 | fn main() { 141 | let mut fd = 0; 142 | let rc = unsafe { 143 | adapter_open_badfd(&mut fd) 144 | }; 145 | assert_eq!(rc, 0); 146 | assert_eq!(fd, 3); 147 | } 148 | "#, 149 | ); 150 | assert_component(&output); 151 | } 152 | 153 | #[test] 154 | fn linker_flags() { 155 | let output = compile( 156 | &[ 157 | "-Clink-arg=--max-memory=65536", 158 | "-Clink-arg=-zstack-size=32", 159 | "-Clink-arg=--global-base=2048", 160 | ], 161 | r#" 162 | fn main() { 163 | } 164 | "#, 165 | ); 166 | assert_component(&output); 167 | } 168 | 169 | #[test] 170 | fn component_type_wit_file() { 171 | let project = Project::new(); 172 | project.file( 173 | "foo.wit", 174 | r#" 175 | package foo:bar; 176 | 177 | interface foo { 178 | bar: func(s: string) -> string; 179 | } 180 | 181 | world root { 182 | import foo; 183 | export foo; 184 | } 185 | "#, 186 | ); 187 | let output = project.compile( 188 | &[ 189 | "-Clink-arg=--component-type", 190 | "-Clink-arg=foo.wit", 191 | "-Clink-arg=--string-encoding", 192 | "-Clink-arg=utf16", 193 | "--crate-type", 194 | "cdylib", 195 | ], 196 | r#" 197 | #[no_mangle] 198 | pub extern "C" fn cabi_realloc(ptr: *mut u8, old_size: i32, align: i32, new_size: i32) -> *mut u8 { 199 | _ = (ptr, old_size, align, new_size); 200 | unreachable!() 201 | } 202 | 203 | #[link(wasm_import_module = "foo:bar/foo")] 204 | extern "C" { 205 | #[link_name = "bar"] 206 | fn import(ptr: *mut u8, len: i32, return_ptr: *mut *mut u8); 207 | } 208 | 209 | #[export_name = "foo:bar/foo#bar"] 210 | pub unsafe extern "C" fn export(ptr: *mut u8, len: i32) -> *mut u8 { 211 | let mut result = std::ptr::null_mut(); 212 | import(ptr, len, &mut result); 213 | result 214 | } 215 | "#, 216 | ); 217 | assert_component(&output); 218 | } 219 | 220 | #[test] 221 | fn skip_component() { 222 | let output = compile( 223 | &["-Clink-arg=--skip-wit-component"], 224 | r#" 225 | fn main() { 226 | } 227 | "#, 228 | ); 229 | assert_module(&output); 230 | } 231 | 232 | #[test] 233 | fn rustc_using_argfile() { 234 | let prefix = (0..200).map(|_| 'a').collect::(); 235 | let p = Project { 236 | tempdir: tempfile::TempDir::with_prefix(&prefix).unwrap(), 237 | }; 238 | 239 | let mut src = String::new(); 240 | for i in 0..1000 { 241 | src.push_str(&format!("mod m{i};\n")); 242 | p.file( 243 | &format!("m{i}.rs"), 244 | &format!("#[no_mangle] pub extern \"C\" fn f{i}() {{}}"), 245 | ); 246 | } 247 | src.push_str("fn main() {}"); 248 | 249 | p.file("args", "@args2"); 250 | p.file("args2", "--skip-wit-component"); 251 | let output = p.compile(&["-Ccodegen-units=1000", "-Clink-arg=@args"], &src); 252 | assert_module(&output); 253 | } 254 | 255 | #[test] 256 | fn hello_world_with_realloc_as_memory_grow() { 257 | let output = compile( 258 | &["-Clink-arg=--realloc-via-memory-grow"], 259 | r#" 260 | fn main() { 261 | println!("hello!"); 262 | } 263 | "#, 264 | ); 265 | assert_component(&output); 266 | } 267 | 268 | // The adapter uses legacy names, so this option will always result in an error 269 | // right now. 270 | #[test] 271 | fn legacy_names_currently_required() { 272 | let result = Project::new().try_compile( 273 | &["-Clink-arg=--reject-legacy-names"], 274 | r#" 275 | fn main() { 276 | println!("hello!"); 277 | } 278 | "#, 279 | false, 280 | ); 281 | let err = result.unwrap_err(); 282 | let err = format!("{err:?}"); 283 | println!("error: {err}"); 284 | assert!(err.contains("unknown or invalid component model import syntax")); 285 | } 286 | --------------------------------------------------------------------------------