[
  {
    "path": ".github/workflows/binaries.yml",
    "content": "on:\n  release:\n    types:\n      - created\n\nname: Upload Release Assets\n\njobs:\n  assets:\n    name: Upload Release Assets\n    strategy:\n      matrix:\n        os: [ ubuntu-latest, macOS-latest ]\n        include:\n          - os: ubuntu-latest\n            name: linux\n          - os: macOS-latest\n            name: macos\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Extract version tag\n        id: version\n        run: echo ::set-output name=value::$(echo ${{ github.ref }} | cut -f 3 -d / )\n\n      - name: Build cargo-dinghy\n        run: |\n          set -ex\n          ZIPDIR=cargo-dinghy-${{ steps.version.outputs.value }}\n          mkdir $ZIPDIR\n          if [ ${{matrix.name}} == \"macos\" ]\n          then\n            # Let's ensure that both aarch64 and x86_64 targets are installed\n            rustup target add aarch64-apple-darwin x86_64-apple-darwin\n            brew install coreutils gnu-tar\n            PATH=$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH\n            PATH=$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH\n            tar --version\n            cargo build  --target aarch64-apple-darwin --target x86_64-apple-darwin --release -p cargo-dinghy \n            lipo -create -output $ZIPDIR/cargo-dinghy target/aarch64-apple-darwin/release/cargo-dinghy target/x86_64-apple-darwin/release/cargo-dinghy\n          else\n            ./musl_build.sh\n            mv target/cargo-dinghy $ZIPDIR\n          fi\n          ls -al $ZIPDIR\n          $ZIPDIR/cargo-dinghy --version\n          file $ZIPDIR/cargo-dinghy\n          md5sum $ZIPDIR/cargo-dinghy\n          tar vczf cargo-dinghy-${{matrix.name}}-${{ steps.version.outputs.value }}.tgz $ZIPDIR\n          md5sum cargo-dinghy-${{matrix.name}}-${{ steps.version.outputs.value }}.tgz\n          mkdir test\n          cd test\n          tar zxf ../cargo-dinghy-${{matrix.name}}-${{ steps.version.outputs.value }}.tgz\n          md5sum $ZIPDIR/cargo-dinghy\n\n      - name: Upload asset\n        uses: softprops/action-gh-release@v1\n        with:\n          files: cargo-dinghy-${{matrix.name}}-${{ steps.version.outputs.value }}.tgz\n          name: ${{ steps.version.outputs.value }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "on:\n  push:\n    tags:\n    - 'cargo-dinghy/*'\n\nname: Create release\n\njobs:\n  release:\n    name: Create release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Extract version tag\n        id: version\n        run: echo ::set-output name=value::$(echo ${{ github.ref }} | cut -f 4 -d /)\n\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@latest\n        env:\n          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN_RELEASE }}\n        with:\n          tag_name: ${{ steps.version.outputs.value }}\n          release_name: ${{ steps.version.outputs.value }}\n\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build and test\n\non:\n  pull_request:\n  schedule:\n    - cron:  '0 5 * * *'\n\njobs:\n  linux:\n    strategy:\n      fail-fast: false\n      matrix:\n        rust: [ 1.85.0, beta, nightly ]\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v1\n    - name: build and test\n      env:\n        RUST_VERSION: ${{matrix.rust}}\n      run: ./.travis.sh\n\n  macos:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ macOS-14, macOS-15 ]\n    runs-on: ${{matrix.os}}\n\n    steps:\n    - uses: actions/checkout@v1\n    - name: build and test\n      run: ./.travis.sh\n\n      #  windows:\n      #    runs-on: windows-2019\n      #\n      #    steps:\n      #    - uses: actions/checkout@v1\n      #    - name: cargo test\n      #      run: cargo test --all\n\n  apple-3rd-tier:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ macOS-14, macOS-15 ]\n        sim: [ tvOS, watchOS ]\n    runs-on: ${{matrix.os}}\n\n    steps:\n    - uses: actions/checkout@v1\n    - name: build and test\n      run: ./.travis.apple-third-tier.sh ${{matrix.sim}}\n      env:\n        RUST_VERSION: 1.85.0\n\n  linux-musl:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v1\n    - name: build and test\n      run: ./musl_build.sh\n"
  },
  {
    "path": ".gitignore",
    "content": "target\n.idea/\n*.iml\n*.back\n"
  },
  {
    "path": ".travis.apple-third-tier.sh",
    "content": "#!/bin/bash\nset -e\nset -x\n\nSIM_TARGET=$1\n\ntitle() {\n    set +x\n    echo -e '\\n\\033[1;33m '$@' \\033[0m\\n'\n    set -x\n}\nif [ -z \"$CARGO_DINGHY\" ]\nthen\n    title \"••• build cargo-dinghy •••\"\n    cargo build -p cargo-dinghy\n    CARGO_DINGHY=\"`pwd`/target/debug/cargo-dinghy -vv\"\nfi\necho RUST_VERSION: ${RUST_VERSION:=1.70.0}\n\nrustup toolchain add $RUST_VERSION\nexport RUSTUP_TOOLCHAIN=$RUST_VERSION\nrustup toolchain add nightly --component rust-src;\n\ntests_sequence_unstable_target() {\n    # There's something odd with using the .cargo/config runner attribute and\n    # workspaces when the runner uses `cargo run --manifest-path ../Cargo.toml\n    # --bin cargo-dinghy ...`\n    title \"testing from project directory for rust target $1 on device $2\"\n    title \"testing from workspace directory\"\n    ( \\\n        cd test-ws \\\n        && cargo clean \\\n        && $CARGO_DINGHY   -d $1 -p $2 +nightly test -Zbuild-std pass \\\n        && ! $CARGO_DINGHY -d $1 -p $2 +nightly test -Zbuild-std fails \\\n        && ! $CARGO_DINGHY -d $1 -p $2 +nightly test -Zbuild-std \\\n    )\n\n    title \"testing from project directory\"\n    ( \\\n        cd test-ws/test-app \\\n        && cargo clean \\\n        && $CARGO_DINGHY   -d $1 -p $2 +nightly test -Zbuild-std pass \\\n        && ! $CARGO_DINGHY -d $1 -p $2 +nightly test -Zbuild-std fails \\\n        && ! $CARGO_DINGHY -d $1 -p $2 +nightly test -Zbuild-std \\\n    )\n\n    title \"test from workspace directory with project filter\"\n    ( \\\n        cd test-ws \\\n        && cargo clean \\\n        && $CARGO_DINGHY   -d $1 -p $2 +nightly test -p test-app -Zbuild-std pass \\\n        && ! $CARGO_DINGHY -d $1 -p $2 +nightly test -p test-app -Zbuild-std fails \\\n        && ! $CARGO_DINGHY -d $1 -p $2 +nightly test -p test-app -Zbuild-std \\\n    )\n}\n\nif [ $SIM_TARGET = tvOS ]\nthen\n     title \"••••• Darwin: tvos simulator tests •••••\"\n     title \"boot a simulator\"\n\n     # *-apple-{tvos,watchos}[-sim] require `-Zbuild-std`\n     TVOS_RUNTIME_ID=$(xcrun simctl list runtimes | grep tvOS | cut -d ' ' -f 7 | tail -1)\n     export TV_SIM_ID=$(xcrun simctl create My-4ktv com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K $TVOS_RUNTIME_ID)\n\n     xcrun simctl boot $TV_SIM_ID\n     if [ \"$(uname -m)\" = \"arm64\" ]; then\n         tests_sequence_unstable_target ${TV_SIM_ID} auto-tvos-aarch64-sim\n     else\n         # The x86 tvOS simulator tripple does not end in -sim.\n         tests_sequence_unstable_target ${TV_SIM_ID} auto-tvos-x86_64\n     fi\n     xcrun simctl delete $TV_SIM_ID\nfi\n\nif [ $SIM_TARGET = watchOS ]\nthen\n     title \"••••• Darwin: watchvos simulator tests •••••\"\n     title \"boot a simulator\"\n     WATCHOS_RUNTIME_ID=$(xcrun simctl list runtimes | grep watchOS | cut -d ' ' -f 7 | tail -1)\n     export WATCHOS_SIM_ID=$(xcrun simctl create My-apple-watch com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-44mm-2nd-generation $WATCHOS_RUNTIME_ID)\n\n     xcrun simctl boot $WATCHOS_SIM_ID\n     if [ \"$(uname -m)\" = \"arm64\" ]; then\n         tests_sequence_unstable_target ${WATCHOS_SIM_ID} auto-watchos-aarch64-sim\n     else\n         tests_sequence_unstable_target ${WATCHOS_SIM_ID} auto-watchos-x86_64-sim\n     fi\n     xcrun simctl delete $WATCHOS_SIM_ID\n\nfi\n\n# This depends on https://github.com/sonos/dinghy/pull/223\nif [ $SIM_TARGET = visionOS ]\nthen\n     if [ \"$(uname -m)\" = \"arm64\" ]; then\n         title \"••••• Darwin: visionOS simulator tests •••••\"\n         title \"boot a simulator\"\n         xcrun simctl list devicetypes vision\n         VISIONOS_DEVICE_TYPE=$(xcrun simctl list devicetypes vision -j | jq -r '.devicetypes[0].identifier')\n         VISIONOS_RUNTIME_ID=$(xcrun simctl list runtimes | grep visionOS | cut -d ' ' -f 7 | tail -1)\n         export VISIONOS_SIM_ID=$(xcrun simctl create My-apple-vision-pro $VISIONOS_DEVICE_TYPE $VISIONOS_RUNTIME_ID)\n\n         xcrun simctl boot $VISIONOS_SIM_ID\n         tests_sequence_unstable_target ${VISIONOS_SIM_ID} auto-visionos-aarch64-sim\n         xcrun simctl delete $VISIONOS_SIM_ID\n     fi\nfi\nrustup default stable\n"
  },
  {
    "path": ".travis.sh",
    "content": "#!/bin/bash\nset -e\nset -x\n\ntitle() {\n    set +x\n    echo -e '\\n\\033[1;33m '$@' \\033[0m\\n'\n    set -x\n}\n\nif [ ! cargo --version ]\nthen\n    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n    PATH=$PATH:$HOME/.cargo/bin\nfi\n\n\nif [ -z \"$CARGO_DINGHY\" ]\nthen\n    title \"••• build cargo-dinghy •••\"\n    cargo build -p cargo-dinghy\n    CARGO_DINGHY=\"`pwd`/target/debug/cargo-dinghy -vv\"\nfi\necho RUST_VERSION: ${RUST_VERSION:=1.85.0}\n\nrustup toolchain add $RUST_VERSION\nexport RUSTUP_TOOLCHAIN=$RUST_VERSION\n\ntitle \"••• test original cargo build •••\"\ncargo build\ncargo test\n\n( \\\n    cd test-ws/test-app \\\n    && export NOT_BUILT_WITH_DINGHY=1 \\\n    && cargo test pass \\\n    && ! NOT_BUILT_WITH_DINGHY=1 cargo test fails \\\n)\n\ntests_sequence() {\n    title \"testing from workspace directory\"\n    ( \\\n        cd test-ws \\\n        && cargo clean \\\n        && $CARGO_DINGHY -d $1 test pass \\\n        && $CARGO_DINGHY -d $1 test --doc \\\n        && ! $CARGO_DINGHY -d $1 test fails \\\n        && ! $CARGO_DINGHY -d $1 test \\\n    )\n\n    title \"testing from project directory\"\n    ( \\\n        cd test-ws/test-app \\\n        && cargo clean \\\n        && $CARGO_DINGHY -d $1 test pass \\\n        && $CARGO_DINGHY -d $1 test --doc \\\n        && ! $CARGO_DINGHY -d $1 test fails \\\n        && ! $CARGO_DINGHY -d $1 test \\\n    )\n\n    title \"test from workspace directory with project filter\"\n    ( \\\n        cd test-ws \\\n        && cargo clean \\\n        && $CARGO_DINGHY -d $1 test -p test-app pass \\\n        && $CARGO_DINGHY -d $1 test -p test-app --doc \\\n        && ! $CARGO_DINGHY -d $1 test -p test-app fails \\\n        && ! $CARGO_DINGHY -d $1 test -p test-app \\\n    )\n}\n\ntests_sequence_aarch64_ios_sim() {\n    title \"testing from workspace directory\"\n    ( \\\n        cd test-ws \\\n        && cargo clean \\\n        && $CARGO_DINGHY   -d $1 -p auto-ios-aarch64-sim test pass \\\n        && $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test --doc \\\n        && ! $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test fails \\\n        && ! $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test \\\n    )\n\n    title \"testing from project directory\"\n    ( \\\n        cd test-ws/test-app \\\n        && cargo clean \\\n        && $CARGO_DINGHY   -d $1 -p auto-ios-aarch64-sim test pass \\\n        && $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test --doc \\\n        && ! $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test fails \\\n        && ! $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test \\\n    )\n\n    title \"test from workspace directory with project filter\"\n    ( \\\n        cd test-ws \\\n        && cargo clean \\\n        && $CARGO_DINGHY   -d $1 -p auto-ios-aarch64-sim test -p test-app pass \\\n        && $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test -p test-app --doc \\\n        && ! $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test -p test-app fails \\\n        && ! $CARGO_DINGHY -d $1 -p auto-ios-aarch64-sim test -p test-app \\\n    )\n}\n\n\nif [ `uname` = Darwin ]\nthen\n     title \"••••• Darwin: ios simulator tests •••••\"\n     title \"boot a simulator\"\n     RUNTIME_ID=$(xcrun simctl list runtimes | grep iOS | cut -d ' ' -f 7 | tail -1)\n\n     # Installed simulators on github runners differ depending on the version\n     # of macos. When the simulator device type ID needs to be updated, select\n     # a new one:\n     # https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#installed-simulators\n     # https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#installed-simulators\n     # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md#installed-simulators\n     export SIM_ID=$(xcrun simctl create My-iphone-se com.apple.CoreSimulator.SimDeviceType.iPhone-SE-3rd-generation $RUNTIME_ID)\n     xcrun simctl boot $SIM_ID\n\n     # The x86_64-apple-ios target seems to not work on an ARM64 host,\n     # and the aarch64-apple-ios-sim target doesn't work on an x86-64 host.\n     if [ \"$(uname -m)\" = \"arm64\" ]; then\n         rustup target add aarch64-apple-ios-sim;\n         tests_sequence_aarch64_ios_sim $SIM_ID\n     else\n         rustup target add x86_64-apple-ios;\n         tests_sequence $SIM_ID\n     fi\n\n     xcrun simctl delete $SIM_ID\n\n    if ios-deploy -c -t 1 > /tmp/ios_devices\n    then\n        device=$(grep \"Found\" /tmp/ios_devices | head -1 | cut -d \" \" -f 3)\n        title \"••••• Darwin: ios-deploy detected a device •••••\"\n        rustup target add aarch64-apple-ios\n        tests_sequence $device\n    fi\nelse\n    echo $ANDROID_SDK_ROOT\n    if [ -n \"$ANDROID_SDK_ROOT\" ]\n    then\n        title \"••••• Linux: android tests •••••\"\n        title \"setup simulator\"\n        rustup target add armv7-linux-androideabi\n\n         ## BEGIN FIX-EMULATOR\n         # Use emulator version 32.1.15 as latest version (36.3.10.0 as of writing)\n\t # doesn't support arm images anymore\n\n         EMULATOR=\"$(pwd)/target/emulator/emulator\"\n         [ -e $EMULATOR ] ||  ( \\\n           cd target/ \\\n           && wget -q https://redirector.gvt1.com/edgedl/android/repository/emulator-linux_x64-10696886.zip \\\n           && unzip emulator-linux_x64-10696886.zip \\\n           && rm emulator-linux_x64-10696886.zip\n         )\n\n\t # to use the bundled emulator instead (if we choose to go the x86 route at some point)\n\t # use the using the following line instead\n\t # EMULATOR=\"$ANDROID_SDK_ROOT/emulator/emulator\"\n\n        # END FIX-EMULATOR\n\n        yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null\n        $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install \"system-images;android-24;default;armeabi-v7a\" \"ndk;27.3.13750724\" \"emulator\" \"platform-tools\" # \"cmdline-tools;latest\"\n        echo no | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/avdmanager create avd -c 1000M --force -n testdinghy -k \"system-images;android-24;default;armeabi-v7a\"\n        ANDROID_AVD_HOME=$HOME/.config/.android/avd $EMULATOR @testdinghy -partition-size 1024 -no-audio -no-boot-anim -no-window -accel on -gpu off &\n        timeout 180 $ANDROID_SDK_ROOT/platform-tools/adb wait-for-device\n\n        export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/27.3.13750724\n\n        tests_sequence android\n    fi\n\n    title \"••••• Linux: script tests (with qemu) •••••\"\n    title \"setup qemu\"\n\n    rustup target add aarch64-unknown-linux-musl\n    sudo apt-get update\n    sudo apt-get -y install --no-install-recommends qemu-system-arm qemu-user binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu\n    echo -e \"[platforms.qemu]\\nrustc_triple='aarch64-unknown-linux-musl'\\ndeb_multiarch='aarch64-linux-gnu'\" > .dinghy.toml\n    echo -e \"[script_devices.qemu]\\nplatform='qemu'\\npath='/tmp/qemu'\" >> .dinghy.toml\n    echo -e \"#!/bin/sh\\nexe=\\$1\\nshift\\n/usr/bin/qemu-aarch64 -L /usr/aarch64-linux-gnu/ \\$exe --test-threads 1 \\\"\\$@\\\"\" > /tmp/qemu\n    chmod +x /tmp/qemu\n\n    tests_sequence qemu\nfi\n\nif [ -n \"$DEPLOY\" ]\nthen\n    if [ `uname` = Linux ]\n    then\n        export OPENSSL_STATIC=yes\n        export OPENSSL_INCLUDE_DIR=/usr/include\n        export OPENSSL_LIB_DIR=$(dirname `find /usr -name libssl.a`)\n        cargo clean\n    fi\n    cargo build --release -p cargo-dinghy\n    mkdir -p cargo-dinghy-$DEPLOY\n    cp target/release/cargo-dinghy cargo-dinghy-$DEPLOY\n    tar vczf cargo-dinghy-$DEPLOY.tgz cargo-dinghy-$DEPLOY\nfi\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: rust\nmatrix:\n  allow_failures:\n  - rust: nightly\n  include:\n  - os: osx\n    osx_image: xcode11.2\n    env: DEPLOY=macos\n  - os: linux\n    dist: bionic\n  - rust: stable\n    dist: bionic\n    env: DEPLOY=travis\n  - rust: beta\n    dist: bionic\n  - rust: nightly\n    dist: bionic\nscript: sh .travis.sh\ndeploy:\n  provider: releases\n  skip_cleanup: true\n  api_key:\n    secure: YJlTT/zACnSNi3jFVXF+pPx615hxUrRe5ZCLLd3bgek/daEI4FIYJQIbO8ZlL8ufx7/SVKg8gjGxNYqDUsd3dozjVlALibogi6XBZfoAWZBkCUQNyoqALzNnTP+PbKme5mRQbtNPp5X0g7KDYoJGUHsvD6FUD7rxYe6QMpbGUvaa89cwFguNvWsjZgGBYtf5HO42u+JE6hXhmO8WGyK0cQYZEdXWn9kUBgQD1ua4+rqn9h35IFveEG0QAq59HNDTfmg67okXJINqYlJ0DzGJM4yN8BH9nSsBD/vGn79Z7nLp6PXOBIvwBY2sRUt3WwVNLGAVCqlmIeRdioQbUvJm09uqgNt8O9vKZYcAxUYja1xfCO5lyUDQ8IYJ5EZU9lSpW3wvalLKswL6bvLQ1ckI2ASe8rYMxsbUZ5TE3fJWuHpuGkaoJnvIvefrR4orB/Gi5cSJ708TBtUwS/Law95hUHPbCKs6nsC1vsoLLj33xZqpoNCZH8BSWyDWsfSuDdiMrSW68LeYe/DxWFuSg/AWfqDXi5xY5CMFzRJ5TgxhZ7wGVvrUTAvripVyPQkewb3fThgLDGqAwboPq9wq7UqiJKT5l8/BOHdlKTEeX5AKlaMCERZtupM6LT/2v3YuaBZpiVnsDW8nWAz0DyEcFcWo/UFl44Njrah9f/t4tJXuF7k=\n  file_glob: true\n  file: cargo-dinghy-*.tgz\n  on:\n    tags: true\n    repo: sonos/dinghy\n    condition: \"$TRAVIS_TAG =~ ^.*$\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [0.8.4](https://github.com/sonos/dinghy/tree/0.8.4) (2025-12-12)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.8.2...0.8.4)\n\n**Merged pull requests:**\n\n- bump macos [\\#261](https://github.com/sonos/dinghy/pull/261) ([kali](https://github.com/kali))\n- use 1.85 for iDevices [\\#259](https://github.com/sonos/dinghy/pull/259) ([kali](https://github.com/kali))\n\n## [0.8.2](https://github.com/sonos/dinghy/tree/0.8.2) (2025-10-07)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.8.1...0.8.2)\n\n**Closed issues:**\n\n- Doctests freeze on Rust 1.89 [\\#255](https://github.com/sonos/dinghy/issues/255)\n\n**Merged pull requests:**\n\n- 1.85.0 [\\#258](https://github.com/sonos/dinghy/pull/258) ([kali](https://github.com/kali))\n- ignore devices with no udid [\\#257](https://github.com/sonos/dinghy/pull/257) ([kali](https://github.com/kali))\n- 1.89 doctests support [\\#256](https://github.com/sonos/dinghy/pull/256) ([fredszaq](https://github.com/fredszaq))\n- fix ci by holding back some deps [\\#254](https://github.com/sonos/dinghy/pull/254) ([kali](https://github.com/kali))\n- Fix display of android platforms in cargo dinghy all-devices [\\#250](https://github.com/sonos/dinghy/pull/250) ([fredszaq](https://github.com/fredszaq))\n\n## [0.8.1](https://github.com/sonos/dinghy/tree/0.8.1) (2025-06-30)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.8.0...0.8.1)\n\n**Closed issues:**\n\n- No devices found for name hint `my\\_android' [\\#252](https://github.com/sonos/dinghy/issues/252)\n\n**Merged pull requests:**\n\n- Use VersionReq api to support X.Y versions \\(needed for iOS 18\\) [\\#253](https://github.com/sonos/dinghy/pull/253) ([hdlj](https://github.com/hdlj))\n- fix workspace resolver warning in test-ws [\\#249](https://github.com/sonos/dinghy/pull/249) ([fredszaq](https://github.com/fredszaq))\n- search ndk in default install location in linux [\\#248](https://github.com/sonos/dinghy/pull/248) ([fredszaq](https://github.com/fredszaq))\n- debug ci [\\#247](https://github.com/sonos/dinghy/pull/247) ([kali](https://github.com/kali))\n- Fix provisioning profile path detection [\\#246](https://github.com/sonos/dinghy/pull/246) ([emricksinisonos](https://github.com/emricksinisonos))\n- Revert \"Disable proc macro tests for tvOS and watchOS\" [\\#244](https://github.com/sonos/dinghy/pull/244) ([fredszaq](https://github.com/fredszaq))\n\n## [0.8.0](https://github.com/sonos/dinghy/tree/0.8.0) (2024-11-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.7.3...0.8.0)\n\n**Closed issues:**\n\n- WathOS CI broken [\\#238](https://github.com/sonos/dinghy/issues/238)\n\n**Merged pull requests:**\n\n- Disable proc macro tests for tvOS and watchOS [\\#243](https://github.com/sonos/dinghy/pull/243) ([fredszaq](https://github.com/fredszaq))\n- Introduce plugin platform [\\#242](https://github.com/sonos/dinghy/pull/242) ([fredszaq](https://github.com/fredszaq))\n- add `skip_source_copy` flag [\\#241](https://github.com/sonos/dinghy/pull/241) ([fredszaq](https://github.com/fredszaq))\n\n## [0.7.3](https://github.com/sonos/dinghy/tree/0.7.3) (2024-10-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.7.2...0.7.3)\n\n**Merged pull requests:**\n\n- XCode 16: profiles location has changed [\\#239](https://github.com/sonos/dinghy/pull/239) ([kali](https://github.com/kali))\n- accept licenses [\\#236](https://github.com/sonos/dinghy/pull/236) ([kali](https://github.com/kali))\n\n## [0.7.2](https://github.com/sonos/dinghy/tree/0.7.2) (2024-06-17)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.7.1...0.7.2)\n\n**Closed issues:**\n\n- About the build performance [\\#232](https://github.com/sonos/dinghy/issues/232)\n- xcrun: error: unable to find utility \"devicectl\", not a developer tool or in PATH [\\#225](https://github.com/sonos/dinghy/issues/225)\n\n**Merged pull requests:**\n\n- remove deprecated atty and tempdir dependencies [\\#235](https://github.com/sonos/dinghy/pull/235) ([fredszaq](https://github.com/fredszaq))\n- cargo fmt [\\#234](https://github.com/sonos/dinghy/pull/234) ([ThibautLorrainSonos](https://github.com/ThibautLorrainSonos))\n- libclang path is in lib and not lib64 in ndk 26+ [\\#233](https://github.com/sonos/dinghy/pull/233) ([ThibautLorrainSonos](https://github.com/ThibautLorrainSonos))\n- Update github org [\\#231](https://github.com/sonos/dinghy/pull/231) ([jayvdb](https://github.com/jayvdb))\n- show id of ssh devices \\(in addtion to their ip\\) in all-devices command [\\#230](https://github.com/sonos/dinghy/pull/230) ([fredszaq](https://github.com/fredszaq))\n- Use matrix in CI for third tier apple simulators [\\#228](https://github.com/sonos/dinghy/pull/228) ([simlay](https://github.com/simlay))\n\n## [0.7.1](https://github.com/sonos/dinghy/tree/0.7.1) (2024-04-11)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.7.0...0.7.1)\n\n**Merged pull requests:**\n\n- fix macos binary upload [\\#227](https://github.com/sonos/dinghy/pull/227) ([kali](https://github.com/kali))\n- only warn if xcrun is missing [\\#226](https://github.com/sonos/dinghy/pull/226) ([kali](https://github.com/kali))\n- Bump msrv to 1.74 [\\#224](https://github.com/sonos/dinghy/pull/224) ([simlay](https://github.com/simlay))\n\n## [0.7.0](https://github.com/sonos/dinghy/tree/0.7.0) (2024-04-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.8...0.7.0)\n\n**Closed issues:**\n\n- Run test on iOS failed: No such file or directory \\(os error 2\\) [\\#216](https://github.com/sonos/dinghy/issues/216)\n- Support cargo-dinghy as a cargo test runner [\\#210](https://github.com/sonos/dinghy/issues/210)\n- Dinghy hangs on macOS-14 runner [\\#207](https://github.com/sonos/dinghy/issues/207)\n- Use `xcrun devicectl` to deploy to iOS devices. [\\#204](https://github.com/sonos/dinghy/issues/204)\n- Faiiled to build what use ndk\\_context project [\\#201](https://github.com/sonos/dinghy/issues/201)\n- Add macOS M1 binary to release CI [\\#200](https://github.com/sonos/dinghy/issues/200)\n- Getting files generated by tests back? [\\#199](https://github.com/sonos/dinghy/issues/199)\n- `undefined symbol: ANativeWindow_setBuffersGeometry` with visual tests using `wgpu` [\\#198](https://github.com/sonos/dinghy/issues/198)\n- watchOS simulator support [\\#194](https://github.com/sonos/dinghy/issues/194)\n- tvOS simulator support [\\#193](https://github.com/sonos/dinghy/issues/193)\n- How to build with non-default crate features? [\\#129](https://github.com/sonos/dinghy/issues/129)\n\n**Merged pull requests:**\n\n- use fs\\_err when it make sense in dinghy\\_lib [\\#222](https://github.com/sonos/dinghy/pull/222) ([kali](https://github.com/kali))\n- follow symlinks while walking dirs \\(and improve error messages\\) [\\#221](https://github.com/sonos/dinghy/pull/221) ([kali](https://github.com/kali))\n- fix when multiple Dinghy app are installed [\\#220](https://github.com/sonos/dinghy/pull/220) ([kali](https://github.com/kali))\n- add changelog generation script in ./pre-release.sh [\\#219](https://github.com/sonos/dinghy/pull/219) ([fredszaq](https://github.com/fredszaq))\n- better error message on pymobiledevice3 failure [\\#218](https://github.com/sonos/dinghy/pull/218) ([kali](https://github.com/kali))\n- android: fix SDK detection on mac. [\\#217](https://github.com/sonos/dinghy/pull/217) ([ashdnazg](https://github.com/ashdnazg))\n- Support for ios17 [\\#215](https://github.com/sonos/dinghy/pull/215) ([kali](https://github.com/kali))\n- Infer the platform when runner is called in standalone mode [\\#213](https://github.com/sonos/dinghy/pull/213) ([fredszaq](https://github.com/fredszaq))\n- Universal macos binary in CI [\\#209](https://github.com/sonos/dinghy/pull/209) ([simlay](https://github.com/simlay))\n- Test on macOS-13 \\(x86\\) and macOS-14 \\(arm\\) [\\#206](https://github.com/sonos/dinghy/pull/206) ([simlay](https://github.com/simlay))\n- android emulator needs to be installed for avdmanager to work [\\#205](https://github.com/sonos/dinghy/pull/205) ([ThibautLorrainSonos](https://github.com/ThibautLorrainSonos))\n- Support tvOS and watchOS simulators [\\#203](https://github.com/sonos/dinghy/pull/203) ([simlay](https://github.com/simlay))\n- use android emulator 32.1.15 in ci as 33.1.23 segfaults [\\#202](https://github.com/sonos/dinghy/pull/202) ([fredszaq](https://github.com/fredszaq))\n- Bump rustix from 0.38.14 to 0.38.19 [\\#196](https://github.com/sonos/dinghy/pull/196) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Fix preferences path after Ventura [\\#195](https://github.com/sonos/dinghy/pull/195) ([ldm0](https://github.com/ldm0))\n\n## [0.6.8](https://github.com/sonos/dinghy/tree/0.6.8) (2023-09-29)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.7...0.6.8)\n\n**Merged pull requests:**\n\n- search android ndk also in ANDROID\\_NDK env var and pass ANDROID\\_NDK var to build if not set \\(cmake compat\\) [\\#192](https://github.com/sonos/dinghy/pull/192) ([fredszaq](https://github.com/fredszaq))\n\n## [0.6.7](https://github.com/sonos/dinghy/tree/0.6.7) (2023-09-14)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.6...0.6.7)\n\n**Closed issues:**\n\n- Maintain CHANGELOG [\\#187](https://github.com/sonos/dinghy/issues/187)\n\n**Merged pull requests:**\n\n- Do not crash dinghy\\_bindgen macro if DINGHY\\_BUILD\\_LIBCLANG\\_PATH is not present [\\#191](https://github.com/sonos/dinghy/pull/191) ([fredszaq](https://github.com/fredszaq))\n\n## [0.6.6](https://github.com/sonos/dinghy/tree/0.6.6) (2023-09-14)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.5...0.6.6)\n\n**Merged pull requests:**\n\n- msrv 1.70 [\\#190](https://github.com/sonos/dinghy/pull/190) ([fredszaq](https://github.com/fredszaq))\n- fix bindgen config when using android ndk [\\#189](https://github.com/sonos/dinghy/pull/189) ([fredszaq](https://github.com/fredszaq))\n\n## [0.6.5](https://github.com/sonos/dinghy/tree/0.6.5) (2023-09-11)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.4...0.6.5)\n\n**Closed issues:**\n\n- `catch_unwind` broken on `armv7-apple-ios` [\\#156](https://github.com/sonos/dinghy/issues/156)\n\n**Merged pull requests:**\n\n- properly setup clang++ as CXX in android auto platforms [\\#188](https://github.com/sonos/dinghy/pull/188) ([fredszaq](https://github.com/fredszaq))\n- cargo update \\(procmacro2 failing to compile on nightly\\) [\\#186](https://github.com/sonos/dinghy/pull/186) ([fredszaq](https://github.com/fredszaq))\n- make RUST\\_BACKTRACE overwritable by --env [\\#185](https://github.com/sonos/dinghy/pull/185) ([bestouff](https://github.com/bestouff))\n- add legacy dinghy crate [\\#184](https://github.com/sonos/dinghy/pull/184) ([fredszaq](https://github.com/fredszaq))\n- bump msrv [\\#183](https://github.com/sonos/dinghy/pull/183) ([kali](https://github.com/kali))\n\n## [0.6.4](https://github.com/sonos/dinghy/tree/0.6.4) (2023-04-13)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.3...0.6.4)\n\n**Merged pull requests:**\n\n- use llvm-ar instead of the binutils one for android ndk 23+ [\\#182](https://github.com/sonos/dinghy/pull/182) ([fredszaq](https://github.com/fredszaq))\n\n## [0.6.3](https://github.com/sonos/dinghy/tree/0.6.3) (2022-11-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.2...0.6.3)\n\n**Closed issues:**\n\n- On the use `std::fs::ReadDir.next()` [\\#177](https://github.com/sonos/dinghy/issues/177)\n- failed to compile cargo-dinghy v0.3.2 - multiple packages link to native library git2 [\\#66](https://github.com/sonos/dinghy/issues/66)\n\n**Merged pull requests:**\n\n- add all shared libs as args in run-with [\\#181](https://github.com/sonos/dinghy/pull/181) ([fredszaq](https://github.com/fredszaq))\n- bump iphone min version [\\#180](https://github.com/sonos/dinghy/pull/180) ([kali](https://github.com/kali))\n- add apt update [\\#179](https://github.com/sonos/dinghy/pull/179) ([kali](https://github.com/kali))\n\n## [0.6.2](https://github.com/sonos/dinghy/tree/0.6.2) (2022-08-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.1...0.6.2)\n\n**Closed issues:**\n\n- No more working on iOS devices 14.x [\\#135](https://github.com/sonos/dinghy/issues/135)\n- Cannot run tests on iOS 14 device [\\#131](https://github.com/sonos/dinghy/issues/131)\n- test --no-fail-fast is not recognized [\\#127](https://github.com/sonos/dinghy/issues/127)\n- Old android device using ro.product.cpu.abi rather than ro.product.cpu.abilist [\\#109](https://github.com/sonos/dinghy/issues/109)\n- ImportError: No module named six [\\#6](https://github.com/sonos/dinghy/issues/6)\n\n**Merged pull requests:**\n\n- fix android toolchain discovery on macOS [\\#178](https://github.com/sonos/dinghy/pull/178) ([fredszaq](https://github.com/fredszaq))\n- Mention ios-deploy in docu [\\#176](https://github.com/sonos/dinghy/pull/176) ([umgefahren](https://github.com/umgefahren))\n\n## [0.6.1](https://github.com/sonos/dinghy/tree/0.6.1) (2022-08-04)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.6.0...0.6.1)\n\n**Closed issues:**\n\n- deploy to iOS devices with ios-deploy [\\#166](https://github.com/sonos/dinghy/issues/166)\n- No device support directory for iOS version 12.5 [\\#165](https://github.com/sonos/dinghy/issues/165)\n- lldb output is variable [\\#158](https://github.com/sonos/dinghy/issues/158)\n\n**Merged pull requests:**\n\n- musl build fix [\\#175](https://github.com/sonos/dinghy/pull/175) ([fredszaq](https://github.com/fredszaq))\n- display compile errors and check cargo exit status in run-with [\\#174](https://github.com/sonos/dinghy/pull/174) ([fredszaq](https://github.com/fredszaq))\n- Dependency cleanup [\\#172](https://github.com/sonos/dinghy/pull/172) ([madsmtm](https://github.com/madsmtm))\n- Support newer versions of the OpenSSL x509 text format [\\#171](https://github.com/sonos/dinghy/pull/171) ([madsmtm](https://github.com/madsmtm))\n\n## [0.6.0](https://github.com/sonos/dinghy/tree/0.6.0) (2022-07-27)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.5.1...0.6.0)\n\n**Closed issues:**\n\n- Share resource files between tests [\\#161](https://github.com/sonos/dinghy/issues/161)\n\n**Merged pull requests:**\n\n- some ios signing logging adjusts [\\#170](https://github.com/sonos/dinghy/pull/170) ([kali](https://github.com/kali))\n- Logging overhaul [\\#169](https://github.com/sonos/dinghy/pull/169) ([fredszaq](https://github.com/fredszaq))\n- use ios-deploy, remove in-house rust partial port [\\#168](https://github.com/sonos/dinghy/pull/168) ([kali](https://github.com/kali))\n- introduce run-with subcommand and transparent copy of files in runner args [\\#167](https://github.com/sonos/dinghy/pull/167) ([fredszaq](https://github.com/fredszaq))\n- do not copy ad-hoc rsync on device if file exists [\\#164](https://github.com/sonos/dinghy/pull/164) ([fredszaq](https://github.com/fredszaq))\n- Use package name instead of runnable id for dir on target [\\#163](https://github.com/sonos/dinghy/pull/163) ([fredszaq](https://github.com/fredszaq))\n- support android ndk 23 and up [\\#162](https://github.com/sonos/dinghy/pull/162) ([fredszaq](https://github.com/fredszaq))\n- Broken implicit wp dep [\\#160](https://github.com/sonos/dinghy/pull/160) ([kali](https://github.com/kali))\n\n## [0.5.1](https://github.com/sonos/dinghy/tree/0.5.1) (2022-07-08)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.5.0...0.5.1)\n\n**Merged pull requests:**\n\n- try to make the excluded bug appear in CI [\\#159](https://github.com/sonos/dinghy/pull/159) ([kali](https://github.com/kali))\n\n## [0.5.0](https://github.com/sonos/dinghy/tree/0.5.0) (2022-07-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.71...0.5.0)\n\n**Closed issues:**\n\n- cargo install dinghy takes too long [\\#39](https://github.com/sonos/dinghy/issues/39)\n\n**Merged pull requests:**\n\n- Remove bundled cargo [\\#157](https://github.com/sonos/dinghy/pull/157) ([fredszaq](https://github.com/fredszaq))\n- Update cargo to v0.62 [\\#154](https://github.com/sonos/dinghy/pull/154) ([madsmtm](https://github.com/madsmtm))\n\n## [0.4.71](https://github.com/sonos/dinghy/tree/0.4.71) (2022-03-21)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.70...0.4.71)\n\n**Closed issues:**\n\n- Support for aarch64-apple-ios-sim in dinghy [\\#147](https://github.com/sonos/dinghy/issues/147)\n\n**Merged pull requests:**\n\n- Fix iOS bundle to make app use the full screen [\\#152](https://github.com/sonos/dinghy/pull/152) ([simlay](https://github.com/simlay))\n- Initial stuff for aarch64 ios simulator support [\\#151](https://github.com/sonos/dinghy/pull/151) ([simlay](https://github.com/simlay))\n\n## [0.4.70](https://github.com/sonos/dinghy/tree/0.4.70) (2022-03-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.69...0.4.70)\n\n**Merged pull requests:**\n\n- bump cargo [\\#150](https://github.com/sonos/dinghy/pull/150) ([kali](https://github.com/kali))\n\n## [0.4.69](https://github.com/sonos/dinghy/tree/0.4.69) (2022-03-17)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.68...0.4.69)\n\n**Closed issues:**\n\n- Rust 2021 Edition [\\#148](https://github.com/sonos/dinghy/issues/148)\n\n**Merged pull requests:**\n\n- Fix iOS simulator tests with lldb 13 [\\#149](https://github.com/sonos/dinghy/pull/149) ([simlay](https://github.com/simlay))\n- tests are actually runnable on host platform [\\#146](https://github.com/sonos/dinghy/pull/146) ([kali](https://github.com/kali))\n- Fix ci weirdness [\\#145](https://github.com/sonos/dinghy/pull/145) ([kali](https://github.com/kali))\n- Fix iOS 9.3 [\\#143](https://github.com/sonos/dinghy/pull/143) ([madsmtm](https://github.com/madsmtm))\n- Fix \"Developer\" typo [\\#142](https://github.com/sonos/dinghy/pull/142) ([madsmtm](https://github.com/madsmtm))\n\n## [0.4.68](https://github.com/sonos/dinghy/tree/0.4.68) (2022-01-19)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.67...0.4.68)\n\n**Merged pull requests:**\n\n- Dont run tests for `proc-macro` crates [\\#144](https://github.com/sonos/dinghy/pull/144) ([madsmtm](https://github.com/madsmtm))\n\n## [0.4.67](https://github.com/sonos/dinghy/tree/0.4.67) (2021-12-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.66...0.4.67)\n\n**Merged pull requests:**\n\n- add support for custom profiles [\\#140](https://github.com/sonos/dinghy/pull/140) ([fredszaq](https://github.com/fredszaq))\n\n## [0.4.66](https://github.com/sonos/dinghy/tree/0.4.66) (2021-11-29)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.63...0.4.66)\n\n**Closed issues:**\n\n- test-app build fails with unable to find library -lgcc [\\#138](https://github.com/sonos/dinghy/issues/138)\n\n**Merged pull requests:**\n\n- manual import from anyhow [\\#139](https://github.com/sonos/dinghy/pull/139) ([kali](https://github.com/kali))\n\n## [0.4.63](https://github.com/sonos/dinghy/tree/0.4.63) (2021-11-04)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.62...0.4.63)\n\n**Closed issues:**\n\n- Support building crates that use resolver 2 [\\#133](https://github.com/sonos/dinghy/issues/133)\n\n**Merged pull requests:**\n\n- update cargo and other dependencies [\\#137](https://github.com/sonos/dinghy/pull/137) ([fredszaq](https://github.com/fredszaq))\n\n## [0.4.62](https://github.com/sonos/dinghy/tree/0.4.62) (2021-07-28)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.61...0.4.62)\n\n**Merged pull requests:**\n\n- Bump cargo [\\#134](https://github.com/sonos/dinghy/pull/134) ([kali](https://github.com/kali))\n- Fixes the iOS test command [\\#132](https://github.com/sonos/dinghy/pull/132) ([Robert-Steiner](https://github.com/Robert-Steiner))\n\n## [0.4.61](https://github.com/sonos/dinghy/tree/0.4.61) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.60...0.4.61)\n\n## [0.4.60](https://github.com/sonos/dinghy/tree/0.4.60) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.59...0.4.60)\n\n## [0.4.59](https://github.com/sonos/dinghy/tree/0.4.59) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.57...0.4.59)\n\n## [0.4.57](https://github.com/sonos/dinghy/tree/0.4.57) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.55...0.4.57)\n\n## [0.4.55](https://github.com/sonos/dinghy/tree/0.4.55) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.54...0.4.55)\n\n## [0.4.54](https://github.com/sonos/dinghy/tree/0.4.54) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.53...0.4.54)\n\n## [0.4.53](https://github.com/sonos/dinghy/tree/0.4.53) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.52...0.4.53)\n\n## [0.4.52](https://github.com/sonos/dinghy/tree/0.4.52) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.51...0.4.52)\n\n## [0.4.51](https://github.com/sonos/dinghy/tree/0.4.51) (2021-02-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.50...0.4.51)\n\n## [0.4.50](https://github.com/sonos/dinghy/tree/0.4.50) (2021-02-12)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.48...0.4.50)\n\n## [0.4.48](https://github.com/sonos/dinghy/tree/0.4.48) (2021-02-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.47...0.4.48)\n\n## [0.4.47](https://github.com/sonos/dinghy/tree/0.4.47) (2021-02-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.46...0.4.47)\n\n## [0.4.46](https://github.com/sonos/dinghy/tree/0.4.46) (2021-02-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.45...0.4.46)\n\n## [0.4.45](https://github.com/sonos/dinghy/tree/0.4.45) (2021-02-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.43...0.4.45)\n\n## [0.4.43](https://github.com/sonos/dinghy/tree/0.4.43) (2021-02-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.41...0.4.43)\n\n**Closed issues:**\n\n- don't work for ios 13.3 on osx 10.15.5 [\\#116](https://github.com/sonos/dinghy/issues/116)\n\n**Merged pull requests:**\n\n- Maintenance [\\#130](https://github.com/sonos/dinghy/pull/130) ([kali](https://github.com/kali))\n- Better error message on rsync exec [\\#128](https://github.com/sonos/dinghy/pull/128) ([fredszaq](https://github.com/fredszaq))\n- Fix some grammar and typos in README.md [\\#126](https://github.com/sonos/dinghy/pull/126) ([geophree](https://github.com/geophree))\n\n## [0.4.41](https://github.com/sonos/dinghy/tree/0.4.41) (2020-10-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.40...0.4.41)\n\n**Closed issues:**\n\n- Copies libdl.so to Android, causing failure to run [\\#124](https://github.com/sonos/dinghy/issues/124)\n- Dinghy on Linux [\\#121](https://github.com/sonos/dinghy/issues/121)\n\n**Merged pull requests:**\n\n- Prevent deployment of libdl.so on Android [\\#125](https://github.com/sonos/dinghy/pull/125) ([tom-bowles](https://github.com/tom-bowles))\n\n## [0.4.40](https://github.com/sonos/dinghy/tree/0.4.40) (2020-09-10)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.39...0.4.40)\n\n**Merged pull requests:**\n\n- Check Android NDK in non legacy path [\\#123](https://github.com/sonos/dinghy/pull/123) ([kafji](https://github.com/kafji))\n- Log on Android NDK not found [\\#122](https://github.com/sonos/dinghy/pull/122) ([kafji](https://github.com/kafji))\n\n## [0.4.39](https://github.com/sonos/dinghy/tree/0.4.39) (2020-08-05)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.38...0.4.39)\n\n**Merged pull requests:**\n\n- iOS environment variable support [\\#120](https://github.com/sonos/dinghy/pull/120) ([Jasper-Bekkers](https://github.com/Jasper-Bekkers))\n- Add arm64e support [\\#119](https://github.com/sonos/dinghy/pull/119) ([Jasper-Bekkers](https://github.com/Jasper-Bekkers))\n\n## [0.4.38](https://github.com/sonos/dinghy/tree/0.4.38) (2020-06-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.37...0.4.38)\n\n**Merged pull requests:**\n\n- Ssh arg port order [\\#118](https://github.com/sonos/dinghy/pull/118) ([kali](https://github.com/kali))\n- Features bugfix [\\#117](https://github.com/sonos/dinghy/pull/117) ([kali](https://github.com/kali))\n\n## [0.4.37](https://github.com/sonos/dinghy/tree/0.4.37) (2020-05-29)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.35...0.4.37)\n\n**Closed issues:**\n\n- Long term solution for URL regression [\\#101](https://github.com/sonos/dinghy/issues/101)\n\n**Merged pull requests:**\n\n- add missing x86\\_64 android device [\\#115](https://github.com/sonos/dinghy/pull/115) ([MarcTreySonos](https://github.com/MarcTreySonos))\n- experimental shell expansion in run [\\#114](https://github.com/sonos/dinghy/pull/114) ([kali](https://github.com/kali))\n- avoid createing huge and useless PKG\\_CONFIG\\_LIBDIR [\\#113](https://github.com/sonos/dinghy/pull/113) ([kali](https://github.com/kali))\n- Allow SSH devices to use the host toolchain [\\#112](https://github.com/sonos/dinghy/pull/112) ([fredszaq](https://github.com/fredszaq))\n\n## [0.4.35](https://github.com/sonos/dinghy/tree/0.4.35) (2020-04-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.34...0.4.35)\n\n**Merged pull requests:**\n\n- bump cargo, and switch to anyhow [\\#111](https://github.com/sonos/dinghy/pull/111) ([kali](https://github.com/kali))\n- eradicate two warnings [\\#108](https://github.com/sonos/dinghy/pull/108) ([kali](https://github.com/kali))\n\n## [0.4.34](https://github.com/sonos/dinghy/tree/0.4.34) (2020-04-07)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.33...0.4.34)\n\n## [0.4.33](https://github.com/sonos/dinghy/tree/0.4.33) (2020-04-07)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.31...0.4.33)\n\n**Merged pull requests:**\n\n- build bench in release mode [\\#110](https://github.com/sonos/dinghy/pull/110) ([kali](https://github.com/kali))\n- some android auto target fixes [\\#91](https://github.com/sonos/dinghy/pull/91) ([kali](https://github.com/kali))\n\n## [0.4.31](https://github.com/sonos/dinghy/tree/0.4.31) (2020-04-07)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.30...0.4.31)\n\n## [0.4.30](https://github.com/sonos/dinghy/tree/0.4.30) (2020-04-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.29...0.4.30)\n\n## [0.4.29](https://github.com/sonos/dinghy/tree/0.4.29) (2020-04-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.28...0.4.29)\n\n## [0.4.28](https://github.com/sonos/dinghy/tree/0.4.28) (2020-04-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.27...0.4.28)\n\n## [0.4.27](https://github.com/sonos/dinghy/tree/0.4.27) (2020-04-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.26...0.4.27)\n\n## [0.4.26](https://github.com/sonos/dinghy/tree/0.4.26) (2020-04-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.25...0.4.26)\n\n## [0.4.25](https://github.com/sonos/dinghy/tree/0.4.25) (2020-04-06)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.24...0.4.25)\n\n**Merged pull requests:**\n\n- Daxpedia windows fix [\\#107](https://github.com/sonos/dinghy/pull/107) ([kali](https://github.com/kali))\n- Fix targets which does not have sysroot [\\#106](https://github.com/sonos/dinghy/pull/106) ([MarcTreySonos](https://github.com/MarcTreySonos))\n- add upload rsync binary step for the ssh devices [\\#105](https://github.com/sonos/dinghy/pull/105) ([MarcTreySonos](https://github.com/MarcTreySonos))\n- Android ndk 19+ documentation [\\#86](https://github.com/sonos/dinghy/pull/86) ([Deluvi](https://github.com/Deluvi))\n\n## [0.4.24](https://github.com/sonos/dinghy/tree/0.4.24) (2020-02-13)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.23...0.4.24)\n\n**Merged pull requests:**\n\n- libc++\\_shared.so must be included for android deployment  [\\#103](https://github.com/sonos/dinghy/pull/103) ([MarcTreySonos](https://github.com/MarcTreySonos))\n\n## [0.4.23](https://github.com/sonos/dinghy/tree/0.4.23) (2020-02-13)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.22...0.4.23)\n\n**Fixed bugs:**\n\n- Revert `url` lib bump to 2.1.0 [\\#104](https://github.com/sonos/dinghy/pull/104) ([Deluvi](https://github.com/Deluvi))\n\n## [0.4.22](https://github.com/sonos/dinghy/tree/0.4.22) (2020-01-30)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.21...0.4.22)\n\n**Closed issues:**\n\n- dinghy\\_test::test\\_file\\_path doesn't work when running tests natively on desktop platform [\\#99](https://github.com/sonos/dinghy/issues/99)\n\n**Merged pull requests:**\n\n- fix ci [\\#102](https://github.com/sonos/dinghy/pull/102) ([kali](https://github.com/kali))\n- use crate url 2.0.0 [\\#97](https://github.com/sonos/dinghy/pull/97) ([MarcTreySonos](https://github.com/MarcTreySonos))\n\n## [0.4.21](https://github.com/sonos/dinghy/tree/0.4.21) (2020-01-27)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.20...0.4.21)\n\n**Merged pull requests:**\n\n- Refactor bindgen related code  [\\#98](https://github.com/sonos/dinghy/pull/98) ([Deluvi](https://github.com/Deluvi))\n\n## [0.4.20](https://github.com/sonos/dinghy/tree/0.4.20) (2020-01-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.19...0.4.20)\n\n**Merged pull requests:**\n\n- Change IosSimDevice to use xcrun simctl launch rather than lldb [\\#96](https://github.com/sonos/dinghy/pull/96) ([simlay](https://github.com/simlay))\n\n## [0.4.19](https://github.com/sonos/dinghy/tree/0.4.19) (2020-01-13)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.18...0.4.19)\n\n**Merged pull requests:**\n\n- Updated dependencies [\\#95](https://github.com/sonos/dinghy/pull/95) ([simlay](https://github.com/simlay))\n\n## [0.4.18](https://github.com/sonos/dinghy/tree/0.4.18) (2019-10-28)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.17...0.4.18)\n\n**Merged pull requests:**\n\n- add a \"private\" framework search path [\\#94](https://github.com/sonos/dinghy/pull/94) ([kali](https://github.com/kali))\n\n## [0.4.17](https://github.com/sonos/dinghy/tree/0.4.17) (2019-10-24)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.16...0.4.17)\n\n## [0.4.16](https://github.com/sonos/dinghy/tree/0.4.16) (2019-09-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.15...0.4.16)\n\n## [0.4.15](https://github.com/sonos/dinghy/tree/0.4.15) (2019-09-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.14...0.4.15)\n\n**Merged pull requests:**\n\n- try on bionic [\\#93](https://github.com/sonos/dinghy/pull/93) ([kali](https://github.com/kali))\n\n## [0.4.14](https://github.com/sonos/dinghy/tree/0.4.14) (2019-09-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.13...0.4.14)\n\n## [0.4.13](https://github.com/sonos/dinghy/tree/0.4.13) (2019-09-08)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.12...0.4.13)\n\n## [0.4.12](https://github.com/sonos/dinghy/tree/0.4.12) (2019-08-29)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.11...0.4.12)\n\n## [0.4.11](https://github.com/sonos/dinghy/tree/0.4.11) (2019-05-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.10...0.4.11)\n\n**Closed issues:**\n\n- Add android app platform version to specify/match a minSdk Version [\\#85](https://github.com/sonos/dinghy/issues/85)\n\n**Merged pull requests:**\n\n- bump osx [\\#90](https://github.com/sonos/dinghy/pull/90) ([kali](https://github.com/kali))\n- Prevent library copy of lib file in the toolchain's sysroot when running on a remote device [\\#88](https://github.com/sonos/dinghy/pull/88) ([Deluvi](https://github.com/Deluvi))\n- Make automatic platform decision for a given device deterministic [\\#84](https://github.com/sonos/dinghy/pull/84) ([Deluvi](https://github.com/Deluvi))\n\n## [0.4.10](https://github.com/sonos/dinghy/tree/0.4.10) (2019-02-26)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.8...0.4.10)\n\n**Closed issues:**\n\n- Fails to sign when there is any non .mobileprovision file in Profiles folder [\\#77](https://github.com/sonos/dinghy/issues/77)\n\n**Merged pull requests:**\n\n- Android ndk 19+ API level choice [\\#83](https://github.com/sonos/dinghy/pull/83) ([Deluvi](https://github.com/Deluvi))\n- Fix multiple versions of the same dynamic lib used [\\#82](https://github.com/sonos/dinghy/pull/82) ([Deluvi](https://github.com/Deluvi))\n\n## [0.4.8](https://github.com/sonos/dinghy/tree/0.4.8) (2019-02-14)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.7...0.4.8)\n\n**Merged pull requests:**\n\n- reactivate toolchain binary shims as they are needed by some projects [\\#79](https://github.com/sonos/dinghy/pull/79) ([fredszaq](https://github.com/fredszaq))\n\n## [0.4.7](https://github.com/sonos/dinghy/tree/0.4.7) (2019-02-13)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.6...0.4.7)\n\n## [0.4.6](https://github.com/sonos/dinghy/tree/0.4.6) (2019-02-12)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.4.5...0.4.6)\n\n**Merged pull requests:**\n\n- various fix for ios [\\#78](https://github.com/sonos/dinghy/pull/78) ([kali](https://github.com/kali))\n\n## [0.4.5](https://github.com/sonos/dinghy/tree/0.4.5) (2019-02-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.16...0.4.5)\n\n**Closed issues:**\n\n- Failure linking backtrace-sys on android: undefined reference to 'getpagesize' [\\#61](https://github.com/sonos/dinghy/issues/61)\n- Error when I try to use dinghy [\\#48](https://github.com/sonos/dinghy/issues/48)\n- Should benchmarks return performance numbers? [\\#44](https://github.com/sonos/dinghy/issues/44)\n- MacOS: Unexpected end of JSON with Xcode CommandLineTools [\\#32](https://github.com/sonos/dinghy/issues/32)\n\n**Merged pull requests:**\n\n- more android fixes and debug info [\\#76](https://github.com/sonos/dinghy/pull/76) ([kali](https://github.com/kali))\n- refactor + android clang support [\\#75](https://github.com/sonos/dinghy/pull/75) ([kali](https://github.com/kali))\n- Script device [\\#74](https://github.com/sonos/dinghy/pull/74) ([kali](https://github.com/kali))\n- update cargo to 0.32 - edition 2018 support [\\#73](https://github.com/sonos/dinghy/pull/73) ([fredszaq](https://github.com/fredszaq))\n- Fix typos in documentation [\\#69](https://github.com/sonos/dinghy/pull/69) ([adrienball](https://github.com/adrienball))\n- support --no-run [\\#67](https://github.com/sonos/dinghy/pull/67) ([kali](https://github.com/kali))\n- filename conflict in \"run\" [\\#65](https://github.com/sonos/dinghy/pull/65) ([kali](https://github.com/kali))\n- some trace! level info, plus move exe to target/ in bundle [\\#64](https://github.com/sonos/dinghy/pull/64) ([kali](https://github.com/kali))\n- Bump dependencies [\\#63](https://github.com/sonos/dinghy/pull/63) ([Eijebong](https://github.com/Eijebong))\n- add static linking helper [\\#62](https://github.com/sonos/dinghy/pull/62) ([MarcTreySonos](https://github.com/MarcTreySonos))\n- Define defualt toolchain directory in .dinghy [\\#60](https://github.com/sonos/dinghy/pull/60) ([rtmvc](https://github.com/rtmvc))\n- do not copy target in target [\\#58](https://github.com/sonos/dinghy/pull/58) ([kali](https://github.com/kali))\n- Update build\\_env.rs [\\#57](https://github.com/sonos/dinghy/pull/57) ([warent](https://github.com/warent))\n- Update dinghy crate to cargo-dinghy [\\#56](https://github.com/sonos/dinghy/pull/56) ([nebuto](https://github.com/nebuto))\n- Allow debug build mode arg [\\#55](https://github.com/sonos/dinghy/pull/55) ([rtmvc](https://github.com/rtmvc))\n- Set permissions before copy [\\#54](https://github.com/sonos/dinghy/pull/54) ([rtmvc](https://github.com/rtmvc))\n- Copy libs for host platform too [\\#53](https://github.com/sonos/dinghy/pull/53) ([rtmvc](https://github.com/rtmvc))\n- Warn if package filtered on platform [\\#52](https://github.com/sonos/dinghy/pull/52) ([rtmvc](https://github.com/rtmvc))\n- Copy ios libs [\\#51](https://github.com/sonos/dinghy/pull/51) ([rtmvc](https://github.com/rtmvc))\n- Copy .so dependencies in target directory for builds [\\#50](https://github.com/sonos/dinghy/pull/50) ([rtmvc](https://github.com/rtmvc))\n- Strip executable copy and not original as cargo might not regenerate … [\\#49](https://github.com/sonos/dinghy/pull/49) ([rtmvc](https://github.com/rtmvc))\n- Copy .so dependencies in target directory for builds [\\#47](https://github.com/sonos/dinghy/pull/47) ([kali](https://github.com/kali))\n- Strip [\\#46](https://github.com/sonos/dinghy/pull/46) ([kali](https://github.com/kali))\n- Fix nightly compiler error [\\#45](https://github.com/sonos/dinghy/pull/45) ([pitdicker](https://github.com/pitdicker))\n- Task/cc rs compat [\\#42](https://github.com/sonos/dinghy/pull/42) ([kali](https://github.com/kali))\n- 0.3 [\\#40](https://github.com/sonos/dinghy/pull/40) ([rtmvc](https://github.com/rtmvc))\n\n## [0.2.16](https://github.com/sonos/dinghy/tree/0.2.16) (2018-02-05)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.15...0.2.16)\n\n**Closed issues:**\n\n- Android emulator support? [\\#37](https://github.com/sonos/dinghy/issues/37)\n- Error installing dinghy [\\#36](https://github.com/sonos/dinghy/issues/36)\n\n## [0.2.15](https://github.com/sonos/dinghy/tree/0.2.15) (2017-11-23)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.14...0.2.15)\n\n**Closed issues:**\n\n- MacOS Cannot build with Xcode 9 \\(9A235\\) [\\#29](https://github.com/sonos/dinghy/issues/29)\n\n**Merged pull requests:**\n\n- Update to cargo 0.22, fix a few warnings [\\#33](https://github.com/sonos/dinghy/pull/33) ([ryanpresciense](https://github.com/ryanpresciense))\n\n## [0.2.14](https://github.com/sonos/dinghy/tree/0.2.14) (2017-10-05)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.13...0.2.14)\n\n**Closed issues:**\n\n- Refactoring some common logic into a library [\\#4](https://github.com/sonos/dinghy/issues/4)\n\n**Merged pull requests:**\n\n- Bump to xcode 9 [\\#31](https://github.com/sonos/dinghy/pull/31) ([klefevre](https://github.com/klefevre))\n- Bump dependencies [\\#30](https://github.com/sonos/dinghy/pull/30) ([klefevre](https://github.com/klefevre))\n\n## [0.2.13](https://github.com/sonos/dinghy/tree/0.2.13) (2017-07-04)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.12...0.2.13)\n\n## [0.2.12](https://github.com/sonos/dinghy/tree/0.2.12) (2017-07-04)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.11...0.2.12)\n\n**Closed issues:**\n\n- Allow specifying port number for ssh [\\#26](https://github.com/sonos/dinghy/issues/26)\n- Need some way of deploying test\\_data even if they are included in .gitignore [\\#24](https://github.com/sonos/dinghy/issues/24)\n\n**Merged pull requests:**\n\n- Bump dependencies [\\#28](https://github.com/sonos/dinghy/pull/28) ([klefevre](https://github.com/klefevre))\n- Implement clean\\_app in ssh, add path and port options [\\#27](https://github.com/sonos/dinghy/pull/27) ([azdlowry](https://github.com/azdlowry))\n- Allow copy\\_git\\_ignored to be added to test data [\\#25](https://github.com/sonos/dinghy/pull/25) ([azdlowry](https://github.com/azdlowry))\n- Android Improvements [\\#23](https://github.com/sonos/dinghy/pull/23) ([azdlowry](https://github.com/azdlowry))\n\n## [0.2.11](https://github.com/sonos/dinghy/tree/0.2.11) (2017-06-09)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.10...0.2.11)\n\n**Merged pull requests:**\n\n- add DINGHY=1 env var when running on Android [\\#22](https://github.com/sonos/dinghy/pull/22) ([fredszaq](https://github.com/fredszaq))\n\n## [0.2.10](https://github.com/sonos/dinghy/tree/0.2.10) (2017-05-29)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.9...0.2.10)\n\n**Merged pull requests:**\n\n- More options [\\#21](https://github.com/sonos/dinghy/pull/21) ([klefevre](https://github.com/klefevre))\n\n## [0.2.9](https://github.com/sonos/dinghy/tree/0.2.9) (2017-04-21)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.8...0.2.9)\n\n**Closed issues:**\n\n- make\\_linux\\_app is creates more and more files each time [\\#19](https://github.com/sonos/dinghy/issues/19)\n\n**Merged pull requests:**\n\n- support more android architechtures [\\#20](https://github.com/sonos/dinghy/pull/20) ([dten](https://github.com/dten))\n\n## [0.2.8](https://github.com/sonos/dinghy/tree/0.2.8) (2017-04-21)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.7...0.2.8)\n\n## [0.2.7](https://github.com/sonos/dinghy/tree/0.2.7) (2017-04-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.6...0.2.7)\n\n**Merged pull requests:**\n\n- Automatic versioning from Cargo.toml [\\#18](https://github.com/sonos/dinghy/pull/18) ([klefevre](https://github.com/klefevre))\n\n## [0.2.6](https://github.com/sonos/dinghy/tree/0.2.6) (2017-04-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.5...0.2.6)\n\n**Closed issues:**\n\n- consider hiding scp/rsyinc output  [\\#16](https://github.com/sonos/dinghy/issues/16)\n- paths for .dinghy.toml [\\#14](https://github.com/sonos/dinghy/issues/14)\n\n## [0.2.5](https://github.com/sonos/dinghy/tree/0.2.5) (2017-04-15)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.4...0.2.5)\n\n**Closed issues:**\n\n- run cargo examples? [\\#11](https://github.com/sonos/dinghy/issues/11)\n\n**Merged pull requests:**\n\n- Workspace test data [\\#17](https://github.com/sonos/dinghy/pull/17) ([kali](https://github.com/kali))\n\n## [0.2.4](https://github.com/sonos/dinghy/tree/0.2.4) (2017-04-11)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.3...0.2.4)\n\n**Merged pull requests:**\n\n- Add spec support for build, test and bench subcommands [\\#15](https://github.com/sonos/dinghy/pull/15) ([klefevre](https://github.com/klefevre))\n\n## [0.2.3](https://github.com/sonos/dinghy/tree/0.2.3) (2017-03-31)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.2...0.2.3)\n\n**Merged pull requests:**\n\n- fix ssh target triple that was hardcoded instead of read from config [\\#12](https://github.com/sonos/dinghy/pull/12) ([fredszaq](https://github.com/fredszaq))\n\n## [0.2.2](https://github.com/sonos/dinghy/tree/0.2.2) (2017-03-31)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.1...0.2.2)\n\n## [0.2.1](https://github.com/sonos/dinghy/tree/0.2.1) (2017-03-29)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.2.0...0.2.1)\n\n**Merged pull requests:**\n\n- chmod from windows needs fixing [\\#13](https://github.com/sonos/dinghy/pull/13) ([dten](https://github.com/dten))\n\n## [0.2.0](https://github.com/sonos/dinghy/tree/0.2.0) (2017-02-10)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.13...0.2.0)\n\n**Merged pull requests:**\n\n- Ssh support [\\#10](https://github.com/sonos/dinghy/pull/10) ([kali](https://github.com/kali))\n- make dinghy work on linux [\\#9](https://github.com/sonos/dinghy/pull/9) ([fredszaq](https://github.com/fredszaq))\n\n## [0.1.13](https://github.com/sonos/dinghy/tree/0.1.13) (2017-01-31)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.12...0.1.13)\n\n**Merged pull requests:**\n\n- Support for multiple travis configurations [\\#8](https://github.com/sonos/dinghy/pull/8) ([kali](https://github.com/kali))\n\n## [0.1.12](https://github.com/sonos/dinghy/tree/0.1.12) (2017-01-26)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.11...0.1.12)\n\n## [0.1.11](https://github.com/sonos/dinghy/tree/0.1.11) (2017-01-26)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.10...0.1.11)\n\n**Closed issues:**\n\n- unresolved name `ensure_shim` when trying to cargo install [\\#5](https://github.com/sonos/dinghy/issues/5)\n\n**Merged pull requests:**\n\n- accept any android device name that isn't whitespace [\\#7](https://github.com/sonos/dinghy/pull/7) ([dten](https://github.com/dten))\n\n## [0.1.10](https://github.com/sonos/dinghy/tree/0.1.10) (2017-01-24)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.9...0.1.10)\n\n## [0.1.9](https://github.com/sonos/dinghy/tree/0.1.9) (2017-01-23)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.8...0.1.9)\n\n## [0.1.8](https://github.com/sonos/dinghy/tree/0.1.8) (2017-01-23)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.7...0.1.8)\n\n## [0.1.7](https://github.com/sonos/dinghy/tree/0.1.7) (2017-01-23)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.6...0.1.7)\n\n## [0.1.6](https://github.com/sonos/dinghy/tree/0.1.6) (2017-01-18)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.5...0.1.6)\n\n**Closed issues:**\n\n- Link error when executing 'cargo install dinghy' [\\#3](https://github.com/sonos/dinghy/issues/3)\n\n## [0.1.5](https://github.com/sonos/dinghy/tree/0.1.5) (2017-01-03)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/0.1.4...0.1.5)\n\n## [0.1.4](https://github.com/sonos/dinghy/tree/0.1.4) (2016-12-16)\n\n[Full Changelog](https://github.com/sonos/dinghy/compare/29e2f6c0b21a11575a4af2a9aba993ab3e2bf549...0.1.4)\n\n**Merged pull requests:**\n\n- missing env error message is wrong [\\#2](https://github.com/sonos/dinghy/pull/2) ([dten](https://github.com/dten))\n- Windows build [\\#1](https://github.com/sonos/dinghy/pull/1) ([dten](https://github.com/dten))\n\n\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"cargo-dinghy\",\n    \"dinghy-build\",\n    \"dinghy-lib\",\n    \"dinghy-test\",\n]\n\n[workspace.dependencies]\ncargo_metadata = \"=0.14.0\"\ncargo-platform = \"=0.1.8\"\n"
  },
  {
    "path": "LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Dinghy\n\n![rustc >= 1.85.0](https://img.shields.io/badge/rustc-%3E%3D1.85.0-brightgreen)\n![MIT/Apache 2](https://img.shields.io/crates/l/dinghy)\n![Build and test](https://github.com/sonos/dinghy/workflows/Build%20and%20test/badge.svg)\n\n## What?\n\nDinghy is a `cargo` extension to bring cargo workflow to cross-compilation situations.\n\nDinghy is specifically useful with \"small\" processor-based devices, like\nAndroid and iOS phones, or small single board computers like the Raspberry Pi.\nSituations where native compilation is not possible, or not practical.\n\nInitially tests and benches were the primary objective of Dinghy, but now\nat Snips we use it to cross-compile our entire platform. This includes setting\nup the stage for `cc` and `pkg-config` crates in one single place.\n\nIf you are a Rust library author, **you can run your tests and benches on\nyour smartphone in minutes.** And you should, at least once in a while.\n\n## Demo\n\nLet's try how BurntSushi's byteorder handles f32 on a few arm devices, two\nsmartphones, and a Raspberry Pi.\n\n![Demo](docs/demo.gif)\n\nPhew. It works.\n\n## How?\n\nOnce dinghy knows about your toolchains and devices, you will be able to run\ntests and benches from a simple cargo command **in any cargo project**, most of\nthe time without altering them.\n\nJust add `dinghy -d some_device` between `cargo` and its subcommand:\n\n```\ncargo dinghy -d my_android test\ncargo dinghy -d my_raspberry bench\n```\n\nBy default, without `-d`, Dinghy will make a native build, just like `cargo` would do.\n\n## Getting started\n\nDepending on your targets and your workstation, the ease of setting\nup Dinghy can vary.\n\n* [Android](docs/android.md) is relatively easy, specifically if you already are\na mobile developer.\n* [iOS](docs/ios.md) setup has a lot of steps, but at least Apple provides everything\nyou will need. Once again, if you are an iOS developer, most of the heavy lifting has\nbeen already done. And if you are not, be aware that you won't have to pay anything.\n* [other remote ssh-accessible devices](docs/ssh.md) are the easiest from dinghy point of view,\nbut you will be on your own to obtain the toolchain for your device architecture and\noperating system. If your device is a Raspberry Pi running raspbian, we can help. :)\n\n## Advanced topics and features\n\n* Some projects need [resources files](docs/files.md) for running their tests or benches. Dinghy\ntries its best to make it work in as many project/target configurations as\npossible but some projects need a bit of help.\n* In some bigger projects, you may need to [filter](docs/filter.md) your project's members depending\non the platform you want to test.\n* Passing [environment](docs/vars.md) variables to your executable may sometimes be useful.\n* Dinghy offers an [overlay](docs/overlay.md) system to \"add\" stuff to your toolchain\nsysroot. This allows you to add \"stuff\" to your build dependencies, like static libraries or headers\nwithout altering the sysroot toolchain.\n* The [`dinghy-build` crate](docs/dinghy-build.md) offers some `build.rs` features that are useful in\nthe context of cross-compilation.\n\n## Using dinghy as a runner only\nIf your project already build for the target platform without dinghy and you only want to use dinghy to run code on a\ndevice, you can use dinghy's bundled runner directly. You simply need to register the dinghy as a runner in `.cargo/config`.\nHere's an example for all apple targets\n\n```toml\n[target.'cfg(all(any(target_arch=\"aarch64\",target_arch=\"x86_64\"),target_vendor=\"apple\",any(target_os=\"ios\",target_os=\"tvos\",target_os=\"apple-watchos\")))']\nrunner = \"cargo dinghy runner --\"\n```\n\nYou can then run your tests directly with `cargo test --target aarch64-apple-ios-sim` for example.\n\nPlease note that the recommended way to use dinghy is as a cargo subcommand as it will set up quite a few things\nautomatically for your project to even build.\n\nThe runner will try to auto-detect the platform if it is not passed (as in the above example)\n\n# License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n## Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "cargo-dinghy/Cargo.toml",
    "content": "[package]\nname = \"cargo-dinghy\"\nversion = \"0.8.5-pre\"\nauthors = [\"Mathieu Poumeyrol <mathieu.poumeyrol@sonos>\", \"Thibaut Lorrain <thibaut.lorrain@sonos.com>\"]\nlicense = \"MIT/Apache-2.0\"\ndescription = \"Cross-compilation made easier\"\nhomepage = \"https://medium.com/snips-ai/dinghy-painless-rust-tests-and-benches-on-ios-and-android-c9f94f81d305#.c2sx7two8\"\nrepository = \"https://github.com/sonos/dinghy\"\nkeywords = [\n    \"tests\", \"mobile\", \"ios\", \"android\", \"cargo\"\n]\ncategories = [ \"development-tools::cargo-plugins\", \"development-tools::testing\" , \"development-tools::profiling\" ]\nreadme = \"../README.md\"\nedition = \"2021\"\n\n[badges]\ntravis-ci = { repository = \"sonos/dinghy\" }\n\n[dependencies]\ndinghy-lib = { path = \"../dinghy-lib\" }\nlog = \"0.4\"\nclap = { version = \"4\", features = [\"derive\"] }\nenv_logger = \"0.10\"\nanyhow = \"1.0.57\"\ncargo_metadata.workspace=true\n"
  },
  {
    "path": "cargo-dinghy/src/cli.rs",
    "content": "use clap::{CommandFactory, Parser, Subcommand};\nuse std::collections::HashSet;\n\n#[derive(Parser, Debug, Clone)]\n#[command(author, version, about, long_about = None)]\npub struct DinghyGeneralArgs {\n    /// Use a specific platform\n    #[arg(long, short)]\n    pub platform: Option<String>,\n\n    /// Make output more verbose, can be passed multiple times\n    #[arg(long, short, action = clap::ArgAction::Count)]\n    pub verbose: u8,\n\n    /// Make output less verbose, can be passed multiple times\n    #[arg(long, short, action = clap::ArgAction::Count)]\n    pub quiet: u8,\n\n    /// Force the use of an overlay during project build, can be passed multiple times\n    #[arg(long, short)]\n    pub overlay: Vec<String>,\n\n    /// Env variables to set on target device e.g. RUST_TRACE=trace, can be passed multiple times\n    #[arg(long, short)]\n    pub env: Vec<String>,\n\n    /// Cleanup target device after completion\n    #[arg(long, short)]\n    pub cleanup: bool,\n\n    /// Strip executable before running it on target\n    #[arg(long, short)]\n    pub strip: bool,\n\n    /// Device hint\n    #[arg(long, short)]\n    pub device: Option<String>,\n\n    /// Either a dinghy subcommand (see cargo dinghy all-dinghy-subcommands) or a\n    /// cargo one (see cargo --list)\n    // this one is here so that the help generated by clap makes sense\n    pub subcommand: Vec<String>,\n}\n\n#[derive(Parser, Debug)]\npub struct SubCommandWrapper {\n    #[command(subcommand)]\n    subcommand: DinghySubcommand,\n}\n\n#[derive(Subcommand, Debug)]\npub enum DinghySubcommand {\n    /// List devices that can be used with Dinghy for the selected platform\n    Devices {},\n    /// List all devices that can be used with Dinghy\n    AllDevices {},\n    /// List all platforms known to dinghy\n    AllPlatforms {},\n    /// List all available dinghy subcommands\n    AllDinghySubcommands {},\n    /// Dinghy runner, used internally to run executables on targets\n    Runner { args: Vec<String> },\n    /// Build an artifact and run it on a target device using the provided wrapper\n    RunWith {\n        /// Wrapper crate to use to run the lib\n        #[arg(long, short('c'))]\n        wrapper_crate: String,\n        // TODO support executables / scripts as wrappers\n        // /// Wrapper executable to use to run the lib\n        // #[clap(long, short('e'))]\n        // wrapper_executable: Option<String>,\n        /// Arguments to cargo build for the artifact\n        lib_build_args: Vec<String>,\n    },\n}\n\n#[derive(Debug)]\npub enum DinghyMode {\n    DinghySubcommand(DinghySubcommand),\n    CargoSubcommand { args: Vec<String> },\n    Naked,\n}\n\n#[derive(Debug)]\npub struct DinghyCli {\n    pub args: DinghyGeneralArgs,\n    pub mode: DinghyMode,\n}\n\nimpl DinghyCli {\n    pub fn parse() -> Self {\n        log::debug!(\"args {:?}\", std::env::args().collect::<Vec<_>>());\n\n        let args = std::env::args().skip(1).skip_while(|it| it == \"dinghy\");\n\n        #[derive(Debug, Default)]\n        struct SplitArgs {\n            general_args: Vec<String>,\n            subcommand: Vec<String>,\n        }\n\n        let args_taking_value = DinghyGeneralArgs::command()\n            .get_arguments()\n            .filter_map(|arg| {\n                if arg.get_value_names().is_some() {\n                    let mut values = vec![];\n                    if let Some(shorts) = arg.get_short_and_visible_aliases() {\n                        values\n                            .append(&mut shorts.iter().map(|short| format!(\"-{}\", short)).collect())\n                    }\n                    if let Some(longs) = arg.get_long_and_visible_aliases() {\n                        values.append(&mut longs.iter().map(|long| format!(\"--{}\", long)).collect())\n                    }\n                    if values.is_empty() {\n                        None\n                    } else {\n                        Some(values)\n                    }\n                } else {\n                    None\n                }\n            })\n            .flatten()\n            .collect::<HashSet<_>>();\n\n        let split_args = args.fold(SplitArgs::default(), |mut split_args, elem| {\n            if !split_args.subcommand.is_empty() {\n                // we've started putting args in the sub command, let's continue\n                split_args.subcommand.push(elem)\n            } else {\n                if elem.starts_with(\"-\") /* This is a new option */\n                    || split_args.general_args\n                    .last()\n                    .map(|it| args_taking_value.contains(it))\n                    .unwrap_or(false)\n                /* value for the previous option */\n                {\n                    split_args.general_args.push(elem)\n                } else {\n                    // leve the start of the subcommand here so that clap can verify it is there\n                    split_args.general_args.push(elem.clone());\n                    // this is the start of the sub command\n                    split_args.subcommand.push(elem)\n                }\n            }\n\n            split_args\n        });\n\n        let cli = DinghyCli {\n            args: Parser::parse_from(\n                vec![\"dinghy\".to_string()]\n                    .into_iter()\n                    .chain(split_args.general_args),\n            ),\n            mode: split_args\n                .subcommand\n                .first()\n                .cloned()\n                .map(|subcommand| {\n                    if DinghySubcommand::has_subcommand(&subcommand) {\n                        DinghyMode::DinghySubcommand(\n                            SubCommandWrapper::parse_from(\n                                vec![\"dinghy\".to_string()]\n                                    .into_iter()\n                                    .chain(split_args.subcommand),\n                            )\n                            .subcommand,\n                        )\n                    } else {\n                        DinghyMode::CargoSubcommand {\n                            args: split_args.subcommand,\n                        }\n                    }\n                })\n                .unwrap_or(DinghyMode::Naked),\n        };\n\n        log::debug!(\"cli {:?}\", cli);\n\n        cli\n    }\n}\n"
  },
  {
    "path": "cargo-dinghy/src/main.rs",
    "content": "use std::convert::identity;\nuse std::env;\nuse std::env::current_dir;\nuse std::io::BufReader;\nuse std::path::{Path, PathBuf};\nuse std::process::{Command, Stdio};\nuse std::sync::Arc;\n\nuse cargo_metadata::camino::Utf8PathBuf;\nuse cargo_metadata::Message;\nuse log::{debug, error, info};\n\nuse dinghy_lib::config::dinghy_config;\nuse dinghy_lib::errors::*;\nuse dinghy_lib::project::Project;\nuse dinghy_lib::utils::{set_current_verbosity, user_facing_log, LogCommandExt};\nuse dinghy_lib::Dinghy;\nuse dinghy_lib::Platform;\nuse dinghy_lib::{Build, SetupArgs};\nuse dinghy_lib::{Device, Runnable};\n\nuse crate::cli::{DinghyCli, DinghyMode, DinghySubcommand, SubCommandWrapper};\n\nmod cli;\n\nfn main() {\n    env_logger::init_from_env(\n        env_logger::Env::new()\n            .filter(\"DINGHY_LOG\")\n            .write_style(\"DINGHY_LOG_STYLE\"),\n    );\n\n    let cli = DinghyCli::parse();\n\n    set_current_verbosity(cli.args.verbose as i8);\n\n    if let Err(e) = run_command(cli) {\n        error!(\"{:?}\", e);\n        // positively ugly.\n        if e.to_string().contains(\"are filtered out on platform\") {\n            std::process::exit(3)\n        } else {\n            std::process::exit(1)\n        }\n    }\n}\n\nfn run_command(cli: DinghyCli) -> Result<()> {\n    let conf = Arc::new(dinghy_config(current_dir()?)?);\n\n    let metadata = cargo_metadata::MetadataCommand::new().exec()?;\n\n    let project = Project::new(&conf, metadata);\n    let dinghy = Dinghy::probe(&conf)?;\n\n    let (platform, device) = select_platform_and_device_from_cli(&cli, &dinghy)?;\n\n    let setup_args = SetupArgs {\n        verbosity: cli.args.verbose as i8 - cli.args.quiet as i8,\n        forced_overlays: cli.args.overlay.clone(),\n        envs: cli.args.env.clone(),\n        cleanup: cli.args.cleanup,\n        strip: cli.args.strip, // TODO this should probably be configurable in the config as well\n        device_id: device.as_ref().map(|d| d.id().to_string()),\n    };\n\n    match cli.mode {\n        DinghyMode::CargoSubcommand { ref args } => {\n            let mut cmd = create_cargo_subcomand(&platform, &device, &project, &setup_args, args)?;\n\n            log::debug!(\"Launching {:?}\", cmd);\n            let status = cmd.log_invocation(2).status()?;\n            log::debug!(\"done\");\n\n            std::process::exit(status.code().unwrap_or_else(|| {\n                log::error!(\"Could not get cargo exit code\");\n                -1\n            }));\n        }\n        DinghyMode::DinghySubcommand(DinghySubcommand::Runner { ref args }) => {\n            debug!(\"starting dinghy runner, args {:?}\", args);\n\n            let exe = args.first().cloned().unwrap();\n            let exe_path = PathBuf::from(&exe);\n\n            let inferred_target = exe_path\n                .parent() // build type\n                .and_then(|path| {\n                    if path\n                        .file_name()\n                        .map(|name| name.to_string_lossy() == \"deps\")\n                        .unwrap_or(false)\n                    {\n                        path.parent()\n                    } else {\n                        Some(path)\n                    }\n                })\n                .and_then(Path::parent) // either \"target\" for the host or the actual target name if we're cross compiling\n                .and_then(Path::file_name)\n                .map(|it| it.to_string_lossy());\n\n            debug!(\"inferred target {:?}\", inferred_target);\n\n            let (mut final_platform, mut final_device) = (platform, device);\n            if let Some(inferred_target) = inferred_target {\n                if final_device.is_none()\n                    && final_platform.rustc_triple() != inferred_target\n                    && cli.args.platform.is_none()\n                {\n                    let platform = dinghy\n                        .platforms()\n                        .into_iter()\n                        .find(|p| p.rustc_triple() == inferred_target);\n                    if let Some(platform) = platform {\n                        let device = find_first_device_for_platform(&cli, &dinghy, &platform);\n                        if let Some(device) = device {\n                            info!(\"Runner was called without explicit platform, we found {} and device {}\", platform.id(), device.id());\n                            final_device = Some(device)\n                        }\n                        final_platform = platform;\n                    }\n                }\n            };\n\n            if let Some(device) = final_device {\n                user_facing_log(\n                    \"Targeting\",\n                    &format!(\n                        \"platform {} and device {}\",\n                        final_platform.id(),\n                        device.id()\n                    ),\n                    0,\n                );\n                let exe_name = exe_path.file_name().unwrap().to_str().unwrap().to_string();\n                let exe_id = if exe_name == \"rust_out\" {\n                    // rustdoc may run concurent runners all with the same exe name, fortunately the parent dir is\n                    // different in that case so lets use that instead as an id\n                    exe_path.parent().unwrap().file_name().unwrap().to_str().unwrap().to_string()\n                } else {\n                    exe_name\n                };\n\n                let (args, files_in_run_args): (Vec<String>, Vec<Option<PathBuf>>) = args\n                    .into_iter()\n                    .skip(1)\n                    .map(|arg| {\n                        if arg.contains(std::path::MAIN_SEPARATOR) {\n                            let path_buf = PathBuf::from(&arg);\n                            if path_buf.exists() {\n                                (\n                                    PathBuf::from(\".\")\n                                        .join(path_buf.file_name().unwrap())\n                                        .to_str()\n                                        .unwrap()\n                                        .to_string(),\n                                    Some(path_buf),\n                                )\n                            } else {\n                                (arg.clone(), None)\n                            }\n                        } else {\n                            (arg.clone(), None)\n                        }\n                    })\n                    .unzip();\n\n                let files_in_run_args =\n                    files_in_run_args.into_iter().filter_map(identity).collect();\n\n                let args_ref = args.iter().map(|s| &s[..]).collect::<Vec<_>>();\n                let envs_ref = cli.args.env.iter().map(|s| &s[..]).collect::<Vec<_>>();\n                final_platform.setup_env(&project, &setup_args)?;\n\n                let mut build = Build {\n                    setup_args,\n                    // TODO these should be probably read from the executable file\n                    dynamic_libraries: vec![],\n                    runnable: Runnable {\n                        id: exe_id,\n                        package_name: std::env::var(\"CARGO_PKG_NAME\")?,\n                        exe: PathBuf::from(exe).canonicalize()?,\n                        // cargo launches the runner inside the dir of the crate\n                        source: PathBuf::from(\".\").canonicalize()?,\n                        skip_source_copy: conf.skip_source_copy,\n                    },\n                    target_path: project.metadata.target_directory.clone().into(),\n                    files_in_run_args,\n                };\n\n                if cli.args.strip {\n                    final_platform.strip(&mut build)?;\n                }\n\n                let bundle = device.run_app(\n                    &project, &build, &args_ref,\n                    &envs_ref, // TODO these are also in the SetupArgs\n                )?;\n\n                // TODO this is not done if the run fails\n                if cli.args.cleanup {\n                    device.clean_app(&bundle)?;\n                }\n            } else {\n                bail!(\"No device for platform {}\", final_platform.id())\n            }\n            Ok(())\n        }\n        DinghyMode::DinghySubcommand(DinghySubcommand::Devices {}) => {\n            match cli\n                .args\n                .platform\n                .as_ref()\n                .map(|name| dinghy.platform_by_name(name))\n            {\n                None => anyhow::bail!(\"No platform provided\"),\n                Some(None) => anyhow::bail!(\"Unknown platform\"),\n                Some(Some(platform)) => show_all_devices_for_platform(&dinghy, platform),\n            }\n        }\n        DinghyMode::DinghySubcommand(DinghySubcommand::AllDevices {}) => show_all_devices(&dinghy),\n        DinghyMode::DinghySubcommand(DinghySubcommand::AllPlatforms {}) => {\n            show_all_platforms(&dinghy)\n        }\n        DinghyMode::DinghySubcommand(DinghySubcommand::AllDinghySubcommands {}) => {\n            use clap::CommandFactory;\n            for sub in SubCommandWrapper::command().get_subcommands() {\n                println!(\n                    \"{}\\n\\t{}\",\n                    sub.get_name(),\n                    sub.get_about().unwrap_or_default()\n                );\n            }\n            Ok(())\n        }\n        DinghyMode::DinghySubcommand(DinghySubcommand::RunWith {\n            wrapper_crate,\n            mut lib_build_args,\n        }) => {\n            let mut build_command = vec![\"build\".to_string(), \"--message-format=json\".to_string()];\n            build_command.append(&mut lib_build_args);\n\n            let mut build_cargo_cmd =\n                create_cargo_subcomand(&platform, &device, &project, &setup_args, &build_command)?;\n\n            log::debug!(\"Launching {:?}\", build_cargo_cmd);\n            let mut child = build_cargo_cmd\n                .stdout(Stdio::piped())\n                .stderr(Stdio::inherit())\n                .log_invocation(2)\n                .spawn()?;\n            log::debug!(\"done\");\n\n            let mut extra_libs = vec![];\n\n            let mut lib_file =\n                cargo_metadata::Message::parse_stream(BufReader::new(child.stdout.take().unwrap()))\n                    .filter_map(|message| match message {\n                        Ok(Message::CompilerArtifact(artifact)) => {\n                            if artifact.target.kind.contains(&\"dylib\".to_string()) {\n                                extra_libs.append(&mut artifact.filenames.clone())\n                            }\n                            Some(artifact)\n                        }\n                        Ok(Message::CompilerMessage(message)) => {\n                            // TODO would be really nice to get color there but current version of\n                            // TODO cargo-metadata doesn't seem to support it\n                            eprintln!(\"{}\", message.message);\n                            None\n                        }\n                        Ok(Message::BuildFinished(build)) => {\n                            if !build.success {\n                                log::debug!(\"cargo reported a build failure\");\n                            }\n                            None\n                        }\n                        Ok(Message::TextLine(text)) => {\n                            eprintln!(\"{}\", text);\n                            None\n                        }\n                        _ => None,\n                    })\n                    .last()\n                    .ok_or_else(|| anyhow!(\"cargo did not produce an artifact\"))?\n                    .filenames\n                    .into_iter()\n                    .next()\n                    .ok_or_else(|| anyhow!(\"no file in cargo artifact\"))?;\n\n            if !extra_libs.is_empty() {\n                // if we have some extra libs, this means we're dealing with a dynamically linked\n                // lib and we need to include the rust stdlib as well, let's try and find it in the\n                // rustup home\n                if let (Ok(rustup_home), Ok(rustup_toolchain)) = (\n                    std::env::var(\"RUSTUP_HOME\"),\n                    std::env::var(\"RUSTUP_TOOLCHAIN\"),\n                ) {\n                    let stdlib_dir = PathBuf::from(rustup_home)\n                        .join(\"toolchains\")\n                        .join(rustup_toolchain)\n                        .join(\"lib\")\n                        .join(\"rustlib\")\n                        .join(platform.rustc_triple())\n                        .join(\"lib\");\n\n                    if let Ok(Some(stdlib)) = stdlib_dir.read_dir().map(|dir| {\n                        dir.filter_map(|entry| {\n                            entry\n                                .and_then(|entry| {\n                                    entry.file_type().map(|file_type| {\n                                        if file_type.is_file()\n                                            && entry\n                                                .path()\n                                                .file_stem()\n                                                .map(|stem| {\n                                                    stem.to_string_lossy().starts_with(\"libstd-\")\n                                                })\n                                                .unwrap_or(false)\n                                            && entry\n                                                .path()\n                                                .extension()\n                                                .map(|ext| {\n                                                    [\"so\", \"dll\", \"dylib\"]\n                                                        .contains(&ext.to_string_lossy().as_ref())\n                                                })\n                                                .unwrap_or(false)\n                                        {\n                                            Some(entry.path())\n                                        } else {\n                                            None\n                                        }\n                                    })\n                                })\n                                .unwrap_or(None)\n                        })\n                        .next()\n                    }) {\n                        if let Ok(stdlib_path_buf) = Utf8PathBuf::from_path_buf(stdlib) {\n                            extra_libs.push(stdlib_path_buf)\n                        }\n                    }\n                }\n            }\n\n            let code = child.wait()?.code();\n\n            match code {\n                Some(0) => { /*expected*/ }\n                Some(c) => std::process::exit(c),\n                None => std::process::exit(-1),\n            }\n\n            if cli.args.strip {\n                let stripped_dir = lib_file\n                    .parent()\n                    .ok_or_else(|| anyhow!(\"failed to get lib dir\"))?\n                    .join(\"stripped\");\n\n                std::fs::create_dir_all(&stripped_dir)?;\n\n                let stripped_lib_file = stripped_dir.join(\n                    lib_file\n                        .file_name()\n                        .ok_or_else(|| anyhow!(\"failed to get lib name\"))?,\n                );\n\n                std::fs::copy(lib_file, &stripped_lib_file)?;\n\n                let mut lib_build = Build {\n                    setup_args: setup_args.clone(),\n                    dynamic_libraries: vec![],\n                    runnable: Runnable {\n                        id: \"\".to_string(),\n                        package_name: \"\".to_string(),\n                        exe: stripped_lib_file.to_path_buf().into(),\n                        source: Default::default(),\n                        skip_source_copy: conf.skip_source_copy,\n                    },\n                    target_path: Default::default(),\n                    files_in_run_args: vec![],\n                };\n                platform.strip(&mut lib_build)?;\n\n                std::fs::copy(lib_build.runnable.exe, &stripped_lib_file)?;\n\n                lib_file = stripped_lib_file;\n            }\n\n            let mut args = vec![\n                \"run\".to_string(),\n                \"-p\".to_string(),\n                wrapper_crate,\n                \"--release\".to_string(),\n                \"--\".to_string(),\n                lib_file.to_string(),\n            ];\n            for extra_lib in extra_libs {\n                args.push(extra_lib.to_string())\n            }\n            let mut run_cargo_cmd =\n                create_cargo_subcomand(&platform, &device, &project, &setup_args, &args)?;\n\n            log::debug!(\"Launching {:?}\", run_cargo_cmd);\n            let status = run_cargo_cmd.log_invocation(2).status()?;\n            log::debug!(\"done\");\n\n            std::process::exit(status.code().unwrap_or_else(|| {\n                log::error!(\"Could not get cargo exit code\");\n                -1\n            }));\n        }\n        DinghyMode::Naked => {\n            anyhow::bail!(\"Naked mode\") // what should we do?\n        }\n    }\n}\n\nfn create_cargo_subcomand(\n    platform: &Arc<Box<dyn Platform>>,\n    device: &Option<Arc<Box<dyn Device>>>,\n    project: &Project,\n    setup_args: &SetupArgs,\n    args: &Vec<String>,\n) -> Result<Command> {\n    info!(\n        \"Targeting platform '{}' and device '{}'\",\n        platform.id(),\n        device.as_ref().map(|it| it.id()).unwrap_or(\"<none>\")\n    );\n\n    user_facing_log(\n        \"Targeting\",\n        &format!(\n            \"platform {} and device {}\",\n            platform.id(),\n            device.as_ref().map(|it| it.id()).unwrap_or(\"<none>\")\n        ),\n        0,\n    );\n\n    let cargo = env::var(\"CARGO\")\n        .map(PathBuf::from)\n        .ok()\n        .unwrap_or_else(|| PathBuf::from(\"cargo\"));\n    let mut cmd = Command::new(cargo);\n\n    for arg in args {\n        cmd.arg(arg);\n    }\n\n    platform.setup_env(&project, &setup_args)?;\n    Ok(cmd)\n}\n\nfn show_all_platforms(dinghy: &Dinghy) -> Result<()> {\n    let mut platforms = dinghy.platforms();\n    platforms.sort_by(|str1, str2| str1.id().cmp(&str2.id()));\n    for pf in platforms.iter() {\n        println!(\"* {} {}\", pf.id(), pf.rustc_triple());\n    }\n    Ok(())\n}\n\nfn show_all_devices(dinghy: &Dinghy) -> Result<()> {\n    println!(\"List of available devices for all platforms:\");\n    show_devices(&dinghy, None)\n}\n\nfn show_all_devices_for_platform(dinghy: &Dinghy, platform: Arc<Box<dyn Platform>>) -> Result<()> {\n    println!(\n        \"List of available devices for platform '{}':\",\n        platform.id()\n    );\n    show_devices(&dinghy, Some(platform))\n}\n\nfn show_devices(dinghy: &Dinghy, platform: Option<Arc<Box<dyn Platform>>>) -> Result<()> {\n    let devices = dinghy\n        .devices()\n        .into_iter()\n        .filter(|device| {\n            platform\n                .as_ref()\n                .map_or(true, |it| it.is_compatible_with(&***device))\n        })\n        .collect::<Vec<_>>();\n\n    if devices.is_empty() {\n        error!(\"No matching device found\");\n        println!(\"No matching device found\");\n    } else {\n        for device in devices {\n            let pf: Vec<_> = dinghy\n                .platforms()\n                .iter()\n                .filter(|pf| pf.is_compatible_with(&**device))\n                .cloned()\n                .collect();\n            println!(\"{}: {:?}\", device, pf);\n        }\n    }\n    Ok(())\n}\n\nfn select_platform_and_device_from_cli(\n    cli: &DinghyCli,\n    dinghy: &Dinghy,\n) -> Result<(Arc<Box<dyn Platform>>, Option<Arc<Box<dyn Device>>>)> {\n    if let Some(platform_name) = cli.args.platform.as_ref() {\n        let platform = dinghy\n            .platform_by_name(platform_name)\n            .ok_or_else(|| anyhow!(\"No '{}' platform found\", platform_name))?;\n\n        let device = find_first_device_for_platform(cli, dinghy, &platform);\n\n        Ok((platform, device))\n    } else if let Some(device_filter) = cli.args.device.as_ref() {\n        let is_banned_auto_platform_id = |id: &str| -> bool {\n            id.contains(\"auto-android\")\n                && (id.contains(\"min\") || id.contains(\"latest\") || id.contains(\"api\"))\n        };\n        let devices = dinghy\n            .devices()\n            .into_iter()\n            .filter(move |it| {\n                format!(\"{:?}\", it)\n                    .to_lowercase()\n                    .contains(&device_filter.to_lowercase())\n            })\n            .collect::<Vec<_>>();\n        if devices.len() == 0 {\n            bail!(\"No devices found for name hint `{}'\", device_filter)\n        }\n        devices\n            .into_iter()\n            .filter_map(|d| {\n                let pf = dinghy\n                    .platforms()\n                    .iter()\n                    .filter(|pf| !is_banned_auto_platform_id(&pf.id()))\n                    .find(|pf| pf.is_compatible_with(&**d))\n                    .cloned();\n                debug!(\n                    \"Looking for platform for {}: found {:?}\",\n                    d.id(),\n                    pf.as_ref().map(|p| p.id())\n                );\n                pf.map(|it| (it, Some(d)))\n            })\n            .next()\n            .ok_or_else(|| {\n                anyhow!(\n                    \"No device and platform combination found for device hint `{}'\",\n                    device_filter\n                )\n            })\n    } else {\n        Ok((dinghy.host_platform(), None))\n    }\n}\n\nfn find_first_device_for_platform(\n    cli: &DinghyCli,\n    dinghy: &Dinghy,\n    platform: &Arc<Box<dyn Platform>>,\n) -> Option<Arc<Box<dyn Device>>> {\n    dinghy\n        .devices()\n        .into_iter()\n        .filter(|device| {\n            cli.args\n                .device\n                .as_ref()\n                .map(|filter| {\n                    format!(\"{:?}\", device)\n                        .to_lowercase()\n                        .contains(&filter.to_lowercase())\n                })\n                .unwrap_or(true)\n        })\n        .filter(|it| platform.is_compatible_with(&**it.as_ref()))\n        .next()\n}\n"
  },
  {
    "path": "dinghy-build/Cargo.toml",
    "content": "[package]\nname = \"dinghy-build\"\nversion = \"0.8.5-pre\"\nauthors = [\"Mathieu Poumeyrol <mathieu.poumeyrol@snips.ai>\"]\nlicense = \"MIT/Apache-2.0\"\ndescription = \"Cross-compilation made easier - helpers for build.rs scripts\"\nhomepage = \"https://medium.com/snips-ai/dinghy-painless-rust-tests-and-benches-on-ios-and-android-c9f94f81d305#.c2sx7two8\"\nrepository = \"https://github.com/sonos/dinghy\"\nkeywords = [\n    \"tests\", \"mobile\", \"ios\", \"android\", \"cargo\"\n]\ncategories = [ \"development-tools::cargo-plugins\", \"development-tools::testing\" , \"development-tools::profiling\" ]\nedition = \"2018\"\n\n[dependencies]\nanyhow = \"1.0.58\"\ncc = \"1\"\nlog=\"0.4\"\n"
  },
  {
    "path": "dinghy-build/src/bindgen_macros.rs",
    "content": "/// Create a new `bindgen::Builder` set up and ready for cross-compilation.\n///\n/// This macro should be used for bindgen versions from 0.49 and above.\n#[macro_export]\nmacro_rules! dinghy_bindgen {\n    () => {{\n        let bindgen = $crate::dinghy_bindgen_pre_0_49!();\n\n        if $crate::build::is_cross_compiling().expect(\"Couldn't determine if it is cross-compiling\")\n        {\n            bindgen.detect_include_paths(false)\n        } else {\n            bindgen\n        }\n    }};\n}\n\n/// Compatibility macro for bindgen versions below 0.49\n#[macro_export]\nmacro_rules! dinghy_bindgen_pre_0_49 {\n    () => {{\n        use $crate::build::is_cross_compiling;\n        use $crate::build_env::sysroot_path;\n        use $crate::utils::path_to_str;\n        use $crate::{Result, Context};\n\n        fn apple_patch(builder: bindgen::Builder) -> Result<bindgen::Builder> {\n            if is_cross_compiling()? {\n                let target = env::var(\"TARGET\")?;\n                if target.contains(\"apple\") && target.contains(\"aarch64\") {\n                    // The official Apple tools use \"-arch arm64\" instead of specifying\n                    // -target directly; -arch only works when the default target is\n                    // Darwin-based to put Clang into \"Apple mode\" as it were. But it does\n                    // sort of explain why arm64 works better than aarch64, which is the\n                    // preferred name everywhere else.\n                    return Ok(builder\n                        .clang_arg(format!(\"-arch\"))\n                        .clang_arg(format!(\"arm64\")));\n                }\n            }\n            Ok(builder)\n        }\n\n        fn libclang_path_patch(builder: bindgen::Builder) -> Result<bindgen::Builder> {\n            if is_cross_compiling()? {\n                if let Ok(libclang_path) = env::var(\"DINGHY_BUILD_LIBCLANG_PATH\") {\n                    env::set_var(\"LIBCLANG_PATH\", libclang_path)\n                }\n            }\n            Ok(builder)\n        }\n\n        fn detect_toolchain(builder: bindgen::Builder) -> Result<bindgen::Builder> {\n            if is_cross_compiling()? {\n                let target = env::var(\"TARGET\")?;\n                let builder = if let Ok(_) = env::var(\"TARGET_SYSROOT\") {\n                    builder.clang_arg(format!(\"--sysroot={}\", path_to_str(&sysroot_path()?)?))\n                } else {\n                    println!(\"cargo:warning=No Sysroot detected, assuming the target is baremetal. If you have a sysroot, you must either define a TARGET_SYSROOT or use Dinghy to build your project.\");\n                    builder\n                };\n                Ok(builder.clang_arg(format!(\"--target={}\", target)))\n            } else {\n                Ok(builder)\n            }\n        }\n\n        fn include_gcc_system_headers(builder: bindgen::Builder) -> Result<bindgen::Builder> {\n            if is_cross_compiling()? {\n                // Add a path to the private headers for the target compiler. Borderline,\n                // as we are likely using a gcc header with clang frontend.\n                let path = cc::Build::new()\n                    .get_compiler()\n                    .to_command()\n                    .arg(\"--print-file-name=include\")\n                    .output()\n                    .with_context(|| \"Couldn't find target GCC executable.\")\n                    .and_then(|output| {\n                        if output.status.success() {\n                            Ok(String::from_utf8(output.stdout)?)\n                        } else {\n                            panic!(\"Couldn't determine target GCC include dir.\")\n                        }\n                    })?;\n\n                Ok(builder.clang_arg(\"-isystem\").clang_arg(path.trim()))\n            } else {\n                Ok(builder)\n            }\n        }\n\n        libclang_path_patch(\n            apple_patch(\n                include_gcc_system_headers(\n                    detect_toolchain(bindgen::Builder::default().clang_arg(\"--verbose\")).unwrap(),\n                )\n                .unwrap(),\n            )\n            .unwrap()\n        )\n        .unwrap()\n    }};\n}\n\n/// Generate a file containing the bindgen bindings in a standard path.\n///\n/// The standard path is `${OUT_DIR}/bindings.rs`.\n///\n/// To use it, simply perform the call like\n/// `generate_default_bindgen_bindings!(bindgen_builder)`\n#[macro_export]\nmacro_rules! generate_bindgen_bindings {\n    ($builder:expr) => {{\n        let out_path = env::var(\"OUT_DIR\")\n            .map(PathBuf::from)\n            .expect(\"Couldn't convert OUT_DIR var into a path\")\n            .join(\"bindings.rs\");\n        $builder\n            .generate()\n            .expect(\"Unable to generate bindings\")\n            .write_to_file(out_path)\n            .expect(\"Unable to write the bindings in the file\")\n    }};\n}\n"
  },
  {
    "path": "dinghy-build/src/build.rs",
    "content": "use super::Result;\n///! Helpers functions to output `cargo:` lines  suitable for build.rs output.\nuse std::env;\nuse std::path::Path;\n\n/// Find out if we are cross-compiling.\npub fn is_cross_compiling() -> Result<bool> {\n    Ok(env::var(\"TARGET\")? != env::var(\"HOST\")?)\n}\n\n/// Adds a `cargo:rustc-link-lib=` line.\npub fn include_path<P: AsRef<Path>>(lib_dir_path: P) -> Result<()> {\n    println!(\"cargo:rustc-link-lib={}\", lib_dir_path.as_ref().display());\n    Ok(())\n}\n\n/// Adds a `cargo:rustc-link-search=` and `cargo:rustc-link-lib=static=` line.\npub fn link_static<P: AsRef<Path>>(lib_name: &str, lib_dir_path: P) -> Result<()> {\n    println!(\n        \"cargo:rustc-link-search={}\",\n        lib_dir_path.as_ref().display()\n    );\n    println!(\"cargo:rustc-link-lib=static={}\", lib_name);\n    Ok(())\n}\n\n/// Adds a `cargo:rustc-link-search=` and `cargo:rustc-link-lib=dylib=` line.\npub fn link_dylib<P: AsRef<Path>>(lib_name: &str, lib_dir_path: P) -> Result<()> {\n    println!(\n        \"cargo:rustc-link-search={}\",\n        lib_dir_path.as_ref().display()\n    );\n    println!(\"cargo:rustc-link-lib=dylib={}\", lib_name);\n    Ok(())\n}\n\n/// Adds a `cargo:rustc-link-search` and `cargo:rustc-link-lib=` line.\npub fn link_lib<P: AsRef<Path>>(lib_name: &str, lib_dir_path: P) -> Result<()> {\n    println!(\n        \"cargo:rustc-link-search={}\",\n        lib_dir_path.as_ref().display()\n    );\n    println!(\"cargo:rustc-link-lib={}\", lib_name);\n    Ok(())\n}\n\n/// Adds a `cargo:rustc-link-lib=dylib=` line.\npub fn link_system_dylib(lib_name: &str) -> Result<()> {\n    println!(\"cargo:rustc-link-lib=dylib={}\", lib_name);\n    Ok(())\n}\n\n/// Adds a `cargo:rustc-link-lib=` line.\npub fn link_system_lib(lib_name: &str) -> Result<()> {\n    println!(\"cargo:rustc-link-lib={}\", lib_name);\n    Ok(())\n}\n\n/// Adds a `cargo:rerun-if-changed=` line.\npub fn rerun_if_changed<P: AsRef<Path>>(filepath: P) {\n    println!(\"cargo:rerun-if-changed={}\", filepath.as_ref().display());\n}\n"
  },
  {
    "path": "dinghy-build/src/build_env.rs",
    "content": "//! Target-aware environment manipulations.\n//!\n//! cc-rs and pkg-config-rs use a similar convention where some environment\n//! variables (like CC, CFLAGS or PKG_CONFIG_PATH) can be tagged with the\n//! current rustc target to distinguish a native build environment and one\n//! or several cross-compilation ones.\n//!\n//! For instance, while compiling for Android arm, `cc-rs` looks first at\n//! CC_arm-linux-androideabi, then CC_arm_linux_androideabi, the TARGET_CC\n//! and finally CC.\n//!\n//! This crates implements some of the same logic and also helps generating\n//! these variables names. It also notify all environment lookup \"back\" to\n//! cargo using `cargo:rerun-if-env-changed` markup.\n\nuse anyhow::{Context, Result};\nuse std::env;\nuse std::ffi::OsStr;\nuse std::ffi::OsString;\nuse std::path::PathBuf;\n\n/// Append a value to a PATH-like (`:`-separated) environment variable.\npub fn append_path_to_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {\n    let mut formatted_value = OsString::new();\n    if let Ok(initial_value) = env::var(key.as_ref()) {\n        formatted_value.push(initial_value);\n        formatted_value.push(\":\");\n    }\n    formatted_value.push(value);\n    env::set_var(key.as_ref(), formatted_value);\n}\n\n/// Append a value to a PATH-like (`:`-separated) environment variable taking\n/// target scoping rules into consideration.\npub fn append_path_to_target_env<K: AsRef<OsStr>, R: AsRef<str>, V: AsRef<OsStr>>(\n    k: K,\n    rustc_triple: Option<R>,\n    v: V,\n) {\n    append_path_to_env(target_key_from_triple(k, rustc_triple), v.as_ref())\n}\n\n/// Build-context aware environment variable access.\n///\n/// If we are running in a build.rs context, register the var to cargo using\n/// `cargo:rerun-if-env-changed`.\npub fn build_env(name: &str) -> Result<String> {\n    let is_build_rs = env::var(\"CARGO_PKG_NAME\").is_ok() && env::var(\"OUT_DIR\").is_ok();\n\n    if is_build_rs {\n        println!(\"cargo:rerun-if-env-changed={}\", name);\n    }\n    Ok(env::var(name)?)\n}\n\n/// Capitalize and replace `-` by `_`.\npub fn envify<S: AsRef<str>>(name: S) -> String {\n    name.as_ref()\n        .chars()\n        .map(|c| c.to_ascii_uppercase())\n        .map(|c| if c == '-' || c == '.' { '_' } else { c })\n        .collect()\n}\n\n/// Set a bunch of environment variables.\npub fn set_all_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(env: &[(K, V)]) {\n    for env_var in env {\n        set_env(env_var.0.as_ref(), env_var.1.as_ref())\n    }\n}\n\n/// Set one environment variable.\npub fn set_env<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {\n    log::debug!(\n        \"Setting environment variable {:?}={:?}\",\n        k.as_ref(),\n        v.as_ref()\n    );\n    env::set_var(k, v);\n}\n\n/// Set one environment variable if not set yet.\npub fn set_env_ifndef<K: AsRef<OsStr>, V: AsRef<OsStr>>(k: K, v: V) {\n    if let Ok(current_env_value) = env::var(k.as_ref()) {\n        log::debug!(\n            \"Ignoring value {:?} as environment variable {:?} already defined with value {:?}\",\n            k.as_ref(),\n            v.as_ref(),\n            current_env_value\n        );\n    } else {\n        log::debug!(\n            \"Setting environment variable {:?}={:?}\",\n            k.as_ref(),\n            v.as_ref()\n        );\n        env::set_var(k, v);\n    }\n}\n\n/// Set one environment variable with target-scoping rules.\npub fn set_target_env<K: AsRef<OsStr>, R: AsRef<str>, V: AsRef<OsStr>>(\n    k: K,\n    rustc_triple: Option<R>,\n    v: V,\n) {\n    set_env(target_key_from_triple(k, rustc_triple), v);\n}\n\n/// Access a required TARGET_SYSROOT variable, suggesting to define it or use\n/// Dinghy.\npub fn sysroot_path() -> Result<PathBuf> {\n    env::var_os(\"TARGET_SYSROOT\")\n        .map(PathBuf::from)\n        .context(\"You must either define a TARGET_SYSROOT or use Dinghy to build your project.\")\n}\n\n/// Access `var_base` directly, or use targetting rules depending on the build\n/// being native or cross.\npub fn target_env(var_base: &str) -> Result<String> {\n    if let Ok(target) = env::var(\"TARGET\") {\n        let is_host = env::var(\"HOST\")? == target;\n        target_env_from_triple(var_base, target.as_str(), is_host)\n    } else {\n        build_env(var_base)\n    }\n}\n\n/// Access `var_base` directly, using targetting rules.\npub fn target_env_from_triple(var_base: &str, triple: &str, is_host: bool) -> Result<String> {\n    build_env(&format!(\"{}_{}\", var_base, triple))\n        .or_else(|_| build_env(&format!(\"{}_{}\", var_base, triple.replace(\"-\", \"_\"))))\n        .or_else(|_| {\n            build_env(&format!(\n                \"{}_{}\",\n                if is_host { \"HOST\" } else { \"TARGET\" },\n                var_base\n            ))\n        })\n        .or_else(|_| build_env(var_base))\n}\n\nfn target_key_from_triple<K: AsRef<OsStr>, R: AsRef<str>>(\n    k: K,\n    rustc_triple: Option<R>,\n) -> OsString {\n    let mut target_key = OsString::new();\n    target_key.push(k);\n    if let Some(rustc_triple) = rustc_triple {\n        target_key.push(\"_\");\n        target_key.push(rustc_triple.as_ref().replace(\"-\", \"_\"));\n    }\n    target_key\n}\n"
  },
  {
    "path": "dinghy-build/src/lib.rs",
    "content": "//! Helpers for build.rs scripts.\n//!\n//! This library is meant to be used in build.rs scripts context.\n//!\n//! It contains a set of standalone functions that encodes some of the\n//! shared wisdom and conventions across build.rs scripts, cargo, dinghy,\n//! cc-rs, pkg-config-rs, bindgen, and others. It also helps providing\n//! cross-compilation arguments to autotools `./configure` scripts.\n\nmod bindgen_macros;\npub mod build;\npub mod build_env;\npub mod utils;\n\nuse crate::build::is_cross_compiling;\nuse crate::build_env::sysroot_path;\nuse crate::build_env::target_env;\nuse crate::utils::path_between;\nuse crate::utils::path_to_str;\nuse std::env;\nuse std::ffi::OsStr;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::process::Command;\n\n#[doc(hidden)]\npub use anyhow::{Context, Result};\n\n/// Decorator for the std::process::Command adding a some chainable helpers.\n///\n/// Mostly useful for calling `./configure` scripts.\npub trait CommandExt {\n    /// Add this argument to the commands, but only on macos.\n    fn arg_for_macos<S: AsRef<OsStr>>(&mut self, arg: S) -> Result<&mut Command>;\n\n    /// Add a `--prefix` to point to a toolchain sysroot or the /, depending on\n    /// dinghy environment.\n    fn configure_prefix<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Command>;\n\n    /// Adds pkgconfig environment variables to point to an eventual cross compiling sysroot.\n    ///\n    /// Usefull for compatibilty with pkg-config-rs up to 0.3.9 or to deal with\n    /// `./configure` scripts.\n    fn with_pkgconfig(&mut self) -> Result<&mut Command>;\n\n    /// Propagate TARGET, TARGET_CC, TARGET_AR and TARGET_SYSROOT to a\n    /// `./configure` script.\n    fn with_toolchain(&mut self) -> Result<&mut Command>;\n}\n\nimpl CommandExt for Command {\n    fn arg_for_macos<S: AsRef<OsStr>>(&mut self, arg: S) -> Result<&mut Command> {\n        if env::var(\"TARGET\")\n            .map(|target| target.contains(\"-apple-darwin\"))\n            .unwrap_or(false)\n        {\n            self.arg(arg.as_ref());\n        }\n        Ok(self)\n    }\n\n    fn configure_prefix<P: AsRef<Path>>(&mut self, prefix_dir: P) -> Result<&mut Command> {\n        self.args(&[\n            \"--prefix\",\n            path_to_str(&path_between(\n                sysroot_path().unwrap_or(PathBuf::from(\"/\")),\n                prefix_dir,\n            ))?,\n        ]);\n        Ok(self)\n    }\n\n    fn with_pkgconfig(&mut self) -> Result<&mut Command> {\n        if is_cross_compiling()? {\n            if let Ok(value) = target_env(\"PKG_CONFIG_PATH\") {\n                log::info!(\"Running command with PKG_CONFIG_PATH:{:?}\", value);\n                self.env(\"PKG_CONFIG_PATH\", value);\n            }\n            if let Ok(value) = target_env(\"PKG_CONFIG_LIBDIR\") {\n                log::info!(\"Running command with PKG_CONFIG_LIBDIR:{:?}\", value);\n                self.env(\"PKG_CONFIG_LIBDIR\", value);\n            }\n            if let Ok(value) = target_env(\"PKG_CONFIG_SYSROOT_DIR\") {\n                log::info!(\"Running command with PKG_CONFIG_SYSROOT_DIR:{:?}\", value);\n                self.env(\"PKG_CONFIG_SYSROOT_DIR\", value);\n            }\n        }\n        Ok(self)\n    }\n\n    fn with_toolchain(&mut self) -> Result<&mut Command> {\n        if is_cross_compiling()? {\n            if let Ok(target) = env::var(\"TARGET\") {\n                self.arg(format!(\"--host={}\", target));\n            }\n            if let Ok(cc) = env::var(\"TARGET_CC\") {\n                self.arg(format!(\"CC={}\", cc));\n            }\n            if let Ok(ar) = env::var(\"TARGET_AR\") {\n                self.arg(format!(\"AR={}\", ar));\n            }\n            if let Ok(sysroot) = env::var(\"TARGET_SYSROOT\") {\n                self.arg(format!(\"--with-sysroot={}\", &sysroot));\n            }\n        }\n        Ok(self)\n    }\n}\n"
  },
  {
    "path": "dinghy-build/src/utils.rs",
    "content": "//! Some helpers around Path and PathBuf manipulations.\n\nuse super::Result;\nuse anyhow::anyhow;\nuse std::path::Path;\nuse std::path::PathBuf;\n\n/// Wraps the annoying PathBuf to string conversion in one single call.\npub fn path_to_str(path: &PathBuf) -> Result<&str> {\n    Ok(path\n        .to_str()\n        .ok_or(anyhow!(\"Not a valid UTF-8 path ({})\", path.display()))?)\n}\n\n/// Finds the path to `to` relative from `from`.\npub fn path_between<P1: AsRef<Path>, P2: AsRef<Path>>(from: P1, to: P2) -> PathBuf {\n    let mut path = PathBuf::from(\"/\");\n    for _ in from.as_ref() {\n        path.push(\"..\");\n    }\n    for dir in to.as_ref().iter().skip(1) {\n        path.push(dir);\n    }\n    path\n}\n"
  },
  {
    "path": "dinghy-lib/Cargo.toml",
    "content": "[package]\nname = \"dinghy-lib\"\nversion = \"0.8.5-pre\"\nauthors = [\"Mathieu Poumeyrol <mathieu.poumeyrol@snips.ai>\"]\nlicense = \"MIT/Apache-2.0\"\ndescription = \"Cross-compilation made easier - see main crate cargo-dinghy\"\nhomepage = \"https://medium.com/snips-ai/dinghy-painless-rust-tests-and-benches-on-ios-and-android-c9f94f81d305#.c2sx7two8\"\nrepository = \"https://github.com/sonos/dinghy\"\nkeywords = [\n    \"tests\", \"mobile\", \"ios\", \"android\", \"cargo\"\n]\nbuild=\"build.rs\"\ncategories = [ \"development-tools::cargo-plugins\", \"development-tools::testing\" , \"development-tools::profiling\" ]\nreadme = \"../README.md\"\nedition = \"2018\"\n\n[dependencies]\nanyhow = \"1\"\ndinghy-build = { path = \"../dinghy-build\" }\ndirs = \"5\"\nfiletime = \"0.2\"\nlog = \"0.4\"\nitertools = \"0.10\"\nplist = \"1\"\nregex = \"1.0\"\njson = \"0.12\"\nignore = \"0.4\"\nserde = { version = \"1.0\", features = [\"derive\"] }\ntoml = \"0.7\"\nshell-escape = \"0.1\"\nwalkdir = \"2.0\"\nwhich = \"=4.0\"\nshellexpand=\"3\"\nsemver = \"1\"\ncargo_metadata.workspace=true\ncargo-platform.workspace=true\ncolored = \"2.0.0\"\nlazy_static = \"1.4.0\"\ndyn-clone = \"1.0.8\"\nfs-err = \"2.11.0\"\ntempfile = \"3.1\"\n\n[dev-dependencies]\ntempfile = \"3.1\"\n"
  },
  {
    "path": "dinghy-lib/build.rs",
    "content": "fn main() {\n    println!(\n        \"cargo:rustc-env=TARGET={}\",\n        std::env::var(\"TARGET\").unwrap()\n    );\n}\n"
  },
  {
    "path": "dinghy-lib/src/android/device.rs",
    "content": "use crate::device::make_remote_app;\nuse crate::errors::*;\nuse crate::platform::regular_platform::RegularPlatform;\nuse crate::project::Project;\nuse crate::utils::{get_current_verbosity, path_to_str, user_facing_log, LogCommandExt};\nuse crate::Build;\nuse crate::BuildBundle;\nuse crate::Device;\nuse crate::DeviceCompatibility;\nuse log::{debug, info, log_enabled};\nuse std::io::Write;\nuse std::{fmt, io, path, process};\n\nstatic ANDROID_WORK_DIR: &str = \"/data/local/tmp/dinghy\";\n\n#[derive(Clone)]\npub struct AndroidDevice {\n    pub adb: path::PathBuf,\n    pub id: String,\n    pub supported_targets: Vec<&'static str>,\n}\n\nimpl AndroidDevice {\n    pub fn from_id(adb: path::PathBuf, id: &str) -> Result<AndroidDevice> {\n        for prop in &[\n            \"ro.product.cpu.abilist\",\n            \"ro.product.cpu.abi\",\n            \"ro.product.cpu.abi2\",\n        ] {\n            let getprop_output = process::Command::new(&adb)\n                .args(&[\"-s\", id, \"shell\", \"getprop\", prop])\n                .log_invocation(3)\n                .output()?;\n            let abilist = String::from_utf8(getprop_output.stdout)?;\n            debug!(\n                \"Android device {}, getprop {} returned {}\",\n                id,\n                prop,\n                abilist.trim()\n            );\n            if abilist.trim().len() > 0 {\n                let supported_targets = abilist\n                    .trim()\n                    .split(\",\")\n                    .filter_map(|abi| {\n                        Some(match abi {\n                            \"arm64-v8a\" => \"aarch64-linux-android\",\n                            \"armeabi-v7a\" => \"armv7-linux-androideabi\",\n                            \"armeabi\" => \"arm-linux-androideabi\",\n                            \"x86\" => \"i686-linux-android\",\n                            \"x86_64\" => \"x86_64-linux-android\",\n                            _ => return None,\n                        })\n                    })\n                    .collect::<Vec<_>>();\n\n                return Ok(AndroidDevice {\n                    adb,\n                    id: id.into(),\n                    supported_targets: supported_targets,\n                });\n            }\n        }\n        bail!(\"Could not match a platform to the device\")\n    }\n\n    fn adb(&self) -> Result<process::Command> {\n        let mut command = process::Command::new(&self.adb);\n        command.arg(\"-s\").arg(&self.id);\n        Ok(command)\n    }\n\n    fn install_app(&self, project: &Project, build: &Build) -> Result<(BuildBundle, BuildBundle)> {\n        info!(\"Install {} to {}\", build.runnable.id, self.id);\n        user_facing_log(\n            \"Installing\",\n            &format!(\"{} to {}\", build.runnable.id, self.id),\n            0,\n        );\n        if !self\n            .adb()?\n            .arg(\"shell\")\n            .arg(\"mkdir\")\n            .arg(\"-p\")\n            .arg(ANDROID_WORK_DIR)\n            .log_invocation(2)\n            .status()?\n            .success()\n        {\n            bail!(\n                \"Failure to create dinghy work dir '{:?}' on target android device\",\n                ANDROID_WORK_DIR\n            )\n        }\n\n        let build_bundle = make_remote_app(project, build)?;\n        let remote_bundle = AndroidDevice::to_remote_bundle(&build_bundle)?;\n\n        self.sync(\n            &build_bundle.bundle_dir,\n            &remote_bundle\n                .bundle_dir\n                .parent()\n                .ok_or_else(|| anyhow!(\"Invalid path {}\", remote_bundle.bundle_dir.display()))?,\n        )?;\n        self.sync(\n            &build_bundle.lib_dir,\n            &remote_bundle\n                .lib_dir\n                .parent()\n                .ok_or_else(|| anyhow!(\"Invalid path {}\", remote_bundle.lib_dir.display()))?,\n        )?;\n\n        debug!(\"Chmod target exe {}\", remote_bundle.bundle_exe.display());\n        if !self\n            .adb()?\n            .arg(\"shell\")\n            .arg(\"chmod\")\n            .arg(\"755\")\n            .arg(&remote_bundle.bundle_exe)\n            .log_invocation(2)\n            .status()?\n            .success()\n        {\n            bail!(\"Failure in android install\");\n        }\n        Ok((build_bundle, remote_bundle))\n    }\n\n    fn sync<FP: AsRef<path::Path>, TP: AsRef<path::Path>>(\n        &self,\n        from_path: FP,\n        to_path: TP,\n    ) -> Result<()> {\n        // Seems overkill...\n        // let _ = self.adb()?.arg(\"shell\").arg(\"rm\").arg(\"-rf\").arg(to_path.as_ref()).status()?;\n        // Need parent as adb\n\n        let mut command = self.adb()?;\n        command\n            .arg(\"push\")\n            .arg(\"--sync\")\n            .arg(from_path.as_ref())\n            .arg(to_path.as_ref());\n        if !log_enabled!(::log::Level::Debug) {\n            command.stdout(::std::process::Stdio::null());\n            command.stderr(::std::process::Stdio::null());\n        }\n        debug!(\"Running {:?}\", command);\n        if !command.log_invocation(2).status()?.success() {\n            bail!(\"Error syncing android directory ({:?})\", command)\n        } else {\n            Ok(())\n        }\n    }\n\n    fn to_remote_bundle(build_bundle: &BuildBundle) -> Result<BuildBundle> {\n        build_bundle.replace_prefix_with(ANDROID_WORK_DIR)\n    }\n}\n\nimpl DeviceCompatibility for AndroidDevice {\n    fn is_compatible_with_regular_platform(&self, platform: &RegularPlatform) -> bool {\n        if platform.id.starts_with(\"auto-android\") {\n            let cpu = platform.id.split(\"-\").nth(2).unwrap();\n            self.supported_targets\n                .iter()\n                .any(|target| target.starts_with(cpu))\n        } else {\n            self.supported_targets\n                .contains(&&*platform.toolchain.binutils_prefix)\n        }\n    }\n}\n\nimpl Device for AndroidDevice {\n    fn clean_app(&self, build_bundle: &BuildBundle) -> Result<()> {\n        let remote_bundle = AndroidDevice::to_remote_bundle(build_bundle)?;\n        debug!(\"Cleaup device\");\n        if !self\n            .adb()?\n            .arg(\"shell\")\n            .arg(\"rm\")\n            .arg(\"-rf\")\n            .arg(&remote_bundle.bundle_dir)\n            .log_invocation(1)\n            .status()?\n            .success()\n        {\n            bail!(\"Failure in android clean\")\n        }\n        if !self\n            .adb()?\n            .arg(\"shell\")\n            .arg(\"rm\")\n            .arg(\"-rf\")\n            .arg(&remote_bundle.lib_dir)\n            .log_invocation(1)\n            .status()?\n            .success()\n        {\n            bail!(\"Failure in android clean\")\n        }\n        Ok(())\n    }\n\n    fn debug_app(\n        &self,\n        _project: &Project,\n        _build: &Build,\n        _args: &[&str],\n        _envs: &[&str],\n    ) -> Result<BuildBundle> {\n        unimplemented!()\n    }\n\n    fn id(&self) -> &str {\n        &self.id\n    }\n\n    fn name(&self) -> &str {\n        \"android device\"\n    }\n\n    fn run_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let args: Vec<String> = args\n            .iter()\n            .map(|&a| ::shell_escape::escape(a.into()).to_string())\n            .collect();\n        let (build_bundle, remote_bundle) = self.install_app(&project, &build)?;\n        let command = format!(\n                \"cd '{}'; RUST_BACKTRACE=1 {} DINGHY=1 LD_LIBRARY_PATH=\\\"{}:$LD_LIBRARY_PATH\\\" {} {} ; echo FORWARD_RESULT_TO_DINGHY_BECAUSE_ADB_DOES_NOT=$?\",\n                path_to_str(&remote_bundle.bundle_dir)?,\n                envs.join(\" \"),\n                path_to_str(&remote_bundle.lib_dir)?,\n                path_to_str(&remote_bundle.bundle_exe)?,\n                args.join(\" \"));\n        info!(\"Run {} on {}\", build.runnable.id, self.id);\n\n        if get_current_verbosity() < 1 {\n            // we log the full command for verbosity > 1, just log a short message when the user\n            // didn't ask for verbose output\n            user_facing_log(\n                \"Running\",\n                &format!(\"{} on {}\", build.runnable.id, self.id),\n                0,\n            );\n        }\n\n        if !self\n            .adb()?\n            .arg(\"shell\")\n            .arg(&command)\n            .log_invocation(1)\n            .output()\n            .with_context(|| format!(\"Couldn't run {} using adb.\", build.runnable.exe.display()))\n            .and_then(|output| {\n                if output.status.success() {\n                    let _ = io::stdout().write(output.stdout.as_slice());\n                    let _ = io::stderr().write(output.stderr.as_slice());\n                    String::from_utf8(output.stdout).with_context(|| {\n                        format!(\"Couldn't run {} using adb.\", build.runnable.exe.display())\n                    })\n                } else {\n                    bail!(\"Couldn't run {} using adb.\", build.runnable.exe.display())\n                }\n            })\n            .map(|output| output.lines().last().unwrap_or(\"\").to_string())\n            .map(|last_line| {\n                last_line.contains(\"FORWARD_RESULT_TO_DINGHY_BECAUSE_ADB_DOES_NOT=0\")\n            })?\n        {\n            bail!(\"Failed\")\n        }\n\n        Ok(build_bundle)\n    }\n}\n\nimpl fmt::Display for AndroidDevice {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"Android/{}\", self.id)\n    }\n}\n\nimpl fmt::Debug for AndroidDevice {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        Ok(fmt.write_str(\n            format!(\n                \"Android {{ \\\"id\\\": \\\"{}\\\", \\\"supported_targets\\\": {:?} }}\",\n                self.id, self.supported_targets\n            )\n            .as_str(),\n        )?)\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/android/mod.rs",
    "content": "use crate::config::PlatformConfiguration;\nuse crate::toolchain::ToolchainConfig;\nuse crate::{Device, Platform, PlatformManager, Result};\nuse fs_err as fs;\nuse std::fs::FileType;\nuse std::{env, path, process};\n\npub use self::device::AndroidDevice;\n\nuse crate::android::platform::AndroidPlatform;\nuse crate::utils::LogCommandExt;\nuse anyhow::{anyhow, bail, Context};\nuse log::debug;\n\nmod device;\nmod platform;\n\npub struct AndroidManager {\n    adb: path::PathBuf,\n}\n\nimpl PlatformManager for AndroidManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        let result = process::Command::new(&self.adb)\n            .arg(\"devices\")\n            .log_invocation(3)\n            .output()?;\n        let mut devices = vec![];\n        let device_regex = ::regex::Regex::new(r#\"^(\\S+)\\tdevice\\r?$\"#)?;\n        for line in String::from_utf8(result.stdout)?.split(\"\\n\").skip(1) {\n            if let Some(caps) = device_regex.captures(line) {\n                let d = AndroidDevice::from_id(self.adb.clone(), &caps[1])?;\n                debug!(\n                    \"Discovered Android device {} ({:?})\",\n                    d, d.supported_targets\n                );\n                devices.push(Box::new(d) as Box<dyn Device>);\n            }\n        }\n        Ok(devices)\n    }\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        if let Some(ndk) = ndk()? {\n            let default_api_level = \"21\";\n            debug!(\"Android NDK: {:?}\", ndk);\n            let version = ndk_version(&ndk)?;\n            let major = version\n                .split(\".\")\n                .next()\n                .ok_or_else(|| anyhow!(\"Invalid version found for ndk {:?}\", &ndk))?;\n            let major: usize = major\n                .parse()\n                .with_context(|| format!(\"Invalid version found for ndk {:?}\", &ndk))?;\n            debug!(\n                \"Android ndk: {:?}, ndk version: {}, major: {}\",\n                ndk, version, major\n            );\n            if major >= 19 {\n                let mut platforms = vec![];\n                let prebuilt = ndk.join(\"toolchains/llvm/prebuilt\");\n                let tools = prebuilt\n                    .read_dir()?\n                    .filter(|it| {\n                        // ensure we only check dirs as macOS may add pesky .DS_Store files...\n                        it.as_ref()\n                            .ok()\n                            .and_then(|it| it.file_type().ok().as_ref().map(FileType::is_dir))\n                            .unwrap_or(false)\n                    })\n                    .next()\n                    .ok_or_else(|| anyhow!(\"No tools in toolchain\"))??;\n                let bin = tools.path().join(\"bin\");\n                debug!(\"Android tools bin: {:?}\", bin);\n                let libclang_path = tools.path().join(if major >= 26 { \"lib\" } else { \"lib64\" });\n                for (rustc_cpu, cc_cpu, binutils_cpu, abi_kind) in &[\n                    (\"aarch64\", \"aarch64\", \"aarch64\", \"android\"),\n                    (\"armv7\", \"armv7a\", \"arm\", \"androideabi\"),\n                    (\"i686\", \"i686\", \"i686\", \"android\"),\n                    (\"x86_64\", \"x86_64\", \"x86_64\", \"android\"),\n                ] {\n                    let mut api_levels: Vec<String> = Vec::new();\n                    for entry in fs::read_dir(tools.path().join(format!(\n                        \"sysroot/usr/lib/{}-linux-{}\",\n                        binutils_cpu, abi_kind\n                    )))? {\n                        let entry = entry?;\n                        if entry.file_type()?.is_dir() {\n                            let folder_name = entry.file_name().into_string().unwrap();\n                            match folder_name.parse::<u32>() {\n                                Ok(_) => api_levels.push(folder_name),\n                                Err(_) => {}\n                            }\n                        }\n                    }\n                    api_levels.sort();\n                    let create_platform = |api: &str, suffix: &str| {\n                        let id = format!(\"auto-android-{}{}\", rustc_cpu, suffix);\n                        let tc = ToolchainConfig {\n                            bin_dir: bin.clone(),\n                            rustc_triple: format!(\"{}-linux-{}\", rustc_cpu, abi_kind),\n                            root: prebuilt.clone(),\n                            sysroot: Some(tools.path().join(\"sysroot\")),\n                            cc: \"clang\".to_string(),\n                            cxx: \"clang++\".to_string(),\n                            binutils_prefix: format!(\"{}-linux-{}\", binutils_cpu, abi_kind),\n                            cc_prefix: format!(\"{}-linux-{}{}\", cc_cpu, abi_kind, api),\n                        };\n                        AndroidPlatform::new(\n                            PlatformConfiguration::default(),\n                            id,\n                            tc,\n                            major,\n                            ndk.clone(),\n                            libclang_path.clone(),\n                        )\n                    };\n                    for api in api_levels.iter() {\n                        platforms.push(create_platform(&api, &format!(\"-api{}\", api))?);\n                    }\n                    if !api_levels.is_empty() {\n                        platforms.push(create_platform(\n                            api_levels\n                                .first()\n                                .expect(\"The api level vector shouldn't be empty\"),\n                            \"-min\",\n                        )?);\n                        platforms.push(create_platform(\n                            api_levels\n                                .last()\n                                .expect(\"The api level vector shouldn't be empty\"),\n                            \"-latest\",\n                        )?);\n                    }\n                    platforms.push(create_platform(default_api_level, \"\")?);\n                }\n                return Ok(platforms);\n            }\n        }\n        return Ok(vec![]);\n    }\n}\n\nimpl AndroidManager {\n    pub fn probe() -> Option<AndroidManager> {\n        match adb() {\n            Ok(adb) => {\n                debug!(\"ADB found: {:?}\", adb);\n                Some(AndroidManager { adb })\n            }\n            Err(_) => {\n                debug!(\"adb not found in path, android disabled\");\n                None\n            }\n        }\n    }\n}\n\nfn probable_sdk_locs() -> Result<Vec<path::PathBuf>> {\n    let mut v = vec![];\n    for var in &[\n        \"ANDROID_HOME\",\n        \"ANDROID_SDK\",\n        \"ANDROID_SDK_ROOT\",\n        \"ANDROID_SDK_HOME\",\n    ] {\n        if let Ok(path) = env::var(var) {\n            let path = path::Path::new(&path);\n            if path.is_dir() {\n                v.push(path.to_path_buf())\n            }\n        }\n    }\n    if let Ok(home) = env::var(\"HOME\") {\n        let mac = path::Path::new(&home).join(\"Library/Android/sdk\");\n        if mac.is_dir() {\n            v.push(mac);\n        }\n        let linux = path::Path::new(&home).join(\"Android/Sdk\");\n        if linux.is_dir() {\n            v.push(linux);\n        }\n    }\n    let casks = path::PathBuf::from(\"/usr/local/Caskroom/android-sdk\");\n    if casks.is_dir() {\n        for kid in casks.read_dir()? {\n            let kid = kid?;\n            if kid.file_name() != \".metadata\" {\n                v.push(kid.path());\n            }\n        }\n    }\n    debug!(\"Candidates SDK: {:?}\", v);\n    Ok(v)\n}\n\nfn ndk() -> Result<Option<path::PathBuf>> {\n    if let Ok(path) = env::var(\"ANDROID_NDK_HOME\") {\n        return Ok(Some(path.into()));\n    }\n    if let Ok(path) = env::var(\"ANDROID_NDK\") {\n        return Ok(Some(path.into()));\n    }\n    for sdk in probable_sdk_locs()? {\n        if sdk.join(\"ndk-bundle/source.properties\").is_file() {\n            return Ok(Some(sdk.join(\"ndk-bundle\")));\n        }\n        if let Some(ndk) = find_non_legacy_ndk(&sdk)? {\n            return Ok(Some(ndk));\n        }\n    }\n    debug!(\"Android NDK not found\");\n    Ok(None)\n}\n\nfn ndk_version(ndk: &path::Path) -> Result<String> {\n    let sources_prop_file = ndk.join(\"source.properties\");\n    let props = fs::read_to_string(&sources_prop_file)\n        .with_context(|| format!(\"Reading prop file {sources_prop_file:?}\"))?;\n    let revision_line = props\n        .split(\"\\n\")\n        .find(|l| l.starts_with(\"Pkg.Revision\"))\n        .with_context(|| {\n            format!(\n                \"{:?} does not contain a Pkg.Revision line. Invalid SDK?\",\n                sources_prop_file\n            )\n        })?;\n    Ok(revision_line.split(\" \").last().unwrap().to_string())\n}\n\nfn adb() -> Result<path::PathBuf> {\n    fn try_out(command: &path::Path) -> bool {\n        match process::Command::new(command)\n            .arg(\"--version\")\n            .stdout(process::Stdio::null())\n            .stderr(process::Stdio::null())\n            .log_invocation(3)\n            .status()\n        {\n            Ok(_) => true,\n            Err(_) => false,\n        }\n    }\n    if let Ok(adb) = env::var(\"DINGHY_ANDROID_ADB\") {\n        return Ok(adb.into());\n    }\n    if let Ok(adb) = ::which::which(\"adb\") {\n        return Ok(adb);\n    }\n    for loc in probable_sdk_locs()? {\n        let adb = loc.join(\"platform-tools/adb\");\n        if try_out(&adb) {\n            return Ok(adb.into());\n        }\n    }\n    bail!(\"Adb could be found\")\n}\n\nfn find_non_legacy_ndk(sdk: &path::Path) -> Result<Option<path::PathBuf>> {\n    let ndk_root = sdk.join(\"ndk\");\n    if !ndk_root.is_dir() {\n        return Ok(None);\n    }\n    let ndk = fs::read_dir(ndk_root)?\n        .filter_map(Result::ok)\n        .filter_map(|directory| {\n            directory\n                .path()\n                .file_name()\n                .and_then(|name| {\n                    let name = name.to_string_lossy();\n                    // Filter out directory if we fail to parse directory name to semver\n                    semver::Version::parse(&name).ok()\n                })\n                .map(|version| (directory, version))\n        })\n        .max_by(|left, right| {\n            let left_version: &semver::Version = &left.1;\n            let right_version: &semver::Version = &right.1;\n            left_version.cmp(right_version)\n        })\n        .map(|tuple| tuple.0.path());\n    Ok(ndk)\n}\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n\n    #[test]\n    fn test_find_non_legacy_ndk() {\n        let sdk_dir = tempfile::tempdir().unwrap();\n        let sdk = sdk_dir.path();\n        let ndk_root = sdk.join(\"ndk\");\n        let ndk_versions = [\"21.1.123456\", \"21.3.6528147\", \"20.1.5948944\"];\n        for version in &ndk_versions {\n            let path = ndk_root.join(version);\n            fs::create_dir_all(path).unwrap();\n        }\n\n        let ndk = find_non_legacy_ndk(sdk).unwrap();\n\n        let expected = ndk_root.join(\"21.3.6528147\");\n        assert_eq!(Some(expected), ndk);\n    }\n\n    #[test]\n    fn test_find_non_legacy_ndk_on_non_existing_directory() {\n        let sdk = tempfile::tempdir().unwrap();\n\n        let ndk = find_non_legacy_ndk(sdk.path()).unwrap();\n\n        assert_eq!(None, ndk);\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/android/platform.rs",
    "content": "use std::fmt::Formatter;\nuse crate::platform::regular_platform::RegularPlatform;\nuse crate::toolchain::ToolchainConfig;\nuse crate::{platform, Result};\nuse crate::{Build, Device, Platform, PlatformConfiguration, Project, SetupArgs};\nuse dinghy_build::build_env::set_env;\nuse std::io::Write;\nuse std::path::PathBuf;\nuse std::process::Command;\n\npub struct AndroidPlatform {\n    regular_platform: Box<dyn Platform>,\n    toolchain_config: ToolchainConfig,\n    ndk_major_version: usize,\n    ndk_path: PathBuf,\n    libclang_path: PathBuf,\n}\n\nimpl std::fmt::Debug for AndroidPlatform {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.id())\n    }\n}\n\nimpl AndroidPlatform {\n    pub fn new(\n        configuration: PlatformConfiguration,\n        id: String,\n        toolchain_config: ToolchainConfig,\n        ndk_major_version: usize,\n        ndk_path: PathBuf,\n        libclang_path: PathBuf,\n    ) -> Result<Box<dyn Platform>> {\n        Ok(Box::new(Self {\n            regular_platform: RegularPlatform::new_with_tc(\n                configuration,\n                id,\n                toolchain_config.clone(),\n            )?,\n            toolchain_config,\n            ndk_major_version,\n            ndk_path,\n            libclang_path,\n        }))\n    }\n}\n\nimpl Platform for AndroidPlatform {\n    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> anyhow::Result<()> {\n        self.regular_platform.setup_env(project, setup_args)?;\n\n        if self.ndk_major_version >= 23 {\n            log::trace!(\"Setup linker with android ndk23+ hack...\");\n\n            let hack_dir = project\n                .metadata\n                .target_directory\n                .join(self.rustc_triple())\n                .join(self.id())\n                .join(\"ndk23-hack\");\n\n            std::fs::create_dir_all(&hack_dir)?;\n\n            let mut hack_file = std::fs::File::create(hack_dir.join(\"libgcc.a\"))?;\n\n            hack_file.write_all(\"INPUT(-lunwind)\".as_bytes())?;\n\n            let mut linker_cmd = self.toolchain_config.generate_linker_command(&setup_args);\n\n            linker_cmd.push_str(\" -L\");\n            linker_cmd.push_str(hack_dir.canonicalize()?.to_str().unwrap());\n\n            self.toolchain_config.setup_linker(\n                &self.id(),\n                &linker_cmd,\n                &project.metadata.workspace_root,\n            )?;\n\n            self.toolchain_config\n                .setup_tool(\"AR\", &self.toolchain_config.naked_executable(\"llvm-ar\"))?;\n        }\n\n        if self.ndk_major_version >= 17 {\n            // bindgen need this to use the proper imports\n            set_env(\"DINGHY_BUILD_LIBCLANG_PATH\", &self.libclang_path)\n        }\n\n        if std::env::var(\"ANDROID_NDK\").is_err() {\n            set_env(\"ANDROID_NDK\", self.ndk_path.canonicalize()?)\n        }\n\n        if std::env::var(\"ANDROID_NDK_HOME\").is_err() {\n            set_env(\"ANDROID_NDK_HOME\", self.ndk_path.canonicalize()?)\n        }\n\n        Ok(())\n    }\n\n    fn id(&self) -> String {\n        self.regular_platform.id()\n    }\n\n    fn is_compatible_with(&self, device: &dyn Device) -> bool {\n        self.regular_platform.is_compatible_with(device)\n    }\n\n    fn is_host(&self) -> bool {\n        self.regular_platform.is_host()\n    }\n\n    fn rustc_triple(&self) -> &str {\n        self.regular_platform.rustc_triple()\n    }\n\n    fn strip(&self, build: &mut Build) -> anyhow::Result<()> {\n        if self.ndk_major_version >= 23 {\n            build.runnable = platform::strip_runnable(\n                &build.runnable,\n                Command::new(self.toolchain_config.naked_executable(\"llvm-strip\")),\n            )?;\n            Ok(())\n        } else {\n            self.regular_platform.strip(build)\n        }\n    }\n\n    fn sysroot(&self) -> anyhow::Result<Option<PathBuf>> {\n        self.regular_platform.sysroot()\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/apple/device.rs",
    "content": "use super::{xcode, AppleSimulatorType};\nuse crate::apple::AppleDevicePlatform;\nuse crate::device::make_remote_app_with_name;\nuse crate::errors::*;\nuse crate::project::Project;\nuse crate::utils::LogCommandExt;\nuse crate::utils::{get_current_verbosity, user_facing_log};\nuse crate::Build;\nuse crate::BuildBundle;\nuse crate::Device;\nuse crate::DeviceCompatibility;\nuse crate::Runnable;\nuse colored::Colorize;\nuse fs_err as fs;\nuse itertools::Itertools;\nuse log::debug;\nuse std::fmt;\nuse std::fmt::Display;\nuse std::fmt::Formatter;\nuse std::io::{BufRead, BufReader};\nuse std::path::Path;\nuse std::process::{self, Stdio};\nuse std::time::Duration;\n\n#[derive(Clone, Debug)]\npub struct IosDevice {\n    pub id: String,\n    pub name: String,\n    pub arch_cpu: &'static str,\n    rustc_triple: String,\n    pub os: String,\n}\n\n#[derive(Clone, Debug)]\npub struct AppleSimDevice {\n    pub id: String,\n    pub name: String,\n    pub os: String,\n    pub sim_type: AppleSimulatorType,\n}\n\nunsafe impl Send for IosDevice {}\n\nimpl IosDevice {\n    pub fn new(name: String, id: String, arch_cpu: &str, os: String) -> Result<IosDevice> {\n        let cpu = match &*arch_cpu {\n            \"arm64\" | \"arm64e\" => \"aarch64\",\n            _ => \"armv7\",\n        };\n        Ok(IosDevice {\n            name,\n            id,\n            os,\n            arch_cpu: cpu.into(),\n            rustc_triple: format!(\"{}-apple-ios\", cpu),\n        })\n    }\n\n    fn is_pre_ios_17(&self) -> Result<bool> {\n        Ok(semver::VersionReq::parse(&self.os)?\n            .comparators\n            .get(0)\n            .ok_or_else(|| anyhow!(\"Invalid iOS version: {}\", self.os))?\n            .major\n            < 17)\n    }\n\n    fn is_locked(&self) -> Result<bool> {\n        let result = process::Command::new(\"xcrun\")\n            .args(\n                \"devicectl device info lockState --quiet --json-output /dev/stdout --device\"\n                    .split_whitespace(),\n            )\n            .arg(&self.id)\n            .log_invocation(1)\n            .output()\n            .context(\"Failed to run devicectl device info lockState\")?;\n        if !result.status.success() {\n            bail!(\"Device lock query failed\\n\",)\n        }\n        Ok(\n            json::parse(std::str::from_utf8(&result.stdout)?)?[\"result\"][\"passcodeRequired\"]\n                .as_bool()\n                .unwrap(),\n        )\n    }\n\n    fn make_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        runnable: &Runnable,\n    ) -> Result<BuildBundle> {\n        let signing = xcode::look_for_signature_settings(&self.id)?\n            .pop()\n            .ok_or_else(|| anyhow!(\"no signing identity found\"))?;\n        let app_id = signing\n            .name\n            .split(\" \")\n            .last()\n            .ok_or_else(|| anyhow!(\"no app id ?\"))?;\n\n        let mut build_bundle = make_apple_app(project, build, runnable, &app_id, None)?;\n        build_bundle.app_id = Some(app_id.to_owned());\n\n        super::xcode::sign_app(&build_bundle, &signing)?;\n        Ok(build_bundle)\n    }\n\n    fn install_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        runnable: &Runnable,\n    ) -> Result<BuildBundle> {\n        user_facing_log(\n            \"Installing\",\n            &format!(\"{} to {} ({})\", build.runnable.id, self.id, self.name),\n            0,\n        );\n        let build_bundle = self.make_app(project, build, runnable)?;\n        let bundle = build_bundle.bundle_dir.to_string_lossy();\n        if self.is_pre_ios_17()? {\n            self.install_app_with_ios_deploy(&bundle)?;\n            return Ok(build_bundle);\n        }\n\n        // xcrun devicectl device install app --device 00008110-001XXXXXXXXXX ./xgen/Build/Products/Release-iphoneos/nilo.app\n        let result = process::Command::new(\"xcrun\")\n            .args(\"devicectl device install app --device\".split_whitespace())\n            .arg(&self.id)\n            .arg(&*bundle)\n            .log_invocation(1)\n            .status()\n            .context(\"Failed to run devicectl device install app\")?;\n        if !result.success() {\n            bail!(\"Installation on device failed\\n\",)\n        }\n        Ok(build_bundle)\n    }\n\n    fn run_remote(\n        &self,\n        build_bundle: &BuildBundle,\n        args: &[&str],\n        envs: &[&str],\n        debugger: bool,\n    ) -> Result<()> {\n        if self.is_pre_ios_17()? {\n            return self.run_remote_with_ios_deploy(build_bundle, args, envs, debugger);\n        }\n        let app_list = process::Command::new(\"pymobiledevice3\")\n            .args(\"apps list --no-color --udid\".split_whitespace())\n            .arg(&self.id)\n            .output()?;\n        let app_list = json::parse(std::str::from_utf8(&app_list.stdout)?).with_context(|| {\n            format!(\n                \"Ran `pymobiledevice3 apps list --no-color --udid {}`, could not parse expected JSON output.\", self.id,\n            )\n        })?;\n        let app_path = build_bundle.bundle_dir.to_string_lossy();\n        let app = app_list\n            .entries()\n            .find(|e| e.0 == build_bundle.app_id.as_ref().unwrap())\n            .unwrap()\n            .1;\n        let remote_path = app[\"Path\"].to_string();\n\n        let tunnel = process::Command::new(\"sudo\")\n            .arg(\"-p\")\n            .arg(format!(\n                \"Please enter %p's password on %h to start a tunnel to '{}' (sudo):\",\n                self.name\n            ))\n            .args(\"pymobiledevice3 remote start-tunnel --script-mode --udid\".split_whitespace())\n            .arg(&self.id)\n            .stderr(Stdio::inherit())\n            .stdin(Stdio::inherit())\n            .stdout(Stdio::piped())\n            .spawn()?;\n        let mut rsd = String::new();\n        BufReader::new(tunnel.stdout.unwrap()).read_line(&mut rsd)?;\n        debug!(\"iOS RSD tunnel started: {rsd}\");\n\n        // start the debugserver\n        let server = process::Command::new(\"pymobiledevice3\")\n            .args(\"developer debugserver start-server --rsd\".split_whitespace())\n            .args(rsd.trim().split_whitespace())\n            .stderr(Stdio::inherit())\n            .stdout(Stdio::piped())\n            .spawn()?;\n        let lldb_connection_string = BufReader::new(server.stdout.unwrap())\n            .lines()\n            .find(|l| l.as_ref().unwrap().contains(\"process connect connect://\"))\n            .unwrap()\n            .unwrap();\n        let connection_details = lldb_connection_string.split_whitespace().nth(3).unwrap();\n        debug!(\"iOS debugserver started: {connection_details}\");\n\n        if self.is_locked()? {\n            eprint!(\n                \"{}\",\n                format!(\"\\n\\n      Please unlock {}! \", &self.name).bright_yellow()\n            );\n            loop {\n                std::thread::sleep(Duration::from_millis(300));\n                if !self.is_locked()? {\n                    eprintln!(\"{}\", \"   All good, yay!\\n\".bright_green());\n                    break;\n                }\n            }\n        }\n\n        let tempdir = tempfile::TempDir::with_prefix(\"dinghy-lldb\")?;\n        let script_path = tempdir.path().join(\"run.lldb\");\n        // see https://stackoverflow.com/questions/77865860/lldb-hangs-when-trying-to-execute-command-with-o\n        // for the terrible async thing\n        std::fs::write(\n            &script_path,\n            format!(\n                \"\nplatform select remote-ios\ntarget create {app_path}\nscript lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('{remote_path}'))\nscript old_debug = lldb.debugger.GetAsync()\nscript lldb.debugger.SetAsync(True)\nprocess connect {connection_details}\nscript lldb.debugger.SetAsync(old_debug)\nrun {}\nexit\n            \",\n                args.iter()\n                    .map(|&s| shell_escape::escape(s.into()))\n                    .join(\" \")\n            ),\n        )?;\n\n        let lldb = process::Command::new(\"lldb\")\n            .arg(\"--batch\")\n            .arg(\"-s\")\n            .arg(script_path)\n            .stderr(Stdio::inherit())\n            .stdout(Stdio::piped())\n            .spawn()?;\n        let mut lines = BufReader::new(lldb.stdout.unwrap()).lines();\n        while !lines.next().unwrap()?.starts_with(\"(lldb) run\") {}\n        for line in lines {\n            let line = line?;\n            println!(\"{}\", line);\n            if line.contains(\"exited with status = \") {\n                let rv = line.split_whitespace().nth(6).unwrap();\n                println!(\"returns: {rv}\");\n                if rv == \"0\" {\n                    return Ok(());\n                } else {\n                    bail!(\"Failed\")\n                }\n            }\n        }\n        Ok(())\n    }\n\n    // LEGACY IOS-DEPLOY BASED WORKFLOW (iOS<17)\n    fn install_app_with_ios_deploy(&self, bundle: &str) -> Result<()> {\n        process::Command::new(\"ios-deploy\")\n            .args(&[\"-i\", &self.id, \"-b\", &bundle, \"-n\"])\n            .log_invocation(1)\n            .output()\n            .context(\"Failed to run ios-deploy\")?\n            .status;\n        Ok(())\n    }\n\n    fn run_remote_with_ios_deploy(\n        &self,\n        build_bundle: &BuildBundle,\n        args: &[&str],\n        envs: &[&str],\n        debugger: bool,\n    ) -> Result<()> {\n        let bundle = build_bundle.bundle_dir.to_string_lossy();\n        let mut command = process::Command::new(\"ios-deploy\");\n        command.args(&[\"-i\", &self.id, \"-b\", &bundle, \"-m\"]);\n        command.args(&[\"-a\", &args.join(\" \")]);\n        command.args(&[\"-s\", &envs.join(\" \")]);\n        command.arg(if debugger { \"-d\" } else { \"-I\" });\n        command.stderr(process::Stdio::inherit());\n        command.stdout(process::Stdio::inherit());\n        let status = command\n            .log_invocation(1)\n            .output()\n            .context(\"Failed to run ios-deploy\")?\n            .status;\n        if !status.success() {\n            bail!(\"Run on device failed\")\n        }\n        Ok(())\n    }\n}\n\nimpl Device for IosDevice {\n    fn clean_app(&self, _build_bundle: &BuildBundle) -> Result<()> {\n        unimplemented!()\n    }\n\n    fn debug_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let build_bundle = self.install_app(project, build, &build.runnable)?;\n        if get_current_verbosity() < 1 {\n            // we log the full command for verbosity > 1, just log a short message when the user\n            // didn't ask for verbose output\n            user_facing_log(\n                \"Debugging\",\n                &format!(\"{} on {}\", build.runnable.id, self.id),\n                0,\n            );\n        }\n        self.run_remote(&build_bundle, args, envs, true)?;\n        Ok(build_bundle)\n    }\n\n    fn id(&self) -> &str {\n        &self.id\n    }\n\n    fn name(&self) -> &str {\n        &self.name\n    }\n\n    fn run_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let build_bundle = self.install_app(project, build, &build.runnable)?;\n        if get_current_verbosity() < 1 {\n            // we log the full command for verbosity > 1, just log a short message when the user\n            // didn't ask for verbose output\n            user_facing_log(\n                \"Running\",\n                &format!(\"{} on {}\", build.runnable.id, self.id),\n                0,\n            );\n        }\n        self.run_remote(&build_bundle, args, envs, false)?;\n        Ok(build_bundle)\n    }\n}\n\nimpl AppleSimDevice {\n    fn install_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        runnable: &Runnable,\n    ) -> Result<BuildBundle> {\n        user_facing_log(\n            \"Installing\",\n            &format!(\"{} to {}\", build.runnable.id, self.id),\n            0,\n        );\n        let build_bundle = self.make_app(project, build, runnable)?;\n        let _ = process::Command::new(\"xcrun\")\n            .args(&[\"simctl\", \"uninstall\", &self.id, \"Dinghy\"])\n            .log_invocation(2)\n            .status()?;\n        let stat = process::Command::new(\"xcrun\")\n            .args(&[\n                \"simctl\",\n                \"install\",\n                &self.id,\n                build_bundle\n                    .bundle_dir\n                    .to_str()\n                    .ok_or_else(|| anyhow!(\"conversion to string\"))?,\n            ])\n            .log_invocation(1)\n            .status()?;\n        if stat.success() {\n            Ok(build_bundle)\n        } else {\n            bail!(\n                \"Failed to install {} for {}\",\n                runnable.exe.display(),\n                self.id\n            )\n        }\n    }\n\n    fn make_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        runnable: &Runnable,\n    ) -> Result<BuildBundle> {\n        make_apple_app(project, build, runnable, \"Dinghy\", Some(&self.sim_type))\n    }\n}\n\nimpl Device for AppleSimDevice {\n    fn clean_app(&self, _build_bundle: &BuildBundle) -> Result<()> {\n        unimplemented!()\n    }\n\n    fn debug_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let runnable = &build.runnable;\n        let build_bundle = self.install_app(project, build, runnable)?;\n        let install_path = String::from_utf8(\n            process::Command::new(\"xcrun\")\n                .args(&[\"simctl\", \"get_app_container\", &self.id, \"Dinghy\"])\n                .log_invocation(2)\n                .output()?\n                .stdout,\n        )?;\n        if get_current_verbosity() < 1 {\n            // we log the full command for verbosity > 1, just log a short message when the user\n            // didn't ask for verbose output\n            user_facing_log(\n                \"Debugging\",\n                &format!(\"{} on {}\", build.runnable.id, self.id),\n                0,\n            );\n        }\n        launch_lldb_simulator(&self, &install_path, args, envs, true)?;\n        Ok(build_bundle)\n    }\n\n    fn id(&self) -> &str {\n        &self.id\n    }\n\n    fn name(&self) -> &str {\n        &self.name\n    }\n\n    fn run_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let build_bundle = self.install_app(&project, &build, &build.runnable)?;\n        if get_current_verbosity() < 1 {\n            // we log the full command for verbosity > 1, just log a short message when the user\n            // didn't ask for verbose output\n            user_facing_log(\n                \"Running\",\n                &format!(\"{} on {}\", build.runnable.id, self.id),\n                0,\n            );\n        }\n        launch_app(&self, args, envs)?;\n        Ok(build_bundle)\n    }\n}\n\nimpl Display for IosDevice {\n    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {\n        write!(\n            fmt,\n            \"{} ({} {} {})\",\n            self.name, self.id, self.arch_cpu, self.os\n        )\n    }\n}\n\nimpl Display for AppleSimDevice {\n    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {\n        write!(fmt, \"{} ({} sim {})\", self.name, self.id, self.os)\n    }\n}\n\nimpl DeviceCompatibility for IosDevice {\n    fn is_compatible_with_simulator_platform(&self, platform: &AppleDevicePlatform) -> bool {\n        if platform.sim.is_some() {\n            return false;\n        }\n\n        if platform.toolchain.rustc_triple == self.rustc_triple.as_str() {\n            return true;\n        }\n        return false;\n    }\n}\n\nimpl DeviceCompatibility for AppleSimDevice {\n    fn is_compatible_with_simulator_platform(&self, platform: &AppleDevicePlatform) -> bool {\n        if let Some(sim) = &platform.sim {\n            self.sim_type == *sim\n        } else {\n            false\n        }\n    }\n}\n\nfn make_apple_app(\n    project: &Project,\n    build: &Build,\n    runnable: &Runnable,\n    app_id: &str,\n    sim_type: Option<&AppleSimulatorType>,\n) -> Result<BuildBundle> {\n    use crate::project;\n    let build_bundle = make_remote_app_with_name(project, build, Some(\"Dinghy.app\"))?;\n    project::rec_copy(&runnable.exe, build_bundle.bundle_dir.join(\"Dinghy\"), false)?;\n    let magic = process::Command::new(\"file\")\n        .arg(\n            runnable\n                .exe\n                .to_str()\n                .ok_or_else(|| anyhow!(\"path conversion to string: {:?}\", runnable.exe))?,\n        )\n        .log_invocation(3)\n        .output()?;\n    let magic = String::from_utf8(magic.stdout)?;\n    let target = magic\n        .split(\" \")\n        .last()\n        .ok_or_else(|| anyhow!(\"empty magic\"))?;\n    xcode::add_plist_to_app(&build_bundle, target, app_id, sim_type)?;\n    Ok(build_bundle)\n}\n\nfn launch_app(dev: &AppleSimDevice, app_args: &[&str], _envs: &[&str]) -> Result<()> {\n    use std::io::Write;\n    let dir = tempfile::TempDir::with_prefix(\"mobiledevice-rs-lldb\")?;\n    let tmppath = dir.path();\n    let mut install_path = String::from_utf8(\n        process::Command::new(\"xcrun\")\n            .args(&[\"simctl\", \"get_app_container\", &dev.id, \"Dinghy\"])\n            .log_invocation(2)\n            .output()?\n            .stdout,\n    )?;\n    install_path.pop();\n    let stdout = Path::new(&install_path)\n        .join(\"stdout\")\n        .to_string_lossy()\n        .into_owned();\n    let stdout_param = &format!(\"--stdout={}\", stdout);\n    let mut xcrun_args: Vec<&str> = vec![\n        \"simctl\",\n        \"launch\",\n        \"--wait-for-debugger\",\n        stdout_param,\n        &dev.id,\n        \"Dinghy\",\n    ];\n    xcrun_args.extend(app_args);\n    debug!(\"Launching app via xcrun using args: {:?}\", xcrun_args);\n    let launch_output = process::Command::new(\"xcrun\")\n        .args(&xcrun_args)\n        .log_invocation(1)\n        .output()?;\n    let launch_output = String::from_utf8_lossy(&launch_output.stdout);\n    debug!(\"xcrun simctl launch output: {:?}\", launch_output);\n\n    // Output from the launch command should be \"Dinghy: $PID\" which is after the 8th character.\n    let dinghy_pid = launch_output.split_at(8).1;\n\n    // Attaching to the processes needs to be done in a script, not a commandline parameter or\n    // lldb will say \"no simulators found\".\n    let lldb_script_filename = tmppath.join(\"lldb-script\");\n    let mut script = fs::File::create(&lldb_script_filename)?;\n    write!(script, \"attach {}\\n\", dinghy_pid)?;\n    write!(script, \"continue\\n\")?;\n    write!(script, \"quit\\n\")?;\n    let output = process::Command::new(\"lldb\")\n        .arg(\"\")\n        .arg(\"-s\")\n        .arg(lldb_script_filename)\n        .output()?;\n    let test_contents = std::fs::read_to_string(&stdout)\n        .with_context(|| format!(\"Reading llvm stdout from {stdout}\"))?;\n    println!(\"{}\", test_contents);\n\n    let output: String = String::from_utf8_lossy(&output.stdout).to_string();\n    debug!(\"lldb script: \\n{}\", output);\n    // The stdout from lldb is something like:\n    //\n    // (lldb) attach 34163\n    // Process 34163 stopped\n    // * thread #1, stop reason = signal SIGSTOP\n    //     frame #0: 0x00000001019cd000 dyld`_dyld_start\n    // dyld`_dyld_start:\n    // ->  0x1019cd000 <+0>: popq   %rdi\n    //     0x1019cd001 <+1>: pushq  $0x0\n    //     0x1019cd003 <+3>: movq   %rsp, %rbp\n    //     0x1019cd006 <+6>: andq   $-0x10, %rsp\n    // Target 0: (Dinghy) stopped.\n    // Executable module set to .....\n    // Architecture set to: x86_64h-apple-ios-.\n    // (lldb) continue\n    // Process 34163 resuming\n    // Process 34163 exited with status = 101 (0x00000065)\n    // (lldb) quit\n    //\n    // We need the \"exit with status\" line which is the 3rd from the last\n    let exit_status_line = output\n        .lines()\n        .rev()\n        .find(|line| line.contains(\"exited with status\"));\n    if let Some(exit_status_line) = exit_status_line {\n        let words: Vec<&str> = exit_status_line.split_whitespace().rev().collect();\n        if let Some(exit_status) = words.get(1) {\n            let exit_status = exit_status.parse::<u32>()?;\n            if exit_status == 0 {\n                Ok(())\n            } else {\n                bail!(\"Test failure, exit code: {}\", exit_status)\n            }\n        } else {\n            panic!(\n                \"Failed to parse lldb exit line for an exit status. {:?}\",\n                words\n            );\n        }\n    } else {\n        panic!(\"Failed to get the exit status line from lldb: {}\", output);\n    }\n}\n\nfn launch_lldb_simulator(\n    dev: &AppleSimDevice,\n    installed: &str,\n    args: &[&str],\n    envs: &[&str],\n    debugger: bool,\n) -> Result<()> {\n    use std::io::Write;\n    use std::process::Command;\n    let dir = tempfile::TempDir::with_prefix(\"mobiledevice-rs-lldb\")?;\n    let tmppath = dir.path();\n    let lldb_script_filename = tmppath.join(\"lldb-script\");\n    {\n        let python_lldb_support = tmppath.join(\"helpers.py\");\n        let helper_py = include_str!(\"helpers.py\");\n        let helper_py = helper_py.replace(\"ENV_VAR_PLACEHOLDER\", &envs.join(\"\\\", \\\"\"));\n        fs::File::create(&python_lldb_support)?.write_fmt(format_args!(\"{}\", &helper_py))?;\n        let mut script = fs::File::create(&lldb_script_filename)?;\n        writeln!(script, \"platform select ios-simulator\")?;\n        writeln!(script, \"target create {}\", installed)?;\n        writeln!(script, \"script pass\")?;\n        writeln!(script, \"command script import {:?}\", python_lldb_support)?;\n        writeln!(\n            script,\n            \"command script add -s synchronous -f helpers.start start\"\n        )?;\n        writeln!(\n            script,\n            \"command script add -f helpers.connect_command connect\"\n        )?;\n        writeln!(script, \"connect connect://{}\", dev.id)?;\n        if !debugger {\n            writeln!(script, \"start {}\", args.join(\" \"))?;\n            writeln!(script, \"quit\")?;\n        }\n    }\n\n    let stat = Command::new(\"xcrun\")\n        .arg(\"lldb\")\n        .arg(\"-Q\")\n        .arg(\"-s\")\n        .arg(lldb_script_filename)\n        .log_invocation(1)\n        .status()?;\n    if stat.success() {\n        Ok(())\n    } else {\n        bail!(\"LLDB returned error code {:?}\", stat.code())\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/apple/helpers.py",
    "content": "import os\nimport lldb\nimport shlex\n\ndef connect_command(debugger, command, result, internal_dict):\n    connect_url = command\n    error = lldb.SBError()\n    process = lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(), connect_url, None, error)\n\ndef set_remote_path(debugger, command, result, internal_dict):\n    device_app = command\n    error = lldb.SBError()\n    lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n\ndef start(debugger, command, result, internal_dict):\n    error = lldb.SBError()\n    info = lldb.SBLaunchInfo(shlex.split(command))\n    info.SetEnvironmentEntries([\"ENV_VAR_PLACEHOLDER\"], True)\n    proc = lldb.target.Launch(info, error)\n    lockedstr = ': Locked'\n    if proc.GetState() != lldb.eStateExited:\n        print(\"process left in lldb state: %s\"%(debugger.StateAsCString(proc.GetState())))\n    if lockedstr in str(error):\n        print('\\nDevice Locked\\n')\n        os._exit(254)\n    elif proc.GetState() == lldb.eStateStopped:\n        thread = proc.GetSelectedThread()\n        print(thread)\n        for frame in thread:\n            print(\"  %s\"%(frame))\n        os._exit(-1)\n    elif not error.Success():\n        print(str(error))\n    if proc.exit_state != 0:\n        os._exit(proc.exit_state)\n\n"
  },
  {
    "path": "dinghy-lib/src/apple/mod.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt::Display;\n\npub use self::device::{AppleSimDevice, IosDevice};\npub use self::platform::AppleDevicePlatform;\nuse crate::{Device, Platform, PlatformManager, Result};\nuse itertools::Itertools;\n\nmod device;\nmod platform;\nmod xcode;\n\nuse anyhow::{anyhow, bail, Context};\nuse log::info;\n\n#[derive(Debug, Clone)]\npub struct SignatureSettings {\n    pub identity: SigningIdentity,\n    pub file: String,\n    pub entitlements: String,\n    pub name: String,\n    #[allow(dead_code)]\n    pub profile: String,\n}\n\n#[derive(Debug, Clone)]\npub struct SigningIdentity {\n    #[allow(dead_code)]\n    pub id: String,\n    pub name: String,\n    pub team: String,\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub enum AppleSimulatorType {\n    Ios,\n    Watchos,\n    Tvos,\n}\nimpl Display for AppleSimulatorType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let val = match self {\n            AppleSimulatorType::Ios => \"ios\",\n            AppleSimulatorType::Watchos => \"watchos\",\n            AppleSimulatorType::Tvos => \"tvos\",\n        };\n        f.write_str(val)\n    }\n}\n\npub struct IosManager {\n    devices: Vec<Box<dyn Device>>,\n}\n\nimpl IosManager {\n    pub fn new() -> Result<Option<IosManager>> {\n        let devices = devices()\n            .context(\"Could not list iOS devices\")?\n            .into_iter()\n            .chain(\n                simulators(AppleSimulatorType::Ios)\n                    .context(\"Could not list iOS simulators\")?\n                    .into_iter(),\n            )\n            .collect();\n        Ok(Some(IosManager { devices }))\n    }\n}\n\nimpl PlatformManager for IosManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        Ok(self.devices.clone())\n    }\n\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        [\n            \"armv7\",\n            \"armv7s\",\n            \"aarch64\",\n            \"i386\",\n            \"x86_64\",\n            \"aarch64-sim\",\n        ]\n        .iter()\n        .map(|arch| {\n            let id = format!(\"auto-ios-{}\", arch);\n            let rustc_triple = if *arch != \"aarch64-sim\" {\n                format!(\"{}-apple-ios\", arch)\n            } else {\n                format!(\"aarch64-apple-ios-sim\")\n            };\n\n            let simulator = if *arch == \"x86_64\" || *arch == \"aarch64-sim\" {\n                Some(AppleSimulatorType::Ios)\n            } else {\n                None\n            };\n\n            AppleDevicePlatform::new(\n                id,\n                &rustc_triple,\n                simulator,\n                crate::config::PlatformConfiguration::default(),\n            )\n            .map(|pf| pf as Box<dyn Platform>)\n        })\n        .collect()\n    }\n}\n\npub struct WatchosManager {\n    devices: Vec<Box<dyn Device>>,\n}\n\nimpl WatchosManager {\n    pub fn new() -> Result<Option<Self>> {\n        let devices = simulators(AppleSimulatorType::Watchos)?;\n        Ok(Some(Self { devices }))\n    }\n}\nimpl PlatformManager for WatchosManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        Ok(self.devices.clone())\n    }\n\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        [\"arm64_32\", \"aarch64\", \"x86_64-sim\", \"aarch64-sim\"]\n            .iter()\n            .map(|arch| {\n                let id = format!(\"auto-watchos-{}\", arch);\n\n                // Apple watch simulator targets are x86_64-apple-watchos-sim or\n                // aarch64-apple-watchos-sim\n                let rustc_triple = if *arch == \"aarch64-sim\" {\n                    format!(\"aarch64-apple-watchos-sim\")\n                } else if *arch == \"x86_64-sim\" {\n                    format!(\"x86_64-apple-watchos-sim\")\n                } else {\n                    format!(\"{}-apple-watchos\", arch)\n                };\n                let simulator = if *arch == \"x86_64-sim\" || *arch == \"aarch64-sim\" {\n                    Some(AppleSimulatorType::Watchos)\n                } else {\n                    None\n                };\n\n                AppleDevicePlatform::new(\n                    id,\n                    &rustc_triple,\n                    simulator,\n                    crate::config::PlatformConfiguration::default(),\n                )\n                .map(|pf| pf as Box<dyn Platform>)\n            })\n            .collect()\n    }\n}\n\npub struct TvosManager {\n    devices: Vec<Box<dyn Device>>,\n}\n\nimpl TvosManager {\n    pub fn new() -> Result<Option<Self>> {\n        let devices = simulators(AppleSimulatorType::Tvos)?;\n        Ok(Some(Self { devices }))\n    }\n}\n\nimpl PlatformManager for TvosManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        Ok(self.devices.clone())\n    }\n\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        [\"aarch64\", \"x86_64\", \"aarch64-sim\"]\n            .iter()\n            .map(|arch| {\n                let id = format!(\"auto-tvos-{}\", arch);\n                let rustc_triple = if *arch != \"aarch64-sim\" {\n                    format!(\"{}-apple-tvos\", arch)\n                } else {\n                    format!(\"aarch64-apple-tvos-sim\")\n                };\n                let simulator = if *arch == \"x86_64\" || *arch == \"aarch64-sim\" {\n                    Some(AppleSimulatorType::Tvos)\n                } else {\n                    None\n                };\n                AppleDevicePlatform::new(\n                    id,\n                    &rustc_triple,\n                    simulator,\n                    crate::config::PlatformConfiguration::default(),\n                )\n                .map(|pf| pf as Box<dyn Platform>)\n            })\n            .collect()\n    }\n}\n\nfn simulators(sim_type: AppleSimulatorType) -> Result<Vec<Box<dyn Device>>> {\n    let sims_list = ::std::process::Command::new(\"xcrun\")\n        .args(&[\n            \"simctl\",\n            \"list\",\n            \"--json\",\n            \"devices\",\n            sim_type.to_string().as_str(),\n        ])\n        .output()?;\n    if !sims_list.status.success() {\n        info!(\n            \"Failed while looking for ios simulators. It this is not expected, you need to make sure `xcrun simctl list --json` works.\"\n            );\n        return Ok(vec![]);\n    }\n    let sims_list = String::from_utf8(sims_list.stdout)?;\n    let sims_list = json::parse(&sims_list)\n        .with_context(|| \"Could not parse output for: `xcrun simctl list --json devices` as json. Please try to make this command work and retry.\")?;\n    let mut sims: Vec<Box<dyn Device>> = vec![];\n    for (ref k, ref v) in sims_list[\"devices\"].entries() {\n        for ref sim in v.members() {\n            if sim[\"state\"] == \"Booted\" {\n                sims.push(Box::new(AppleSimDevice {\n                    name: sim[\"name\"]\n                        .as_str()\n                        .ok_or_else(|| anyhow!(\"unexpected simulator list format (missing name)\"))?\n                        .to_string(),\n                    id: sim[\"udid\"]\n                        .as_str()\n                        .ok_or_else(|| anyhow!(\"unexpected simulator list format (missing udid)\"))?\n                        .to_string(),\n                    os: k.split(\" \").last().unwrap().to_string(),\n                    sim_type: sim_type.clone(),\n                }))\n            }\n        }\n    }\n    Ok(sims)\n}\n\nfn devices() -> Result<Vec<Box<dyn Device>>> {\n    let mut devices: HashMap<String, IosDevice> = Default::default();\n    if which::which(\"xcrun\").is_err() {\n        log::warn!(\"xcrun not found. Apple devices support disabled. Consider installing XCode and its command line tools.\");\n        return Ok(vec![]);\n    }\n    if !std::process::Command::new(\"xcrun\")\n        .arg(\"--find\")\n        .arg(\"devicectl\")\n        .output()?\n        .status\n        .success()\n    {\n        log::warn!(\"xcrun devicectl not found. Apple devices support disabled. Consider updating XCode and its command line tools.\");\n        return Ok(vec![]);\n    }\n    devices_from_devicectl(&mut devices)?;\n    devices_from_ios_deploy(&mut devices)?;\n    Ok(devices\n        .into_values()\n        .map(|d| Box::new(d) as _)\n        .collect_vec())\n}\n\nfn devices_from_devicectl(devices: &mut HashMap<String, IosDevice>) -> Result<()> {\n    let tempdir = tempfile::TempDir::with_prefix(\"dinghy-ios\")?;\n    let tmpjson = tempdir.path().join(\"json\");\n    let devicectl = std::process::Command::new(\"xcrun\")\n        .args(\"devicectl list devices --quiet --json-output\".split_whitespace().collect_vec())\n        .arg(&tmpjson)\n        .stderr(std::process::Stdio::inherit())\n        .output()\n        .context(\"Failed to launch xcrun command. Please check that \\\"xcrun devicectl list devices\\\" works\")?;\n    if !devicectl.status.success() {\n        bail!(\"xcrun command failed. Please check that \\\"xcrun devicectl list devices\\\" works.\\n{devicectl:?}\");\n    }\n    let txt = std::fs::read_to_string(&tmpjson)\n        .with_context(|| format!(\"Reading devicectl json output {tmpjson:?}\"))?;\n    for device in json::parse(&txt)?[\"result\"][\"devices\"].members() {\n        let Some(udid) = device[\"hardwareProperties\"][\"udid\"]\n            .as_str()\n            .map(|s| s.to_string())\n        else {\n            continue;\n        };\n        let device = IosDevice::new(\n            device[\"deviceProperties\"][\"name\"]\n                .as_str()\n                .context(\"no name in device json\")?\n                .to_string(),\n            udid.clone(),\n            device[\"hardwareProperties\"][\"cpuType\"][\"name\"]\n                .as_str()\n                .context(\"no cpuType in device json\")?,\n            device[\"deviceProperties\"][\"osVersionNumber\"]\n                .as_str()\n                .context(\"no osVersionNumber\")?\n                .to_string(),\n        )?;\n        devices.insert(udid, device);\n    }\n    Ok(())\n}\n\nfn devices_from_ios_deploy(devices: &mut HashMap<String, IosDevice>) -> Result<()> {\n    let list = ::std::process::Command::new(\"ios-deploy\")\n        .stderr(std::process::Stdio::inherit())\n        .args(&[\"-c\", \"--json\", \"-t\", \"1\"])\n        .output();\n    let list = match list {\n        Ok(l) => l,\n        Err(e) => {\n            info!(\n                \"Could not execute ios-deploy to look for legacy (before iOS 17) iOS devices ({}). Consider installing ios-deploy (`brew install ios-deploy`...) for legacy iOS support.\", e);\n            return Ok(());\n        }\n    };\n    if !list.status.success() {\n        info!(\n            \"ios-deploy returned an error while listing devices. It this is not expected, you need to make sure `ios-deploy --json -c -t 1` works as expected. ios-deploy is needed for pre-ios17 devices.\"\n            );\n        return Ok(());\n    }\n    // ios-deploy outputs each device as a multiline json dict, with separator or delimiter. make\n    // it a json array.\n    let list = String::from_utf8(list.stdout)?.replace(\"}{\", \"},{\");\n    let list = format!(\"[{}]\", list);\n    let list = ::json::parse(&list)\n        .with_context(|| \"Could not parse output for: `ios-deploy --json -c -t 1` as json. Please try to make this command work and retry.\")?;\n    for json in list.members() {\n        let device = &json[\"Device\"];\n        let id = device[\"DeviceIdentifier\"]\n            .as_str()\n            .context(\"DeviceIdentifier expected to be a string\")?\n            .to_owned();\n        let name = device[\"DeviceName\"]\n            .as_str()\n            .context(\"DeviceName expected to be a string\")?\n            .to_owned();\n        let arch_cpu = device[\"modelArch\"].as_str().unwrap_or(\"arm64\");\n        let ios_version = device[\"ProductVersion\"]\n            .as_str()\n            .context(\"ProductVersion expected to be a string\")?\n            .to_string();\n        devices.insert(\n            name.clone(),\n            IosDevice::new(name, id, &arch_cpu, ios_version)?,\n        );\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "dinghy-lib/src/apple/platform.rs",
    "content": "use crate::config::PlatformConfiguration;\nuse crate::errors::*;\nuse crate::overlay::Overlayer;\nuse crate::project::Project;\nuse crate::toolchain::Toolchain;\nuse crate::Build;\nuse crate::Device;\nuse crate::Platform;\nuse crate::SetupArgs;\nuse dinghy_build::build_env::set_env;\nuse std::fmt::{Debug, Display, Formatter};\nuse std::process;\n\nuse super::AppleSimulatorType;\n\npub struct AppleDevicePlatform {\n    id: String,\n    pub sim: Option<AppleSimulatorType>,\n    pub toolchain: Toolchain,\n    pub configuration: PlatformConfiguration,\n}\n\nimpl Debug for AppleDevicePlatform {\n    fn fmt(&self, fmt: &mut Formatter) -> ::std::fmt::Result {\n        write!(fmt, \"{}\", self.id)\n    }\n}\n\nimpl AppleDevicePlatform {\n    pub fn new(\n        id: String,\n        rustc_triple: &str,\n        simulator: Option<AppleSimulatorType>,\n        configuration: PlatformConfiguration,\n    ) -> Result<Box<dyn Platform>> {\n        Ok(Box::new(AppleDevicePlatform {\n            id,\n            sim: simulator,\n            toolchain: Toolchain {\n                rustc_triple: rustc_triple.to_string(),\n            },\n            configuration,\n        }))\n    }\n\n    fn sysroot_path(&self) -> Result<String> {\n        let sdk_name = match self.sim {\n            Some(AppleSimulatorType::Ios) => \"iphonesimulator\",\n            Some(AppleSimulatorType::Tvos) => \"appletvsimulator\",\n            Some(AppleSimulatorType::Watchos) => \"watchsimulator\",\n            None => \"iphoneos\",\n        };\n        let xcrun = process::Command::new(\"xcrun\")\n            .args(&[\"--sdk\", sdk_name, \"--show-sdk-path\"])\n            .output()?;\n        Ok(String::from_utf8(xcrun.stdout)?.trim_end().to_string())\n    }\n}\n\nimpl Platform for AppleDevicePlatform {\n    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> Result<()> {\n        let sysroot = self.sysroot_path()?;\n        Overlayer::overlay(&self.configuration, self, project, &self.sysroot_path()?)?;\n        self.toolchain.setup_cc(self.id().as_str(), \"gcc\")?;\n        set_env(\"TARGET_SYSROOT\", &sysroot);\n        self.toolchain.setup_linker(\n            &self.id(),\n            &format!(\"cc -isysroot {}\", sysroot),\n            &project.metadata.workspace_root,\n        )?;\n        self.toolchain.setup_runner(&self.id(), setup_args)?;\n        self.toolchain.setup_target()?;\n        self.toolchain.setup_pkg_config()?;\n        Ok(())\n    }\n\n    fn id(&self) -> String {\n        self.id.to_string()\n    }\n\n    fn is_compatible_with(&self, device: &dyn Device) -> bool {\n        device.is_compatible_with_simulator_platform(self)\n    }\n\n    fn is_host(&self) -> bool {\n        false\n    }\n\n    fn rustc_triple(&self) -> &str {\n        &self.toolchain.rustc_triple\n    }\n\n    fn strip(&self, build: &mut Build) -> Result<()> {\n        let mut command = ::std::process::Command::new(\"xcrun\");\n        command.arg(\"strip\");\n        build.runnable = crate::platform::strip_runnable(&build.runnable, command)?;\n        Ok(())\n    }\n\n    fn sysroot(&self) -> Result<Option<std::path::PathBuf>> {\n        self.sysroot_path().map(|s| Some(s.into()))\n    }\n}\n\nimpl Display for AppleDevicePlatform {\n    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {\n        if self.sim.is_some() {\n            write!(f, \"XCode targetting Apple device Simulator\")\n        } else {\n            write!(f, \"XCode targetting Ios Device\")\n        }\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/apple/xcode.rs",
    "content": "use super::{AppleSimulatorType, SignatureSettings, SigningIdentity};\nuse crate::errors::*;\nuse fs_err as fs;\nuse log::{debug, trace};\nuse std::io::Write;\nuse std::{io, process};\n\nuse crate::utils::LogCommandExt;\nuse crate::BuildBundle;\n\npub fn add_plist_to_app(\n    bundle: &BuildBundle,\n    arch: &str,\n    app_bundle_id: &str,\n    sim_type: Option<&AppleSimulatorType>,\n) -> Result<()> {\n    let mut plist = fs::File::create(bundle.bundle_dir.join(\"Info.plist\"))?;\n    writeln!(plist, r#\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\"#)?;\n    writeln!(\n        plist,\n        r#\"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\"#\n    )?;\n    writeln!(plist, r#\"<plist version=\"1.0\"><dict>\"#)?;\n    writeln!(plist, \"<key>CFBundleName</key><string>Dinghy</string>\",)?;\n    writeln!(\n        plist,\n        \"<key>CFBundleExecutable</key><string>Dinghy</string>\",\n    )?;\n    writeln!(\n        plist,\n        \"<key>CFBundleIdentifier</key><string>{}</string>\",\n        app_bundle_id\n    )?;\n    writeln!(plist, \"<key>CFBundleVersion</key>\")?;\n    writeln!(plist, \"<string>{}</string>\", arch)?;\n    writeln!(plist, \"<key>CFBundleShortVersionString</key>\")?;\n    writeln!(plist, \"<string>{}</string>\", arch)?;\n    match sim_type {\n        // The iOS/tvOS simulator have the same plist as an iOS device.\n        Some(AppleSimulatorType::Ios) | Some(AppleSimulatorType::Tvos) | None => {\n            writeln!(plist, \"<key>UIRequiredDeviceCapabilities</key>\")?;\n            writeln!(plist, \"<array><string>{}</string></array>\", arch)?;\n            writeln!(plist, \"<key>UILaunchStoryboardName</key>\")?;\n            writeln!(plist, \"<string></string>\")?;\n        }\n        Some(AppleSimulatorType::Watchos) => {\n            writeln!(plist, \"<key>MinimumOSVersion</key><string>8.0</string>\",)?;\n            writeln!(plist, \"<key>WKApplication</key><true/>\",)?;\n            writeln!(plist, \"<key>WKWatchOnly</key><true/>\")?;\n        }\n    }\n    writeln!(plist, r#\"</dict></plist>\"#)?;\n    Ok(())\n}\n\npub fn sign_app(bundle: &BuildBundle, settings: &SignatureSettings) -> Result<()> {\n    debug!(\n        \"Will sign {:?} with team: {} using key: {} and profile: {}\",\n        bundle.bundle_dir, settings.identity.team, settings.identity.name, settings.file\n    );\n\n    let entitlements = bundle.root_dir.join(\"entitlements.xcent\");\n    debug!(\"entitlements file: {}\", entitlements.to_str().unwrap_or(\"\"));\n    let mut plist = fs::File::create(&entitlements)?;\n    writeln!(plist, r#\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\"#)?;\n    writeln!(\n        plist,\n        r#\"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\"#\n    )?;\n    writeln!(plist, r#\"<plist version=\"1.0\"><dict>\"#)?;\n    writeln!(plist, \"{}\", settings.entitlements)?;\n    writeln!(plist, r#\"</dict></plist>\"#)?;\n\n    let result = process::Command::new(\"codesign\")\n        .args(&[\"-s\", &*settings.identity.name, \"--entitlements\"])\n        .arg(entitlements)\n        .arg(&bundle.bundle_dir)\n        .log_invocation(2)\n        .status()?;\n    if !result.success() {\n        bail!(\"Failure to sign application: codesign utility returned non-zero\");\n    }\n    Ok(())\n}\n\npub fn look_for_signature_settings(device_id: &str) -> Result<Vec<SignatureSettings>> {\n    let identity_regex = ::regex::Regex::new(r#\"^ *[0-9]+\\) ([A-Z0-9]{40}) \"(.+)\"$\"#)?;\n    let subject_regex = ::regex::Regex::new(r#\"OU *= *([^,]+)\"#)?;\n    let mut identities: Vec<SigningIdentity> = vec![];\n    let find_identities = process::Command::new(\"security\")\n        .args(&[\"find-identity\", \"-v\", \"-p\", \"codesigning\"])\n        .log_invocation(3)\n        .output()?;\n    for line in String::from_utf8(find_identities.stdout)?.split(\"\\n\") {\n        if let Some(caps) = identity_regex.captures(&line) {\n            let name: String = caps[2].into();\n            if !name.starts_with(\"iPhone Developer: \") && !name.starts_with(\"Apple Development:\") {\n                continue;\n            }\n            let subject = process::Command::new(\"sh\")\n                .arg(\"-c\")\n                .arg(format!(\n                    \"security find-certificate -a -c \\\"{}\\\" -p | openssl x509 -text | \\\n                     grep Subject:\",\n                    name\n                ))\n                .log_invocation(3)\n                .output()?;\n            let subject = String::from_utf8(subject.stdout)?;\n            if let Some(ou) = subject_regex.captures(&subject) {\n                identities.push(SigningIdentity {\n                    id: caps[1].into(),\n                    name: caps[2].into(),\n                    team: ou[1].into(),\n                })\n            }\n        }\n    }\n    debug!(\"Possible signing identities: {:?}\", identities);\n    let mut settings = vec![];\n    let home = dirs::home_dir().expect(\"can't get HOME dir\");\n    let profiles_dir = [\n        \"Library/MobileDevice/Provisioning Profiles\",\n        \"Library/Developer/Xcode/UserData/Provisioning Profiles\", // xcode 16 and above\n    ];\n    for file in profiles_dir\n        .iter()\n        .filter_map(|path| fs::read_dir(home.join(path)).ok())\n        .flatten()\n    {\n        let file = file?;\n        if file.path().starts_with(\".\")\n            || file\n                .path()\n                .extension()\n                .map(|ext| ext.to_string_lossy() != \"mobileprovision\")\n                .unwrap_or(true)\n        {\n            trace!(\n                \" - skipping {:?} (not a mobileprovision profile)\",\n                file.path()\n            );\n            continue;\n        }\n        let decoded = process::Command::new(\"security\")\n            .arg(\"cms\")\n            .arg(\"-D\")\n            .arg(\"-i\")\n            .arg(file.path())\n            .log_invocation(3)\n            .output()?;\n        let plist = plist::Value::from_reader(io::Cursor::new(&decoded.stdout))\n            .with_context(|| format!(\"While reading profile {:?}\", file.path()))?;\n        let dict = plist\n            .as_dictionary()\n            .ok_or_else(|| anyhow!(\"plist root should be a dictionary\"))?;\n        let devices = if let Some(d) = dict.get(\"ProvisionedDevices\") {\n            d\n        } else {\n            trace!(\" - skipping {:?} (no devices)\", file.path());\n            continue;\n        };\n        let devices = if let Some(ds) = devices.as_array() {\n            ds\n        } else {\n            bail!(\"ProvisionedDevices expected to be array\")\n        };\n        if !devices.contains(&plist::Value::String(device_id.into())) {\n            trace!(\" - skipping {:?} (not matching target device)\", file.path());\n            continue;\n        }\n        let name = dict\n            .get(\"Name\")\n            .ok_or_else(|| anyhow!(format!(\"No name in profile {:?}\", file.path())))?;\n        let name = name\n            .as_string()\n            .ok_or_else(|| anyhow!(\"Name should have been a string in {:?}\", file.path()))?;\n        if !name.ends_with(\"Dinghy\") && !name.ends_with(\" *\") {\n            trace!(\" - skipping {:?} (wrong app)\", file.path());\n            continue;\n        }\n        // TODO: check date in future\n        let team = dict\n            .get(\"TeamIdentifier\")\n            .ok_or_else(|| anyhow!(\"no TeamIdentifier\"))?;\n        let team = team\n            .as_array()\n            .ok_or_else(|| anyhow!(\"TeamIdentifier should be an array\"))?;\n        let team = team\n            .first()\n            .ok_or_else(|| anyhow!(\"empty TeamIdentifier\"))?\n            .as_string()\n            .ok_or_else(|| anyhow!(\"TeamIdentifier should be a String\"))?\n            .to_string();\n        let identity = identities.iter().find(|i| i.team == team);\n        if identity.is_none() {\n            trace!(\n                \" - skipping {:?} (no identity in profile for team)\",\n                file.path()\n            );\n            continue;\n        }\n        let identity = identity.unwrap();\n        trace!(\" - accepting {:?}\", file.path());\n        let entitlements = String::from_utf8(decoded.stdout)?\n            .split(\"\\n\")\n            .skip_while(|line| !line.contains(\"<key>Entitlements</key>\"))\n            .skip(2)\n            .take_while(|line| !line.contains(\"</dict>\"))\n            .collect::<Vec<&str>>()\n            .join(\"\\n\");\n        settings.push(SignatureSettings {\n            entitlements: entitlements,\n            file: file\n                .path()\n                .to_str()\n                .ok_or_else(|| anyhow!(\"filename should be utf8\"))?\n                .into(),\n            name: if name.ends_with(\" *\") {\n                \"org.zoy.kali.Dinghy\".into()\n            } else {\n                name.into()\n            },\n            identity: identity.clone(),\n            profile: file.path().to_str().unwrap().into(),\n        });\n    }\n    Ok(settings)\n}\n"
  },
  {
    "path": "dinghy-lib/src/config.rs",
    "content": "use itertools::Itertools;\nuse serde::de;\nuse serde::{Deserialize, Serialize};\nuse std::fmt;\nuse std::result;\nuse std::{collections, path};\n\nuse crate::errors::*;\n\n#[derive(Clone, Debug)]\npub struct TestData {\n    pub id: String,\n    pub base: path::PathBuf,\n    pub source: String,\n    pub target: String,\n    pub copy_git_ignored: bool,\n}\n\n#[derive(Serialize, Debug, Clone)]\npub struct TestDataConfiguration {\n    pub copy_git_ignored: bool,\n    pub source: String,\n    pub target: Option<String>,\n}\n\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub struct DetailedTestDataConfiguration {\n    pub source: String,\n    pub copy_git_ignored: bool,\n    pub target: Option<String>,\n}\n\nimpl<'de> de::Deserialize<'de> for TestDataConfiguration {\n    fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>\n    where\n        D: de::Deserializer<'de>,\n    {\n        struct TestDataVisitor;\n\n        impl<'de> de::Visitor<'de> for TestDataVisitor {\n            type Value = TestDataConfiguration;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n                formatter.write_str(\n                    \"a path like \\\"tests/my_test_data\\\" or a \\\n                     detailed dependency like { source = \\\n                     \\\"tests/my_test_data\\\", copy_git_ignored = true }\",\n                )\n            }\n\n            fn visit_str<E>(self, s: &str) -> result::Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                Ok(TestDataConfiguration {\n                    copy_git_ignored: false,\n                    source: s.to_owned(),\n                    target: None,\n                })\n            }\n\n            fn visit_map<V>(self, map: V) -> result::Result<Self::Value, V::Error>\n            where\n                V: de::MapAccess<'de>,\n            {\n                let mvd = de::value::MapAccessDeserializer::new(map);\n                let detailed = DetailedTestDataConfiguration::deserialize(mvd)?;\n                Ok(TestDataConfiguration {\n                    copy_git_ignored: detailed.copy_git_ignored,\n                    source: detailed.source,\n                    target: detailed.target,\n                })\n            }\n        }\n\n        deserializer.deserialize_any(TestDataVisitor)\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct Configuration {\n    pub platforms: collections::BTreeMap<String, PlatformConfiguration>,\n    pub ssh_devices: collections::BTreeMap<String, SshDeviceConfiguration>,\n    pub script_devices: collections::BTreeMap<String, ScriptDeviceConfiguration>,\n    pub test_data: Vec<TestData>,\n    pub skip_source_copy: bool,\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug, Default)]\nstruct ConfigurationFileContent {\n    pub platforms: Option<collections::BTreeMap<String, PlatformConfiguration>>,\n    pub ssh_devices: Option<collections::BTreeMap<String, SshDeviceConfiguration>>,\n    pub script_devices: Option<collections::BTreeMap<String, ScriptDeviceConfiguration>>,\n    pub test_data: Option<collections::BTreeMap<String, TestDataConfiguration>>,\n    pub skip_source_copy: Option<bool>,\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug, Default)]\npub struct PlatformConfiguration {\n    pub deb_multiarch: Option<String>,\n    pub env: Option<collections::HashMap<String, String>>,\n    pub overlays: Option<collections::HashMap<String, OverlayConfiguration>>,\n    pub rustc_triple: Option<String>,\n    pub sysroot: Option<String>,\n    pub toolchain: Option<String>,\n}\n\nimpl PlatformConfiguration {\n    pub fn empty() -> Self {\n        PlatformConfiguration {\n            deb_multiarch: None,\n            env: None,\n            overlays: None,\n            rustc_triple: None,\n            sysroot: None,\n            toolchain: None,\n        }\n    }\n\n    pub fn env(&self) -> Vec<(String, String)> {\n        self.env\n            .as_ref()\n            .map(|it| {\n                it.iter()\n                    .map(|(key, value)| (key.to_string(), value.to_string()))\n                    .collect_vec()\n            })\n            .unwrap_or(vec![])\n    }\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug, Default)]\npub struct OverlayConfiguration {\n    pub path: String,\n    pub scope: Option<String>,\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug)]\npub struct SshDeviceConfiguration {\n    pub hostname: String,\n    pub username: String,\n    pub port: Option<u16>,\n    pub path: Option<String>,\n    pub target: Option<String>,\n    pub toolchain: Option<String>,\n    pub platform: Option<String>,\n    #[serde(default)]\n    pub remote_shell_vars: collections::HashMap<String, String>,\n    pub install_adhoc_rsync_local_path: Option<String>,\n    pub use_legacy_scp_protocol_for_adhoc_rsync_copy: Option<bool>,\n}\n\n#[derive(Clone, Serialize, Deserialize, Debug)]\npub struct ScriptDeviceConfiguration {\n    pub path: String,\n    pub platform: Option<String>,\n}\n\nimpl Configuration {\n    pub fn merge(&mut self, file: &path::Path) -> Result<()> {\n        let other = read_config_file(&file)?;\n        if let Some(pfs) = other.platforms {\n            self.platforms.extend(pfs)\n        }\n        self.ssh_devices\n            .extend(other.ssh_devices.unwrap_or(collections::BTreeMap::new()));\n        self.script_devices\n            .extend(other.script_devices.unwrap_or(collections::BTreeMap::new()));\n        for (id, source) in other.test_data.unwrap_or(collections::BTreeMap::new()) {\n            // TODO Remove key\n            self.test_data.push(TestData {\n                id: id.to_string(),\n                base: file.to_path_buf(),\n                source: source.source.clone(),\n                target: source.target.unwrap_or(source.source.clone()),\n                copy_git_ignored: source.copy_git_ignored,\n            })\n        }\n        if let Some(skip_source_copy) = other.skip_source_copy {\n            self.skip_source_copy = skip_source_copy\n        }\n        Ok(())\n    }\n}\n\nfn read_config_file<P: AsRef<path::Path>>(file: P) -> Result<ConfigurationFileContent> {\n    let file = file.as_ref();\n    let data = std::fs::read_to_string(file).with_context(|| format!(\"Reading {file:?}\"))?;\n    Ok(toml::from_str(&data)?)\n}\n\npub fn dinghy_config<P: AsRef<path::Path>>(dir: P) -> Result<Configuration> {\n    let mut conf = Configuration::default();\n\n    let mut files_to_try = vec![];\n    let dir = dir.as_ref().to_path_buf();\n    let mut d = dir.as_path();\n    while d.parent().is_some() {\n        files_to_try.push(d.join(\"dinghy.toml\"));\n        files_to_try.push(d.join(\".dinghy.toml\"));\n        files_to_try.push(d.join(\".dinghy\").join(\"dinghy.toml\"));\n        files_to_try.push(d.join(\".dinghy\").join(\".dinghy.toml\"));\n        d = d.parent().unwrap();\n    }\n    files_to_try.push(d.join(\".dinghy.toml\"));\n    if let Some(home) = dirs::home_dir() {\n        if !dir.starts_with(&home) {\n            files_to_try.push(home.join(\"dinghy.toml\"));\n            files_to_try.push(home.join(\".dinghy.toml\"));\n            files_to_try.push(home.join(\".dinghy\").join(\"dinghy.toml\"));\n            files_to_try.push(home.join(\".dinghy\").join(\".dinghy.toml\"));\n        }\n    }\n    for file in files_to_try {\n        if path::Path::new(&file).exists() {\n            log::debug!(\"Loading configuration from {:?}\", file);\n            conf.merge(&file)?;\n        } else {\n            log::trace!(\"No configuration found at {:?}\", file);\n        }\n    }\n\n    log::debug!(\"Configuration: {:#?}\", conf);\n\n    Ok(conf)\n}\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn load_config_with_str_test_data() {\n        let config_file = ::std::env::current_exe()\n            .unwrap()\n            .parent()\n            .unwrap()\n            .join(\"../../../test-ws/test-app/.dinghy.toml\");\n        super::read_config_file(config_file).unwrap();\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/device.rs",
    "content": "use crate::errors::*;\nuse crate::project;\nuse crate::project::{rec_copy, Project};\nuse crate::utils::copy_and_sync_file;\nuse crate::Build;\nuse crate::BuildBundle;\nuse fs_err as fs;\nuse log::debug;\nuse std::path::Path;\n\npub fn make_remote_app(project: &Project, build: &Build) -> Result<BuildBundle> {\n    make_remote_app_with_name(project, build, None)\n}\n\npub fn make_remote_app_with_name(\n    project: &Project,\n    build: &Build,\n    bundle_name: Option<&str>,\n) -> Result<BuildBundle> {\n    fn is_sysroot_library(path: &Path) -> bool {\n        path.ancestors()\n            .find(|ancestor_path| ancestor_path.ends_with(\"sysroot/usr/lib\"))\n            .is_some()\n            && (!path\n                .file_name()\n                .unwrap()\n                .to_str()\n                .unwrap()\n                .eq_ignore_ascii_case(\"libc++_shared.so\")\n                && !path.to_str().unwrap().contains(\"android\"))\n    }\n\n    let root_dir = build.target_path.join(\"dinghy\").join(build.runnable.id.clone());\n    let bundle_path = match bundle_name {\n        Some(name) => root_dir.join(&build.runnable.package_name).join(name),\n        None => root_dir.join(&build.runnable.package_name),\n    };\n    let bundle_libs_path = root_dir.join(\"overlay\");\n    let bundle_target_path = &bundle_path;\n    let bundle_exe_path = bundle_target_path.join(format!(\"_dinghy_{}\", &build.runnable.id));\n\n    debug!(\"Removing previous bundle {:?}\", bundle_path);\n    let _ = fs::remove_dir_all(&bundle_path);\n    let _ = fs::remove_dir_all(&bundle_libs_path);\n    let _ = fs::remove_dir_all(&bundle_target_path);\n\n    debug!(\"Making bundle {:?}\", bundle_path);\n    fs::create_dir_all(&bundle_path)?;\n    fs::create_dir_all(&bundle_libs_path)?;\n    fs::create_dir_all(&bundle_target_path)?;\n\n    debug!(\n        \"Copying exe {:?} to bundle {:?}\",\n        &build.runnable.exe, bundle_exe_path\n    );\n    copy_and_sync_file(&build.runnable.exe, &bundle_exe_path).with_context(|| {\n        format!(\n            \"Couldn't copy {} to {}\",\n            &build.runnable.exe.display(),\n            &bundle_exe_path.display()\n        )\n    })?;\n\n    debug!(\"Copying dynamic libs to bundle\");\n    for src_lib_path in &build.dynamic_libraries {\n        let target_lib_path = bundle_libs_path.join(\n            src_lib_path\n                .file_name()\n                .ok_or_else(|| anyhow!(\"Invalid file name {:?}\", src_lib_path.file_name()))?,\n        );\n        if !is_sysroot_library(&src_lib_path) {\n            debug!(\n                \"Copying dynamic lib {} to {}\",\n                src_lib_path.display(),\n                target_lib_path.display()\n            );\n            copy_and_sync_file(&src_lib_path, &target_lib_path).with_context(|| {\n                format!(\n                    \"Couldn't copy {} to {}\",\n                    src_lib_path.display(),\n                    &target_lib_path.display()\n                )\n            })?;\n        } else {\n            debug!(\n                \"Dynamic lib {} will not be copied as it is a sysroot library\",\n                src_lib_path.display()\n            );\n        }\n    }\n\n    for file_in_run_args in &build.files_in_run_args {\n        let dst = bundle_target_path.join(\n            file_in_run_args\n                .file_name()\n                .ok_or_else(|| anyhow!(\"no file name\"))?,\n        );\n        if file_in_run_args.is_dir() {\n            rec_copy(file_in_run_args, dst, true)?;\n        } else {\n            copy_and_sync_file(&file_in_run_args, &dst).with_context(|| {\n                format!(\n                    \"Couldn't copy {} to {}\",\n                    file_in_run_args.display(),\n                    &root_dir.display()\n                )\n            })?;\n        }\n    }\n\n    if !build.runnable.skip_source_copy {\n        debug!(\n            \"Copying src {} to bundle {}\",\n            build.runnable.source.display(),\n            bundle_path.display()\n        );\n        project::rec_copy_excl(\n            &build.runnable.source,\n            &bundle_path,\n            false,\n            &[build.runnable.source.join(\"target\")],\n        )?;\n    } else {\n        debug!(\"Skipping source copy to bundle {}\", bundle_path.display());\n    }\n\n    debug!(\"Copying test_data to bundle {}\", bundle_path.display());\n    project.copy_test_data(&bundle_path)?;\n\n    Ok(BuildBundle {\n        id: build.runnable.id.clone(),\n        bundle_dir: bundle_path.to_path_buf(),\n        bundle_exe: bundle_exe_path.to_path_buf(),\n        lib_dir: bundle_libs_path.to_path_buf(),\n        root_dir,\n        app_id: None,\n    })\n}\n"
  },
  {
    "path": "dinghy-lib/src/host/mod.rs",
    "content": "use crate::{Configuration, Device, Platform, PlatformConfiguration, PlatformManager, Result};\n\nmod platform;\n\npub use self::platform::HostPlatform;\n\npub struct HostManager {\n    host_conf: PlatformConfiguration,\n}\n\nimpl HostManager {\n    pub fn probe(conf: &Configuration) -> Option<HostManager> {\n        let host_conf = conf\n            .platforms\n            .get(\"host\")\n            .map(|it| (*it).clone())\n            .unwrap_or(PlatformConfiguration::empty());\n        Some(HostManager { host_conf })\n    }\n\n    fn platform(&self) -> Result<HostPlatform> {\n        platform::HostPlatform::new(self.host_conf.clone())\n    }\n}\n\nimpl PlatformManager for HostManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        Ok(vec![])\n    }\n\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        Ok(vec![Box::new(self.platform()?)])\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/host/platform.rs",
    "content": "use crate::config::PlatformConfiguration;\nuse crate::overlay::Overlayer;\nuse crate::platform;\nuse crate::project::Project;\nuse crate::utils::LogCommandExt;\nuse crate::Build;\nuse crate::Device;\nuse crate::Platform;\nuse crate::Result;\nuse crate::SetupArgs;\nuse anyhow::anyhow;\nuse dinghy_build::build_env::{envify, set_all_env, set_env};\nuse std::fmt::{Debug, Formatter};\nuse std::io::BufRead;\nuse std::process::{Command, Stdio};\n\n#[derive(Clone)]\npub struct HostPlatform {\n    pub configuration: PlatformConfiguration,\n    pub id: String,\n}\n\nimpl HostPlatform {\n    pub fn new(configuration: PlatformConfiguration) -> Result<HostPlatform> {\n        Ok(HostPlatform {\n            configuration,\n            id: \"host\".to_string(),\n        })\n    }\n}\n\nimpl Debug for HostPlatform {\n    fn fmt(&self, fmt: &mut Formatter) -> ::std::fmt::Result {\n        write!(fmt, \"{}\", self.id)\n    }\n}\n\nimpl Platform for HostPlatform {\n    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> Result<()> {\n        // Set custom env variables specific to the platform\n        set_all_env(&self.configuration.env());\n\n        let triple = std::process::Command::new(\"rustc\")\n            .arg(\"-vV\")\n            .stdout(Stdio::piped())\n            .log_invocation(3)\n            .output()?\n            .stdout\n            .lines()\n            .find_map(|line| {\n                line.ok()\n                    .and_then(|line| line.strip_prefix(\"host: \").map(ToString::to_string))\n            })\n            .ok_or_else(|| anyhow!(\"could not get host triple from rustc\"))?;\n\n        set_env(\n            format!(\"CARGO_TARGET_{}_RUNNER\", envify(triple)),\n            setup_args.get_runner_command(\"host\"),\n        );\n\n        Overlayer::overlay(&self.configuration, self, project, \"/\")?;\n\n        Ok(())\n    }\n\n    fn id(&self) -> String {\n        \"host\".to_string()\n    }\n\n    fn is_compatible_with(&self, device: &dyn Device) -> bool {\n        device.is_compatible_with_host_platform(self)\n    }\n\n    fn is_host(&self) -> bool {\n        true\n    }\n\n    fn rustc_triple(&self) -> &str {\n        std::env!(\"TARGET\")\n    }\n\n    fn strip(&self, build: &mut Build) -> Result<()> {\n        log::info!(\"Stripping {}\", build.runnable.exe.display());\n        build.runnable = platform::strip_runnable(&build.runnable, Command::new(\"strip\"))?;\n\n        Ok(())\n    }\n\n    fn sysroot(&self) -> Result<Option<std::path::PathBuf>> {\n        Ok(Some(std::path::PathBuf::from(\"/\")))\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/lib.rs",
    "content": "#![type_length_limit = \"2149570\"]\npub mod errors {\n    pub use anyhow::{anyhow, bail, Context, Error, Result};\n}\n\nmod android;\n#[cfg(target_os = \"macos\")]\nmod apple;\npub mod config;\npub mod device;\nmod host;\npub mod overlay;\npub mod platform;\npub mod plugin;\npub mod project;\nmod script;\nmod ssh;\nmod toolchain;\npub mod utils;\n\npub use crate::config::Configuration;\n\n#[cfg(target_os = \"macos\")]\nuse crate::apple::{IosManager, TvosManager, WatchosManager};\nuse crate::config::PlatformConfiguration;\n\nuse crate::platform::regular_platform::RegularPlatform;\nuse crate::project::Project;\nuse anyhow::{anyhow, Context};\nuse dyn_clone::DynClone;\nuse std::fmt::Display;\nuse std::{path, sync};\n\nuse crate::errors::Result;\n\npub struct Dinghy {\n    devices: Vec<sync::Arc<Box<dyn Device>>>,\n    platforms: Vec<(String, sync::Arc<Box<dyn Platform>>)>,\n}\n\nimpl Dinghy {\n    pub fn probe(conf: &sync::Arc<Configuration>) -> Result<Dinghy> {\n        let mut managers: Vec<Box<dyn PlatformManager>> = vec![];\n        if let Some(man) = host::HostManager::probe(conf) {\n            managers.push(Box::new(man));\n        }\n        if let Some(man) = android::AndroidManager::probe() {\n            managers.push(Box::new(man));\n        }\n        if let Some(man) = script::ScriptDeviceManager::probe(conf.clone()) {\n            managers.push(Box::new(man));\n        }\n        if let Some(man) = ssh::SshDeviceManager::probe(conf.clone()) {\n            managers.push(Box::new(man));\n        }\n        #[cfg(target_os = \"macos\")]\n        {\n            std::thread::sleep(std::time::Duration::from_millis(100));\n            if let Some(man) = IosManager::new().context(\"Could not initialize iOS manager\")? {\n                managers.push(Box::new(man));\n            }\n            if let Some(man) = TvosManager::new().context(\"Could not initialize tvOS manager\")? {\n                managers.push(Box::new(man));\n            }\n            if let Some(man) = WatchosManager::new().context(\"Could not initialize tvOS manager\")? {\n                managers.push(Box::new(man));\n            }\n        }\n        if let Some(man) = plugin::PluginManager::probe(conf.clone()) {\n            managers.push(Box::new(man));\n        }\n\n        let mut devices = vec![];\n        let mut platforms = vec![];\n        for man in managers.into_iter() {\n            devices.extend(\n                man.devices()\n                    .context(\"Could not list devices\")?\n                    .into_iter()\n                    .map(|it| sync::Arc::new(it)),\n            );\n            platforms.extend(\n                man.platforms()\n                    .context(\"Could not list platforms\")?\n                    .into_iter()\n                    .map(|it| (it.id(), sync::Arc::new(it))),\n            );\n        }\n        for (platform_name, platform_conf) in &conf.platforms {\n            if platform_name == \"host\" {\n                continue;\n            }\n            let rustc_triple = platform_conf\n                .rustc_triple\n                .as_ref()\n                .ok_or_else(|| anyhow!(\"Platform {} has no rustc_triple\", platform_name))?;\n            let pf = RegularPlatform::new(\n                platform_conf.clone(),\n                platform_name.to_string(),\n                rustc_triple.clone(),\n                platform_conf\n                    .toolchain\n                    .clone()\n                    .map(|it| path::PathBuf::from(it))\n                    .or(dirs::home_dir()\n                        .map(|it| it.join(\".dinghy\").join(\"toolchain\").join(platform_name)))\n                    .with_context(|| format!(\"Toolchain missing for platform {}\", platform_name))?,\n            )\n            .with_context(|| format!(\"Could not assemble platform {}\", platform_name))?;\n            platforms.push((pf.id(), sync::Arc::new(pf)));\n        }\n        Ok(Dinghy { devices, platforms })\n    }\n\n    pub fn devices(&self) -> Vec<sync::Arc<Box<dyn Device>>> {\n        self.devices.clone()\n    }\n\n    pub fn host_platform(&self) -> sync::Arc<Box<dyn Platform>> {\n        self.platforms[0].1.clone()\n    }\n\n    pub fn platforms(&self) -> Vec<sync::Arc<Box<dyn Platform>>> {\n        self.platforms\n            .iter()\n            .map(|&(_, ref platform)| platform.clone())\n            .collect()\n    }\n\n    pub fn platform_by_name(\n        &self,\n        platform_name_filter: &str,\n    ) -> Option<sync::Arc<Box<dyn Platform>>> {\n        self.platforms\n            .iter()\n            .filter(|&&(ref platform_name, _)| platform_name == platform_name_filter)\n            .map(|&(_, ref platform)| platform.clone())\n            .next()\n    }\n}\n\npub trait Device: std::fmt::Debug + Display + DeviceCompatibility + DynClone {\n    fn clean_app(&self, build_bundle: &BuildBundle) -> Result<()>;\n\n    fn debug_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle>;\n\n    fn id(&self) -> &str;\n\n    fn name(&self) -> &str;\n\n    fn run_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle>;\n}\n\ndyn_clone::clone_trait_object!(Device);\n\npub trait DeviceCompatibility {\n    fn is_compatible_with_regular_platform(&self, _platform: &RegularPlatform) -> bool {\n        false\n    }\n\n    fn is_compatible_with_host_platform(&self, _platform: &host::HostPlatform) -> bool {\n        false\n    }\n\n    #[cfg(target_os = \"macos\")]\n    fn is_compatible_with_simulator_platform(\n        &self,\n        _platform: &apple::AppleDevicePlatform,\n    ) -> bool {\n        false\n    }\n}\n\npub trait Platform: std::fmt::Debug {\n    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> Result<()>;\n\n    fn id(&self) -> String;\n\n    fn is_compatible_with(&self, device: &dyn Device) -> bool;\n\n    fn is_host(&self) -> bool;\n    fn rustc_triple(&self) -> &str;\n\n    fn strip(&self, build: &mut Build) -> Result<()>;\n    fn sysroot(&self) -> Result<Option<path::PathBuf>>;\n}\n\nimpl Display for dyn Platform {\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(fmt, \"{}\", self.id())\n    }\n}\n\npub trait PlatformManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>>;\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>>;\n}\n\n#[derive(Clone, Debug)]\npub struct Build {\n    pub setup_args: SetupArgs,\n    pub dynamic_libraries: Vec<path::PathBuf>,\n    pub runnable: Runnable,\n    pub target_path: path::PathBuf,\n    pub files_in_run_args: Vec<path::PathBuf>,\n}\n\n#[derive(Clone, Debug)]\npub struct SetupArgs {\n    pub verbosity: i8,\n    pub forced_overlays: Vec<String>,\n    pub envs: Vec<String>,\n    pub cleanup: bool,\n    pub strip: bool,\n    pub device_id: Option<String>,\n}\n\nimpl SetupArgs {\n    pub fn get_runner_command(&self, platform_id: &str) -> String {\n        let mut extra_args = String::new();\n        if self.verbosity > 0 {\n            for _ in 0..self.verbosity {\n                extra_args.push_str(\"-v \")\n            }\n        }\n        if self.verbosity < 0 {\n            for _ in 0..-self.verbosity {\n                extra_args.push_str(\"-q \")\n            }\n        }\n        if self.cleanup {\n            extra_args.push_str(\"--cleanup \")\n        }\n        if self.strip {\n            extra_args.push_str(\"--strip \")\n        }\n        if let Some(device_id) = &self.device_id {\n            extra_args.push_str(\"-d \");\n            extra_args.push_str(&device_id);\n            extra_args.push(' ');\n        }\n        for env in &self.envs {\n            extra_args.push_str(\"-e \");\n            extra_args.push_str(env);\n            extra_args.push(' ');\n        }\n\n        format!(\n            \"{} -p {} {}runner --\",\n            std::env::current_exe().unwrap().to_str().unwrap(),\n            platform_id,\n            extra_args\n        )\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct BuildBundle {\n    pub id: String,\n    pub bundle_dir: path::PathBuf,\n    pub bundle_exe: path::PathBuf,\n    pub lib_dir: path::PathBuf,\n    pub root_dir: path::PathBuf,\n    pub app_id: Option<String>,\n}\n\nimpl BuildBundle {\n    fn replace_prefix_with<P: AsRef<path::Path>>(&self, path: P) -> Result<Self> {\n        Ok(BuildBundle {\n            bundle_dir: utils::normalize_path(\n                &path\n                    .as_ref()\n                    .to_path_buf()\n                    .join(self.bundle_dir.strip_prefix(&self.root_dir)?),\n            ),\n            bundle_exe: utils::normalize_path(\n                &path\n                    .as_ref()\n                    .to_path_buf()\n                    .join(self.bundle_exe.strip_prefix(&self.root_dir)?),\n            ),\n            lib_dir: utils::normalize_path(\n                &path\n                    .as_ref()\n                    .to_path_buf()\n                    .join(self.lib_dir.strip_prefix(&self.root_dir)?),\n            ),\n            root_dir: path.as_ref().to_path_buf(),\n            ..self.clone()\n        })\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct Runnable {\n    pub id: String,\n    pub package_name: String,\n    pub exe: path::PathBuf,\n    pub source: path::PathBuf,\n    pub skip_source_copy: bool,\n}\n"
  },
  {
    "path": "dinghy-lib/src/overlay.rs",
    "content": "use crate::config::PlatformConfiguration;\nuse crate::errors::*;\nuse crate::project::Project;\nuse crate::utils::contains_file_with_ext;\nuse crate::utils::destructure_path;\nuse crate::utils::file_has_ext;\nuse crate::utils::lib_name_from;\nuse crate::Platform;\nuse dinghy_build::build_env::append_path_to_target_env;\nuse dinghy_build::build_env::envify;\nuse dinghy_build::build_env::set_env_ifndef;\nuse dinghy_build::utils::path_between;\nuse dirs::home_dir;\nuse fs_err::{create_dir_all, read_dir, remove_dir_all, File};\nuse itertools::Itertools;\nuse std::io::Write;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse walkdir::WalkDir;\n\n#[derive(Clone, Debug)]\npub enum OverlayScope {\n    Application,\n    System,\n}\n\n#[derive(Clone, Debug)]\npub struct Overlay {\n    pub id: String,\n    pub path: PathBuf,\n    pub scope: OverlayScope,\n}\n\n#[derive(Clone, Debug)]\npub struct Overlayer {\n    platform_id: String,\n    rustc_triple: Option<String>,\n    sysroot: PathBuf,\n    work_dir: PathBuf,\n}\n\nimpl Overlayer {\n    pub fn overlay<P: AsRef<Path>>(\n        configuration: &PlatformConfiguration,\n        platform: &dyn Platform,\n        project: &Project,\n        sysroot: P,\n    ) -> Result<()> {\n        let overlayer = Overlayer {\n            platform_id: platform.id().to_string(),\n            rustc_triple: Some(platform.rustc_triple().to_string()),\n            sysroot: sysroot.as_ref().to_path_buf(),\n            work_dir: project.overlay_work_dir(platform)?,\n        };\n\n        let mut path_to_try = vec![];\n        let project_path = project.project_dir()?;\n        let mut current_path = project_path.as_path();\n        while current_path.parent().is_some() {\n            path_to_try.push(\n                current_path\n                    .join(\".dinghy\")\n                    .join(\"overlay\")\n                    .join(&overlayer.platform_id),\n            );\n            if let Some(parent_path) = current_path.parent() {\n                current_path = parent_path;\n            } else {\n                break;\n            }\n        }\n\n        // Project may be outside home directory. So add it too.\n        if let Some(dinghy_home_dir) = home_dir().map(|it| {\n            it.join(\".dinghy\")\n                .join(\"overlay\")\n                .join(&overlayer.platform_id)\n        }) {\n            if !path_to_try.contains(&dinghy_home_dir) {\n                path_to_try.push(dinghy_home_dir)\n            }\n        }\n\n        overlayer.apply_overlay(\n            Overlayer::from_conf(configuration)?\n                .into_iter()\n                .chain(path_to_try.into_iter().flat_map(|path_to_try| {\n                    Overlayer::from_directory(path_to_try).unwrap_or_default()\n                }))\n                .unique_by(|overlay| overlay.id.clone())\n                .collect_vec(),\n        )\n    }\n\n    fn from_conf(configuration: &PlatformConfiguration) -> Result<Vec<Overlay>> {\n        Ok(configuration\n            .overlays\n            .as_ref()\n            .unwrap_or(&::std::collections::HashMap::new())\n            .into_iter()\n            .map(|(overlay_id, overlay_conf)| Overlay {\n                id: overlay_id.to_string(),\n                path: PathBuf::from(overlay_conf.path.as_str()),\n                scope: OverlayScope::Application,\n            })\n            .collect())\n    }\n\n    fn from_directory<P: AsRef<Path>>(overlay_root_dir: P) -> Result<Vec<Overlay>> {\n        Ok(read_dir(overlay_root_dir.as_ref())?\n            .filter_map(|it| it.ok()) // Ignore invalid directories\n            .map(|it| it.path())\n            .filter(|it| it.is_dir())\n            .filter_map(destructure_path)\n            .map(|(overlay_dir_path, overlay_dir_name)| Overlay {\n                id: overlay_dir_name,\n                path: overlay_dir_path.to_path_buf(),\n                scope: OverlayScope::Application,\n            })\n            .collect())\n    }\n\n    fn apply_overlay<I>(&self, overlays: I) -> Result<()>\n    where\n        I: IntoIterator<Item = Overlay>,\n    {\n        let pkg_config_env_var = self\n            .rustc_triple\n            .as_ref()\n            .map(|_| \"PKG_CONFIG_LIBDIR\")\n            // Fallback on PKG_CONFIG_LIBPATH for host as it doesn't erase pkg_config paths\n            .unwrap_or(\"PKG_CONFIG_LIBPATH\");\n\n        // Setup overlay work directory\n        if let Err(error) = remove_dir_all(&self.work_dir) {\n            if self.work_dir.exists() {\n                log::warn!(\n                    \"Couldn't cleanup directory overlay work directory {} ({:?})\",\n                    self.work_dir.display(),\n                    error\n                )\n            }\n        }\n        create_dir_all(&self.work_dir)?;\n        append_path_to_target_env(\n            pkg_config_env_var,\n            self.rustc_triple.as_ref(),\n            &self.work_dir,\n        );\n\n        for overlay in overlays {\n            log::debug!(\"Overlaying '{}'\", overlay.id.as_str());\n            let mut has_pkg_config_files = false;\n\n            let pkg_config_path_list = WalkDir::new(&overlay.path)\n                .into_iter()\n                .filter_map(|entry| entry.ok()) // Ignore unreadable directories, maybe could warn...\n                .filter(|entry| entry.file_type().is_dir())\n                .filter(|dir| {\n                    dir.file_name() == \"pkgconfig\" || contains_file_with_ext(dir.path(), \".pc\")\n                })\n                .map(|pkg_config_path| pkg_config_path.path().to_path_buf());\n\n            for pkg_config_path in pkg_config_path_list {\n                log::debug!(\n                    \"Discovered pkg-config directory '{}'\",\n                    pkg_config_path.display()\n                );\n                append_path_to_target_env(\n                    pkg_config_env_var,\n                    self.rustc_triple.as_ref(),\n                    pkg_config_path,\n                );\n                has_pkg_config_files = true;\n            }\n            if !has_pkg_config_files {\n                self.generate_pkg_config_file(&overlay)?;\n                append_path_to_target_env(\n                    pkg_config_env_var,\n                    self.rustc_triple.as_ref(),\n                    &overlay.path,\n                );\n            }\n\n            // Override the 'prefix' pkg-config variable for the specified overlay only.\n            set_env_ifndef(\n                envify(format!(\"PKG_CONFIG_{}_PREFIX\", overlay.id)),\n                path_between(&self.sysroot, &overlay.path),\n            );\n        }\n        Ok(())\n    }\n\n    fn generate_pkg_config_file(&self, overlay: &Overlay) -> Result<()> {\n        fn write_pkg_config_file<P: AsRef<Path>, T: AsRef<str>>(\n            pc_file_path: P,\n            name: &str,\n            libs: &[T],\n        ) -> Result<()> {\n            log::debug!(\n                \"Generating pkg-config pc file {}\",\n                pc_file_path.as_ref().display()\n            );\n            let mut pc_file = File::create(pc_file_path.as_ref())?;\n            pc_file.write_all(b\"prefix:/\")?;\n            pc_file.write_all(b\"\\nexec_prefix:${prefix}\")?;\n            pc_file.write_all(b\"\\nName: \")?;\n            pc_file.write_all(name.as_bytes())?;\n            pc_file.write_all(b\"\\nDescription: \")?;\n            pc_file.write_all(name.as_bytes())?;\n            pc_file.write_all(b\"\\nVersion: unspecified\")?;\n            pc_file.write_all(b\"\\nLibs: -L${prefix} \")?;\n            for lib in libs {\n                pc_file.write_all(b\" -l\")?;\n                pc_file.write_all(lib.as_ref().as_bytes())?;\n            }\n            pc_file.write_all(b\"\\nCflags: -I${prefix}\")?;\n            Ok(())\n        }\n\n        let pc_file = self.work_dir.join(format!(\"{}.pc\", self.platform_id));\n        let lib_list = WalkDir::new(&overlay.path)\n            .max_depth(1)\n            .into_iter()\n            .filter_map(|entry| entry.ok()) // Ignore unreadable files, maybe could warn...\n            .filter(|entry| file_has_ext(entry.path(), \".so\"))\n            .filter_map(|e| lib_name_from(e.path()).ok())\n            .collect_vec();\n\n        write_pkg_config_file(pc_file.as_path(), overlay.id.as_str(), &lib_list).with_context(\n            || {\n                format!(\n                    \"Dinghy couldn't generate pkg-config pc file {}\",\n                    pc_file.as_path().display()\n                )\n            },\n        )\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/platform/mod.rs",
    "content": "use crate::utils::{file_name_as_str, LogCommandExt};\nuse crate::Result;\nuse crate::Runnable;\nuse std::fs;\nuse std::process::Command;\n\nuse anyhow::{bail, Context};\nuse log::debug;\n\npub mod regular_platform;\n\npub fn strip_runnable(runnable: &Runnable, mut command: Command) -> Result<Runnable> {\n    let exe_stripped_name = file_name_as_str(&runnable.exe)?;\n\n    let mut stripped_runnable = runnable.clone();\n    stripped_runnable.exe = runnable\n        .exe\n        .parent()\n        .map(|it| it.join(format!(\"{}-stripped\", exe_stripped_name)))\n        .with_context(|| format!(\"{} is not a valid executable name\", &runnable.exe.display()))?;\n\n    // Backup old runnable\n    fs::copy(&runnable.exe, &stripped_runnable.exe)?;\n\n    let command = command.arg(&stripped_runnable.exe);\n    debug!(\"Running command {:?}\", command);\n\n    let output = command.log_invocation(2).output()?;\n    if !output.status.success() {\n        bail!(\n            \"Error while stripping {}\\nError: {}\",\n            &stripped_runnable.exe.display(),\n            String::from_utf8(output.stdout)?\n        )\n    }\n\n    debug!(\n        \"{} unstripped size = {} and stripped size = {}\",\n        runnable.exe.display(),\n        fs::metadata(&runnable.exe)?.len(),\n        fs::metadata(&stripped_runnable.exe)?.len()\n    );\n    Ok(stripped_runnable)\n}\n"
  },
  {
    "path": "dinghy-lib/src/platform/regular_platform.rs",
    "content": "use crate::config::PlatformConfiguration;\nuse crate::overlay::Overlayer;\nuse crate::platform;\nuse crate::project::Project;\nuse crate::toolchain::ToolchainConfig;\nuse crate::Build;\nuse crate::Device;\nuse crate::Platform;\nuse crate::Result;\nuse crate::SetupArgs;\nuse dinghy_build::build_env::set_all_env;\nuse std::fmt::{Debug, Display, Formatter};\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::process::Command;\n\nuse anyhow::anyhow;\nuse fs_err::read_dir;\nuse log::trace;\n\npub struct RegularPlatform {\n    pub configuration: PlatformConfiguration,\n    pub id: String,\n    pub toolchain: ToolchainConfig,\n}\n\nimpl Debug for RegularPlatform {\n    fn fmt(&self, fmt: &mut Formatter) -> ::std::fmt::Result {\n        write!(fmt, \"{}\", self.id)\n    }\n}\n\nimpl RegularPlatform {\n    pub fn new<P: AsRef<Path>>(\n        configuration: PlatformConfiguration,\n        id: String,\n        rustc_triple: String,\n        toolchain_path: P,\n    ) -> Result<Box<dyn Platform>> {\n        if let Some(prefix) = configuration.deb_multiarch.clone() {\n            return Ok(Box::new(RegularPlatform {\n                configuration,\n                id,\n                toolchain: ToolchainConfig {\n                    bin_dir: \"/usr/bin\".into(),\n                    rustc_triple,\n                    root: \"/\".into(),\n                    sysroot: Some(\"/\".into()),\n                    cc: \"gcc\".to_string(),\n                    cxx: \"c++\".to_string(),\n                    binutils_prefix: prefix.clone(),\n                    cc_prefix: prefix.clone(),\n                },\n            }));\n        }\n        let toolchain_path = toolchain_path.as_ref();\n        let toolchain_bin_path = toolchain_path.join(\"bin\");\n\n        let mut bin: Option<PathBuf> = None;\n        let mut prefix: Option<String> = None;\n        for file in read_dir(&toolchain_bin_path)? {\n            let file = file?;\n            if file.file_name().to_string_lossy().ends_with(\"-gcc\")\n                || file.file_name().to_string_lossy().ends_with(\"-gcc.exe\")\n            {\n                bin = Some(toolchain_bin_path);\n                prefix = Some(\n                    file.file_name()\n                        .to_string_lossy()\n                        .replace(\".exe\", \"\")\n                        .replace(\"-gcc\", \"\"),\n                );\n                break;\n            }\n        }\n        let bin_dir = bin.ok_or_else(|| anyhow!(\"no bin/*-gcc found in toolchain\"))?;\n        let tc_triple = prefix\n            .ok_or_else(|| anyhow!(\"no gcc in toolchain\"))?\n            .to_string();\n        let sysroot = find_sysroot(&toolchain_path)?;\n\n        let toolchain = ToolchainConfig {\n            bin_dir,\n            rustc_triple,\n            root: toolchain_path.into(),\n            sysroot,\n            cc: \"gcc\".to_string(),\n            cxx: \"c++\".to_string(),\n            binutils_prefix: tc_triple.clone(),\n            cc_prefix: tc_triple,\n        };\n        Self::new_with_tc(configuration, id, toolchain)\n    }\n\n    pub fn new_with_tc(\n        configuration: PlatformConfiguration,\n        id: String,\n        toolchain: ToolchainConfig,\n    ) -> Result<Box<dyn Platform>> {\n        Ok(Box::new(RegularPlatform {\n            configuration,\n            id,\n            toolchain,\n        }))\n    }\n}\n\nimpl Display for RegularPlatform {\n    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {\n        write!(f, \"{:?}\", self.toolchain.root)\n    }\n}\n\nimpl Platform for RegularPlatform {\n    fn setup_env(&self, project: &Project, setup_args: &SetupArgs) -> Result<()> {\n        // Cleanup environment\n        set_all_env(&[(\"LIBRARY_PATH\", \"\"), (\"LD_LIBRARY_PATH\", \"\")]);\n        // Set custom env variables specific to the platform\n        set_all_env(&self.configuration.env());\n\n        if let Some(sr) = &self.toolchain.sysroot {\n            Overlayer::overlay(&self.configuration, self, project, &sr)?;\n        }\n\n        self.toolchain\n            .setup_cc(&self.id, &self.toolchain.cc_executable(&self.toolchain.cc))?;\n\n        if Path::new(&self.toolchain.binutils_executable(\"ar\")).exists() {\n            self.toolchain\n                .setup_tool(\"AR\", &self.toolchain.binutils_executable(\"ar\"))?;\n        }\n        if Path::new(&self.toolchain.binutils_executable(\"as\")).exists() {\n            self.toolchain\n                .setup_tool(\"AS\", &self.toolchain.binutils_executable(\"as\"))?;\n        }\n        if Path::new(&self.toolchain.cc_executable(&self.toolchain.cxx)).exists() {\n            self.toolchain\n                .setup_tool(\"CXX\", &self.toolchain.cc_executable(&self.toolchain.cxx))?;\n        }\n        if Path::new(&self.toolchain.cc_executable(\"cpp\")).exists() {\n            self.toolchain\n                .setup_tool(\"CPP\", &self.toolchain.cc_executable(\"cpp\"))?;\n        }\n        if Path::new(&self.toolchain.binutils_executable(\"gfortran\")).exists() {\n            self.toolchain\n                .setup_tool(\"FC\", &self.toolchain.binutils_executable(\"gfortran\"))?;\n        }\n        trace!(\"Setup linker...\");\n        self.toolchain.setup_linker(\n            &self.id,\n            &self.toolchain.generate_linker_command(&setup_args),\n            &project.metadata.workspace_root,\n        )?;\n\n        trace!(\"Setup pkg-config\");\n        self.toolchain.setup_pkg_config()?;\n        trace!(\"Setup sysroot...\");\n        self.toolchain.setup_sysroot();\n        trace!(\"Setup shims...\");\n        self.toolchain\n            .shim_executables(&self.id, &project.metadata.workspace_root)?;\n        trace!(\"Setup runner...\");\n        self.toolchain.setup_runner(&self.id, setup_args)?;\n        trace!(\"Setup target...\");\n        self.toolchain.setup_target()?;\n        Ok(())\n    }\n\n    fn id(&self) -> String {\n        self.id.clone()\n    }\n\n    fn is_compatible_with(&self, device: &dyn Device) -> bool {\n        device.is_compatible_with_regular_platform(self)\n    }\n\n    fn is_host(&self) -> bool {\n        false\n    }\n\n    fn rustc_triple(&self) -> &str {\n        &self.toolchain.rustc_triple\n    }\n\n    fn strip(&self, build: &mut Build) -> Result<()> {\n        build.runnable = platform::strip_runnable(\n            &build.runnable,\n            Command::new(self.toolchain.binutils_executable(\"strip\")),\n        )?;\n\n        Ok(())\n    }\n\n    fn sysroot(&self) -> Result<Option<std::path::PathBuf>> {\n        Ok(self.toolchain.sysroot.clone())\n    }\n}\n\nfn find_sysroot<P: AsRef<Path>>(toolchain_path: P) -> Result<Option<PathBuf>> {\n    let toolchain = toolchain_path.as_ref();\n    let immediate = toolchain.join(\"sysroot\");\n    if immediate.is_dir() {\n        let sysroot = immediate\n            .to_str()\n            .ok_or_else(|| anyhow!(\"sysroot is not utf-8\"))?;\n        return Ok(Some(sysroot.into()));\n    }\n    for subdir in toolchain.read_dir()? {\n        let subdir = subdir?;\n        let maybe = subdir.path().join(\"sysroot\");\n        if maybe.is_dir() {\n            let sysroot = maybe\n                .to_str()\n                .ok_or_else(|| anyhow!(\"sysroot is not utf-8\"))?;\n            return Ok(Some(sysroot.into()));\n        }\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "dinghy-lib/src/plugin/mod.rs",
    "content": "use crate::config::{PlatformConfiguration, ScriptDeviceConfiguration, SshDeviceConfiguration};\nuse crate::platform::regular_platform::RegularPlatform;\nuse crate::{Configuration, Device, Platform, PlatformManager};\nuse anyhow::{anyhow, bail, Context, Result};\nuse log::debug;\nuse serde::{Deserialize, Serialize};\nuse std::collections::BTreeMap;\nuse std::os::unix::fs::PermissionsExt;\nuse std::process::Command;\nuse std::sync::Arc;\nuse std::{env, fs};\n\n/// This platform manager will auto-detect any executable in the PATH that starts with\n/// `cargo-dinghy-` and try to use them as a plugin to provide devices and platforms.\n///\n/// To be a valid plugin, an executable must implement the following subcommands:\n/// - `devices`: must output a TOML file with a `DevicePluginOutput` structure\n/// - `platforms`: must output a TOML file with a `BTreeMap<String, PlatformConfiguration>` structure\n///\n/// Here is example of output for a `cargo-dinghy-foo` plugin configuring a `bar` device and a `baz`\n/// platform:\n///\n/// ```no_compile\n/// $ cargo-dinghy-foo devices\n/// [ssh_devices.bar]\n/// hostname = \"127.0.0.1\"\n/// username = \"user\"\n///\n/// $ cargo-dinghy-foo platforms\n/// [baz]\n/// rustc_triple = \"aarch64-unknown-linux-gnu\"\n/// toolchain = \"/path/to/toolchain\"\n/// ```\n/// This is quite useful if you have a bench of devices and platforms that can be auto-detected\n/// or are already configured in another tool.\npub struct PluginManager {\n    conf: Arc<Configuration>,\n    auto_detected_plugins: Vec<String>,\n}\n\nimpl PluginManager {\n    pub fn probe(conf: Arc<Configuration>) -> Option<PluginManager> {\n        let auto_detected_plugins = auto_detect_plugins();\n\n        if auto_detected_plugins.is_empty() {\n            debug!(\"No auto-detected plugins found\");\n            None\n        } else {\n            debug!(\"Auto-detected plugins: {:?}\", auto_detected_plugins);\n            Some(Self {\n                conf,\n                auto_detected_plugins,\n            })\n        }\n    }\n    fn create_script_devices(\n        &self,\n        provider: &String,\n        script_devices: BTreeMap<String, ScriptDeviceConfiguration>,\n    ) -> Vec<Box<dyn Device>> {\n        script_devices\n            .into_iter()\n            .filter_map(|(id, conf)| {\n                if self.conf.script_devices.get(&id).is_none() {\n                    debug!(\"registering script device {id} from {provider}\");\n                    Some(Box::new(crate::script::ScriptDevice { id, conf }) as _)\n                } else {\n                    debug!(\"ignoring script device {id} from {provider} as is was already registered in configuration\");\n                    None\n                }\n            })\n            .collect()\n    }\n\n    fn create_ssh_devices(\n        &self,\n        provider: &String,\n        ssh_devices: BTreeMap<String, SshDeviceConfiguration>,\n    ) -> Vec<Box<dyn Device>> {\n        ssh_devices.into_iter().filter_map(|(id, conf)| {\n            if self.conf.script_devices.get(&id).is_none() {\n                debug!(\"registering ssh device {id} from {provider}\");\n                Some(Box::new(crate::ssh::SshDevice {\n                    id,\n                    conf,\n                }) as _)\n            } else {\n                debug!(\"ignoring ssh device {id} from {provider} as is was already registered in configuration\");\n                None\n            }\n        }).collect()\n    }\n}\n\nimpl PlatformManager for PluginManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        let mut result: Vec<Box<dyn Device>> = vec![];\n\n        self.auto_detected_plugins.iter().for_each(|provider| {\n            match get_devices_from_plugin(provider) {\n                Ok(DevicePluginOutput{script_devices, ssh_devices}) => {\n                    if let Some(script_devices) = script_devices {\n                        result.append(&mut self.create_script_devices(provider, script_devices))\n                    }\n\n                    if let Some(ssh_devices) = ssh_devices {\n                        result.append(&mut self.create_ssh_devices(provider, ssh_devices))\n                    }\n\n                }\n                Err(e) => {\n                    debug!(\n                        \"failed to get devices from auto detected script provider: {provider}, {e:?}\",\n                    );\n                }\n            }\n        });\n\n        Ok(result)\n    }\n\n    fn platforms(&self) -> anyhow::Result<Vec<Box<dyn Platform>>> {\n        let mut script_platforms = BTreeMap::new();\n\n        self.auto_detected_plugins.iter().for_each(\n            |provider| match get_platforms_from_plugin(provider) {\n                Ok(platforms) => {\n                    platforms.into_iter().for_each(|(id, platform)| {\n                        if script_platforms.get(&id).is_none() && self.conf.platforms.get(&id).is_none() {\n                            debug!(\"registering platform {id} from {provider}\");\n                            script_platforms.insert(id.clone(), platform);\n                        } else {\n                            debug!(\n                                \"ignoring platform {id} from plugin {provider} as is was already registered\"\n                            );\n                        }\n                    });\n                }\n                Err(e) => {\n                    debug!(\n                        \"failed to get platforms from auto detected script provider: {provider}, {:?}\",\n                        e\n                    );\n                }\n            },\n        );\n\n        Ok(script_platforms.into_values().collect())\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct DevicePluginOutput {\n    pub ssh_devices: Option<BTreeMap<String, SshDeviceConfiguration>>,\n    pub script_devices: Option<BTreeMap<String, ScriptDeviceConfiguration>>,\n}\n\nfn get_devices_from_plugin(plugin: &str) -> Result<DevicePluginOutput> {\n    let output = Command::new(plugin).arg(\"devices\").output()?;\n\n    if !output.status.success() {\n        bail!(\"failed to get devices from auto detected script provider: {:?}, non success return code\", plugin);\n    }\n\n    Ok(toml::from_str(\n        &String::from_utf8(output.stdout)\n            .with_context(|| format!(\"Failed to parse string output from {plugin} devices\"))?,\n    )\n    .with_context(|| format!(\"Failed to parse toml output from {plugin} devices\"))?)\n}\n\nfn get_platforms_from_plugin(plugin: &str) -> Result<BTreeMap<String, Box<dyn Platform>>> {\n    let output = Command::new(plugin).arg(\"platforms\").output()?;\n\n    if !output.status.success() {\n        bail!(\"failed to get platforms from auto detected script provider: {:?}, non success return code\", plugin);\n    }\n\n    let platform_configs = toml::from_str::<BTreeMap<String, PlatformConfiguration>>(\n        &String::from_utf8(output.stdout)\n            .with_context(|| format!(\"Failed to parse string output from {plugin} platforms\"))?,\n    )\n    .with_context(|| format!(\"Failed to parse toml output from {plugin} platforms\"))?;\n\n    platform_configs\n        .into_iter()\n        .map(|(name, conf)| {\n            let triple = conf\n                .rustc_triple\n                .clone()\n                .ok_or_else(|| anyhow!(\"Platform {name} from {plugin} has no rustc_triple\"))?;\n            let toolchain = conf\n                .toolchain\n                .clone()\n                .ok_or_else(|| anyhow!(\"Toolchain missing for platform {name} from {plugin}\"))?;\n            Ok((\n                name.clone(),\n                RegularPlatform::new(conf, name, triple, toolchain)?,\n            ))\n        })\n        .collect()\n}\n\n// dinghy will auto-detect any executable in the PATH that starts with `cargo-dinghy-` and try to\n// use it as a plugin.\nfn auto_detect_plugins() -> Vec<String> {\n    let mut binaries = Vec::new();\n\n    if let Some(paths) = env::var_os(\"PATH\") {\n        for path in env::split_paths(&paths) {\n            if let Ok(entries) = fs::read_dir(&path) {\n                for entry in entries.filter_map(|e| e.ok()) {\n                    let path = entry.path();\n                    if let Some(file_name) = path.file_name().and_then(|name| name.to_str()) {\n                        if file_name.starts_with(\"cargo-dinghy-\")\n                            && (path.is_file()\n                                && path\n                                    .metadata()\n                                    .map(|m| m.permissions().mode() & 0o111 != 0)\n                                    .unwrap_or(false))\n                        {\n                            binaries.push(file_name.to_string());\n                        }\n                    }\n                }\n            }\n        }\n    }\n    binaries.sort(); // ensure a deterministic order\n    binaries\n}\n"
  },
  {
    "path": "dinghy-lib/src/project.rs",
    "content": "use crate::config::Configuration;\nuse crate::utils::copy_and_sync_file;\nuse crate::Platform;\nuse crate::Result;\nuse crate::Runnable;\nuse anyhow::anyhow;\nuse anyhow::Context;\nuse cargo_metadata::Metadata;\nuse fs_err as fs;\nuse ignore::WalkBuilder;\nuse log::{debug, trace};\nuse std::fs::File;\nuse std::io::prelude::*;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::sync::Arc;\n\n#[derive(Debug)]\npub struct Project {\n    pub conf: Arc<Configuration>,\n    pub metadata: Metadata,\n}\n\nimpl Project {\n    pub fn new(conf: &Arc<Configuration>, metadata: Metadata) -> Project {\n        Project {\n            conf: Arc::clone(conf),\n            metadata,\n        }\n    }\n\n    pub fn project_dir(&self) -> Result<PathBuf> {\n        Ok(self.metadata.workspace_root.clone().into_std_path_buf())\n    }\n\n    pub fn overlay_work_dir(&self, platform: &dyn Platform) -> Result<PathBuf> {\n        Ok(self\n            .target_dir(platform.rustc_triple())?\n            .join(platform.rustc_triple()))\n    }\n\n    pub fn target_dir(&self, triple: &str) -> Result<PathBuf> {\n        Ok(self\n            .metadata\n            .target_directory\n            .clone()\n            .into_std_path_buf()\n            .join(triple))\n    }\n\n    pub fn link_test_data(&self, runnable: &Runnable) -> Result<PathBuf> {\n        let test_data_path = runnable\n            .exe\n            .parent()\n            .and_then(|it| it.parent())\n            .map(|it| it.join(\"dinghy\"))\n            .map(|it| it.join(runnable.exe.file_name().unwrap()))\n            .map(|it| it.join(\"test_data\"))\n            .unwrap();\n\n        fs::create_dir_all(&test_data_path)?;\n        let test_data_cfg_path = test_data_path.join(\"test_data.cfg\");\n        let mut test_data_cfg = File::create(&test_data_cfg_path)?;\n        debug!(\"Generating {}\", test_data_cfg_path.display());\n\n        for td in self.conf.test_data.iter() {\n            let target_path = td\n                .base\n                .parent()\n                .unwrap_or(&PathBuf::from(\"/\"))\n                .join(&td.source);\n            let target_path = target_path\n                .to_str()\n                .ok_or_else(|| anyhow!(\"Invalid UTF-8 path {}\", target_path.display()))?;\n\n            test_data_cfg.write_all(td.id.as_bytes())?;\n            test_data_cfg.write_all(b\":\")?;\n            test_data_cfg.write_all(target_path.as_bytes())?;\n            test_data_cfg.write_all(b\"\\n\")?;\n        }\n        Ok(test_data_path)\n    }\n\n    pub fn copy_test_data<T: AsRef<Path>>(&self, app_path: T) -> Result<()> {\n        let app_path = app_path.as_ref();\n        let test_data_path = app_path.join(\"test_data\");\n        fs::create_dir_all(&test_data_path)?;\n\n        for td in self.conf.test_data.iter() {\n            let file = td\n                .base\n                .parent()\n                .unwrap_or(&PathBuf::from(\"/\"))\n                .join(&td.source);\n            if Path::new(&file).exists() {\n                let metadata = file.metadata()?;\n                let dst = test_data_path.join(&td.id);\n                if metadata.is_dir() {\n                    rec_copy(file, dst, td.copy_git_ignored)?;\n                } else {\n                    copy_and_sync_file(file, dst)?;\n                }\n            } else {\n                log::warn!(\n                    \"configuration required test_data `{:?}` but it could not be found\",\n                    td\n                );\n            }\n        }\n        Ok(())\n    }\n}\n\npub fn rec_copy<P1: AsRef<Path>, P2: AsRef<Path>>(\n    src: P1,\n    dst: P2,\n    copy_ignored_test_data: bool,\n) -> Result<()> {\n    let empty: &[&str] = &[];\n    rec_copy_excl(src, dst, copy_ignored_test_data, empty)\n}\n\npub fn rec_copy_excl<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path> + ::std::fmt::Debug>(\n    src: P1,\n    dst: P2,\n    copy_ignored_test_data: bool,\n    more_exclude: &[P3],\n) -> Result<()> {\n    let src = src.as_ref();\n    let dst = dst.as_ref();\n    let ignore_file = src.join(\".dinghyignore\");\n    debug!(\n        \"Copying recursively from {} to {} excluding {:?}\",\n        src.display(),\n        dst.display(),\n        more_exclude\n    );\n\n    let mut walker = WalkBuilder::new(src);\n    walker.follow_links(true);\n    walker.git_ignore(!copy_ignored_test_data);\n    walker.add_ignore(ignore_file);\n    for entry in walker.build() {\n        let entry = entry?;\n        let metadata = entry.metadata()?;\n\n        if more_exclude.iter().any(|ex| entry.path().starts_with(ex)) {\n            debug!(\"Exclude {:?}\", entry.path());\n            continue;\n        }\n        trace!(\n            \"Processing entry {:?} is_dir:{:?}\",\n            entry.path(),\n            metadata.is_dir()\n        );\n\n        let path = entry.path().strip_prefix(src)?;\n\n        // Check if root path is a file or a directory\n        let target = if path.parent().is_none() && metadata.is_file() {\n            fs::create_dir_all(\n                &dst.parent()\n                    .ok_or_else(|| anyhow!(\"Invalid file {}\", dst.display()))?,\n            )?;\n            dst.to_path_buf()\n        } else {\n            dst.join(path)\n        };\n\n        if metadata.is_dir() {\n            if target.exists() && target.is_file() {\n                fs::remove_file(&target)?;\n            }\n            trace!(\"Creating directory {}\", target.display());\n            fs::create_dir_all(&target)?;\n        } else if metadata.is_file() {\n            if target.exists() && !target.is_file() {\n                trace!(\"Remove 2 {:?}\", target);\n                fs::remove_dir_all(&target)?;\n            }\n            if !target.exists()\n                || target.metadata()?.len() != entry.metadata()?.len()\n                || target.metadata()?.modified()? < entry.metadata()?.modified()?\n            {\n                if target.exists() && target.metadata()?.permissions().readonly() {\n                    fs::remove_dir_all(&target)?;\n                }\n                trace!(\"Copying {} to {}\", entry.path().display(), target.display());\n                copy_and_sync_file(entry.path(), &target)\n                    .with_context(|| format!(\"Syncing {entry:?} and {target:?}\"))?;\n            } else {\n                trace!(\"{} is already up-to-date\", target.display());\n            }\n        } else {\n            debug!(\"ignored {:?} ({:?})\", path, metadata);\n        }\n    }\n    trace!(\n        \"Copied recursively from {} to {} excluding {:?}\",\n        src.display(),\n        dst.display(),\n        more_exclude\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "dinghy-lib/src/script/device.rs",
    "content": "use crate::config::ScriptDeviceConfiguration;\nuse crate::utils::LogCommandExt;\nuse crate::*;\nuse anyhow::bail;\nuse std::{fmt, fs, process};\n\n#[derive(Debug, Clone)]\npub struct ScriptDevice {\n    pub id: String,\n    pub conf: ScriptDeviceConfiguration,\n}\n\nimpl ScriptDevice {\n    fn command(&self, _build: &Build) -> Result<process::Command> {\n        if fs::metadata(&self.conf.path).is_err() {\n            bail!(\"Can not read {:?} for {}.\", self.conf.path, self.id);\n        }\n        let mut cmd = process::Command::new(&self.conf.path);\n        cmd.env(\"DINGHY_TEST_DATA\", &*self.id);\n        cmd.env(\"DINGHY_DEVICE\", &*self.id);\n        if let Some(ref pf) = self.conf.platform {\n            cmd.env(\"DINGHY_PLATFORM\", &*pf);\n        }\n        Ok(cmd)\n    }\n}\n\nimpl Device for ScriptDevice {\n    fn clean_app(&self, _build_bundle: &BuildBundle) -> Result<()> {\n        Ok(())\n    }\n\n    fn debug_app(\n        &self,\n        _project: &Project,\n        _build: &Build,\n        _args: &[&str],\n        _envs: &[&str],\n    ) -> Result<BuildBundle> {\n        unimplemented!()\n    }\n\n    fn id(&self) -> &str {\n        &self.id\n    }\n\n    fn name(&self) -> &str {\n        &self.id\n    }\n\n    fn run_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let root_dir = build.target_path.join(\"dinghy\");\n        let bundle_path = &build.runnable.source;\n\n        log::trace!(\"About to start runner script...\");\n        let test_data_path = project.link_test_data(&build.runnable)?;\n\n        let status = self\n            .command(build)?\n            .arg(&build.runnable.exe)\n            .current_dir(&build.runnable.source)\n            .env(\"DINGHY_TEST_DATA_PATH\", test_data_path)\n            .args(args)\n            .envs(\n                envs.iter()\n                    .map(|kv| {\n                        Ok((\n                            kv.split(\"=\")\n                                .nth(0)\n                                .ok_or_else(|| anyhow!(\"Wrong env spec\"))?,\n                            kv.split(\"=\")\n                                .nth(1)\n                                .ok_or_else(|| anyhow!(\"Wrong env spec\"))?,\n                        ))\n                    })\n                    .collect::<Result<Vec<_>>>()?,\n            )\n            .log_invocation(1)\n            .status()?;\n        if !status.success() {\n            bail!(\"Test failed\")\n        }\n\n        Ok(BuildBundle {\n            id: build.runnable.id.clone(),\n            bundle_dir: bundle_path.to_path_buf(),\n            bundle_exe: build.runnable.exe.to_path_buf(),\n            lib_dir: build.target_path.clone(),\n            root_dir: root_dir.clone(),\n            app_id: None,\n        })\n    }\n}\n\nimpl DeviceCompatibility for ScriptDevice {\n    fn is_compatible_with_regular_platform(&self, platform: &RegularPlatform) -> bool {\n        self.conf\n            .platform\n            .as_ref()\n            .map_or(false, |it| *it == platform.id)\n    }\n}\n\nimpl Display for ScriptDevice {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{}\", self.id)\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/script/mod.rs",
    "content": "use crate::{Configuration, Device, Platform, PlatformManager, Result};\nuse std::sync;\n\nmod device;\n\npub use self::device::ScriptDevice;\n\npub struct ScriptDeviceManager {\n    conf: sync::Arc<Configuration>,\n}\n\nimpl ScriptDeviceManager {\n    pub fn probe(conf: sync::Arc<Configuration>) -> Option<ScriptDeviceManager> {\n        Some(ScriptDeviceManager { conf })\n    }\n}\n\nimpl PlatformManager for ScriptDeviceManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        Ok(self\n            .conf\n            .script_devices\n            .iter()\n            .map(|(k, conf)| {\n                Box::new(ScriptDevice {\n                    id: k.clone(),\n                    conf: conf.clone(),\n                }) as _\n            })\n            .collect())\n    }\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        Ok(vec![])\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/ssh/device.rs",
    "content": "use crate::config::SshDeviceConfiguration;\nuse crate::device::make_remote_app;\nuse crate::errors::*;\nuse crate::host::HostPlatform;\nuse crate::platform::regular_platform::RegularPlatform;\nuse crate::project::Project;\nuse crate::utils::{get_current_verbosity, path_to_str, user_facing_log, LogCommandExt};\nuse crate::Build;\nuse crate::BuildBundle;\nuse crate::Device;\nuse crate::DeviceCompatibility;\nuse std::fmt;\nuse std::fmt::Formatter;\nuse std::fmt::{Debug, Display};\nuse std::io::IsTerminal;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::process::Command;\n\n#[derive(Clone)]\npub struct SshDevice {\n    pub id: String,\n    pub conf: SshDeviceConfiguration,\n}\n\nimpl SshDevice {\n    fn install_app(&self, project: &Project, build: &Build) -> Result<(BuildBundle, BuildBundle)> {\n        user_facing_log(\n            \"Installing\",\n            &format!(\"{} to {}\", build.runnable.id, self.id),\n            0,\n        );\n\n        log::debug!(\"make_remote_app {}\", build.runnable.id);\n        let build_bundle = make_remote_app(project, build)?;\n\n        log::trace!(\"make_remote_app {} done\", build.runnable.id);\n        let remote_bundle = self.to_remote_bundle(&build_bundle)?;\n        log::trace!(\"Create remote dir: {:?}\", remote_bundle.bundle_dir);\n\n        let _ = self\n            .ssh_command()?\n            .arg(\"mkdir\")\n            .arg(\"-p\")\n            .arg(&remote_bundle.bundle_dir)\n            .log_invocation(2)\n            .status();\n\n        log::info!(\"Install {} to {}\", build.runnable.id, self.id);\n        self.sync(&build_bundle.bundle_dir, &remote_bundle.bundle_dir)?;\n        self.sync(&build_bundle.lib_dir, &remote_bundle.lib_dir)?;\n        Ok((build_bundle, remote_bundle))\n    }\n\n    fn ssh_command(&self) -> Result<Command> {\n        let mut command = Command::new(\"ssh\");\n        if let Some(port) = self.conf.port {\n            command.arg(\"-p\").arg(&format!(\"{}\", port));\n        }\n        if std::io::stdout().is_terminal() {\n            command.arg(\"-t\").arg(\"-o\").arg(\"LogLevel=QUIET\");\n        }\n        command.arg(format!(\"{}@{}\", self.conf.username, self.conf.hostname));\n        Ok(command)\n    }\n\n    fn sync_rsync(&self) -> Result<String> {\n        match &self.conf.install_adhoc_rsync_local_path {\n            Some(rsync) => {\n                let rsync_path = PathBuf::from(self.conf.path.clone().unwrap_or(\"/tmp\".into()))\n                    .join(\"dinghy\")\n                    .join(\"rsync\");\n                let rsync_path = rsync_path\n                    .to_str()\n                    .ok_or_else(|| anyhow!(\"Could not format rsync remote path\"))?;\n\n                if self\n                    .ssh_command()?\n                    .arg(\"[\")\n                    .arg(\"-f\")\n                    .arg(rsync_path)\n                    .arg(\"]\")\n                    .log_invocation(2)\n                    .status()?\n                    .success()\n                {\n                    log::debug!(\"ad-hoc rsync already present on device, skipping copy\")\n                } else {\n                    let mut command = Command::new(\"scp\");\n                    if let Some(true) = self.conf.use_legacy_scp_protocol_for_adhoc_rsync_copy {\n                        command.arg(\"-O\");\n                    }\n                    command.arg(\"-q\");\n                    if let Some(port) = self.conf.port {\n                        command.arg(\"-P\").arg(&format!(\"{}\", port));\n                    }\n                    command.arg(format!(\"{}\", rsync));\n                    command.arg(format!(\n                        \"{}@{}:{}\",\n                        self.conf.username, self.conf.hostname, rsync_path\n                    ));\n                    log::debug!(\"Running {:?}\", command);\n                    if !command.log_invocation(3).status()?.success() {\n                        bail!(\"Error copying rsync binary ({:?})\", command)\n                    }\n                }\n                Ok(rsync_path.to_string())\n            }\n            None => Ok(\"/usr/bin/rsync\".to_string()),\n        }\n    }\n\n    fn sync<FP: AsRef<Path>, TP: AsRef<Path>>(&self, from_path: FP, to_path: TP) -> Result<()> {\n        let rsync = self.sync_rsync();\n        let rsync = match rsync {\n            Ok(rsync_path) => rsync_path,\n            Err(error) => bail!(\"Problem with rsync on the target: {:?}\", error),\n        };\n        let mut command = Command::new(\"rsync\");\n        command.arg(&format!(\"--rsync-path={}\", rsync));\n        command.arg(\"-a\").arg(\"-v\");\n        if let Some(port) = self.conf.port {\n            command.arg(\"-e\").arg(&*format!(\"ssh -p {}\", port));\n        };\n        if !log::log_enabled!(::log::Level::Debug) {\n            command.stdout(::std::process::Stdio::null());\n            command.stderr(::std::process::Stdio::null());\n        }\n        command\n            .arg(&format!(\"{}/\", path_to_str(&from_path.as_ref())?))\n            .arg(&format!(\n                \"{}@{}:{}/\",\n                self.conf.username,\n                self.conf.hostname,\n                path_to_str(&to_path.as_ref())?\n            ));\n        log::debug!(\"Running {:?}\", command);\n        if !command\n            .log_invocation(1)\n            .status()\n            .with_context(|| format!(\"failed to run '{:?}'\", command))?\n            .success()\n        {\n            bail!(\"Error syncing ssh directory ({:?})\", command)\n        } else {\n            Ok(())\n        }\n    }\n\n    fn to_remote_bundle(&self, build_bundle: &BuildBundle) -> Result<BuildBundle> {\n        let remote_prefix =\n            PathBuf::from(self.conf.path.clone().unwrap_or(\"/tmp\".into())).join(\"dinghy\");\n        build_bundle.replace_prefix_with(remote_prefix)\n    }\n}\n\nimpl DeviceCompatibility for SshDevice {\n    fn is_compatible_with_regular_platform(&self, platform: &RegularPlatform) -> bool {\n        self.conf\n            .platform\n            .as_ref()\n            .map_or(false, |it| *it == platform.id)\n    }\n\n    fn is_compatible_with_host_platform(&self, platform: &HostPlatform) -> bool {\n        self.conf\n            .platform\n            .as_ref()\n            .map_or(true, |it| *it == platform.id)\n    }\n}\n\nimpl Device for SshDevice {\n    fn clean_app(&self, build_bundle: &BuildBundle) -> Result<()> {\n        let status = self\n            .ssh_command()?\n            .arg(&format!(\n                \"rm -rf {}\",\n                path_to_str(&build_bundle.bundle_exe)?\n            ))\n            .log_invocation(1)\n            .status()?;\n        if !status.success() {\n            bail!(\"test fail.\")\n        }\n        Ok(())\n    }\n\n    fn debug_app(\n        &self,\n        _project: &Project,\n        _build: &Build,\n        _args: &[&str],\n        _envs: &[&str],\n    ) -> Result<BuildBundle> {\n        unimplemented!()\n    }\n\n    fn id(&self) -> &str {\n        &self.id\n    }\n\n    fn name(&self) -> &str {\n        &self.id\n    }\n\n    fn run_app(\n        &self,\n        project: &Project,\n        build: &Build,\n        args: &[&str],\n        envs: &[&str],\n    ) -> Result<BuildBundle> {\n        let remote_shell_vars_as_context = |a: &str| -> Option<std::borrow::Cow<str>> {\n            self.conf.remote_shell_vars.get(a).map(|s| s.into())\n        };\n        let args: Vec<String> = args\n            .iter()\n            .map(|&a| {\n                shellexpand::full_with_context_no_errors(\n                    a,\n                    || remote_shell_vars_as_context(\"HOME\"),\n                    remote_shell_vars_as_context,\n                )\n            })\n            .map(|a| ::shell_escape::escape(a).to_string())\n            .collect();\n        log::info!(\"Install {:?}\", build.runnable.id);\n        let (build_bundle, remote_bundle) = self.install_app(&project, &build)?;\n        log::debug!(\"Installed {:?}\", build.runnable.id);\n        let command = format!(\n            \"cd '{}' ; RUST_BACKTRACE=1 {} DINGHY=1 LD_LIBRARY_PATH=\\\"{}:$LD_LIBRARY_PATH\\\" {} {}\",\n            path_to_str(&remote_bundle.bundle_dir)?,\n            envs.join(\" \"),\n            path_to_str(&remote_bundle.lib_dir)?,\n            path_to_str(&remote_bundle.bundle_exe)?,\n            args.join(\" \")\n        );\n        log::trace!(\"Ssh command: {}\", command);\n        log::info!(\"Run {} on {}\", build.runnable.id, self.id,);\n        if get_current_verbosity() < 1 {\n            // we log the full command for verbosity > 1, just log a short message when the user\n            // didn't ask for verbose output\n            user_facing_log(\n                \"Running\",\n                &format!(\"{} on {}\", build.runnable.id, self.id),\n                0,\n            );\n        }\n\n        let status = self\n            .ssh_command()?\n            .arg(&command)\n            .log_invocation(1)\n            .status()?;\n        if !status.success() {\n            bail!(\"Failed\")\n        }\n\n        Ok(build_bundle)\n    }\n}\n\nimpl Debug for SshDevice {\n    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {\n        Ok(fmt.write_str(format!(\"Ssh {{ \\\"id\\\": \\\"{}\\\", \\\"hostname\\\": \\\"{}\\\", \\\"username\\\": \\\"{}\\\", \\\"port\\\": \\\"{}\\\" }}\",\n                                     self.id,\n                                     self.conf.hostname,\n                                     self.conf.username,\n                                     self.conf.port.as_ref().map_or(\"none\".to_string(), |it| it.to_string())).as_str())?)\n    }\n}\n\nimpl Display for SshDevice {\n    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {\n        write!(fmt, \"{} ({})\", self.id, self.conf.hostname)\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/ssh/mod.rs",
    "content": "mod device;\nuse crate::{Configuration, Device, Platform, PlatformManager, Result};\nuse std::sync;\n\npub use self::device::SshDevice;\n\npub struct SshDeviceManager {\n    conf: sync::Arc<Configuration>,\n}\n\nimpl SshDeviceManager {\n    pub fn probe(conf: sync::Arc<Configuration>) -> Option<SshDeviceManager> {\n        Some(SshDeviceManager { conf })\n    }\n}\n\nimpl PlatformManager for SshDeviceManager {\n    fn devices(&self) -> Result<Vec<Box<dyn Device>>> {\n        Ok(self\n            .conf\n            .ssh_devices\n            .iter()\n            .map(|(k, conf)| {\n                Box::new(SshDevice {\n                    id: k.clone(),\n                    conf: conf.clone(),\n                }) as _\n            })\n            .collect())\n    }\n    fn platforms(&self) -> Result<Vec<Box<dyn Platform>>> {\n        Ok(vec![])\n    }\n}\n"
  },
  {
    "path": "dinghy-lib/src/toolchain.rs",
    "content": "use crate::errors::*;\nuse crate::SetupArgs;\nuse dinghy_build::build_env::append_path_to_env;\nuse dinghy_build::build_env::append_path_to_target_env;\nuse dinghy_build::build_env::envify;\nuse dinghy_build::build_env::set_env;\nuse dinghy_build::build_env::set_target_env;\nuse itertools::Itertools;\nuse std::io::Write;\n#[cfg(unix)]\nuse std::os::unix::fs::PermissionsExt;\nuse std::path::PathBuf;\nuse std::{fs, path};\nuse walkdir::WalkDir;\n\n#[cfg(not(target_os = \"windows\"))]\nstatic GLOB_ARGS: &str = r#\"\"$@\"\"#;\n#[cfg(target_os = \"windows\")]\nstatic GLOB_ARGS: &str = r#\"%*\"#;\n\n#[derive(Clone, Debug)]\npub struct Toolchain {\n    pub rustc_triple: String,\n}\n\nimpl Toolchain {\n    pub fn setup_tool(&self, var: &str, exe: &str) -> Result<()> {\n        set_env(format!(\"TARGET_{}\", var), exe);\n        set_env(format!(\"{}_{}\", var, self.rustc_triple), exe);\n        Ok(())\n    }\n\n    pub fn setup_cc(&self, _id: &str, compiler_command: &str) -> Result<()> {\n        set_env(\"TARGET_CC\", compiler_command);\n        set_env(format!(\"CC_{}\", self.rustc_triple), compiler_command);\n        Ok(())\n    }\n\n    pub fn setup_linker<P: AsRef<path::Path>>(\n        &self,\n        id: &str,\n        linker_command: &str,\n        workspace_root: P,\n    ) -> Result<()> {\n        let shim = create_shim(\n            workspace_root,\n            &self.rustc_triple,\n            id,\n            \"linker\",\n            format!(\"{} {}\", linker_command, GLOB_ARGS).as_str(),\n        )?;\n        set_env(\n            format!(\"CARGO_TARGET_{}_LINKER\", envify(self.rustc_triple.as_str())).as_str(),\n            shim,\n        );\n        Ok(())\n    }\n\n    pub fn setup_pkg_config(&self) -> Result<()> {\n        set_env(\"PKG_CONFIG_ALLOW_CROSS\", \"1\");\n        set_target_env(\"PKG_CONFIG_LIBPATH\", Some(&self.rustc_triple), \"\");\n        Ok(())\n    }\n\n    pub fn setup_runner(&self, platform_id: &str, setup_args: &SetupArgs) -> Result<()> {\n        set_env(\n            format!(\"CARGO_TARGET_{}_RUNNER\", envify(self.rustc_triple.as_str())).as_str(),\n            setup_args.get_runner_command(platform_id),\n        );\n        Ok(())\n    }\n\n    pub fn setup_target(&self) -> Result<()> {\n        set_env(\"CARGO_BUILD_TARGET\", &self.rustc_triple);\n        Ok(())\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct ToolchainConfig {\n    pub bin_dir: PathBuf,\n    pub root: PathBuf,\n    pub rustc_triple: String,\n    pub sysroot: Option<PathBuf>,\n    pub cc: String,\n    pub cxx: String,\n    pub binutils_prefix: String,\n    pub cc_prefix: String,\n}\n\nimpl ToolchainConfig {\n    pub fn cc_executable(&self, name_without_triple: &str) -> String {\n        self.bin_dir\n            .join(format!(\"{}-{}\", self.cc_prefix, name_without_triple))\n            .to_string_lossy()\n            .to_string()\n    }\n\n    pub fn binutils_executable(&self, name_without_triple: &str) -> String {\n        self.bin_dir\n            .join(format!(\"{}-{}\", self.binutils_prefix, name_without_triple))\n            .to_string_lossy()\n            .to_string()\n    }\n\n    pub fn naked_executable(&self, name: &str) -> String {\n        self.bin_dir.join(name).to_string_lossy().to_string()\n    }\n\n    pub fn setup_pkg_config(&self) -> Result<()> {\n        self.as_toolchain().setup_pkg_config()?;\n\n        if self.root.parent().is_some() {\n            append_path_to_target_env(\n                \"PKG_CONFIG_LIBDIR\",\n                Some(&self.rustc_triple),\n                WalkDir::new(self.root.to_string_lossy().as_ref())\n                    .into_iter()\n                    .filter_map(|e| e.ok()) // Ignore unreadable files, maybe could warn...\n                    .filter(|e| e.file_name() == \"pkgconfig\" && e.file_type().is_dir())\n                    .map(|e| e.path().to_string_lossy().into_owned())\n                    .join(\":\"),\n            );\n        }\n\n        if let Some(sr) = &self.sysroot {\n            set_target_env(\"PKG_CONFIG_SYSROOT_DIR\", Some(&self.rustc_triple), &sr);\n        }\n        Ok(())\n    }\n\n    pub fn setup_sysroot(&self) {\n        if let Some(sr) = &self.sysroot {\n            set_env(\"TARGET_SYSROOT\", sr);\n        }\n    }\n\n    pub fn setup_tool(&self, var: &str, command: &str) -> Result<()> {\n        self.as_toolchain().setup_tool(var, command)\n    }\n\n    pub fn setup_cc(&self, id: &str, compiler_command: &str) -> Result<()> {\n        self.as_toolchain().setup_cc(id, compiler_command)\n    }\n\n    pub fn generate_linker_command(&self, setup_args: &SetupArgs) -> String {\n        let mut linker_cmd = self.cc_executable(&*self.cc);\n        linker_cmd.push_str(\" \");\n        if setup_args.verbosity > 0 {\n            linker_cmd.push_str(\"-Wl,--verbose -v\")\n        }\n        if let Some(sr) = &self.sysroot {\n            linker_cmd.push_str(&format!(\" --sysroot {}\", sr.display()));\n        }\n        for forced_overlay in &setup_args.forced_overlays {\n            linker_cmd.push_str(\" -l\");\n            linker_cmd.push_str(&forced_overlay);\n            // TODO Add -L\n        }\n\n        linker_cmd\n    }\n\n    pub fn setup_linker<P: AsRef<path::Path>>(\n        &self,\n        id: &str,\n        linker_command: &str,\n        workspace_root: P,\n    ) -> Result<()> {\n        self.as_toolchain()\n            .setup_linker(id, linker_command, workspace_root)\n    }\n\n    pub fn setup_runner(&self, platform_id: &str, setup_args: &SetupArgs) -> Result<()> {\n        self.as_toolchain().setup_runner(platform_id, setup_args)\n    }\n\n    pub fn setup_target(&self) -> Result<()> {\n        self.as_toolchain().setup_target()\n    }\n\n    pub fn shim_executables<P: AsRef<path::Path>>(\n        &self,\n        id: &str,\n        workspace_root: P,\n    ) -> Result<()> {\n        let workspace_root = workspace_root.as_ref();\n        let shims_path = workspace_root\n            .join(\"target\")\n            .join(&self.rustc_triple)\n            .join(id);\n\n        for exe in self.bin_dir.read_dir()? {\n            let exe = exe?;\n            let exe_file_name = exe.file_name();\n            let exe_path = exe.path();\n            let exe_path = exe_path.to_string_lossy();\n\n            let rustified_exe = &exe_file_name\n                .to_string_lossy()\n                .replace(self.binutils_prefix.as_str(), self.rustc_triple.as_str())\n                .replace(self.cc_prefix.as_str(), self.rustc_triple.as_str());\n            log::trace!(\"Shim {} -> {}\", exe_path, rustified_exe);\n            create_shim(\n                workspace_root,\n                self.rustc_triple.as_str(),\n                id,\n                rustified_exe,\n                &format!(\"{} {}\", exe_path, GLOB_ARGS),\n            )?;\n        }\n        append_path_to_env(\"PATH\", shims_path.to_string_lossy().as_ref());\n        Ok(())\n    }\n\n    fn as_toolchain(&self) -> Toolchain {\n        Toolchain {\n            rustc_triple: self.rustc_triple.clone(),\n        }\n    }\n}\n\nfn create_shim<P: AsRef<path::Path>>(\n    root: P,\n    rustc_triple: &str,\n    id: &str,\n    name: &str,\n    shell: &str,\n) -> Result<PathBuf> {\n    let target_shim_path = root.as_ref().join(\"target\").join(rustc_triple).join(id);\n    fs::create_dir_all(&target_shim_path)?;\n    let mut shim = target_shim_path.join(name);\n    if cfg!(target_os = \"windows\") {\n        shim.set_extension(\"bat\");\n    };\n    let mut linker_shim = fs::File::create(&shim)?;\n    if !cfg!(target_os = \"windows\") {\n        writeln!(linker_shim, \"#!/bin/sh\")?;\n    }\n    linker_shim.write_all(shell.as_bytes())?;\n    writeln!(linker_shim, \"\\n\")?;\n    #[cfg(unix)]\n    fs::set_permissions(&shim, PermissionsExt::from_mode(0o777))?;\n    Ok(shim)\n}\n"
  },
  {
    "path": "dinghy-lib/src/utils.rs",
    "content": "use crate::errors::Result;\nuse anyhow::{anyhow, bail};\nuse filetime::set_file_times;\nuse filetime::FileTime;\nuse fs_err as fs;\nuse lazy_static::lazy_static;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::sync::atomic::{AtomicI8, Ordering};\n\npub fn copy_and_sync_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {\n    let from = &from.as_ref();\n    let to = &to.as_ref();\n\n    if !from.exists() {\n        bail!(\"Source {from:?} is missing\")\n    }\n\n    if !to.parent().unwrap().exists() {\n        bail!(\"Target directory is missing\")\n    }\n\n    // Make target file writeable if it is read-only.\n    if to.exists() {\n        let mut permissions = fs::metadata(&to)?.permissions();\n        if permissions.readonly() {\n            permissions.set_readonly(false);\n            fs::set_permissions(&to, permissions)?;\n        }\n    }\n\n    log::trace!(\"copy {:?} to {:?}\", from, to);\n    fs::copy(&from, &to)?;\n\n    // Keep filetime to avoid useless sync on some devices (e.g. Android).\n    let from_metadata = from.metadata()?;\n    let atime = FileTime::from_last_access_time(&from_metadata);\n    let mtime = FileTime::from_last_modification_time(&from_metadata);\n    set_file_times(&to, atime, mtime)?;\n\n    Ok(())\n}\n\npub fn path_to_str<'a>(path: &'a Path) -> Result<&'a str> {\n    Ok(path\n        .to_str()\n        .ok_or_else(|| anyhow!(\"Path is invalid '{}'\", path.display()))?)\n}\n\npub fn normalize_path(path: &Path) -> PathBuf {\n    PathBuf::from(path.to_string_lossy().replace(\"\\\\\", \"/\"))\n}\n\npub fn contains_file_with_ext(dir_path: &Path, ext: &str) -> bool {\n    if !dir_path.is_dir() {\n        return false;\n    };\n    if let Ok(path) = dir_path.read_dir() {\n        for file in path {\n            if let Ok(file) = file {\n                if file.file_name().to_string_lossy().ends_with(ext) {\n                    return true;\n                }\n            }\n        }\n    }\n    false\n}\n\npub fn destructure_path<P: AsRef<Path>>(path: P) -> Option<(PathBuf, String)> {\n    let path = path.as_ref();\n    path.file_name()\n        .and_then(|it| it.to_str())\n        .map(|name| (path.to_path_buf(), name.to_string()))\n}\n\npub fn file_has_ext(file_path: &Path, ext: &str) -> bool {\n    file_path.is_file()\n        && file_path\n            .file_name()\n            .and_then(|it| it.to_str())\n            .map(|it| it.ends_with(ext))\n            .unwrap_or(false)\n}\n\npub fn is_library(file_path: &Path) -> bool {\n    file_path.is_file()\n        && file_path\n            .file_name()\n            .and_then(|it| it.to_str())\n            .map(|it| {\n                it.ends_with(\".so\")\n                    || it.contains(\".so.\")\n                    || it.ends_with(\".dylib\")\n                    || it.ends_with(\".a\")\n            })\n            .unwrap_or(false)\n}\n\npub fn lib_name_from(file_path: &Path) -> Result<String> {\n    let file_name = file_path\n        .file_name()\n        .and_then(|it| it.to_str())\n        .ok_or_else(|| {\n            anyhow!(\n                \"'{}' doesn't point to a valid lib name\",\n                file_path.display()\n            )\n        })?;\n\n    let (start_index, end_index) = file_name\n        .find(\".so\")\n        .map(|end_index| (if file_name.starts_with(\"lib\") { 3 } else { 0 }, end_index))\n        .unwrap_or((0, file_name.len()));\n\n    if start_index == end_index {\n        bail!(\n            \"'{}' doesn't point to a valid lib name\",\n            file_path.display()\n        );\n    } else {\n        Ok(file_name[start_index..end_index].to_string())\n    }\n}\n\npub fn file_name_as_str(file_path: &Path) -> Result<&str> {\n    Ok(file_path\n        .file_name()\n        .and_then(|it| it.to_str())\n        .ok_or_else(|| anyhow!(\"'{}' is not a valid file name\", file_path.display()))?)\n}\n\nlazy_static! {\n    static ref CURRENT_VERBOSITY: AtomicI8 = AtomicI8::new(0);\n}\n\npub fn set_current_verbosity(verbosity: i8) {\n    CURRENT_VERBOSITY.store(verbosity, Ordering::SeqCst)\n}\npub fn get_current_verbosity() -> i8 {\n    CURRENT_VERBOSITY.load(Ordering::SeqCst)\n}\n\npub fn user_facing_log(category: &str, message: &str, verbosity: i8) {\n    use colored::Colorize;\n    if verbosity <= get_current_verbosity() {\n        eprintln!(\"{:>12} {}\", category.blue().bold(), message)\n    }\n}\n\npub trait LogCommandExt {\n    fn log_invocation(&mut self, verbosity: i8) -> &mut Self;\n}\n\nimpl LogCommandExt for Command {\n    fn log_invocation(&mut self, verbosity: i8) -> &mut Self {\n        user_facing_log(\n            \"Running\",\n            &format!(\n                \"{}{:?}\",\n                if verbosity + 1 < get_current_verbosity() {\n                    self.get_envs()\n                        .map(|(var_name, var_value)| {\n                            format!(\n                                \"{}={:?} \",\n                                var_name.to_str().unwrap(),\n                                var_value.and_then(|it| it.to_str()).unwrap_or(\"\")\n                            )\n                        })\n                        .fold(String::new(), |mut result, env| {\n                            result.push_str(&env);\n                            result\n                        })\n                } else {\n                    String::new()\n                },\n                self\n            ),\n            verbosity,\n        );\n        self\n    }\n}\n"
  },
  {
    "path": "dinghy-test/Cargo.toml",
    "content": "[package]\nname = \"dinghy-test\"\nversion = \"0.8.5-pre\"\nauthors = [\"Mathieu Poumeyrol <mathieu.poumeyrol@snips.ai>\"]\nlicense = \"MIT/Apache-2.0\"\ndescription = \"Cross-compilation made easier - see main crate cargo-dinghy\"\nhomepage = \"https://medium.com/snips-ai/dinghy-painless-rust-tests-and-benches-on-ios-and-android-c9f94f81d305#.c2sx7two8\"\nrepository = \"https://github.com/sonos/dinghy\"\nkeywords = [\n    \"tests\", \"mobile\", \"ios\", \"android\", \"cargo\"\n]\ncategories = [ \"development-tools::cargo-plugins\", \"development-tools::testing\" , \"development-tools::profiling\" ]\nreadme = \"../README.md\"\nedition = \"2018\"\n"
  },
  {
    "path": "dinghy-test/src/lib.rs",
    "content": "use std::env;\nuse std::fs::File;\nuse std::io::prelude::*;\nuse std::path::PathBuf;\n\npub fn test_project_path() -> PathBuf {\n    if cfg!(any(\n        target_os = \"ios\",\n        target_os = \"watchos\",\n        target_os = \"tvos\",\n        target_os = \"android\"\n    )) || env::var(\"DINGHY\").is_ok()\n    {\n        let current_exe = env::current_exe().expect(\"Current exe path not accessible\");\n\n        current_exe\n            .parent()\n            .expect(&format!(\n                \"Current exe path is invalid {}\",\n                current_exe.display()\n            ))\n            .into()\n    } else {\n        PathBuf::from(\".\")\n    }\n}\n\npub fn test_file_path(test_data_id: &str) -> PathBuf {\n    try_test_file_path(test_data_id).expect(&format!(\"Couldn't find test data {}\", test_data_id))\n}\n\npub fn try_test_file_path(test_data_id: &str) -> Option<PathBuf> {\n    let current_exe = env::current_exe().expect(\"Current exe path not accessible\");\n\n    if cfg!(any(\n        target_os = \"ios\",\n        target_os = \"watchos\",\n        target_os = \"tvos\",\n        target_os = \"android\"\n    )) || env::var(\"DINGHY\").is_ok()\n    {\n        current_exe\n            .parent()\n            .map(|it| it.join(\"test_data\"))\n            .map(|it| it.join(test_data_id))\n    } else {\n        let test_data_path = current_exe\n            .parent()\n            .and_then(|it| it.parent())\n            .map(|it| it.join(\"dinghy\"))\n            .map(|it| it.join(current_exe.file_name().unwrap()))\n            .map(|it| it.join(\"test_data\"));\n        let test_data_path = match test_data_path {\n            None => return None,\n            Some(test_data_cfg_path) => test_data_cfg_path,\n        };\n\n        let test_data_cfg_path = test_data_path.join(\"test_data.cfg\");\n\n        let mut contents = String::new();\n        let test_data_cfg =\n            File::open(&test_data_cfg_path).and_then(|mut f| f.read_to_string(&mut contents));\n        if let Err(_) = test_data_cfg {\n            return None;\n        }\n\n        contents\n            .lines()\n            .map(|line| line.split(\":\"))\n            .map(|mut line| (line.next(), line.next()))\n            .find(|&(id, _)| id.map(|it| it == test_data_id).unwrap_or(false))\n            .and_then(|(_, path)| path)\n            .map(PathBuf::from)\n    }\n}\n"
  },
  {
    "path": "docs/android.md",
    "content": "## Getting started - Android phone\n\n### Dinghy setup\n\nAssuming [rustup](http://rustup.rs) is already installed...\n\n```\ncargo install cargo-dinghy\n\n# If it's already installed, add '--force'\ncargo install cargo-dinghy --force\n```\n\n### ADB\n\n`adb` must be in your $PATH and your phone must have debugging enabled.\nenabled. See [adb doc](https://developer.android.com/studio/command-line/adb.html) .\n`adb devices -l` must show your phone like that when you connect it.\n\n```\n% adb devices -l\nList of devices attached\n3100b123456789       device usb:341966848X product:a3xeltexx model:SM_A310F device:a3xelte\n```\n\nNow dinghy should also \"see\" your phone:\n\n```\n% cargo dinghy all-devices\nList of available devices for all platforms:\nHost { }\nAndroid { \"id\": \"3100b123456789\", \"supported_targets\": [\"armv7-linux-androideabi\", \"arm-linux-androideabi\"] }\n```\n\n### Android NDK\n\nStarting NDK version r19, it is possible to use the NDK directly without building a standalone Android toolchain. It is the new recommended way to build Android binaries using Dinghy.\n\nFirst, download the [Android NDK](https://developer.android.com/ndk/downloads) or install the Android SDK.\n\nIf you downloaded the NDK, make sure to set the environment variable `ANDROID_NDK_HOME` to point to the extracted NDK folder. If you installed the SDK, Dinghy should detect it automatically.\n\nIf you did everything correctly, Dinghy should be able to recognize a large quantities of platforms. The following is an example of what you should see :\n\n```\n% cargo dinghy all-platforms\n* auto-android-aarch64 (aarch64-linux-android)\n* auto-android-aarch64-api21 (aarch64-linux-android)\n[...]\n* auto-android-aarch64-api28 (aarch64-linux-android)\n* auto-android-aarch64-latest (aarch64-linux-android)\n* auto-android-aarch64-min (aarch64-linux-android)\n* auto-android-armv7 (armv7-linux-androideabi)\n* auto-android-armv7-api16 (armv7-linux-androideabi)\n[...]\n* auto-android-armv7-api28 (armv7-linux-androideabi)\n* auto-android-armv7-latest (armv7-linux-androideabi)\n* auto-android-armv7-min (armv7-linux-androideabi)\n* auto-android-i686 (i686-linux-android)\n* auto-android-i686-api16 (i686-linux-android)\n[...]\n* auto-android-i686-api28 (i686-linux-android)\n* auto-android-i686-latest (i686-linux-android)\n* auto-android-i686-min (i686-linux-android)\n* auto-android-x86_64 (x86_64-linux-android)\n* auto-android-x86_64-api21 (x86_64-linux-android)\n[...]\n* auto-android-x86_64-api28 (x86_64-linux-android)\n* auto-android-x86_64-latest (x86_64-linux-android)\n* auto-android-x86_64-min (x86_64-linux-android)\n* host\n```\n\nAs you can notice, there is a huge amount of new platforms that have appeared as `auto-android-[ARCHITECTURE]`. You can use those new platforms to build your binaries using the architecture you want but also the Android API level you want to use. You can explicitly build by providing the API level you want or you can ask either the earliest API level available or the latest API level using the aliases `min` and `latest`. You can also use the default API level which will default for the API level 21, which is the first common API level with 64 bits based architecture and 32 bits based architecture.\n\nIf you get all the platforms, your NDK is set up. To finish your setup, you should [install the appropriate Rust target](#rust-target).\n\n### Android standalone toolchain\n\nBefore Android NDK version r19, Dinghy couldn't use the NDK directly, so you had to set up a standalone toolchain for your phone architecture: dinghy gave you the possible ones, (`arm` or `armv7` here). It is still possible to use that procedure, but the recommended procedure is now to use the Android NDK directly. Here are the steps to achieve:\n\n* [download and install Android NDK](https://developer.android.com/ndk/downloads/index.html) (please note that only r18 and previous are working to generate a standalone toolchain)\n* [build a stand-alone Android toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html#creating_the_toolchain) matching your phone architecture\n\nNote that `arm64` and `aarch64` are two names to the same architecture.\n\nAfter you generated your toolchain, you'll need to declare the platform in `~/.dinghy.toml`. It links everything together: the rustc target and your android toolchain.\n\n```\n[platforms.android-armv7]\nrustc_triple=\"armv7-linux-androideabi\"\ntoolchain=\"<path to your armv7-linux-androideabi standalone toolchain>\"\n```\n\n### Rust target\n\nAfter you set up the NDK or the standalone toolchain, you may need to ask rustup to install the relevant target.\n\n```\nrustup target install armv7-linux-androideabi\n```\n\nARMv7 is the most likely architecture for Android devices. However, maybe yours is one of these (two last are very unlikely):\n\n```\nrustup target install arm-linux-androideabi\nrustup target install aarch64-linux-android\nrustup target install i686-linux-android\nrustup target install x86_64-linux-android\n```\n\n### Try it\n\nLet's try it with the Dinghy demo project. The project tests with \"pass\" in the name is supposed to pass, the one with fail should break.\n\n```\n% git clone https://github.com/sonos/dinghy\n% cd dinghy/test-ws\n[...]\n# these ones should pass\n% cargo dinghy -d android test pass\n[...]\n# this one shall not pass\n% cargo dinghy -d android test fail\n[...]\n```\n\nThat's it! Enjoy!\n"
  },
  {
    "path": "docs/dinghy-build.md",
    "content": "## Build script helpers [WIP]\n\nDinghy also provides a dinghy-helper crate to help with the writing of build scripts that performs cross-compilation, and more specifically:\n\n- CommandExt: a std::process::Command extension that can:\n  - Setup pkg-config environment variables for a subprocess (`PKG_CONFIG_LIBDIR`, ... e.g. when running Automake `./configure`)\n  - Setup toolchain environment variables for a subprocess (`TARGET_CC`, ...)\n  - A few other useful methods (e.g. `configure_prefix()` to set the prefix to rust output dir when running Automake `./configure`)\n- BindGenBuilderExt: a bindgen::Builder extension to help writing C to rust bindings that supports cross compilation properly (see `new_bindgen_with_cross_compilation_support()`).\n\n*This is still a WIP. Beware of possible breaking changes*\n\n\n"
  },
  {
    "path": "docs/files.md",
    "content": "## Sending project files to the devices\n\nSome tests are relying on the presence of files at relative paths to be able\nto proceed. But we can not always control where we will be executing from (we\ncan not always do `cd someplace` before running the tests).\n\nSo, the tests are \"bundled\" in the following way:\n\n* root dinghy test directory\n    * test_executable\n    * recursive copy of the not-ignorable files and directories from your projects\n    * test_data contains configurable data to be sent to the device\n        * some_file\n        * some_dir\n\nAnything in .gitignore or .dinghyignore will not be bundled.\n\nTo open your test file easily, you can use the dinghy-test crate in your tests which contains a helper function to access your project directory:\n\n```rust\n#[cfg(test)]\nextern crate dinghy_test;\n\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn my_test() {\n        let my_file_path = dinghy_test::test_project_path().join(\"tests/data_1.txt\");\n        // ...\n    }\n}\n```\n\n## Sending more files to the devices\n\nNow let's assume you have out-of-repository files to send over. You can do that\nby adding it in `.dinghy.toml` (you'll probably want this one in the project\ndirectory, or just above it if the data is shared among several cargo projects).\n\n```toml\n[test_data]\nthe_data = \"../data-2017-02-05\"\nconf_file = \"/etc/some/file\"\n```\n\nThe keys are the name under which to look for files below \"test_data\" in the\nbundles, and the values are what to be copied (from your development workstation).\n\nBy default anything in `.gitignore` or `.dinghyignore` is not copied, however if\nyou need .gitignore'd files to be copied it can be excluded by adding\n`copy_git_ignored = true`:\n\n```toml\n[test_data]\nthe_data = { source = \"../data-2017-02-05\", copy_git_ignored = true }\nconf_file = \"/etc/some/file\"\n```\n\nThen you can use again the dinghy-test crate to access your specific test data directory:\n\n```rust\n#[cfg(test)]\nextern crate dinghy_test;\n\n#[cfg(test)]\nmod tests {\n    #[test]\n    fn my_test() {\n        let my_test_data_path = dinghy_test::test_file_path(\"the_data\");\n        let my_test_file_path = dinghy_test::test_file_path(\"conf_file\");\n        // ...\n    }\n}\n```\n\nIn order to accomodate tests running in \"cargo native\" and \"dinghy mode\" you may\ndefine and use a helper like that in your tests to find the test files:\n\n```rust\n#[cfg(test)]\nfn test_resources() -> std::path::PathBuf {\n    dinghy_test::try_test_file_path(\"test-resources\").unwrap_or(\"../test-resources\".into())\n}\n\n```\n"
  },
  {
    "path": "docs/filter.md",
    "content": "\n### Package filtering\n\nRust workspace builds all contained packages by default. However, when a worspace project builds multiple platforms, some packages might not be buildable on all targets. To help with that situation, Dinghy has a filter option which allows benching, building or testing only the packages compatible with the current target platform.\n\nTo make it works, modify the package toml to include some Dinghy metadata specifying the allowed rustc triples. For example, here we allow android only:\n```toml\n[package.metadata.dinghy]\nallowed_rustc_triples = [ \"armv7-linux-androideabi\", \"aarch64-linux-android\", \"i686-linux-android\", \"x86_64-linux-android\" ]\n```\n\nOr specifying the rustc triples to ignore. For example, here we disallow ios:\n```toml\n[package.metadata.dinghy]\nignored_rustc_triples = [\"aarch64-apple-ios\", \"armv7-apple-ios\", \"armv7s-apple-ios\", \"i386-apple-ios\", \"x86_64-apple-ios\"]\n```\n"
  },
  {
    "path": "docs/ios.md",
    "content": "## Getting started - iOS phone\n\n### Dinghy setup\n\nAssuming [rustup](http://rustup.rs) is already installed...\n\n```\ncargo install cargo-dinghy\n```\n\n### Additional iOS Requirements\n\nYou will need:\n\n * XCode (the full thing, not restricted to the command line)\n * [`pymobiledevice3`](https://github.com/doronz88/pymobiledevice3) for deploying and running code on the devices. `python3 -m pip install -U pymobiledevice3` should do the trick.\n * optionnaly, for iOS devices running iOS version lower than 17, you need [ios-deploy](https://github.com/ios-control/ios-deploy): `brew install ios-deploy`\n\n### iOS phone\n\nOn iOS, things are made complicated by the fact that there is no way to run a\nnaked executable on a device: you need to make it an app, and sign it before\nsending it to your phone. Setting up app signature requires some manipulations\nthat we could not find a way to dispense with through XCode. The good news is,\nthis is a constant time thing: *you'll have do it once in a while, but it will\ncover all your Rust projects*.\n\nAgain, we don't need a paying account.\n\n### Creating a signing id\n\nYou may skip most of this section if you're already setup to ship code to\nyour phone.\n\n* You'll need an Apple ID. Chances are you already have one, but you can\n    an account there: https://appleid.apple.com/account .\n* Go to XCode > Preferences/Settings > Accounts, and make sure your Apple ID is listed,\n    or add it (`+` bottom well-hidden bottom left).\n* View Details (bottom right this time)\n* Click the \"Create\" button in front of \"iOS Development\" in the top half of\n    this windows.\n\n### Creating a certificate\n\n* Plug-in your iPhone (or the device you want to run test on).\n* Fire up XCode, and /create a new XCode project/.\n* Pick /iOS/, /Single view application/.\n* Options box:\n    * Make the bundle identifier `some.unique.domainame.Dinghy`.\n    * So Product Name is `Dinghy`, and `Organization identifier` is something\n        that will be unique and looks like a domain name in reverse.\n    * Pick your team.\n    * Leave the rest alone\n* Save it somewhere.\n* You should see the dreadful projects settings screen. The only thing that is\n    relevant for us is the \"Signing\" bit.\n    * If you see your Team there, and nothing red or yellows shows up, then\n        you're OK. In some cases, you have to push the \"repair\" button to\n        actually obtain a certificate from Apple.\n\nIf you're using a Apple ID free account, you will need to come back once a\nweek to refresh the certificate (aka \"repair\"). Paying account generate\nlonger living certificates, so you need this less often.\n\n### Trust the certificate on your phone\n\nOn top of project window, make the target \"Dinghy>Your Device\", and run the\nproject (play button). XCode may ask you to go to your phone settings\nand Trust the certificate. It's in the XCode > Preferences/Settings > General >\n[your dev account name]. It should then start your empty app on the phone.\n\nAt this point, we're ready to roll, dinghy should detect XCode and the various\ntoolchain on its own.\n\n### Try it\n\nLet's try it with dinghy demo project. The project tests with \"pass\" in the\nname are supposed to pass, the one with fail should break.\n\n```\n% git clone https://github.com/sonos/dinghy\n% cd dinghy/test-ws\n[...]\n# these ones should pass\n% cargo dinghy -d iphone test pass\n[...]\n# this one shall not pass\n% cargo dinghy -d iphone test fail\n[...]\n```\n\n### Simulator\n\nThere's a [known bug with lldb and the ios\nsimulator](https://bugs.llvm.org/show_bug.cgi?id=36580) as such, dinghy will\nuse lldb to attach to the process on macOS to get the exit status from the\nsimulator.  On Catalina (and probably earlier), this means the user will be\nprompted for higher permissions.\n\n### Debugging tips\n\nIf you got lost somewhere, here are a few hints to help you make sense of\nwhat is happening. This is more or less what Dinghy use when fishing for\nyour signing identity.\n\n#### `security find-identity -p codesigning`\n\nShows you the codesigning identities available where you are. You should see\none or more identities line, made of a long capitalize hex identifier, followed\nby a \"name\". The name is very structured: For iOS development , its starts\nwith the string \"iPhone Developer: \", followed by an email (for an Apple Id\naccount) or the name of your team. Then comes the developer identifier in\nparenthesis.\n\n#### `security find-certificate -a -c \"name\" -p | openssl x509 -text`\n\n\"name\" is the identity name (the string between double quotes) from the command\nbefore.\n\nShows you a certificate that makes the developer a part of a Team.\nThe certificate is signed and issued by Apple (Issuer:), but the interesting\npart is the Subject line: CN= is the same developer name string, and OU= is\nthe actual Team identifier.\n\n#### Look for provisioning certificates\n\nLast but not least, we need one more certificate that proves that you and\nyour team has the right to deploy an App (identified by the bundle id we have\nchosen while creating the project) on one (or more) devices.\n\nThese certificates are in `Library/MobileDevice/Provisioning\\ Profiles`.\n\nTo read them, you'll need to do\n\n```\nsecurity cms -D -i  ~/Library/MobileDevice/Provisioning\\ Profiles\\....mobileprovision\n```\n\nThe more interesting keys here are `TeamIdentifier`, `ProvisionedDevices` which\nare relatively explicit, and the `Entitlements` dictionary. These entitlements\nspecify what the certificate is valid for, that is, signing an app identified\nby a name.\n\nDinghy will actually scan this directory to find one that it can use (this is\nwhere the app name being \"Dinghy\" plays a role).\n\nPhew.\n"
  },
  {
    "path": "docs/overlay.md",
    "content": "### Overlays\n\nA toolchain might not contains all the required dependencies for your project. To help with situation, Dinghy offers overlays.\n\n#### Overlay configuration\n\nBy default, Dinghy will look for overlays in the `dinghy/overlay/<platform>` directory next to your configuration file.\n\nOverlay directories can also be specified using the overlays section of your platform configuration:\n```toml\n[platforms.android-arm64]\noverlays={ mydep={ path=\"/mypath\" } } \n```\n\n#### Overlay directory\n\nAn overlay is a directory which contains the required *.so*, *.h* and *.pc* files for a dependency. For example:\n```\nmy-overlay\n|- libmylib.so\n|- bmylib.h\n|- bmylib.pc\n```\n\nAn overlay is like an additional sysroot. So you can also create directories and subdirectories. For example, we use a tensorflow overlay on our android projects with the following structure:\n```\nandroid-arm64\n|- overlay\n    |- tensorflow\n        |- usr\n            |- include\n                |- pkgconfig\n                    |- tensorflow.pc\n            |- lib\n                |- tensorflow.so\n```\n\nDinghy looks for:\n- *.pc* files either in the overlay root or in all sub-directories named `pkgconfig`.\n- *.so* libraries in the overlay directory and all of its subdirectories.\n\n#### Overlay pkg-config\n\nDinghy uses pkg-config to append dependencies during the compilation process (technically speaking using `PKG_CONFIG_LIBDIR`).\n\nBy default, if no pkgconfig *.pc* file is found, Dinghy will generate one before the build. In such a case, the overlay directory itself is appended as include and linking path in the pkgconfig files along all the `.so` files founds in its root. For example:\n```\nprefix=/\n\nName: mylib\nDescription: mylib\nRequires:\nVersion: unspecified\n\nLibs: -L${prefix} -lmylib\nCflags: -I${prefix}\n```\n\nIdeally, you should create a *.pc* file to make sure all compilation flags are set-up correctly. For example, our tensorflow overlay includes the following *.pc*:\n```\nprefix=/\nexec_prefix=${prefix}\nlibdir=${exec_prefix}/usr/lib\nincludedir=${prefix}/usr/include\n\nName: Tensorflow\nDescription: Tensorflow for Android\nRequires:\nVersion: 1.5\n\nCflags: -I${includedir}\nLibs: -L${libdir} -ltensorflow -lstdc++ -landroid -lz\n```\n\nOverlays are usually outside the toolchain sysroot. As a consequence, Dinghy must overrides the `prefix` pkg-config variable to provide a correct overlay path relative to the toolchain sysroot, despite being outside of it.\nHence, when writing a *.pc* file, it's very *important* to:\n- Define a `prefix` variable that Dinghy can override\n- Consider that this `prefix` points to the root of the overlay directory\n\n#### Overlay runtime\n\nTo make sure overlays are available at runtime, during benches, run or tests, Dinghy will copied all the `.so` files linked by the linker script during a build on the target device before running the appropriate executable.\n\n\n"
  },
  {
    "path": "docs/ssh.md",
    "content": "## Getting started - Ssh device\n\nSsh setup is useful if you want to target small-ish devices that can host an\noperating system but are too slow to iterate over Rust compilation/test cycles\ncomfortably. Think... something like a Raspberry, a NAS, or an old x86 box\nin your garage.\n\n### Dinghy setup\n\nAssuming [rustup](http://rustup.rs) is already installed...\n\n```\ncargo install cargo-dinghy\n\n# If it's already installed, add '--force'\ncargo install cargo-dinghy --force\n```\n\n### Finding a toolchain\n\nThis is the hard part. Rust or dinghy will not solve anything magically here.\nIf you're lucky, your hardware vendor has provided you with one you can actually use.\nOr somebody on the internet did the hard work.\nOr else https://crosstool-ng.github.io/ will become your best friend.\n\nDinghy will assume the toolchain looks relatively \"regular\". That is, it expects to\nfind something that looks like a `sysroot`, a directory called bin with a compiler\nand binutils.\n\nOnce you have this toolchain, that can compile and link a simple C helloworld\nto something running on your device, you're ready to start playing with rust and dinghy.\n\n### Install Rust target\n\n```\nrustup target install arm-unknown-linux-gnueabi\n```\n\n### Configure dinghy\n\nThe minimum configuration to be added on `~/.dinghy.toml` should look like that. You need\nto define a platform by linking rustc target to your toolchain, and to link your device and\naccount to this platform. We recommend that you add your ssh key to the account you want to\nlink to, or the password prompts will drive you crazy.\n\n```\n[platforms.raspbian-stretch]\nrustc_triple=\"arm-unknown-linux-gnueabihf\"\ntoolchain=\"/path/to/a/toolchain/for/arm-unknown-linux-gnueabi\"\n\n[ssh_devices]\nraspi = { hostname = \"raspi.local\", username=\"pi\", platform=\"raspbian-stretch\" }\n```\n\n### Try it\n\nLet's try it with dinghy demo project. The project tests with \"pass\" in the\nname are supposed to pass, the one with fail should break.\n\n```\n% git clone https://github.com/sonos/dinghy\n% cd dinghy/test-ws\n[...]\n# these ones should pass\n% cargo dinghy -d raspi test pass\n[...]\n# this one shall not pass\n% cargo dinghy -d raspi test fail\n[...]\n```\n\nThat's it! Enjoy!\n"
  },
  {
    "path": "docs/vars.md",
    "content": "\n### Environment variables\n\nDinghy allows defining environment variables per-platform. These variables will be set during the whole build process (including build.rs scripts targeting either the host or target platforms).\n\n```toml\n[platforms.my-platform]\nenv={ MY_ENV=\"my-value\" }\n```\n\nHere is an example environment variable that helps running openssl build script for the host platform (when another dependency build script uses openssl to download some stuff) during an android cross compilation build:\n```toml\n[platforms.android-arm64]\nenv={ X86_64_UNKNOWN_LINUX_GNU_OPENSSL_DIR = \"/usr\" } \n```\n\nIt's possible to setup environment variables for a (non cross-compilation) build running for the host platform too:\n```toml\n[platforms.host]\nenv={ MY_ENV=\"my-value\" }\n```\n\n"
  },
  {
    "path": "legacy/Cargo.toml",
    "content": "[package]\nname = \"dinghy\"\nversion = \"0.3.0\"\nedition = \"2021\"\nauthors = [\"Mathieu Poumeyrol <mathieu.poumeyrol@sonos>\", \"Thibaut Lorrain <thibaut.lorrain@sonos.com>\"]\nlicense = \"MIT/Apache-2.0\"\ndescription = \"[Deprecated] use cargo-dinghy instead\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n\n[workspace]\n"
  },
  {
    "path": "legacy/README.md",
    "content": "# Deprecated\n\nDinghy used to be deployed on the `dinghy` name on crates.io, we've since moved to `cargo-dinghy` to have the same name as the binary.\n\nThe use of the `dinghy` crate is deprecated, use `cargo-dinghy` instead.\n\nInstall it with the following command:\n\n```\n$ cargo install cargo-dinghy\n```\n\nThis crate is just a placeholder to be deployed on crates.io with the `dinghy` name, it fails on compilation to prompt users to install the correct crate.\n"
  },
  {
    "path": "legacy/build.rs",
    "content": "fn main() {\n    panic!(\"\n\n\n\nThe use of the dinghy crate is deprecated, use cargo-dinghy instead.\n\nInstall it with the following command:\n\n\\t$ cargo install cargo-dinghy\n\n\n\n    \");\n}\n"
  },
  {
    "path": "legacy/src/main.rs",
    "content": "fn main() {}\n"
  },
  {
    "path": "musl_build.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nRUST_TRIPLE=x86_64-unknown-linux-musl\n\nrustup target add $RUST_TRIPLE\n\ncargo build --target $RUST_TRIPLE --release -p cargo-dinghy\n\nmv target/${RUST_TRIPLE}/release/cargo-dinghy target/cargo-dinghy\n"
  },
  {
    "path": "post-release.sh",
    "content": "#!/bin/sh\n\nVERSION=$1\nCRATES=\"dinghy-build dinghy-test dinghy-lib cargo-dinghy\"\n\nif [ -z \"$VERSION\" ]\nthen\n    echo \"Usage: $0 <version>\" \n    exit 1\nfi\n\n# set_version cargo-dinghy/Cargo.toml 0.3.0\nset_version() {\n    FILE=$1\n    VERSION=$2\n    sed -i.back \"s/^version *= *\\\".*\\\"/version = \\\"$2\\\"/\" $FILE\n    sed -i.back \"s/^\\(dinghy-[^ =]*\\).*/\\\\1 = { path = \\\"..\\/\\\\1\\\" }/\" $FILE\n}\n\nset -ex\n\nfor c in $CRATES\ndo\n    set_version $c/Cargo.toml $VERSION\ndone\n\n(cd cargo-dinghy ; cargo update)\n(cd test-ws ; cargo update)\n\ngit commit . -m \"post-release $VERSION\"\ngit push\n"
  },
  {
    "path": "pre-release.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nNEW_VERSION=$1\nif [[ -z \"${NEW_VERSION}\" ]]; then\n  echo \"usage: $0 <new version>\"\n  exit 1\nfi\n\nif [[ -z \"${CHANGELOG_GITHUB_TOKEN}\" ]]; then\n  echo \"You need to set a CHANGELOG_GITHUB_TOKEN environment variable with a Github token\"\n  echo \"Click this link to generate the token, it doesn't need any additional scopes\"\n  echo \"    https://github.com/settings/tokens/new?description=Dinghy%20Changelog%20Generator\"\n  exit 1\nfi\n\nif ! gem list -i github_changelog_generator > /dev/null; then\n  echo \"Please install the ruby gem github_changelog_generator using the following command:\"\n  echo \"    gem install github_changelog_generator\"\n  exit 1\nfi\n\nif ! git diff-index --quiet HEAD --; then\n  echo \"Git workspace is not clean, clean it before proceeding\"\n  exit 1\nfi\n\necho \"Generating changelog\"\n\nPATH=\"$(gem environment user_gemhome)/bin:$(gem environment gemhome)/bin:$PATH\"  github_changelog_generator --no-verbose -u sonos -p dinghy --include-tags-regex \"^0\\..+\\..+\" --future-release \"${NEW_VERSION}\"\n\nprintf \"Here are the updates to the changelog \\n\\n\\n\"\n\ngit --no-pager diff\n\nprintf \"\\n\\n\\nDoes this seem correct (Y/n)? \"\n\nread -r ANSWER\n\nif [[ -n \"${ANSWER}\" ]] && ! [[ \"${ANSWER}\" == [Yy]* ]]; then\n  echo \"Aborting, go fix things\"\n  exit 1\nfi\n\ngit commit -am \"changelog for $NEW_VERSION\"\n\n"
  },
  {
    "path": "release.sh",
    "content": "#!/usr/bin/env bash\n\nCRATE=$1\nVERSION=$2\nCRATES=\"dinghy-build dinghy-test dinghy-lib cargo-dinghy\"\n\nif [ -z \"$VERSION\" ]\nthen\n    echo \"Usage: $0 <crate> <version>\" \n    echo crates order is: $CRATES\n    exit 1\nfi\n\nset -ex\n\nif ! [[ $(git --no-pager log HEAD^..HEAD --pretty=format:\"%s\") == \"changelog for $VERSION\" ]] && [[ -z \"${SKIP_CHANGELOG_CHECK}\" ]]\nthen\n  echo \"Please generate the changelog first using ./pre-release $VERSION\"\n  exit 1\nfi\n\nif [ \"$CRATE\" = \"all\" ]\nthen\n    for c in $CRATES\n    do\n        SKIP_CHANGELOG_CHECK=1 $0 $c $VERSION\n    done\n    exit 0\nfi\n\n# set_version cargo-dinghy/Cargo.toml 0.3.0\nset_version() {\n    FILE=$1\n    VERSION=$2\n    sed -i.back \"s/^version *= *\\\".*\\\"/version = \\\"$2\\\"/\" $FILE\n    sed -i.back \"s/^\\(dinghy-[^ =]*\\).*/\\\\1 = \\\"$2\\\"/\" $FILE\n}\n\nset_version $CRATE/Cargo.toml $VERSION\ngit commit . -m \"release $CRATE/$VERSION\"\n(cd $CRATE ; cargo publish)\n\nif [ \"$CRATE\" = \"cargo-dinghy\" ]\nthen\n    git tag \"cargo-dinghy/$VERSION\"\n    git push --tags\nfi\nsleep 15\n"
  },
  {
    "path": "rust-toolchain",
    "content": "1.85.0\n"
  },
  {
    "path": "test-ws/.dinghy.toml",
    "content": "[test_data]\ndinghy_license = { source = \"../LICENSE\", copy_git_ignored = true }\n"
  },
  {
    "path": "test-ws/Cargo.toml",
    "content": "[workspace]\nmembers = [\"test-app\", \"test-proc-macro\", \"test-bin\"]\nresolver = \"2\"\n"
  },
  {
    "path": "test-ws/rust-toolchain",
    "content": "1.89.0\n"
  },
  {
    "path": "test-ws/test-app/.dinghy.toml",
    "content": "[test_data]\ndinghy_source = \"../..\"\n"
  },
  {
    "path": "test-ws/test-app/Cargo.toml",
    "content": "[package]\nname = \"test-app\"\nversion = \"0.1.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nedition = \"2021\"\nworkspace = \"../\"\n\n[features]\nmy-feature = []\n\n[dev-dependencies]\ndinghy-test = { path = \"../../dinghy-test\" }\n"
  },
  {
    "path": "test-ws/test-app/src/lib.rs",
    "content": "#[cfg(test)]\nextern crate dinghy_test;\n\n\n/// this is an example doctest with lots of code blocks that will each generate a separate test.\n///\n/// ```\n/// assert_eq!(2 + 2, 4);\n/// ```\n///\n/// ```\n/// assert_eq!(3 + 3, 6);\n/// ```\n///\n/// ```\n/// assert_eq!(4 + 4, 8);\n/// ```\n///\n/// ```\n/// assert_eq!(5 + 5, 10);\n/// ```\n///\n/// ```\n/// assert_eq!(6 + 6, 12);\n/// ```\n///\n/// ```\n/// assert_eq!(7 + 7, 14);\n/// ```\n\npub fn example_function() -> i32 {\n    2 + 2\n}\n\n#[cfg(test)]\nmod tests {\n    mod pass {\n        use dinghy_test::test_file_path;\n        use dinghy_test::test_project_path;\n        use dinghy_test::try_test_file_path;\n        use std::path;\n\n        #[test]\n        fn it_finds_source_files() {\n            println!(\"pwd: {:?}\", ::std::env::current_dir());\n            println!(\"test_project_path: {:?}\", test_project_path());\n            assert!(test_project_path().join(\"src/lib.rs\").exists());\n        }\n\n        #[test]\n        fn it_finds_test_data_files() {\n            println!(\"pwd: {:?}\", ::std::env::current_dir());\n            let license = if let Err(_) = ::std::env::var(\"NOT_BUILT_WITH_DINGHY\") {\n                test_file_path(\"dinghy_source\").join(\"LICENSE\")\n            } else {\n                try_test_file_path(\"dinghy_source\")\n                    .unwrap_or(path::PathBuf::from(\"../..\"))\n                    .join(\"LICENSE\")\n            };\n            println!(\"Found path: {:?}\", license);\n            assert!(\n                license.exists(),\n                \"File from dinghy_source not found: {:?}\",\n                license\n            );\n            let license = if let Err(_) = ::std::env::var(\"NOT_BUILT_WITH_DINGHY\") {\n                test_file_path(\"dinghy_license\")\n            } else {\n                try_test_file_path(\"dinghy_license\").unwrap_or(path::PathBuf::from(\"../../LICENSE\"))\n            };\n            println!(\"Found path: {:?}\", license);\n            assert!(\n                license.exists(),\n                \"File dinghy_license not found: {:?}\",\n                license\n            );\n        }\n\n        #[test]\n        fn it_works() {}\n    }\n\n    mod fails {\n        #[test]\n        fn it_fails() {\n            panic!(\"Failing as expected\");\n        }\n    }\n\n    mod feature {\n        #[test]\n        fn has_feature() {\n            assert!(cfg!(feature = \"my-feature\"))\n        }\n    }\n}\n"
  },
  {
    "path": "test-ws/test-bin/Cargo.toml",
    "content": "[package]\nname = \"test-bin\"\nversion = \"0.1.0\"\nedition = \"2021\"\nworkspace = \"../\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n"
  },
  {
    "path": "test-ws/test-bin/src/main.rs",
    "content": "fn main() {\n    for (name, value) in std::env::vars() {\n        println!(\"env {} -> {}\", name, value);\n    }\n\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "test-ws/test-proc-macro/Cargo.toml",
    "content": "[package]\nname = \"test-proc-macro\"\nversion = \"0.1.0\"\nedition = \"2021\"\nworkspace = \"../\"\n\n[lib]\nproc-macro = true\n"
  },
  {
    "path": "test-ws/test-proc-macro/src/lib.rs",
    "content": ""
  }
]