├── .github
└── workflows
│ ├── bump.yml
│ ├── check.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── plugin
├── build.gradle
└── src
│ ├── main
│ ├── groovy
│ │ └── com
│ │ │ └── nishtahir
│ │ │ └── Versions.groovy
│ ├── kotlin
│ │ └── com
│ │ │ └── nishtahir
│ │ │ ├── CargoBuildTask.kt
│ │ │ ├── CargoExtension.kt
│ │ │ ├── Extensions.kt
│ │ │ ├── GenerateLinkerWrapperTask.kt
│ │ │ ├── GenerateToolchainsTask.kt
│ │ │ └── RustAndroidPlugin.kt
│ └── resources
│ │ └── com
│ │ └── nishtahir
│ │ ├── linker-wrapper.bat
│ │ ├── linker-wrapper.py
│ │ └── linker-wrapper.sh
│ └── test
│ ├── groovy
│ └── com
│ │ └── nishtahir
│ │ ├── AbstractTest.groovy
│ │ ├── CargoBuildTest.groovy
│ │ ├── CargoTargetTest.groovy
│ │ ├── MultiVersionTest.groovy
│ │ ├── NdkVersionTest.groovy
│ │ ├── SimpleAndroidApp.groovy
│ │ ├── SimpleCargoProject.groovy
│ │ └── TestVersions.groovy
│ └── resources
│ ├── SpockConfig.groovy
│ └── rust
│ ├── .cargo
│ └── config
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── samples
├── app
│ ├── build.gradle
│ ├── settings.gradle
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── nishtahir
│ │ │ └── androidrust
│ │ │ └── ExampleInstrumentedTest.java
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── nishtahir
│ │ │ └── androidrust
│ │ │ ├── JNICallback.java
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
├── library
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── settings.gradle
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── nishtahir
│ │ │ └── library
│ │ │ └── ExampleInstrumentedTest.java
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── rust
│ ├── .gitignore
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
└── unittest
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── settings.gradle
│ └── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── nishtahir
│ │ └── androidrust
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── nishtahir
│ │ │ └── androidrust
│ │ │ ├── JNACallback.java
│ │ │ ├── JNICallback.java
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── nishtahir
│ └── androidrust
│ └── ExampleUnitTest.java
├── settings.gradle
└── version.properties
/.github/workflows/bump.yml:
--------------------------------------------------------------------------------
1 | name: Bump version
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | new_version:
7 | type: string
8 | description: New version number, like x.y.z
9 | required: true
10 | changelog:
11 | type: string
12 | description: Single line of details to prepend to `CHANGELOG`.
13 | required: true
14 |
15 | jobs:
16 | bump-version:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 |
22 | # Everything here was cribbed from or inspired by
23 | # https://github.com/oflynned/android-version-bump/blob/b9f6de7f8bdf25de3f695843265debf7c3919272.
24 | - name: Bump version
25 | id: bump_version
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 | run: |
29 | old_version=$(sed -e 's/.*=//' version.properties)
30 | echo "Bumping old version: $old_version to new version ${{ github.event.inputs.new_version }}."
31 | echo "Changelog:"
32 | echo "- ${{ github.event.inputs.changelog }}"
33 |
34 | echo "# ${{ github.event.inputs.new_version }}" >> new_CHANGELOG.md
35 | echo >> new_CHANGELOG.md
36 | echo "- ${{ github.event.inputs.changelog }}" >> new_CHANGELOG.md
37 | echo >> new_CHANGELOG.md
38 | cat CHANGELOG.md >> new_CHANGELOG.md
39 | mv new_CHANGELOG.md CHANGELOG.md
40 |
41 | sed -i -e "s/$old_version/${{ github.event.inputs.new_version }}/" version.properties README.md
42 |
43 | echo "version=${{ github.event.inputs.new_version }}" > version.properties
44 |
45 | git diff
46 |
47 | git config --global user.name "${GITHUB_USER:-Automated Version Bump}"
48 | git config --global user.email "${GITHUB_EMAIL:-rust-android-gradle@users.noreply.github.com}"
49 |
50 | git add --all
51 | git commit -m "Prep for releasing version ${{ github.event.inputs.new_version }}."
52 |
53 | git log -n 1
54 |
55 | git tag "v${{ github.event.inputs.new_version }}"
56 | remote="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
57 |
58 | git push "${remote}" --follow-tags
59 | git push "${remote}" --tags
60 |
61 | echo "::set-output name=new_tag::v${{ github.event.inputs.new_version }}"
62 |
63 | - name: Create release
64 | id: create_release
65 | uses: actions/create-release@v1
66 | env:
67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
68 | with:
69 | tag_name: ${{ steps.bump_version.outputs.new_tag }}
70 | release_name: ${{ steps.bump_version.outputs.new_tag }}
71 | body: "- ${{ github.event.inputs.changelog }}"
72 | prerelease: false
73 | draft: true
74 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | # Controls when the action will run. Triggers the workflow on push or pull
4 | # request events, but only for the `master` branch (generally) or the `citest`
5 | # branch (for testing).
6 | on:
7 | push:
8 | branches: [master, citest]
9 | pull_request:
10 | branches: [master]
11 | schedule:
12 | # N.b. * is a special character in YAML so you have to quote this
13 | # string. 10:20 UTC (2:20 Pacific), once a week on Tuesday, to
14 | # verify this stays healthy over time.
15 | - cron: '20 10 * * 3'
16 |
17 | jobs:
18 | generate_versions:
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | # Checks-out your repository under $GITHUB_WORKSPACE for the job.
23 | - uses: actions/checkout@v2
24 |
25 | - name: Gradle test
26 | run: |
27 | ./gradlew -p plugin generateTestTasksJson
28 |
29 | - id: setup-matrix
30 | run: echo "::set-output name=matrix::$(cat plugin/build/build-resources/androidTestTasks.json)"
31 |
32 | - name: debug
33 | run: echo ${{ steps.setup-matrix.outputs.matrix }}
34 |
35 | outputs:
36 | matrix: ${{ steps.setup-matrix.outputs.matrix }}
37 |
38 | samples:
39 | # The type of runner that the job will run on
40 | runs-on: ${{ matrix.os }}
41 |
42 | strategy:
43 | fail-fast: false
44 | matrix:
45 | os:
46 | - ubuntu-latest
47 | - macos-latest
48 | - windows-latest
49 |
50 | steps:
51 | # Checks-out your repository under $GITHUB_WORKSPACE for the job.
52 | - uses: actions/checkout@v2
53 |
54 | - name: Setup Rust
55 | run: |
56 | rustup toolchain install stable
57 | rustup target add x86_64-linux-android
58 | rustup target add x86_64-unknown-linux-gnu
59 | rustup target add aarch64-linux-android
60 |
61 | - name: Setup Java 11
62 | uses: actions/setup-java@v2
63 | with:
64 | distribution: 'temurin'
65 | java-version: 11
66 | cache: 'gradle'
67 |
68 | - name: Assemble samples/app
69 | run: |
70 | ./gradlew -p samples/app :assembleDebug --info --warning-mode all
71 |
72 | - name: Assemble samples/library
73 | run: |
74 | ./gradlew -p samples/library :assembleDebug --info --warning-mode all
75 |
76 | # Work around https://github.com/actions/cache/issues/454.
77 | - name: Gradle stop
78 | run: |
79 | ./gradlew --stop
80 |
81 | android_unversioned_tests:
82 | # The type of runner that the job will run on
83 | runs-on: ${{ matrix.os }}
84 |
85 | strategy:
86 | fail-fast: false
87 | matrix:
88 | os:
89 | - ubuntu-latest
90 | - macos-latest
91 | - windows-latest
92 |
93 | steps:
94 | # Checks-out your repository under $GITHUB_WORKSPACE for the job.
95 | - uses: actions/checkout@v2
96 |
97 | - name: Setup Rust
98 | run: |
99 | rustup toolchain install stable
100 | rustup target add x86_64-linux-android
101 | rustup target add x86_64-unknown-linux-gnu
102 | rustup target add aarch64-linux-android
103 |
104 | # Use Java 8
105 | - name: Setup Java 8
106 | uses: actions/setup-java@v2
107 | with:
108 | distribution: 'temurin'
109 | java-version: 8
110 | cache: 'gradle'
111 |
112 | - name: Gradle setup
113 | run: |
114 | ./gradlew -p plugin tasks --warning-mode all
115 |
116 | - name: Gradle test
117 | run: |
118 | ./gradlew -p plugin test --info --warning-mode all
119 |
120 | # Work around https://github.com/actions/cache/issues/454.
121 | - name: Gradle stop
122 | run: |
123 | ./gradlew --stop
124 |
125 | android_version_tests:
126 | needs: [generate_versions] # , sanity_check]
127 |
128 | # The type of runner that the job will run on
129 | runs-on: ${{ matrix.os }}
130 |
131 | strategy:
132 | fail-fast: false
133 | matrix:
134 | os:
135 | - ubuntu-latest
136 | - macos-latest
137 | - windows-latest
138 | androidTestTask: ${{ fromJson(needs.generate_versions.outputs.matrix) }}
139 |
140 | # Steps represent a sequence of tasks that will be executed as part of the job
141 | steps:
142 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
143 | - uses: actions/checkout@v2
144 |
145 | - name: Setup Rust
146 | run: |
147 | rustup toolchain install stable
148 | rustup target add x86_64-linux-android
149 | rustup target add x86_64-unknown-linux-gnu
150 | rustup target add aarch64-linux-android
151 |
152 | # Use Java 8
153 | - name: Setup Java 8
154 | uses: actions/setup-java@v2
155 | with:
156 | distribution: 'temurin'
157 | java-version: 8
158 | cache: 'gradle'
159 |
160 | - name: Gradle setup
161 | run: |
162 | ./gradlew -p plugin tasks --warning-mode all
163 |
164 | - name: Gradle test
165 | run: |
166 | ./gradlew -p plugin ${{ matrix.androidTestTask }} --info --warning-mode all
167 |
168 | # Work around https://github.com/actions/cache/issues/454.
169 | - name: Gradle stop
170 | run: |
171 | ./gradlew --stop
172 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish plugin
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 |
14 | - name: Set up JDK 8
15 | uses: actions/setup-java@v2
16 | with:
17 | java-version: '8'
18 | distribution: 'temurin'
19 |
20 | - name: Write gradle.properties
21 | env:
22 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }}
23 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }}
24 | run: |
25 | mkdir -p $HOME/.gradle/
26 | echo "gradle.publish.key=${GRADLE_PUBLISH_KEY}" >> ~/.gradle/gradle.properties
27 | echo "gradle.publish.secret=${GRADLE_PUBLISH_SECRET}" >> ~/.gradle/gradle.properties
28 |
29 | - name: Publish plugin
30 | run: |
31 | ./gradlew publishPlugins
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.apk
2 | *.ap_
3 | *.dex
4 | *.class
5 | bin/
6 | gen/
7 | out/
8 | .gradle/
9 | build/
10 | local.properties
11 | proguard/
12 | *.log
13 | .navigation/
14 | captures/
15 | *.iml
16 | .idea/
17 | .externalNativeBuild
18 | cmake-build-debug/
19 | *.iws
20 | /out/
21 | .idea_modules/
22 | atlassian-ide-plugin.xml
23 | .idea/replstate.xml
24 | com_crashlytics_export_strings.xml
25 | crashlytics.properties
26 | crashlytics-build.properties
27 | fabric.properties
28 | modules.xml
29 | .idea/misc.xml
30 | *.ipr
31 | /target/
32 | /samples/rust/target/
33 | Cargo.lock
34 | **/*.rs.bk
35 | .gradle
36 | **/build/
37 | gradle-app.setting
38 | !gradle-wrapper.jar
39 | .gradletasknamecache
40 |
41 | .cargo
42 | **/maven-repo/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.9.6
2 |
3 | - Added option to add a build-id during linking
4 |
5 | # 0.9.5
6 |
7 | - Do not use deprecated module for Python 3.13 compatibility
8 |
9 | # 0.9.4
10 |
11 | - Always use the NDK version from .
12 |
13 | # 0.9.3
14 |
15 | - Fix issues with Android NDK 23+: use `llvm-ar`, support `-lgcc` rewriting on Windows.
16 |
17 | # 0.9.2
18 |
19 | - Support Android NDK 23.
20 |
21 | # 0.9.1
22 |
23 | - Add Desktop targets `darwin-x86-64`, which will supersede `darwin`, and `darwin-aarch64`.
24 |
25 | # 0.9.0
26 |
27 | - Support multiple Android Gradle Plugin versions. Everything here is based on https://github.com/gradle/android-cache-fix-gradle-plugin; many thanks to that project for paving the way here.
28 | - Allow `module` and `targetDirectory` to be absolute paths. This is used in the test harness at the moment.
29 |
30 | # 0.8.7
31 |
32 | - Use per-platform API level for selecting toolchain.
33 |
34 | # 0.8.6
35 |
36 | - Revert a change that prevented publishing.
37 |
38 | # 0.8.5
39 |
40 | - Allow to use `+nightly`, etc, with `cargo { rustupChannel = "..." }`. Fixes #24.
41 | - Allow to set `cargo { (cargo|python|rustc)Command = "..." }`. Fixes #48.
42 |
43 | # 0.8.4
44 |
45 | - The plugin tries to interoperate with Rust bindgen out of the box by setting `CLANG_PATH`.
46 | - We no longer invoke `cargo` for the Gradle `clean` target.
47 |
48 | # 0.8.3
49 |
50 | - Plugin now supports using prebuilt NDK toolchains.
51 |
52 | # 0.8.2
53 |
54 | - Avoid passing `--target` to cargo for the default target.
55 | - The `exec` callback is now invoked as late as possible.
56 | - The `CARGO_TARGET_DIR` environment variable should now be respected, if it is set.
57 | - Various parts of the plugin's documentation have been improved.
58 |
59 | # 0.8.1
60 |
61 | - Added `extraCargoBuildArguments`.
62 |
63 | # 0.8.0
64 |
65 | - **breaking** Further split "win32-x86-64" into "win32-x86-64-{gnu,msvc}".
66 | - Fixed bug with DLL libraries in with JNA: expect "foo.dll" instead
67 | of "libfoo.dll".
68 |
69 | # 0.7.0
70 |
71 | - Added per-target pass-through variables.
72 | - Separated "default" target into multiple Desktop targets:
73 | "linux-x86-64, "darwin", "win32-x86-64".
74 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Community Participation Guidelines
2 |
3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines.
4 | For more details, please read the
5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
6 |
7 | ## How to Report
8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
9 |
10 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Nish Tahir
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rust Android Gradle Plugin
2 |
3 | Cross compile Rust Cargo projects for Android targets.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Usage
12 |
13 | Add the plugin to your root `build.gradle`, like:
14 |
15 | ```groovy
16 | buildscript {
17 | repositories {
18 | maven {
19 | url "https://plugins.gradle.org/m2/"
20 | }
21 | }
22 | dependencies {
23 | classpath 'org.mozilla.rust-android-gradle:plugin:0.9.6'
24 | }
25 | }
26 | ```
27 |
28 | or
29 |
30 | ```groovy
31 | buildscript {
32 | //...
33 | }
34 |
35 | plugins {
36 | id "org.mozilla.rust-android-gradle.rust-android" version "0.9.6"
37 | }
38 | ```
39 |
40 | In your *project's* build.gradle, `apply plugin` and add the `cargo` configuration:
41 |
42 | ```groovy
43 | android { ... }
44 |
45 | apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
46 |
47 | cargo {
48 | module = "../rust" // Or whatever directory contains your Cargo.toml
49 | libname = "rust" // Or whatever matches Cargo.toml's [package] name.
50 | targets = ["arm", "x86"] // See bellow for a longer list of options
51 | }
52 | ```
53 |
54 | Install the rust toolchains for your target platforms:
55 |
56 | ```sh
57 | rustup target add armv7-linux-androideabi # for arm
58 | rustup target add i686-linux-android # for x86
59 | rustup target add aarch64-linux-android # for arm64
60 | rustup target add x86_64-linux-android # for x86_64
61 | rustup target add x86_64-unknown-linux-gnu # for linux-x86-64
62 | rustup target add x86_64-apple-darwin # for darwin x86_64 (if you have an Intel MacOS)
63 | rustup target add aarch64-apple-darwin # for darwin arm64 (if you have a M1 MacOS)
64 | rustup target add x86_64-pc-windows-gnu # for win32-x86-64-gnu
65 | rustup target add x86_64-pc-windows-msvc # for win32-x86-64-msvc
66 | ...
67 | ```
68 |
69 | Finally, run the `cargoBuild` task to cross compile:
70 | ```sh
71 | ./gradlew cargoBuild
72 | ```
73 | Or add it as a dependency to one of your other build tasks, to build your rust code when you normally build your project:
74 | ```gradle
75 | tasks.whenTaskAdded { task ->
76 | if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) {
77 | task.dependsOn 'cargoBuild'
78 | }
79 | }
80 | ```
81 |
82 | ## Configuration
83 |
84 | The `cargo` Gradle configuration accepts many options.
85 |
86 | ### Linking Java code to native libraries
87 |
88 | Generated libraries will be added to the Android `jniLibs` source-sets, when correctly referenced in
89 | the `cargo` configuration through the `libname` and/or `targetIncludes` options. The latter
90 | defaults to `["lib${libname}.so", "lib${libname}.dylib", "{$libname}.dll"]`, so the following configuration will
91 | include all `libbackend` libraries generated in the Rust project in `../rust`:
92 |
93 | ```
94 | cargo {
95 | module = "../rust"
96 | libname = "backend"
97 | }
98 | ```
99 |
100 | Now, Java code can reference the native library using, e.g.,
101 |
102 | ```java
103 | static {
104 | System.loadLibrary("backend");
105 | }
106 | ```
107 |
108 | ### Native `apiLevel`
109 |
110 | The [Android NDK](https://developer.android.com/ndk/guides/stable_apis) also fixes an API level,
111 | which can be specified using the `apiLevel` option. This option defaults to the minimum SDK API
112 | level. As of API level 21, 64-bit builds are possible; and conversely, the `arm64` and `x86_64`
113 | targets require `apiLevel >= 21`.
114 |
115 | ### Cargo release profile
116 |
117 | The `profile` option selects between the `--debug` and `--release` profiles in `cargo`. *Defaults
118 | to `debug`!*
119 |
120 | ### Extension reference
121 |
122 | ### module
123 |
124 | The path to the Rust library to build with Cargo; required. `module` can be absolute; if it is not,
125 | it is interpreted as a path relative to the Gradle `projectDir`.
126 |
127 | ```groovy
128 | cargo {
129 | // Note: path is either absolute, or relative to the gradle project's `projectDir`.
130 | module = '../rust'
131 | }
132 | ```
133 |
134 | ### libname
135 |
136 | The library name produced by Cargo; required.
137 |
138 | `libname` is used to determine which native libraries to include in the produced AARs and/or APKs.
139 | See also [`targetIncludes`](#targetincludes).
140 |
141 | `libname` is also used to determine the ELF SONAME to declare in the Android libraries produced by
142 | Cargo. Different versions of the Android system linker
143 | [depend on the ELF SONAME](https://android-developers.googleblog.com/2016/06/android-changes-for-ndk-developers.html).
144 |
145 | In `Cargo.toml`:
146 |
147 | ```toml
148 | [lib]
149 | name = "test"
150 | ```
151 |
152 | In `build.gradle`:
153 |
154 | ```groovy
155 | cargo {
156 | libname = 'test'
157 | }
158 | ```
159 |
160 | ### targets
161 |
162 | A list of Android targets to build with Cargo; required.
163 |
164 | Valid targets for **Android** are:
165 |
166 | ```
167 | 'arm',
168 | 'arm64',
169 | 'x86',
170 | 'x86_64'
171 | ```
172 | Valid targets for **Desktop** are:
173 | ```
174 | 'linux-x86-64',
175 | 'darwin-x86-64',
176 | 'darwin-aarch64',
177 | 'win32-x86-64-gnu',
178 | 'win32-x86-64-msvc'
179 | ```
180 |
181 | The desktop targets are useful for testing native code in Android unit tests that run on the host,
182 | not on the target device. Better support for this feature is
183 | [planned](https://github.com/ncalexan/rust-android-gradle/issues/13).
184 |
185 | ```groovy
186 | cargo {
187 | targets = ['arm', 'x86', 'linux-x86-64']
188 | }
189 | ```
190 |
191 | ### prebuiltToolchains
192 |
193 | When set to `true` (which requires NDK version 19+), use the prebuilt toolchains bundled with the
194 | NDK. When set to `false`, generate per-target architecture standalone NDK toolchains using
195 | `make_standalone_toolchain.py`. When unset, use the prebuilt toolchains if the NDK version is 19+,
196 | and fall back to generated toolchains for older NDK versions.
197 |
198 | Defaults to `null`.
199 |
200 | ```groovy
201 | cargo {
202 | prebuiltToolchains = true
203 | }
204 | ```
205 |
206 | ### verbose
207 |
208 | When set, execute `cargo build` with or without the `--verbose` flag. When unset, respect the
209 | Gradle log level: execute `cargo build` with or without the `--verbose` flag according to whether
210 | the log level is at least `INFO`. In practice, this makes `./gradlew ... --info` (and `./gradlew
211 | ... --debug`) execute `cargo build --verbose ...`.
212 |
213 | Defaults to `null`.
214 |
215 | ```groovy
216 | cargo {
217 | verbose = true
218 | }
219 | ```
220 |
221 | ### profile
222 |
223 | The Cargo [release profile](https://doc.rust-lang.org/book/second-edition/ch14-01-release-profiles.html#customizing-builds-with-release-profiles) to build.
224 |
225 | Defaults to `"debug"`.
226 |
227 | ```groovy
228 | cargo {
229 | profile = 'release'
230 | }
231 | ```
232 |
233 | ### features
234 |
235 | Set the Cargo [features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section).
236 |
237 | Defaults to passing no flags to `cargo`.
238 |
239 | To pass `--all-features`, use
240 | ```groovy
241 | cargo {
242 | features {
243 | all()
244 | }
245 | }
246 | ```
247 |
248 | To pass an optional list of `--features`, use
249 | ```groovy
250 | cargo {
251 | features {
252 | defaultAnd("x")
253 | defaultAnd("x", "y")
254 | }
255 | }
256 | ```
257 |
258 | To pass `--no-default-features`, and an optional list of replacement `--features`, use
259 | ```groovy
260 | cargo {
261 | features {
262 | noDefaultBut()
263 | noDefaultBut("x")
264 | noDefaultBut "x", "y"
265 | }
266 | }
267 | ```
268 |
269 | ### targetDirectory
270 |
271 | The target directory into which Cargo writes built outputs. You will likely need to specify this
272 | if you are using a [cargo virtual workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html),
273 | as our default will likely fail to locate the correct target directory.
274 |
275 | Defaults to `${module}/target`. `targetDirectory` can be absolute; if it is not, it is interpreted
276 | as a path relative to the Gradle `projectDir`.
277 |
278 | Note that if `CARGO_TARGET_DIR` (see https://doc.rust-lang.org/cargo/reference/environment-variables.html)
279 | is specified in the environment, it takes precedence over `targetDirectory`, as cargo will output
280 | all build artifacts to it, regardless of what is being built, or where it was invoked.
281 |
282 | You may also override `CARGO_TARGET_DIR` variable by setting `rust.cargoTargetDir` in
283 | `local.properties`, however it seems very unlikely that this will be useful, as we don't pass this
284 | information to cargo itself. That said, it can be used to control where we search for the built
285 | library on a per-machine basis.
286 |
287 | ```groovy
288 | cargo {
289 | // Note: path is either absolute, or relative to the gradle project's `projectDir`.
290 | targetDirectory = 'path/to/workspace/root/target'
291 | }
292 | ```
293 |
294 | ### targetIncludes
295 |
296 | Which Cargo outputs to consider JNI libraries.
297 |
298 | Defaults to `["lib${libname}.so", "lib${libname}.dylib", "{$libname}.dll"]`.
299 |
300 | ```groovy
301 | cargo {
302 | targetIncludes = ['libnotlibname.so']
303 | }
304 | ```
305 |
306 | ### apiLevel
307 |
308 | The Android NDK API level to target. NDK API levels are not the same as SDK API versions; they are
309 | updated less frequently. For example, SDK API versions 18, 19, and 20 all target NDK API level 18.
310 |
311 | Defaults to the minimum SDK version of the Android project's default configuration.
312 |
313 | ```groovy
314 | cargo {
315 | apiLevel = 21
316 | }
317 | ```
318 |
319 | You may specify the API level per target in `targets` using the `apiLevels` option. At most one of
320 | `apiLevel` and `apiLevels` may be specified. `apiLevels` must have an entry for each target in
321 | `targets`.
322 |
323 | ```groovy
324 | cargo {
325 | targets = ["arm", "x86_64"]
326 | apiLevels = [
327 | "arm": 16,
328 | "x86_64": 21,
329 | ]
330 | }
331 | ```
332 |
333 | ### extraCargoBuildArguments
334 |
335 | Sometimes, you need to do things that the plugin doesn't anticipate. Use `extraCargoBuildArguments`
336 | to append a list of additional arguments to each `cargo build` invocation.
337 |
338 | ```groovy
339 | cargo {
340 | extraCargoBuildArguments = ['a', 'list', 'of', 'strings']
341 | }
342 | ```
343 |
344 | ### generateBuildId
345 |
346 | Generate a build-id for the shared library during the link phase.
347 |
348 | ### exec
349 |
350 | This is a callback taking the `ExecSpec` we're going to use to invoke `cargo build`, and
351 | the relevant toolchain. It's called for each invocation of `cargo build`. This generally
352 | is useful for the following scenarios:
353 |
354 | 1. Specifying target-specific environment variables.
355 | 1. Adding target-specific flags to the command line.
356 | 1. Removing/modifying environment variables or command line options the rust-android-gradle plugin would
357 | provide by default.
358 |
359 | ```groovy
360 | cargo {
361 | exec { spec, toolchain ->
362 | if (toolchain.target != "x86_64-apple-darwin") {
363 | // Don't statically link on macOS desktop builds, for some
364 | // entirely hypothetical reason.
365 | spec.environment("EXAMPLELIB_STATIC", "1")
366 | }
367 | }
368 | }
369 | ```
370 |
371 | ## Specifying NDK toolchains
372 |
373 | The plugin can either use prebuilt NDK toolchain binaries, or search for (and if missing, build)
374 | NDK toolchains as generated by `make_standalone_toolchain.py`.
375 |
376 | A prebuilt NDK toolchain will be used if:
377 | 1. `rust.prebuiltToolchain=true` in the per-(multi-)project `${rootDir}/local.properties`
378 | 1. `prebuiltToolchain=true` in the `cargo { ... }` block (if not overridden by `local.properties`)
379 | 1. The discovered NDK is version 19 or higher (if not overridden per above)
380 |
381 | The toolchains are rooted in a single Android NDK toolchain directory. In order of preference, the
382 | toolchain root directory is determined by:
383 |
384 | 1. `rust.androidNdkToolchainDir` in the per-(multi-)project `${rootDir}/local.properties`
385 | 1. the environment variable `ANDROID_NDK_TOOLCHAIN_DIR`
386 | 1. `${System.getProperty(java.io.tmpdir)}/rust-android-ndk-toolchains`
387 |
388 | Note that the Java system property `java.io.tmpdir` is not necessarily `/tmp`, including on macOS hosts.
389 |
390 | Each target architecture toolchain is named like `$arch-$apiLevel`: for example, `arm-16` or `arm64-21`.
391 |
392 | ## Specifying local targets
393 |
394 | When developing a project that consumes `rust-android-gradle` locally, it's often convenient to
395 | temporarily change the set of Rust target architectures. In order of preference, the plugin
396 | determines the per-project targets by:
397 |
398 | 1. `rust.targets.${project.Name}` for each project in `${rootDir}/local.properties`
399 | 1. `rust.targets` in `${rootDir}/local.properties`
400 | 1. the `cargo { targets ... }` block in the per-project `build.gradle`
401 |
402 | The targets are split on `','`. For example:
403 |
404 | ```
405 | rust.targets.library=linux-x86-64
406 | rust.targets=arm,linux-x86-64,darwin
407 | ```
408 |
409 | ## Specifying paths to sub-commands (Python, Cargo, and Rustc)
410 |
411 | The plugin invokes Python, Cargo and Rustc. In order of preference, the plugin determines what command to invoke for Python by:
412 |
413 | 1. the value of `cargo { pythonCommand = "..." }`, if non-empty
414 | 1. `rust.pythonCommand` in `${rootDir}/local.properties`
415 | 1. the environment variable `RUST_ANDROID_GRADLE_PYTHON_COMMAND`
416 | 1. the default, `python`
417 |
418 | In order of preference, the plugin determines what command to invoke for Cargo by:
419 |
420 | 1. the value of `cargo { cargoCommand = "..." }`, if non-empty
421 | 1. `rust.cargoCommand` in `${rootDir}/local.properties`
422 | 1. the environment variable `RUST_ANDROID_GRADLE_CARGO_COMMAND`
423 | 1. the default, `cargo`
424 |
425 | In order of preference, the plugin determines what command to invoke for `rustc` by:
426 |
427 | 1. the value of `cargo { rustcCommand = "..." }`, if non-empty
428 | 1. `rust.rustcCommand` in `${rootDir}/local.properties`
429 | 1. the environment variable `RUST_ANDROID_GRADLE_RUSTC_COMMAND`
430 | 1. the default, `rustc`
431 |
432 | (Note that failure to locate `rustc` is not fatal, however it may result in rebuilding the code more often than is necessary).
433 |
434 | Paths must be host operating system specific. For example, on Windows:
435 |
436 | ```properties
437 | rust.pythonCommand=c:\Python27\bin\python
438 | ```
439 |
440 | On Linux,
441 | ```shell
442 | env RUST_ANDROID_GRADLE_CARGO_COMMAND=$HOME/.cargo/bin/cargo ./gradlew ...
443 | ```
444 |
445 | ## Specifying Rust channel
446 |
447 | Rust is released to three different "channels": stable, beta, and nightly (see
448 | https://rust-lang.github.io/rustup/concepts/channels.html). The `rustup` tool, which is how most
449 | people install Rust, allows multiple channels to be installed simultaneously and to specify which
450 | channel to use by invoking `cargo +channel ...`.
451 |
452 | In order of preference, the plugin determines what channel to invoke `cargo` with by:
453 |
454 | 1. the value of `cargo { rustupChannel = "..." }`, if non-empty
455 | 1. `rust.rustupChannel` in `${rootDir}/local.properties`
456 | 1. the environment variable `RUST_ANDROID_GRADLE_RUSTUP_CHANNEL`
457 | 1. the default, no channel specified (which `cargo` installed via `rustup` generally defaults to the
458 | `stable` channel)
459 |
460 | The channel should be recognized by `cargo` installed via `rustup`, i.e.:
461 | - `"stable"`
462 | - `"beta"`
463 | - `"nightly"`
464 |
465 | A single leading `'+'` will be stripped, if present.
466 |
467 | (Note that Cargo installed by a method other than `rustup` will generally not understand `+channel`
468 | and builds will likely fail.)
469 |
470 | ## Passing arguments to cargo
471 |
472 | The plugin passes project properties named like `RUST_ANDROID_GRADLE_target_..._KEY=VALUE` through
473 | to the Cargo invocation for the given Rust `target` as `KEY=VALUE`. Target should be upper-case
474 | with "-" replaced by "_". (See [the links from this Cargo issue](https://github.com/rust-lang/cargo/issues/5690).) So, for example,
475 |
476 | ```groovy
477 | project.RUST_ANDROID_GRADLE_I686_LINUX_ANDROID_FOO=BAR
478 | ```
479 | and
480 | ```shell
481 | ./gradlew -PRUST_ANDROID_GRADLE_ARMV7_LINUX_ANDROIDEABI_FOO=BAR ...
482 | ```
483 | and
484 | ```
485 | env ORG_GRADLE_PROJECT_RUST_ANDROID_GRADLE_ARMV7_LINUX_ANDROIDEABI_FOO=BAR ./gradlew ...
486 | ```
487 | all set `FOO=BAR` in the `cargo` execution environment (for the "armv7-linux-androideabi` Rust
488 | target, corresponding to the "x86" target in the plugin).
489 |
490 | # Development
491 |
492 | At top-level, the `publish` Gradle task updates the Maven repository
493 | under `build/local-repo`:
494 |
495 | ```
496 | $ ./gradlew publish
497 | ...
498 | $ ls -al build/local-repo/org/mozilla/rust-android-gradle/org.mozilla.rust-android-gradle.gradle.plugin/0.4.0/org.mozilla.rust-android-gradle.gradle.plugin-0.4.0.pom
499 | -rw-r--r-- 1 nalexander staff 670 18 Sep 10:09
500 | build/local-repo/org/mozilla/rust-android-gradle/org.mozilla.rust-android-gradle.gradle.plugin/0.4.0/org.mozilla.rust-android-gradle.gradle.plugin-0.4.0.pom
501 | ```
502 |
503 | ## Sample projects
504 |
505 | The easiest way to get started is to run the sample projects. The sample projects have dependency
506 | substitutions configured so that changes made to `plugin/` are reflected in the sample projects
507 | immediately.
508 |
509 | ```
510 | $ ./gradlew -p samples/library :assembleDebug
511 | ...
512 | $ file samples/library/build/outputs/aar/library-debug.aar
513 | samples/library/build/outputs/aar/library-debug.aar: Zip archive data, at least v1.0 to extract
514 | ```
515 |
516 | ```
517 | $ ./gradlew -p samples/app :assembleDebug
518 | ...
519 | $ file samples/app/build/outputs/apk/debug/app-debug.apk
520 | samples/app/build/outputs/apk/debug/app-debug.apk: Zip archive data, at least v?[0] to extract
521 | ```
522 |
523 | ## Testing Local changes
524 |
525 | An easy way to locally test changes made in this plugin is to simply add this to your project's `settings.gradle`:
526 |
527 | ```gradle
528 | // Switch this to point to your local plugin dir
529 | includeBuild('../rust-android-gradle') {
530 | dependencySubstitution {
531 | // As required.
532 | substitute module('gradle.plugin.org.mozilla.rust-android-gradle:plugin') with project(':plugin')
533 | }
534 | }
535 | ```
536 |
537 | # Publishing
538 |
539 | ## Automatically via the Bump version Github Actions workflow
540 |
541 | You will need to be a collaborator. First, manually invoke the [Bump version Github Actions
542 | workflow](https://github.com/mozilla/rust-android-gradle/actions/workflows/bump.yml). Specify a
543 | version (like "x.y.z", without quotes) and a single line changelog entry. (This entry will have a
544 | dash prepended, so that it would look normal in a list. This is working around [the lack of a
545 | multi-line input in Github
546 | Actions](https://github.community/t/multiline-inputs-for-workflow-dispatch/163906).) This will push
547 | a preparatory commit updating version numbers and the changelog like [this
548 | one](https://github.com/mozilla/rust-android-gradle/commit/2a637d1797a5d0b5063b8d2f0a3d4a4938511154),
549 | and make a **draft** Github Release with a name like `vx.y.z`. After verifying that tests pass,
550 | navigate to [the releases panel](https://github.com/mozilla/rust-android-gradle/releases) and edit
551 | the release, finally pressing "Publish release". The release Github workflow will build and publish
552 | the plugin, although it may take some days for it to be reflected on the Gradle plugin portal.
553 |
554 | ## By hand
555 |
556 | You will need credentials to publish to the [Gradle plugin portal](https://plugins.gradle.org/) in
557 | the appropriate place for the [`plugin-publish`](https://plugins.gradle.org/docs/publish-plugin) to
558 | find them. Usually, that's in `~/.gradle/gradle.properties`.
559 |
560 | At top-level, the `publishPlugins` Gradle task publishes the plugin for consumption:
561 |
562 | ```
563 | $ ./gradlew publishPlugins
564 | ...
565 | Publishing plugin org.mozilla.rust-android-gradle.rust-android version 0.8.1
566 | Publishing artifact build/libs/plugin-0.8.1.jar
567 | Publishing artifact build/libs/plugin-0.8.1-sources.jar
568 | Publishing artifact build/libs/plugin-0.8.1-javadoc.jar
569 | Publishing artifact build/publish-generated-resources/pom.xml
570 | Activating plugin org.mozilla.rust-android-gradle.rust-android version 0.8.1
571 | ```
572 |
573 | ## Real projects
574 |
575 | To test in a real project, use the local Maven repository in your `build.gradle`, like:
576 |
577 | ```gradle
578 | buildscript {
579 | repositories {
580 | maven {
581 | url "file:///Users/nalexander/Mozilla/rust-android-gradle/build/local-repo"
582 | }
583 | }
584 |
585 | dependencies {
586 | classpath 'org.mozilla.rust-android-gradle:plugin:0.9.0'
587 | }
588 | }
589 | ```
590 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | Properties versionProperties = new Properties()
3 | versionProperties.load(new FileInputStream("$project.rootDir/version.properties"))
4 |
5 | ext.kotlin_version = '1.3.50'
6 | ext.agp_version = '4.0.1'
7 | ext.plugin_version = versionProperties.getProperty("version")
8 |
9 | repositories {
10 | google()
11 | maven {
12 | url "https://plugins.gradle.org/m2/"
13 | }
14 | }
15 | dependencies {
16 | classpath "com.android.tools.build:gradle:$agp_version"
17 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18 | }
19 | }
20 |
21 | subprojects {
22 | repositories {
23 | google()
24 | maven {
25 | url "https://plugins.gradle.org/m2/"
26 | }
27 | }
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
34 | wrapper {
35 | distributionType = Wrapper.DistributionType.ALL
36 | }
37 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/plugin/build.gradle:
--------------------------------------------------------------------------------
1 | import groovy.json.JsonBuilder
2 | import org.gradle.util.VersionNumber
3 |
4 | plugins {
5 | id 'com.gradle.plugin-publish' version '0.14.0'
6 | id "org.gradle.test-retry" version "1.2.0"
7 | }
8 |
9 | apply plugin: "java-gradle-plugin"
10 | apply plugin: "maven-publish"
11 | apply plugin: "groovy"
12 | apply plugin: "kotlin"
13 |
14 | gradlePlugin {
15 | plugins {
16 | rustAndroidGradlePlugin {
17 | id = 'org.mozilla.rust-android-gradle.rust-android'
18 | implementationClass = 'com.nishtahir.RustAndroidPlugin'
19 | displayName = 'Plugin for building Rust with Cargo in Android projects'
20 | description = 'A plugin that helps build Rust JNI libraries with Cargo for use in Android projects.'
21 | }
22 | }
23 | }
24 |
25 | group 'org.mozilla.rust-android-gradle'
26 | version "$plugin_version"
27 |
28 | def isCI = (System.getenv('CI') ?: 'false').toBoolean()
29 |
30 | // Maps supported Android plugin versions to the versions of Gradle that support it
31 | def supportedVersions = [
32 | "7.0.0": ["7.1.1"],
33 | "4.2.2": ["6.8.3", "7.1.1"],
34 | "4.1.3": ["6.5.1", "6.8.3"],
35 | "4.0.2": ["6.1.1", "6.8.3"],
36 | "3.6.4": ["5.6.4", "6.8.3"],
37 | "3.5.4": ["5.4.1", "5.6.4", "6.8.3"],
38 | "3.1.2": ["4.10.2"]
39 | ]
40 |
41 | // A local repo we publish our library to for testing in order to workaround limitations
42 | // in the TestKit plugin classpath.
43 | def localRepo = file("$buildDir/local-repo")
44 | publishing {
45 | repositories {
46 | maven {
47 | url = localRepo.toURI()
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 | implementation gradleApi()
54 | compileOnly "com.android.tools.build:gradle:${agp_version}"
55 |
56 | testImplementation gradleTestKit()
57 | testImplementation "com.android.tools.build:gradle:${agp_version}"
58 | testImplementation platform("org.spockframework:spock-bom:2.0-M5-groovy-3.0")
59 | testImplementation("org.spockframework:spock-core") { exclude group: 'org.codehaus.groovy' }
60 | testImplementation("org.spockframework:spock-junit4") { exclude group: 'org.codehaus.groovy' }
61 | testImplementation "org.junit.jupiter:junit-jupiter-api"
62 | }
63 |
64 | compileKotlin {
65 | kotlinOptions.jvmTarget = "1.8"
66 | }
67 | compileTestKotlin {
68 | kotlinOptions.jvmTarget = "1.8"
69 | }
70 |
71 | pluginBundle {
72 | website = 'https://github.com/mozilla/rust-android-gradle'
73 | vcsUrl = 'https://github.com/mozilla/rust-android-gradle.git'
74 | tags = ['rust', 'cargo', 'android']
75 | }
76 |
77 |
78 | // Generate a json file that contains the matrix of Gradle and AGP versions to test against.
79 | def generatedResources = "$buildDir/generated-resources/main"
80 | tasks.register('generateVersions') {
81 | def outputFile = file("$generatedResources/versions.json")
82 | inputs.property "version", version
83 | inputs.property "supportedVersions", supportedVersions
84 | outputs.dir generatedResources
85 | doLast {
86 | outputFile.text = new JsonBuilder([
87 | version: version,
88 | supportedVersions: supportedVersions
89 | ]).toPrettyString()
90 | }
91 | }
92 |
93 | sourceSets {
94 | main {
95 | output.dir(generatedResources, builtBy: tasks.named('generateVersions'))
96 | }
97 | }
98 |
99 | // This is used by github actions to split out jobs by Android version test task
100 | def generatedBuildResources = "$buildDir/build-resources"
101 | tasks.register('generateTestTasksJson') {
102 | def outputFile = file("${generatedBuildResources}/androidTestTasks.json")
103 | inputs.property "supportedVersions", supportedVersions
104 | outputs.dir generatedBuildResources
105 | doLast {
106 | outputFile.text = new JsonBuilder(
107 | // Fails in CI with issues invoking Java 11. The single test that
108 | // requires Java 11 succeeds. To be investigated in the future.
109 | // ['test'] +
110 | (supportedVersions.keySet().collect {androidVersion -> androidTestTaskName(androidVersion) })
111 | ).toString()
112 | }
113 | }
114 |
115 | // Configuration common to all test tasks
116 | tasks.withType(Test).configureEach {
117 | dependsOn publish
118 | systemProperty "local.repo", localRepo.toURI()
119 | useJUnitPlatform()
120 | retry {
121 | maxRetries = isCI ? 1 : 0
122 | maxFailures = 20
123 | }
124 | }
125 |
126 | // Generate a test task for each Android version and run the tests annotated with the MultiVersionTest category
127 | supportedVersions.keySet().each { androidVersion ->
128 | def testTaskName = androidTestTaskName(androidVersion)
129 | def jdkVersion = jdkVersionFor(androidVersion)
130 | def versionSpecificTest = tasks.register(testTaskName, Test) {
131 | description = "Runs the multi-version tests for AGP ${androidVersion} (JDK version ${jdkVersion})"
132 | group = "verification"
133 |
134 | javaLauncher = javaToolchains.launcherFor {
135 | languageVersion = jdkVersion
136 | }
137 |
138 | systemProperty 'org.gradle.android.testVersion', androidVersion
139 | }
140 |
141 | tasks.named('check').configure {
142 | dependsOn versionSpecificTest
143 | }
144 | }
145 |
146 | static def androidTestTaskName(String androidVersion) {
147 | return "testAndroid${normalizeVersion(androidVersion)}"
148 | }
149 |
150 | static def normalizeVersion(String version) {
151 | return version.replaceAll('[.\\-]', '_')
152 | }
153 |
154 | static def jdkVersionFor(String version) {
155 | def jdkVersion = VersionNumber.parse(version) > VersionNumber.parse("7.0.0-alpha01") ? 11 : 8
156 |
157 | return JavaLanguageVersion.of(jdkVersion)
158 | }
159 |
--------------------------------------------------------------------------------
/plugin/src/main/groovy/com/nishtahir/Versions.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import com.google.common.collect.ImmutableMultimap
4 | import com.google.common.collect.ImmutableSortedSet
5 | import com.google.common.collect.Multimap
6 | import groovy.json.JsonSlurper
7 | import groovy.transform.CompileStatic
8 | import groovy.transform.TypeCheckingMode
9 | import org.gradle.util.GradleVersion
10 | import org.gradle.util.VersionNumber
11 |
12 | @CompileStatic(TypeCheckingMode.SKIP)
13 | class Versions {
14 | static final VersionNumber PLUGIN_VERSION;
15 | static final Set SUPPORTED_GRADLE_VERSIONS
16 | static final Set SUPPORTED_ANDROID_VERSIONS
17 | static final Multimap SUPPORTED_VERSIONS_MATRIX
18 |
19 | static {
20 | def versions = new JsonSlurper().parse(Versions.classLoader.getResource("versions.json"))
21 | PLUGIN_VERSION = VersionNumber.parse(versions.version)
22 |
23 | def builder = ImmutableMultimap.builder()
24 | versions.supportedVersions.each { String androidVersion, List gradleVersions ->
25 | builder.putAll(android(androidVersion), gradleVersions.collect { gradle(it) })
26 | }
27 | def matrix = builder.build()
28 |
29 | SUPPORTED_VERSIONS_MATRIX = matrix
30 | SUPPORTED_ANDROID_VERSIONS = ImmutableSortedSet.copyOf(matrix.keySet())
31 | SUPPORTED_GRADLE_VERSIONS = ImmutableSortedSet.copyOf(matrix.values())
32 | }
33 |
34 | static VersionNumber android(String version) {
35 | VersionNumber.parse(version)
36 | }
37 |
38 | static GradleVersion gradle(String version) {
39 | GradleVersion.version(version)
40 | }
41 |
42 | static VersionNumber earliestMaybeSupportedAndroidVersion() {
43 | VersionNumber earliestSupported = SUPPORTED_ANDROID_VERSIONS.min()
44 | // "alpha" is lower than null
45 | return new VersionNumber(earliestSupported.major, earliestSupported.minor, 0, "alpha")
46 | }
47 |
48 | static VersionNumber latestAndroidVersion() {
49 | return SUPPORTED_ANDROID_VERSIONS.max()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt:
--------------------------------------------------------------------------------
1 | package com.nishtahir;
2 |
3 | import com.android.build.gradle.*
4 | import org.apache.tools.ant.taskdefs.condition.Os
5 | import org.gradle.api.DefaultTask
6 | import org.gradle.api.GradleException
7 | import org.gradle.api.Project
8 | import org.gradle.api.logging.LogLevel
9 | import org.gradle.api.tasks.Input
10 | import org.gradle.api.tasks.TaskAction
11 | import java.io.ByteArrayOutputStream
12 | import java.io.File
13 |
14 | open class CargoBuildTask : DefaultTask() {
15 | @Input
16 | var toolchain: Toolchain? = null
17 |
18 | @Input
19 | var ndk: Ndk? = null
20 |
21 | @Suppress("unused")
22 | @TaskAction
23 | fun build() = with(project) {
24 | extensions[CargoExtension::class].apply {
25 | // Need to capture the value to dereference smoothly.
26 | val toolchain = toolchain
27 | if (toolchain == null) {
28 | throw GradleException("toolchain cannot be null")
29 | }
30 |
31 | val ndk = ndk ?: throw GradleException("ndk cannot be null")
32 |
33 | project.plugins.all {
34 | when (it) {
35 | is AppPlugin -> buildProjectForTarget(project, toolchain, ndk, this)
36 | is LibraryPlugin -> buildProjectForTarget(project, toolchain, ndk, this)
37 | }
38 | }
39 | // CARGO_TARGET_DIR can be used to force the use of a global, shared target directory
40 | // across all rust projects on a machine. Use it if it's set, otherwise use the
41 | // configured `targetDirectory` value, and fall back to `${module}/target`.
42 | //
43 | // We also allow this to be specified in `local.properties`, not because this is
44 | // something you should ever need to do currently, but we don't want it to ruin anyone's
45 | // day if it turns out we're wrong about that.
46 | val target =
47 | getProperty("rust.cargoTargetDir", "CARGO_TARGET_DIR")
48 | ?: targetDirectory
49 | ?: "${module!!}/target"
50 |
51 | val defaultTargetTriple = getDefaultTargetTriple(project, rustcCommand)
52 |
53 | var cargoOutputDir = File(if (toolchain.target == defaultTargetTriple) {
54 | "${target}/${profile}"
55 | } else {
56 | "${target}/${toolchain.target}/${profile}"
57 | })
58 | if (!cargoOutputDir.isAbsolute) {
59 | cargoOutputDir = File(project.project.projectDir, cargoOutputDir.path)
60 | }
61 | cargoOutputDir = cargoOutputDir.canonicalFile
62 |
63 | val intoDir = File(buildDir, "rustJniLibs/${toolchain.folder}")
64 | intoDir.mkdirs()
65 |
66 | copy { spec ->
67 | spec.from(cargoOutputDir)
68 | spec.into(intoDir)
69 |
70 | // Need to capture the value to dereference smoothly.
71 | val targetIncludes = targetIncludes
72 | if (targetIncludes != null) {
73 | spec.include(targetIncludes.asIterable())
74 | } else {
75 | // It's safe to unwrap, since we bailed at configuration time if this is unset.
76 | val libname = libname!!
77 | spec.include("lib${libname}.so")
78 | spec.include("lib${libname}.dylib")
79 | spec.include("${libname}.dll")
80 | }
81 | }
82 | }
83 | }
84 |
85 | inline fun buildProjectForTarget(project: Project, toolchain: Toolchain, ndk: Ndk, cargoExtension: CargoExtension) {
86 | val apiLevel = cargoExtension.apiLevels[toolchain.platform]!!
87 | val defaultTargetTriple = getDefaultTargetTriple(project, cargoExtension.rustcCommand)
88 |
89 | project.exec { spec ->
90 | with(spec) {
91 | standardOutput = System.out
92 | val module = File(cargoExtension.module!!)
93 | if (module.isAbsolute) {
94 | workingDir = module
95 | } else {
96 | workingDir = File(project.project.projectDir, module.path)
97 | }
98 | workingDir = workingDir.canonicalFile
99 |
100 | val theCommandLine = mutableListOf(cargoExtension.cargoCommand)
101 |
102 | if (!cargoExtension.rustupChannel.isEmpty()) {
103 | val hasPlusSign = cargoExtension.rustupChannel.startsWith("+")
104 | val maybePlusSign = if (!hasPlusSign) "+" else ""
105 |
106 | theCommandLine.add(maybePlusSign + cargoExtension.rustupChannel)
107 | }
108 |
109 | theCommandLine.add("build")
110 |
111 | // Respect `verbose` if it is set; otherwise, log if asked to
112 | // with `--info` or `--debug` from the command line.
113 | if (cargoExtension.verbose ?: project.logger.isEnabled(LogLevel.INFO)) {
114 | theCommandLine.add("--verbose")
115 | }
116 |
117 | val features = cargoExtension.featureSpec.features
118 | // We just pass this along to cargo as something space separated... AFAICT
119 | // you're allowed to have featureSpec with spaces in them, but I don't think
120 | // there's a way to specify them in the cargo command line -- rustc accepts
121 | // them if passed in directly with `--cfg`, and cargo will pass them to rustc
122 | // if you use them as default featureSpec.
123 | when (features) {
124 | is Features.All -> {
125 | theCommandLine.add("--all-features")
126 | }
127 | is Features.DefaultAnd -> {
128 | if (!features.featureSet.isEmpty()) {
129 | theCommandLine.add("--features")
130 | theCommandLine.add(features.featureSet.joinToString(" "))
131 | }
132 | }
133 | is Features.NoDefaultBut -> {
134 | theCommandLine.add("--no-default-features")
135 | if (!features.featureSet.isEmpty()) {
136 | theCommandLine.add("--features")
137 | theCommandLine.add(features.featureSet.joinToString(" "))
138 | }
139 | }
140 | }
141 |
142 | if (cargoExtension.profile != "debug") {
143 | // Cargo is rigid: it accepts "--release" for release (and
144 | // nothing for dev). This is a cheap way of allowing only
145 | // two values.
146 | theCommandLine.add("--${cargoExtension.profile}")
147 | }
148 | if (toolchain.target != defaultTargetTriple) {
149 | // Only providing --target for the non-default targets means desktop builds
150 | // can share the build cache with `cargo build`/`cargo test`/etc invocations,
151 | // instead of requiring a large amount of redundant work.
152 | theCommandLine.add("--target=${toolchain.target}")
153 | }
154 |
155 | // Target-specific environment configuration, passed through to
156 | // the underlying `cargo build` invocation.
157 | val toolchain_target = toolchain.target.toUpperCase().replace('-', '_')
158 | val prefix = "RUST_ANDROID_GRADLE_TARGET_${toolchain_target}_"
159 |
160 | // For ORG_GRADLE_PROJECT_RUST_ANDROID_GRADLE_TARGET_x_KEY=VALUE, set KEY=VALUE.
161 | project.logger.info("Passing through project properties with prefix '${prefix}' (environment variables with prefix 'ORG_GRADLE_PROJECT_${prefix}'")
162 | project.properties.forEach { (key, value) ->
163 | if (key.startsWith(prefix)) {
164 | val realKey = key.substring(prefix.length)
165 | project.logger.debug("Passing through environment variable '${key}' as '${realKey}=${value}'")
166 | environment(realKey, value)
167 | }
168 | }
169 |
170 | // Cross-compiling to Android requires toolchain massaging.
171 | if (toolchain.type != ToolchainType.DESKTOP) {
172 | val ndkPath = ndk.path
173 | val ndkVersionMajor = ndk.versionMajor
174 |
175 | val toolchainDirectory = if (toolchain.type == ToolchainType.ANDROID_PREBUILT) {
176 | environment("CARGO_NDK_MAJOR_VERSION", ndkVersionMajor)
177 |
178 | val hostTag = if (Os.isFamily(Os.FAMILY_WINDOWS)) {
179 | if (Os.isArch("x86_64") || Os.isArch("amd64")) {
180 | "windows-x86_64"
181 | } else {
182 | "windows"
183 | }
184 | } else if (Os.isFamily(Os.FAMILY_MAC)) {
185 | "darwin-x86_64"
186 | } else {
187 | "linux-x86_64"
188 | }
189 | File("$ndkPath/toolchains/llvm/prebuilt", hostTag)
190 | } else {
191 | cargoExtension.toolchainDirectory
192 | }
193 |
194 | val linker_wrapper =
195 | if (System.getProperty("os.name").startsWith("Windows")) {
196 | File(project.rootProject.buildDir, "linker-wrapper/linker-wrapper.bat")
197 | } else {
198 | File(project.rootProject.buildDir, "linker-wrapper/linker-wrapper.sh")
199 | }
200 | environment("CARGO_TARGET_${toolchain_target}_LINKER", linker_wrapper.path)
201 |
202 | val cc = File(toolchainDirectory, "${toolchain.cc(apiLevel)}").path;
203 | val cxx = File(toolchainDirectory, "${toolchain.cxx(apiLevel)}").path;
204 | val ar = File(toolchainDirectory, "${toolchain.ar(apiLevel, ndkVersionMajor)}").path;
205 |
206 | // For build.rs in `cc` consumers: like "CC_i686-linux-android". See
207 | // https://github.com/alexcrichton/cc-rs#external-configuration-via-environment-variables.
208 | environment("CC_${toolchain.target}", cc)
209 | environment("CXX_${toolchain.target}", cxx)
210 | environment("AR_${toolchain.target}", ar)
211 |
212 | // Set CLANG_PATH in the environment, so that bindgen (or anything
213 | // else using clang-sys in a build.rs) works properly, and doesn't
214 | // use host headers and such.
215 | val shouldConfigure = cargoExtension.getFlagProperty(
216 | "rust.autoConfigureClangSys",
217 | "RUST_ANDROID_GRADLE_AUTO_CONFIGURE_CLANG_SYS",
218 | // By default, only do this for non-desktop platforms. If we're
219 | // building for desktop, things should work out of the box.
220 | toolchain.type != ToolchainType.DESKTOP
221 | )
222 | if (shouldConfigure) {
223 | environment("CLANG_PATH", cc)
224 | }
225 |
226 | // Configure our linker wrapper.
227 | environment("RUST_ANDROID_GRADLE_PYTHON_COMMAND", cargoExtension.pythonCommand)
228 | environment("RUST_ANDROID_GRADLE_LINKER_WRAPPER_PY",
229 | File(project.rootProject.buildDir, "linker-wrapper/linker-wrapper.py").path)
230 | environment("RUST_ANDROID_GRADLE_CC", cc)
231 | if (cargoExtension.generateBuildId) {
232 | environment("RUST_ANDROID_GRADLE_CC_LINK_ARG", "-Wl,--build-id,-soname,lib${cargoExtension.libname!!}.so")
233 | } else {
234 | environment("RUST_ANDROID_GRADLE_CC_LINK_ARG", "-Wl,-soname,lib${cargoExtension.libname!!}.so")
235 | }
236 | }
237 |
238 | cargoExtension.extraCargoBuildArguments?.let {
239 | theCommandLine.addAll(it)
240 | }
241 |
242 | commandLine = theCommandLine
243 | }
244 | if (cargoExtension.exec != null) {
245 | (cargoExtension.exec!!)(spec, toolchain)
246 | }
247 | }.assertNormalExitValue()
248 | }
249 | }
250 |
251 | // This can't be private/internal as it's called from `buildProjectForTarget`.
252 | fun getDefaultTargetTriple(project: Project, rustc: String): String? {
253 | val stdout = ByteArrayOutputStream()
254 | val result = project.exec { spec ->
255 | spec.standardOutput = stdout
256 | spec.commandLine = listOf(rustc, "--version", "--verbose")
257 | }
258 | if (result.exitValue != 0) {
259 | project.logger.warn(
260 | "Failed to get default target triple from rustc (exit code: ${result.exitValue})")
261 | return null
262 | }
263 | val output = stdout.toString()
264 |
265 | // The `rustc --version --verbose` output contains a number of lines like `key: value`.
266 | // We're only interested in `host: `, which corresponds to the default target triple.
267 | val triplePrefix = "host: "
268 |
269 | val triple = output.split("\n")
270 | .find { it.startsWith(triplePrefix) }
271 | ?.let { it.substring(triplePrefix.length).trim() }
272 |
273 | if (triple == null) {
274 | project.logger.warn("Failed to parse `rustc -Vv` output! (Please report a rust-android-gradle bug)")
275 | } else {
276 | project.logger.info("Default rust target triple: $triple")
277 | }
278 | return triple
279 | }
280 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/nishtahir/CargoExtension.kt:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.api.Action
4 | import org.gradle.api.GradleException
5 | import org.gradle.api.Project
6 | import org.gradle.process.ExecSpec
7 | import java.io.File
8 | import java.util.*
9 |
10 | sealed class Features {
11 | class All() : Features()
12 |
13 | data class DefaultAnd(val featureSet: Set) : Features()
14 |
15 | data class NoDefaultBut(val featureSet: Set) : Features()
16 | }
17 |
18 | data class FeatureSpec(var features: Features? = null) {
19 | fun all() {
20 | this.features = Features.All()
21 | }
22 |
23 | fun defaultAnd(featureSet: Array) {
24 | this.features = Features.DefaultAnd(featureSet.toSet())
25 | }
26 |
27 | fun noDefaultBut(featureSet: Array) {
28 | this.features = Features.NoDefaultBut(featureSet.toSet())
29 | }
30 | }
31 |
32 | // `CargoExtension` is documented in README.md.
33 | open class CargoExtension {
34 | lateinit var localProperties: Properties
35 |
36 | var module: String? = null
37 | var libname: String? = null
38 | var targets: List? = null
39 | var prebuiltToolchains: Boolean? = null
40 | var profile: String = "debug"
41 | var verbose: Boolean? = null
42 | var targetDirectory: String? = null
43 | var targetIncludes: Array? = null
44 | var apiLevel: Int? = null
45 | var apiLevels: Map = mapOf()
46 | var extraCargoBuildArguments: List? = null
47 | var generateBuildId: Boolean = false
48 |
49 | // It would be nice to use a receiver here, but there are problems interoperating with Groovy
50 | // and Kotlin that are just not worth working out. Another JVM language, yet another dynamic
51 | // invoke solution :(
52 | var exec: ((ExecSpec, Toolchain) -> Unit)? = null
53 |
54 | var featureSpec: FeatureSpec = FeatureSpec()
55 |
56 | fun features(action: Action) {
57 | action.execute(featureSpec)
58 | }
59 |
60 | val toolchainDirectory: File
61 | get() {
62 | // Share a single toolchain directory, if one is configured. Prefer "local.properties"
63 | // to "ANDROID_NDK_TOOLCHAIN_DIR" to "$TMP/rust-android-ndk-toolchains".
64 | val local: String? = localProperties.getProperty("rust.androidNdkToolchainDir")
65 | if (local != null) {
66 | return File(local).absoluteFile
67 | }
68 |
69 | val globalDir: String? = System.getenv("ANDROID_NDK_TOOLCHAIN_DIR")
70 | if (globalDir != null) {
71 | return File(globalDir).absoluteFile
72 | }
73 |
74 | var defaultDir = File(System.getProperty("java.io.tmpdir"), "rust-android-ndk-toolchains")
75 | return defaultDir.absoluteFile
76 | }
77 |
78 | var cargoCommand: String = ""
79 | get() {
80 | return if (!field.isEmpty()) {
81 | field
82 | } else {
83 | getProperty("rust.cargoCommand", "RUST_ANDROID_GRADLE_CARGO_COMMAND") ?: "cargo"
84 | }
85 | }
86 |
87 | var rustupChannel: String = ""
88 | get() {
89 | return if (!field.isEmpty()) {
90 | field
91 | } else {
92 | getProperty("rust.rustupChannel", "RUST_ANDROID_GRADLE_RUSTUP_CHANNEL") ?: ""
93 | }
94 | }
95 |
96 | var pythonCommand: String = ""
97 | get() {
98 | return if (!field.isEmpty()) {
99 | field
100 | } else {
101 | getProperty("rust.pythonCommand", "RUST_ANDROID_GRADLE_PYTHON_COMMAND") ?: "python"
102 | }
103 | }
104 |
105 | // Required so that we can parse the default triple out of `rustc --version --verbose`. Sadly,
106 | // there seems to be no way to get this information out of cargo directly. Failure to locate
107 | // this isn't fatal, however.
108 | var rustcCommand: String = ""
109 | get() {
110 | return if (!field.isEmpty()) {
111 | field
112 | } else {
113 | getProperty("rust.rustcCommand", "RUST_ANDROID_GRADLE_RUSTC_COMMAND") ?: "rustc"
114 | }
115 | }
116 |
117 | fun getFlagProperty(camelCaseName: String, snakeCaseName: String, ifUnset: Boolean): Boolean {
118 | val propVal = getProperty(camelCaseName, snakeCaseName)
119 | if (propVal == "1" || propVal == "true") {
120 | return true
121 | }
122 | if (propVal == "0" || propVal == "false") {
123 | return false
124 | }
125 | if (propVal == null || propVal == "") {
126 | return ifUnset
127 | }
128 | throw GradleException("Illegal value for property \"$camelCaseName\" / \"$snakeCaseName\". Must be 0/1/true/false if set")
129 | }
130 |
131 | internal fun getProperty(camelCaseName: String, snakeCaseName: String): String? {
132 | val local: String? = localProperties.getProperty(camelCaseName)
133 | if (local != null) {
134 | return local
135 | }
136 | val global: String? = System.getenv(snakeCaseName)
137 | if (global != null) {
138 | return global
139 | }
140 | return null
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/nishtahir/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.api.plugins.ExtensionContainer
4 | import kotlin.reflect.KClass
5 |
6 | operator fun ExtensionContainer.get(type: KClass): T = getByType(type.java)
7 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/nishtahir/GenerateLinkerWrapperTask.kt:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.api.tasks.Sync
4 |
5 | open class GenerateLinkerWrapperTask : Sync() {
6 | }
7 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/nishtahir/GenerateToolchainsTask.kt:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import java.io.File
4 |
5 | import com.android.build.gradle.*
6 | import org.gradle.api.DefaultTask
7 | import org.gradle.api.GradleException
8 | import org.gradle.api.Project
9 | import org.gradle.api.tasks.TaskAction
10 |
11 | open class GenerateToolchainsTask : DefaultTask() {
12 |
13 | @TaskAction
14 | @Suppress("unused")
15 | fun generateToolchainTask() {
16 | project.plugins.all {
17 | when (it) {
18 | is AppPlugin -> configureTask(project)
19 | is LibraryPlugin -> configureTask(project)
20 | }
21 | }
22 | }
23 |
24 | inline fun configureTask(project: Project) {
25 | val cargoExtension = project.extensions[CargoExtension::class]
26 | val app = project.extensions[T::class]
27 | val ndkPath = app.ndkDirectory
28 |
29 | // It's safe to unwrap, since we bailed at configuration time if this is unset.
30 | val targets = cargoExtension.targets!!
31 |
32 | toolchains
33 | .filter { it.type == ToolchainType.ANDROID_GENERATED }
34 | .filter { (arch) -> targets.contains(arch) }
35 | .forEach { (arch) ->
36 | // We ensure all architectures have an API level at configuration time
37 | val apiLevel = cargoExtension.apiLevels[arch]!!
38 |
39 | if (arch.endsWith("64") && apiLevel < 21) {
40 | throw GradleException("Can't target 64-bit ${arch} with API level < 21 (${apiLevel})")
41 | }
42 |
43 | // Always regenerate the toolchain, even if it exists
44 | // already. It is fast to do so and fixes any issues
45 | // with partially reclaimed temporary files.
46 | val dir = File(cargoExtension.toolchainDirectory, arch + "-" + apiLevel)
47 | project.exec { spec ->
48 | spec.standardOutput = System.out
49 | spec.errorOutput = System.out
50 | spec.commandLine(cargoExtension.pythonCommand)
51 | spec.args("$ndkPath/build/tools/make_standalone_toolchain.py",
52 | "--arch=$arch",
53 | "--api=$apiLevel",
54 | "--install-dir=${dir}",
55 | "--force")
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/nishtahir/RustAndroidPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import com.android.build.gradle.*
4 | import org.gradle.api.DefaultTask
5 | import org.gradle.api.GradleException
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.api.file.DuplicatesStrategy
9 | import java.io.File
10 | import java.util.Properties
11 |
12 | const val RUST_TASK_GROUP = "rust"
13 |
14 | enum class ToolchainType {
15 | ANDROID_PREBUILT,
16 | ANDROID_GENERATED,
17 | DESKTOP,
18 | }
19 |
20 | // See https://forge.rust-lang.org/platform-support.html.
21 | val toolchains = listOf(
22 | Toolchain("linux-x86-64",
23 | ToolchainType.DESKTOP,
24 | "x86_64-unknown-linux-gnu",
25 | "",
26 | "",
27 | "desktop/linux-x86-64"),
28 | // This should eventually go away: the darwin-x86-64 target will supersede it.
29 | // https://github.com/mozilla/rust-android-gradle/issues/77
30 | Toolchain("darwin",
31 | ToolchainType.DESKTOP,
32 | "x86_64-apple-darwin",
33 | "",
34 | "",
35 | "desktop/darwin"),
36 | Toolchain("darwin-x86-64",
37 | ToolchainType.DESKTOP,
38 | "x86_64-apple-darwin",
39 | "",
40 | "",
41 | "desktop/darwin-x86-64"),
42 | Toolchain("darwin-aarch64",
43 | ToolchainType.DESKTOP,
44 | "aarch64-apple-darwin",
45 | "",
46 | "",
47 | "desktop/darwin-aarch64"),
48 | Toolchain("win32-x86-64-msvc",
49 | ToolchainType.DESKTOP,
50 | "x86_64-pc-windows-msvc",
51 | "",
52 | "",
53 | "desktop/win32-x86-64"),
54 | Toolchain("win32-x86-64-gnu",
55 | ToolchainType.DESKTOP,
56 | "x86_64-pc-windows-gnu",
57 | "",
58 | "",
59 | "desktop/win32-x86-64"),
60 | Toolchain("arm",
61 | ToolchainType.ANDROID_GENERATED,
62 | "armv7-linux-androideabi",
63 | "arm-linux-androideabi",
64 | "arm-linux-androideabi",
65 | "android/armeabi-v7a"),
66 | Toolchain("arm64",
67 | ToolchainType.ANDROID_GENERATED,
68 | "aarch64-linux-android",
69 | "aarch64-linux-android",
70 | "aarch64-linux-android",
71 | "android/arm64-v8a"),
72 | Toolchain("x86",
73 | ToolchainType.ANDROID_GENERATED,
74 | "i686-linux-android",
75 | "i686-linux-android",
76 | "i686-linux-android",
77 | "android/x86"),
78 | Toolchain("x86_64",
79 | ToolchainType.ANDROID_GENERATED,
80 | "x86_64-linux-android",
81 | "x86_64-linux-android",
82 | "x86_64-linux-android",
83 | "android/x86_64"),
84 | Toolchain("arm",
85 | ToolchainType.ANDROID_PREBUILT,
86 | "armv7-linux-androideabi", // This is correct. "Note: For 32-bit ARM, the compiler is prefixed with
87 | "armv7a-linux-androideabi", // armv7a-linux-androideabi, but the binutils tools are prefixed with
88 | "arm-linux-androideabi", // arm-linux-androideabi. For other architectures, the prefixes are the same
89 | "android/armeabi-v7a"), // for all tools." (Ref: https://developer.android.com/ndk/guides/other_build_systems#overview )
90 | Toolchain("arm64",
91 | ToolchainType.ANDROID_PREBUILT,
92 | "aarch64-linux-android",
93 | "aarch64-linux-android",
94 | "aarch64-linux-android",
95 | "android/arm64-v8a"),
96 | Toolchain("x86",
97 | ToolchainType.ANDROID_PREBUILT,
98 | "i686-linux-android",
99 | "i686-linux-android",
100 | "i686-linux-android",
101 | "android/x86"),
102 | Toolchain("x86_64",
103 | ToolchainType.ANDROID_PREBUILT,
104 | "x86_64-linux-android",
105 | "x86_64-linux-android",
106 | "x86_64-linux-android",
107 | "android/x86_64")
108 | )
109 |
110 | data class Ndk(val path: File, val version: String) {
111 | val versionMajor: Int
112 | get() = version.split(".").first().toInt()
113 | }
114 |
115 | data class Toolchain(val platform: String,
116 | val type: ToolchainType,
117 | val target: String,
118 | val compilerTriple: String,
119 | val binutilsTriple: String,
120 | val folder: String) {
121 | fun cc(apiLevel: Int): File =
122 | if (System.getProperty("os.name").startsWith("Windows")) {
123 | if (type == ToolchainType.ANDROID_PREBUILT) {
124 | File("bin", "$compilerTriple$apiLevel-clang.cmd")
125 | } else {
126 | File("$platform-$apiLevel/bin", "$compilerTriple-clang.cmd")
127 | }
128 | } else {
129 | if (type == ToolchainType.ANDROID_PREBUILT) {
130 | File("bin", "$compilerTriple$apiLevel-clang")
131 | } else {
132 | File("$platform-$apiLevel/bin", "$compilerTriple-clang")
133 | }
134 | }
135 |
136 | fun cxx(apiLevel: Int): File =
137 | if (System.getProperty("os.name").startsWith("Windows")) {
138 | if (type == ToolchainType.ANDROID_PREBUILT) {
139 | File("bin", "$compilerTriple$apiLevel-clang++.cmd")
140 | } else {
141 | File("$platform-$apiLevel/bin", "$compilerTriple-clang++.cmd")
142 | }
143 | } else {
144 | if (type == ToolchainType.ANDROID_PREBUILT) {
145 | File("bin", "$compilerTriple$apiLevel-clang++")
146 | } else {
147 | File("$platform-$apiLevel/bin", "$compilerTriple-clang++")
148 | }
149 | }
150 |
151 | fun ar(apiLevel: Int, ndkVersionMajor: Int): File =
152 | if (ndkVersionMajor >= 23) {
153 | File("bin", "llvm-ar")
154 | } else if (type == ToolchainType.ANDROID_PREBUILT) {
155 | File("bin", "$binutilsTriple-ar")
156 | } else {
157 | File("$platform-$apiLevel/bin", "$binutilsTriple-ar")
158 | }
159 | }
160 |
161 | @Suppress("unused")
162 | open class RustAndroidPlugin : Plugin {
163 | internal lateinit var cargoExtension: CargoExtension
164 |
165 | override fun apply(project: Project) {
166 | with(project) {
167 | cargoExtension = extensions.create("cargo", CargoExtension::class.java)
168 |
169 | afterEvaluate {
170 | plugins.all {
171 | when (it) {
172 | is AppPlugin -> configurePlugin(this)
173 | is LibraryPlugin -> configurePlugin(this)
174 | }
175 | }
176 | }
177 |
178 | }
179 | }
180 |
181 | private inline fun configurePlugin(project: Project) = with(project) {
182 | cargoExtension.localProperties = Properties()
183 |
184 | val localPropertiesFile = File(project.rootDir, "local.properties")
185 | if (localPropertiesFile.exists()) {
186 | cargoExtension.localProperties.load(localPropertiesFile.inputStream())
187 | }
188 |
189 | if (cargoExtension.module == null) {
190 | throw GradleException("module cannot be null")
191 | }
192 |
193 | if (cargoExtension.libname == null) {
194 | throw GradleException("libname cannot be null")
195 | }
196 |
197 | // Allow to set targets, including per-project, in local.properties.
198 | val localTargets: String? =
199 | cargoExtension.localProperties.getProperty("rust.targets.${project.name}") ?:
200 | cargoExtension.localProperties.getProperty("rust.targets")
201 | if (localTargets != null) {
202 | cargoExtension.targets = localTargets.split(',').map { it.trim() }
203 | }
204 |
205 | if (cargoExtension.targets == null) {
206 | throw GradleException("targets cannot be null")
207 | }
208 |
209 | // Ensure that an API level is specified for all targets
210 | val apiLevel = cargoExtension.apiLevel
211 | if (cargoExtension.apiLevels.size > 0) {
212 | if (apiLevel != null) {
213 | throw GradleException("Cannot set both `apiLevel` and `apiLevels`")
214 | }
215 | } else {
216 | val default = if (apiLevel != null) {
217 | apiLevel
218 | } else {
219 | extensions[T::class].defaultConfig.minSdkVersion!!.apiLevel
220 | }
221 | cargoExtension.apiLevels = cargoExtension.targets!!.map { it to default }.toMap()
222 | }
223 | val missingApiLevelTargets = cargoExtension.targets!!.toSet().minus(
224 | cargoExtension.apiLevels.keys)
225 | if (missingApiLevelTargets.size > 0) {
226 | throw GradleException("`apiLevels` missing entries for: $missingApiLevelTargets")
227 | }
228 |
229 | extensions[T::class].apply {
230 | sourceSets.getByName("main").jniLibs.srcDir(File("$buildDir/rustJniLibs/android"))
231 | sourceSets.getByName("test").resources.srcDir(File("$buildDir/rustJniLibs/desktop"))
232 | }
233 |
234 | // Determine the NDK version, if present
235 | val ndk = extensions[T::class].ndkDirectory.let {
236 | val ndkSourceProperties = Properties()
237 | val ndkSourcePropertiesFile = File(it, "source.properties")
238 | if (ndkSourcePropertiesFile.exists()) {
239 | ndkSourceProperties.load(ndkSourcePropertiesFile.inputStream())
240 | }
241 | val ndkVersion = ndkSourceProperties.getProperty("Pkg.Revision", "0.0")
242 | Ndk(path = it, version = ndkVersion)
243 | }
244 |
245 | // Determine whether to use prebuilt or generated toolchains
246 | val usePrebuilt =
247 | cargoExtension.localProperties.getProperty("rust.prebuiltToolchains")?.equals("true") ?:
248 | cargoExtension.prebuiltToolchains ?:
249 | (ndk.versionMajor >= 19);
250 |
251 | if (usePrebuilt && ndk.versionMajor < 19) {
252 | throw GradleException("usePrebuilt = true requires NDK version 19+")
253 | }
254 |
255 | val generateToolchain = if (!usePrebuilt) {
256 | tasks.maybeCreate("generateToolchains",
257 | GenerateToolchainsTask::class.java).apply {
258 | group = RUST_TASK_GROUP
259 | description = "Generate standard toolchain for given architectures"
260 | }
261 | } else {
262 | null
263 | }
264 |
265 | // Fish linker wrapper scripts from our Java resources.
266 | val generateLinkerWrapper = rootProject.tasks.maybeCreate("generateLinkerWrapper", GenerateLinkerWrapperTask::class.java).apply {
267 | group = RUST_TASK_GROUP
268 | description = "Generate shared linker wrapper script"
269 | }
270 |
271 | generateLinkerWrapper.apply {
272 | // From https://stackoverflow.com/a/320595.
273 | from(rootProject.zipTree(File(RustAndroidPlugin::class.java.protectionDomain.codeSource.location.toURI()).path))
274 | include("**/linker-wrapper*")
275 | into(File(rootProject.buildDir, "linker-wrapper"))
276 | eachFile {
277 | it.path = it.path.replaceFirst("com/nishtahir", "")
278 | }
279 | fileMode = 493 // 0755 in decimal; Kotlin doesn't have octal literals (!).
280 | includeEmptyDirs = false
281 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
282 | }
283 |
284 | val buildTask = tasks.maybeCreate("cargoBuild",
285 | DefaultTask::class.java).apply {
286 | group = RUST_TASK_GROUP
287 | description = "Build library (all targets)"
288 | }
289 |
290 | cargoExtension.targets!!.forEach { target ->
291 | val theToolchain = toolchains
292 | .filter {
293 | if (usePrebuilt) {
294 | it.type != ToolchainType.ANDROID_GENERATED
295 | } else {
296 | it.type != ToolchainType.ANDROID_PREBUILT
297 | }
298 | }
299 | .find { it.platform == target }
300 | if (theToolchain == null) {
301 | throw GradleException("Target ${target} is not recognized (recognized targets: ${toolchains.map { it.platform }.sorted()}). Check `local.properties` and `build.gradle`.")
302 | }
303 |
304 | val targetBuildTask = tasks.maybeCreate("cargoBuild${target.capitalize()}",
305 | CargoBuildTask::class.java).apply {
306 | group = RUST_TASK_GROUP
307 | description = "Build library ($target)"
308 | toolchain = theToolchain
309 | this.ndk = ndk
310 | }
311 |
312 | if (!usePrebuilt) {
313 | targetBuildTask.dependsOn(generateToolchain!!)
314 | }
315 | targetBuildTask.dependsOn(generateLinkerWrapper)
316 | buildTask.dependsOn(targetBuildTask)
317 | }
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/com/nishtahir/linker-wrapper.bat:
--------------------------------------------------------------------------------
1 | "%RUST_ANDROID_GRADLE_PYTHON_COMMAND%" "%RUST_ANDROID_GRADLE_LINKER_WRAPPER_PY%" %*
2 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/com/nishtahir/linker-wrapper.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, print_function, unicode_literals
2 |
3 | import os
4 | import shlex
5 | import subprocess
6 | import sys
7 |
8 | args = [
9 | os.environ["RUST_ANDROID_GRADLE_CC"],
10 | os.environ["RUST_ANDROID_GRADLE_CC_LINK_ARG"],
11 | ] + sys.argv[1:]
12 |
13 |
14 | def update_in_place(arglist):
15 | # The `gcc` library is not included starting from NDK version 23.
16 | # Work around by using `unwind` replacement.
17 | ndk_major_version = os.environ["CARGO_NDK_MAJOR_VERSION"]
18 | if ndk_major_version.isdigit():
19 | if 23 <= int(ndk_major_version):
20 | for i, arg in enumerate(arglist):
21 | if arg.startswith("-lgcc"):
22 | # This is one way to preserve line endings.
23 | arglist[i] = "-lunwind" + arg[len("-lgcc") :]
24 |
25 |
26 | update_in_place(args)
27 |
28 | for arg in args:
29 | if arg.startswith("@"):
30 | fileargs = open(arg[1:], "r").read().splitlines(keepends=True)
31 | update_in_place(fileargs)
32 | open(arg[1:], "w").write("".join(fileargs))
33 |
34 |
35 | # This only appears when the subprocess call fails, but it's helpful then.
36 | printable_cmd = " ".join(shlex.quote(arg) for arg in args)
37 | print(printable_cmd)
38 |
39 | sys.exit(subprocess.call(args))
40 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/com/nishtahir/linker-wrapper.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Invoke linker-wrapper.py with the correct Python command.
4 | "${RUST_ANDROID_GRADLE_PYTHON_COMMAND}" "${RUST_ANDROID_GRADLE_LINKER_WRAPPER_PY}" "$@"
5 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/AbstractTest.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.testkit.runner.GradleRunner
4 | import org.junit.Rule
5 | import org.junit.rules.TemporaryFolder
6 | import spock.lang.Specification
7 |
8 | class AbstractTest extends Specification {
9 | @Rule TemporaryFolder temporaryFolder
10 | File cacheDir
11 |
12 | def setup() {
13 | cacheDir = temporaryFolder.newFolder()
14 | }
15 |
16 | def withGradleVersion(String gradleVersion) {
17 | GradleRunner.create()
18 | .withGradleVersion(gradleVersion)
19 | .forwardOutput()
20 | .withDebug(false)
21 | }
22 |
23 | File file(String path) {
24 | return new File(temporaryFolder.root, path)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/CargoBuildTest.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.api.GradleException
4 | import org.gradle.testkit.runner.BuildResult
5 | import org.gradle.testkit.runner.TaskOutcome
6 | import spock.lang.Unroll
7 |
8 | import com.nishtahir.Versions
9 |
10 |
11 | @MultiVersionTest
12 | class CargoBuildTest extends AbstractTest {
13 | @Unroll
14 | def "cargoBuild is invoked with #gradleVersion and Android plugin #androidVersion"() {
15 | given:
16 | SimpleAndroidApp.builder(temporaryFolder.root, cacheDir)
17 | .withAndroidVersion(androidVersion)
18 | .withKotlinDisabled()
19 | // TODO: .withCargo(...)
20 | .build()
21 | .writeProject()
22 |
23 | SimpleCargoProject.builder(temporaryFolder.root)
24 | .withTargets(["x86_64"])
25 | .build()
26 | .writeProject()
27 |
28 | when:
29 | BuildResult buildResult = withGradleVersion(gradleVersion.version)
30 | .withProjectDir(temporaryFolder.root)
31 | .withArguments('cargoBuild', '--info', '--stacktrace')
32 | // .withDebug(true)
33 | .build()
34 |
35 | // To ease debugging.
36 | temporaryFolder.root.eachFileRecurse {
37 | println(it)
38 | }
39 |
40 | then:
41 | buildResult.task(':app:cargoBuild').outcome == TaskOutcome.SUCCESS
42 | buildResult.task(':library:cargoBuild').outcome == TaskOutcome.SUCCESS
43 | new File(temporaryFolder.root, "app/build/rustJniLibs/android/x86_64/librust.so").exists()
44 | new File(temporaryFolder.root, "library/build/rustJniLibs/android/x86_64/librust.so").exists()
45 |
46 | where:
47 | [androidVersion, gradleVersion] << TestVersions.allCandidateTestVersions.entries().collect { [it.key, it.value] }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/CargoTargetTest.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.testkit.runner.BuildResult
4 | import org.gradle.testkit.runner.TaskOutcome
5 | import spock.lang.Unroll
6 |
7 | import com.nishtahir.Versions
8 |
9 | class CargoTargetTest extends AbstractTest {
10 | @Unroll
11 | def "cargoBuild produces #location for target #target"() {
12 | given:
13 | def androidVersion = TestVersions.latestAndroidVersionForCurrentJDK()
14 |
15 | SimpleAndroidApp.builder(temporaryFolder.root, cacheDir)
16 | .withAndroidVersion(androidVersion)
17 | .withKotlinDisabled()
18 | // TODO: .withCargo(...)
19 | .build()
20 | .writeProject()
21 |
22 | SimpleCargoProject.builder(temporaryFolder.root)
23 | .withTargets([target])
24 | .build()
25 | .writeProject()
26 |
27 | when:
28 | BuildResult buildResult = withGradleVersion(TestVersions.latestSupportedGradleVersionFor(androidVersion).version)
29 | .withProjectDir(temporaryFolder.root)
30 | .withArguments('cargoBuild', '--info', '--stacktrace')
31 | // .withDebug(true)
32 | .build()
33 |
34 | // To ease debugging.
35 | temporaryFolder.root.eachFileRecurse {
36 | println(it)
37 | }
38 |
39 | then:
40 | buildResult.task(':app:cargoBuild').outcome == TaskOutcome.SUCCESS
41 | buildResult.task(':library:cargoBuild').outcome == TaskOutcome.SUCCESS
42 | new File(temporaryFolder.root, "app/build/rustJniLibs/${location}").exists()
43 | new File(temporaryFolder.root, "library/build/rustJniLibs/${location}").exists()
44 |
45 | where:
46 | [target, location] << [
47 | // Sadly, cross-compiling to macOS targets fails at this time: see
48 | // https://github.com/rust-lang/rust/issues/84984.
49 | // ["darwin", "desktop/darwin/librust.dylib"],
50 | // And so does cross-compiling from macOS to Linux targets.
51 | // ["linux-x86-64", "desktop/linux-x86-64/librust.so"],
52 | ["arm64", "android/arm64-v8a/librust.so"],
53 | ["x86_64", "android/x86_64/librust.so"],
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/MultiVersionTest.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import java.lang.annotation.ElementType
4 | import java.lang.annotation.Inherited
5 | import java.lang.annotation.Retention
6 | import java.lang.annotation.RetentionPolicy
7 | import java.lang.annotation.Target
8 |
9 | /**
10 | * Represents tests that span multiple versions of Android Gradle Plugin and need to be executed
11 | * with multiple versions of the JDK.
12 | */
13 | @Inherited
14 | @Retention(RetentionPolicy.RUNTIME)
15 | @Target(ElementType.TYPE)
16 | @interface MultiVersionTest { }
17 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/NdkVersionTest.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.testkit.runner.BuildResult
4 | import org.gradle.testkit.runner.TaskOutcome
5 | import spock.lang.Unroll
6 |
7 | import com.nishtahir.Versions
8 |
9 | class NdkVersionTest extends AbstractTest {
10 | @Unroll
11 | def "cargoBuild works with Android NDK version #ndkVersion"() {
12 | given:
13 | def androidVersion = TestVersions.latestAndroidVersionForCurrentJDK()
14 | def target = "x86_64"
15 | def location = "android/x86_64/librust.so"
16 |
17 | SimpleAndroidApp.builder(temporaryFolder.root, cacheDir)
18 | .withAndroidVersion(androidVersion)
19 | .withNdkVersion(ndkVersion)
20 | .withKotlinDisabled()
21 | // TODO: .withCargo(...)
22 | .build()
23 | .writeProject()
24 |
25 | SimpleCargoProject.builder(temporaryFolder.root)
26 | .withTargets([target])
27 | .build()
28 | .writeProject()
29 |
30 | // To ease debugging.
31 | temporaryFolder.root.eachFileRecurse {
32 | System.err.println("before> ${it}")
33 | if (it.path.endsWith(".gradle")) {
34 | System.err.println(it.text)
35 | }
36 | }
37 |
38 | when:
39 | BuildResult buildResult = withGradleVersion(TestVersions.latestSupportedGradleVersionFor(androidVersion).version)
40 | .withProjectDir(temporaryFolder.root)
41 | .withArguments('cargoBuild', '--info', '--stacktrace')
42 | // .withDebug(true)
43 | .build()
44 |
45 | // To ease debugging.
46 | temporaryFolder.root.eachFileRecurse {
47 | System.err.println("after> ${it}")
48 | }
49 |
50 | then:
51 | buildResult.task(':app:cargoBuild').outcome == TaskOutcome.SUCCESS
52 | buildResult.task(':library:cargoBuild').outcome == TaskOutcome.SUCCESS
53 | new File(temporaryFolder.root, "app/build/rustJniLibs/${location}").exists()
54 | new File(temporaryFolder.root, "library/build/rustJniLibs/${location}").exists()
55 |
56 | where:
57 | ndkVersion << [
58 | // Partial list of NDK versions supported by Github Actions, per
59 | // https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md
60 | "26.3.11579264",
61 | "27.2.12479018",
62 | ]
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/SimpleAndroidApp.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import org.gradle.api.GradleException
4 | import org.gradle.util.VersionNumber
5 |
6 | import static com.nishtahir.Versions.android
7 |
8 | class SimpleAndroidApp {
9 | final File projectDir
10 | private final File cacheDir
11 | final VersionNumber androidVersion
12 | final VersionNumber ndkVersion
13 | final VersionNumber kotlinVersion
14 | private final boolean kotlinEnabled
15 | private final boolean kaptWorkersEnabled
16 |
17 | private SimpleAndroidApp(File projectDir, File cacheDir, VersionNumber androidVersion, VersionNumber ndkVersion, VersionNumber kotlinVersion, boolean kotlinEnabled, boolean kaptWorkersEnabled) {
18 | this.projectDir = projectDir
19 | this.cacheDir = cacheDir
20 | this.androidVersion = androidVersion
21 | this.ndkVersion = ndkVersion
22 | this.kotlinVersion = kotlinVersion
23 | this.kotlinEnabled = kotlinEnabled
24 | this.kaptWorkersEnabled = kaptWorkersEnabled
25 | }
26 |
27 | def writeProject() {
28 | def app = 'app'
29 | def appPackage = 'org.gradle.android.example.app'
30 | def appActivity = 'AppActivity'
31 |
32 | def library = 'library'
33 | def libPackage = 'org.gradle.android.example.library'
34 | def libraryActivity = 'LibraryActivity'
35 |
36 | file("settings.gradle") << """
37 | buildCache {
38 | local {
39 | directory = "${cacheDir.absolutePath.replace(File.separatorChar, '/' as char)}"
40 | }
41 | }
42 | """.stripIndent()
43 |
44 | file("build.gradle") << """
45 | buildscript {
46 | repositories {
47 | google()
48 | mavenCentral()
49 | maven {
50 | url = "${System.getProperty("local.repo")}"
51 | }
52 | }
53 | dependencies {
54 | classpath ('com.android.tools.build:gradle:$androidVersion') { force = true }
55 | classpath "org.mozilla.rust-android-gradle:plugin:${Versions.PLUGIN_VERSION}"
56 | ${kotlinPluginDependencyIfEnabled}
57 | }
58 | }
59 | """.stripIndent()
60 |
61 | writeActivity(library, libPackage, libraryActivity)
62 | file("${library}/src/main/AndroidManifest.xml") << """
63 |
65 |
66 | """.stripIndent()
67 |
68 | writeActivity(app, appPackage, appActivity)
69 | file("${app}/src/main/AndroidManifest.xml") << """
70 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
82 |
83 |
86 |
87 |
88 |
89 |
90 | """.stripIndent()
91 | file("${app}/src/main/res/values/strings.xml") << '''
92 |
93 | Android Gradle
94 | '''.stripIndent()
95 |
96 | file('settings.gradle') << """
97 | include ':${app}'
98 | include ':${library}'
99 | """.stripIndent()
100 |
101 | file("${app}/build.gradle") << subprojectConfiguration("com.android.application") << """
102 | android.defaultConfig.applicationId "org.gradle.android.test.app"
103 | """.stripIndent() << activityDependency() <<
104 | """
105 | dependencies {
106 | implementation project(':${library}')
107 | }
108 | """.stripIndent()
109 |
110 | file("${library}/build.gradle") << subprojectConfiguration("com.android.library") << activityDependency()
111 |
112 | file("gradle.properties") << """
113 | android.useAndroidX=true
114 | org.gradle.jvmargs=-Xmx2048m
115 | kapt.use.worker.api=${kaptWorkersEnabled}
116 | """.stripIndent()
117 |
118 | configureAndroidSdkHome()
119 | }
120 |
121 | private String getKotlinPluginDependencyIfEnabled() {
122 | return kotlinEnabled ? """
123 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
124 | """ : ""
125 | }
126 |
127 | private subprojectConfiguration(String androidPlugin) {
128 | """
129 | apply plugin: "$androidPlugin"
130 | ${kotlinPluginsIfEnabled}
131 | apply plugin: "org.mozilla.rust-android-gradle.rust-android"
132 |
133 | repositories {
134 | google()
135 | mavenCentral()
136 | }
137 |
138 | dependencies {
139 | ${kotlinDependenciesIfEnabled}
140 | }
141 |
142 | android {
143 | ${maybeNdkVersion}
144 | compileSdkVersion 28
145 | buildToolsVersion "29.0.3"
146 | defaultConfig {
147 | minSdkVersion 28
148 | targetSdkVersion 28
149 |
150 | lintOptions {
151 | checkReleaseBuilds false
152 | }
153 | }
154 | }
155 | """.stripIndent()
156 | }
157 |
158 | private String getMaybeNdkVersion() {
159 | def isAndroid34x = androidVersion >= android("3.4.0")
160 | if (isAndroid34x) {
161 | return """ndkVersion '${ndkVersion}'"""
162 | } else {
163 | return ""
164 | }
165 | }
166 |
167 | private String getKotlinPluginsIfEnabled() {
168 | return kotlinEnabled ? """
169 | apply plugin: "kotlin-android"
170 | apply plugin: "kotlin-kapt"
171 | """ : ""
172 | }
173 |
174 | private String getKotlinDependenciesIfEnabled() {
175 | return kotlinEnabled ? """
176 | implementation "org.jetbrains.kotlin:kotlin-stdlib"
177 | """ : ""
178 | }
179 |
180 | private writeActivity(String basedir, String packageName, String className) {
181 | String resourceName = className.toLowerCase()
182 |
183 | file("${basedir}/src/main/java/${packageName.replaceAll('\\.', '/')}/${className}.java") << """
184 | package ${packageName};
185 |
186 | import org.joda.time.LocalTime;
187 |
188 | import android.app.Activity;
189 | import android.os.Bundle;
190 | import android.widget.TextView;
191 |
192 | public class ${className} extends Activity {
193 |
194 | @Override
195 | public void onCreate(Bundle savedInstanceState) {
196 | super.onCreate(savedInstanceState);
197 | setContentView(R.layout.${resourceName}_layout);
198 | }
199 |
200 | @Override
201 | public void onStart() {
202 | super.onStart();
203 | LocalTime currentTime = new LocalTime();
204 | TextView textView = (TextView) findViewById(R.id.text_view);
205 | textView.setText("The current local time is: " + currentTime);
206 | }
207 | }
208 | """.stripIndent()
209 |
210 | file("${basedir}/src/test/java/${packageName.replaceAll('\\.', '/')}/JavaUserTest.java") << """
211 | package ${packageName};
212 |
213 | public class JavaUserTest {
214 | }
215 | """.stripIndent()
216 |
217 | file("${basedir}/src/main/res/layout/${resourceName}_layout.xml") << '''
218 |
223 |
228 |
229 | '''.stripIndent()
230 |
231 | file("${basedir}/src/main/rs/${resourceName}.rs") << '''
232 | #pragma version(1)
233 | #pragma rs java_package_name(com.example.myapplication)
234 |
235 | static void addintAccum(int *accum, int val) {
236 | *accum += val;
237 | }
238 | '''.stripIndent()
239 | }
240 |
241 | private static String activityDependency() {
242 | """
243 | dependencies {
244 | implementation 'joda-time:joda-time:2.7'
245 | }
246 | """.stripIndent()
247 | }
248 |
249 | private void configureAndroidSdkHome() {
250 | file('local.properties').text = ""
251 | def env = System.getenv("ANDROID_HOME")
252 | if (!env) {
253 | def androidSdkHome = new File("${System.getProperty("user.home")}/Library/Android/sdk")
254 | file('local.properties').text += "sdk.dir=${androidSdkHome.absolutePath.replace(File.separatorChar, '/' as char)}"
255 | }
256 | // def env = System.getenv("ANDROID_NDK_HOME")
257 | // if (!env) {
258 | // def androidNdkHome = new File("${System.getProperty("user.home")}/Library/Android/sdk")
259 | // file('local.properties').text += "sdk.dir=${androidSdkHome.absolutePath.replace(File.separatorChar, '/' as char)}"
260 | // }
261 | }
262 |
263 | def file(String path) {
264 | def file = new File(projectDir, path)
265 | file.parentFile.mkdirs()
266 | return file
267 | }
268 |
269 | static Builder builder(File projectDir, File cacheDir) {
270 | return new Builder(projectDir, cacheDir)
271 | }
272 |
273 | static class Builder {
274 | boolean kotlinEnabled = true
275 | boolean kaptWorkersEnabled = true
276 |
277 | VersionNumber androidVersion = Versions.latestAndroidVersion()
278 | VersionNumber ndkVersion = Versions.latestAndroidVersion() >= android("3.4.0") ? VersionNumber.parse("26.3.11579264") : null
279 |
280 | VersionNumber kotlinVersion = VersionNumber.parse("1.3.72")
281 | File projectDir
282 | File cacheDir
283 |
284 | Builder(File projectDir, File cacheDir) {
285 | this.projectDir = projectDir
286 | this.cacheDir = cacheDir
287 | }
288 |
289 | Builder withKotlinDisabled() {
290 | this.kotlinEnabled = false
291 | return this
292 | }
293 |
294 | Builder withKotlinVersion(VersionNumber kotlinVersion) {
295 | this.kotlinVersion = kotlinVersion
296 | return this
297 | }
298 |
299 | Builder withKaptWorkersDisabled() {
300 | this.kaptWorkersEnabled = false
301 | return this
302 | }
303 |
304 | Builder withAndroidVersion(VersionNumber androidVersion) {
305 | this.androidVersion = androidVersion
306 | if (this.androidVersion < android("3.4.0")) {
307 | this.ndkVersion = null
308 | }
309 | return this
310 | }
311 |
312 | Builder withAndroidVersion(String androidVersion) {
313 | return withAndroidVersion(android(androidVersion))
314 | }
315 |
316 | Builder withNdkVersion(VersionNumber ndkVersion) {
317 | this.ndkVersion = ndkVersion
318 | return this
319 | }
320 |
321 | Builder withNdkVersion(String ndkVersion) {
322 | return withNdkVersion(VersionNumber.parse(ndkVersion))
323 | }
324 |
325 | Builder withProjectDir(File projectDir) {
326 | this.projectDir = projectDir
327 | return this
328 | }
329 |
330 | Builder withCacheDir(File cacheDir) {
331 | this.cacheDir = cacheDir
332 | return this
333 | }
334 |
335 | SimpleAndroidApp build() {
336 | return new SimpleAndroidApp(projectDir, cacheDir, androidVersion, ndkVersion, kotlinVersion, kotlinEnabled, kaptWorkersEnabled)
337 | }
338 | }
339 | }
340 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/SimpleCargoProject.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | class SimpleCargoProject {
4 | File projectDir
5 | List targets
6 |
7 | SimpleCargoProject(File projectDir, List targets) {
8 | this.projectDir = projectDir
9 | this.targets = targets
10 | }
11 |
12 | static class Builder {
13 | File projectDir
14 | List targets
15 |
16 | Builder(File projectDir) {
17 | this.projectDir = projectDir
18 | }
19 |
20 | def withTargets(targets) {
21 | this.targets = targets
22 | return this
23 | }
24 |
25 | def build() {
26 | if (targets.isEmpty()) {
27 | throw new IllegalStateException("No targets provided")
28 | }
29 | return new SimpleCargoProject(this.projectDir, this.targets)
30 | }
31 | }
32 |
33 | static def builder(File projectDir) {
34 | return new Builder(projectDir)
35 | }
36 |
37 | def file(String path) {
38 | def file = new File(projectDir, path)
39 | file.parentFile.mkdirs()
40 | return file
41 | }
42 |
43 | def writeProject() {
44 | def cargoModuleFile = new File(this.class.classLoader.getResource("rust/Cargo.toml").path).parentFile
45 | def targetDirectoryFile = new File(cargoModuleFile.parentFile, "target")
46 |
47 | // On Windows, path components are backslash-separated. We need to
48 | // express the path as Groovy source, which means backslashes need to be
49 | // escaped. The easiest way is to replace backslashes with forward
50 | // slashes.
51 | def module = cargoModuleFile.path.replace("\\", "/")
52 | def targetDirectory = targetDirectoryFile.path.replace("\\", "/")
53 |
54 | def targetStrings = targets.collect({"\"${it}\"" }).join(", ")
55 |
56 | file('app/build.gradle') << """
57 | cargo {
58 | module = "${module}"
59 | targetDirectory = "${targetDirectory}"
60 | targets = [${targetStrings}]
61 | libname = "rust"
62 | }
63 | """.stripIndent()
64 |
65 | file('library/build.gradle') << """
66 | cargo {
67 | module = "${module}"
68 | targetDirectory = "${targetDirectory}"
69 | targets = [${targetStrings}]
70 | libname = "rust"
71 | }
72 | """.stripIndent()
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/plugin/src/test/groovy/com/nishtahir/TestVersions.groovy:
--------------------------------------------------------------------------------
1 | package com.nishtahir
2 |
3 | import com.google.common.collect.ImmutableMultimap
4 | import com.google.common.collect.Multimap
5 | import org.gradle.util.GradleVersion
6 | import org.gradle.util.VersionNumber
7 |
8 |
9 | class TestVersions {
10 | static Multimap getAllCandidateTestVersions() {
11 | def testedVersion = System.getProperty('org.gradle.android.testVersion')
12 | if (testedVersion) {
13 | return ImmutableMultimap.copyOf(Versions.SUPPORTED_VERSIONS_MATRIX.entries().findAll {it.key == VersionNumber.parse(testedVersion) })
14 | } else {
15 | return Versions.SUPPORTED_VERSIONS_MATRIX
16 | }
17 | }
18 |
19 | static VersionNumber latestAndroidVersionForCurrentJDK() {
20 | String currentJDKVersion = System.getProperty("java.version");
21 | if (currentJDKVersion.startsWith("1.")) {
22 | return allCandidateTestVersions.keySet().findAll {it < VersionNumber.parse("7.0.0-alpha01")}.max()
23 | }
24 | return allCandidateTestVersions.keySet().max()
25 | }
26 |
27 | static GradleVersion latestGradleVersion() {
28 | return allCandidateTestVersions.values().max()
29 | }
30 |
31 | static GradleVersion latestSupportedGradleVersionFor(String androidVersion) {
32 | return latestSupportedGradleVersionFor(VersionNumber.parse(androidVersion))
33 | }
34 |
35 | static GradleVersion latestSupportedGradleVersionFor(VersionNumber androidVersion) {
36 | return allCandidateTestVersions.asMap().find {it.key.major == androidVersion.major && it.key.minor == androidVersion.minor }?.value?.max()
37 | }
38 |
39 | static VersionNumber getLatestVersionForAndroid(String version) {
40 | VersionNumber versionNumber = VersionNumber.parse(version)
41 | return allCandidateTestVersions.keySet().findAll { it.major == versionNumber.major && it.minor == versionNumber.minor }?.max()
42 | }
43 |
44 | static List getLatestAndroidVersions() {
45 | def minorVersions = allCandidateTestVersions.keySet().collect { "${it.major}.${it.minor}" }
46 | return minorVersions.collect { getLatestVersionForAndroid(it) }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/plugin/src/test/resources/SpockConfig.groovy:
--------------------------------------------------------------------------------
1 | import com.nishtahir.MultiVersionTest
2 |
3 | def testAndroidVersion = System.getProperty('org.gradle.android.testVersion')
4 |
5 | runner {
6 | if (testAndroidVersion) {
7 | include MultiVersionTest
8 | } else {
9 | exclude MultiVersionTest
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/plugin/src/test/resources/rust/.cargo/config:
--------------------------------------------------------------------------------
1 | [build]
2 | target-dir = "../target"
3 |
--------------------------------------------------------------------------------
/plugin/src/test/resources/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust"
3 | version = "0.2.0"
4 | authors = ["Nick Alexander "]
5 |
6 | [dependencies]
7 | jni = "0.5.2"
8 |
9 | [lib]
10 | crate_type = ["staticlib", "dylib"]
11 |
12 | [features]
13 | default = ["foo"]
14 | foo = []
15 | bar = []
16 |
--------------------------------------------------------------------------------
/plugin/src/test/resources/rust/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate jni;
2 |
3 | use std::ffi::CString;
4 | use std::os::raw::c_char;
5 |
6 | use jni::JNIEnv;
7 | use jni::objects::{JClass, JObject, JValue};
8 |
9 | pub type Callback = unsafe extern "C" fn(*const c_char) -> ();
10 |
11 | #[no_mangle]
12 | #[allow(non_snake_case)]
13 | pub extern "C" fn invokeCallbackViaJNA(callback: Callback) {
14 | let s = CString::new("Hello from Rust").unwrap();
15 | unsafe { callback(s.as_ptr()); }
16 | }
17 |
18 | #[no_mangle]
19 | #[allow(non_snake_case)]
20 | pub extern "C" fn Java_com_nishtahir_androidrust_MainActivity_invokeCallbackViaJNI(
21 | env: JNIEnv,
22 | _class: JClass,
23 | callback: JObject
24 | ) {
25 | let s = String::from("Hello from Rust");
26 | let response = env.new_string(&s)
27 | .expect("Couldn't create java string!");
28 | env.call_method(callback, "callback", "(Ljava/lang/String;)V",
29 | &[JValue::from(JObject::from(response))]).unwrap();
30 | }
31 |
--------------------------------------------------------------------------------
/samples/app/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.agp_version = '7.0.0'
3 | repositories {
4 | google()
5 | maven {
6 | url "https://plugins.gradle.org/m2/"
7 | }
8 | }
9 | dependencies {
10 | classpath "com.android.tools.build:gradle:$agp_version"
11 | classpath 'org.mozilla.rust-android-gradle:rust-android:+'
12 | }
13 | }
14 |
15 | apply plugin: 'com.android.application'
16 | apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
17 |
18 | android {
19 | compileSdkVersion 27
20 | ndkVersion "26.3.11579264"
21 |
22 | defaultConfig {
23 | applicationId "com.nishtahir.androidrust"
24 | minSdkVersion 21
25 | targetSdkVersion 27
26 | versionCode 1
27 | versionName "1.0"
28 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
29 | }
30 | buildTypes {
31 | release {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | }
37 |
38 | cargo {
39 | module = "../rust"
40 | targets = ["x86_64", "arm64"]
41 | libname = "rust"
42 | }
43 |
44 | repositories {
45 | google()
46 | mavenCentral()
47 | }
48 |
49 | dependencies {
50 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
51 | exclude group: 'com.android.support', module: 'support-annotations'
52 | })
53 | implementation 'com.android.support:appcompat-v7:27.1.1'
54 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
55 | testImplementation 'junit:junit:4.12'
56 | }
57 |
58 | afterEvaluate {
59 | // The `cargoBuild` task isn't available until after evaluation.
60 | android.applicationVariants.all { variant ->
61 | def productFlavor = ""
62 | variant.productFlavors.each {
63 | productFlavor += "${it.name.capitalize()}"
64 | }
65 | def buildType = "${variant.buildType.name.capitalize()}"
66 | tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"])
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/samples/app/settings.gradle:
--------------------------------------------------------------------------------
1 | includeBuild('../..') {
2 | dependencySubstitution {
3 | // As required.
4 | substitute module('org.mozilla.rust-android-gradle:rust-android') with project(':plugin')
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/samples/app/src/androidTest/java/com/nishtahir/androidrust/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.nishtahir.androidrust", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/samples/app/src/main/java/com/nishtahir/androidrust/JNICallback.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | public interface JNICallback {
4 | public void callback(String string);
5 | }
6 |
--------------------------------------------------------------------------------
/samples/app/src/main/java/com/nishtahir/androidrust/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.util.Log;
6 | import android.widget.TextView;
7 |
8 | public class MainActivity extends AppCompatActivity implements JNICallback {
9 |
10 | private static final String TAG = "MainActivity";
11 |
12 | // Used to load the 'rust' library on application startup.
13 | static {
14 | System.loadLibrary("rust");
15 | }
16 |
17 | TextView textView;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 | textView = (TextView) findViewById(R.id.sample_text);
24 |
25 | invokeCallbackViaJNI(this);
26 | }
27 |
28 | /**
29 | * A native method that is implemented by the 'rust' native library,
30 | * which is packaged with this application.
31 | */
32 | public static native void invokeCallbackViaJNI(JNICallback callback);
33 |
34 | @Override
35 | public void callback(String string) {
36 | textView.append("From JNI: " + string + "\n");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidRust
3 |
4 |
--------------------------------------------------------------------------------
/samples/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/samples/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/samples/library/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.agp_version = '7.0.0'
3 | repositories {
4 | google()
5 | maven {
6 | url "https://plugins.gradle.org/m2/"
7 | }
8 | }
9 | dependencies {
10 | classpath "com.android.tools.build:gradle:$agp_version"
11 | classpath 'org.mozilla.rust-android-gradle:rust-android:+'
12 | }
13 | }
14 |
15 | apply plugin: 'com.android.library'
16 | apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
17 |
18 | android {
19 | compileSdkVersion 27
20 | ndkVersion "26.3.11579264"
21 |
22 | defaultConfig {
23 | minSdkVersion 21
24 | targetSdkVersion 27
25 | versionCode 1
26 | versionName "1.0"
27 |
28 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
29 | }
30 | buildTypes {
31 | release {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | }
37 |
38 | cargo {
39 | module = "../rust"
40 | targets = ["x86_64", "arm64"]
41 | libname = "rust"
42 |
43 | features {
44 | defaultAnd "foo", "bar"
45 | noDefaultBut("foo", "bar")
46 | all()
47 | }
48 |
49 | exec = { spec, toolchain ->
50 | spec.environment("TEST", "test")
51 | }
52 | }
53 |
54 | repositories {
55 | google()
56 | mavenCentral()
57 | }
58 |
59 | dependencies {
60 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
61 | exclude group: 'com.android.support', module: 'support-annotations'
62 | })
63 | implementation 'com.android.support:appcompat-v7:27.1.1'
64 | testImplementation 'junit:junit:4.12'
65 | }
66 |
67 | afterEvaluate {
68 | // The `cargoBuild` task isn't available until after evaluation.
69 | android.libraryVariants.all { variant ->
70 | def productFlavor = ""
71 | variant.productFlavors.each {
72 | productFlavor += "${it.name.capitalize()}"
73 | }
74 | def buildType = "${variant.buildType.name.capitalize()}"
75 | tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"])
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/samples/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/nishtahir/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/samples/library/settings.gradle:
--------------------------------------------------------------------------------
1 | includeBuild('../..') {
2 | dependencySubstitution {
3 | // As required.
4 | substitute module('org.mozilla.rust-android-gradle:rust-android') with project(':plugin')
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/samples/library/src/androidTest/java/com/nishtahir/library/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.library;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.nishtahir.library.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/samples/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | library
3 |
4 |
--------------------------------------------------------------------------------
/samples/rust/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | **/*.rs.bk
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/samples/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust"
3 | version = "0.2.0"
4 | authors = ["Nick Alexander "]
5 |
6 | [dependencies]
7 | jni = "0.5.2"
8 |
9 | [lib]
10 | crate_type = ["staticlib", "dylib"]
11 |
12 | [features]
13 | default = ["foo"]
14 | foo = []
15 | bar = []
16 |
17 |
--------------------------------------------------------------------------------
/samples/rust/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate jni;
2 |
3 | use std::ffi::CString;
4 | use std::os::raw::c_char;
5 |
6 | use jni::JNIEnv;
7 | use jni::objects::{JClass, JObject, JValue};
8 |
9 | pub type Callback = unsafe extern "C" fn(*const c_char) -> ();
10 |
11 | #[no_mangle]
12 | #[allow(non_snake_case)]
13 | pub extern "C" fn invokeCallbackViaJNA(callback: Callback) {
14 | let s = CString::new("Hello from Rust").unwrap();
15 | unsafe { callback(s.as_ptr()); }
16 | }
17 |
18 | #[no_mangle]
19 | #[allow(non_snake_case)]
20 | pub extern "C" fn Java_com_nishtahir_androidrust_MainActivity_invokeCallbackViaJNI(
21 | env: JNIEnv,
22 | _class: JClass,
23 | callback: JObject
24 | ) {
25 | let s = String::from("Hello from Rust");
26 | let response = env.new_string(&s)
27 | .expect("Couldn't create java string!");
28 | env.call_method(callback, "callback", "(Ljava/lang/String;)V",
29 | &[JValue::from(JObject::from(response))]).unwrap();
30 | }
31 |
--------------------------------------------------------------------------------
/samples/unittest/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.agp_version = '4.0.1'
3 | repositories {
4 | google()
5 | maven {
6 | url "https://plugins.gradle.org/m2/"
7 | }
8 | }
9 | dependencies {
10 | classpath "com.android.tools.build:gradle:${agp_version}"
11 | classpath 'org.mozilla.rust-android-gradle:rust-android:+'
12 | }
13 | }
14 |
15 | apply plugin: 'com.android.application'
16 | apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
17 |
18 | android {
19 | compileSdkVersion 27
20 | defaultConfig {
21 | applicationId "com.nishtahir.androidrust"
22 | minSdkVersion 21
23 | targetSdkVersion 27
24 | versionCode 1
25 | versionName "1.0"
26 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
27 | }
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
32 | }
33 | }
34 | ndkVersion "22.1.7171670"
35 | sourceSets {
36 | test.resources.srcDirs += "$buildDir/rustJniLibs/desktop"
37 | }
38 | }
39 |
40 | cargo {
41 | module = "../rust"
42 | targets = ["x86_64", "linux-x86-64"] // "x86", "x86_64", "arm64"]
43 | libname = "rust"
44 | }
45 |
46 | repositories {
47 | google()
48 | }
49 |
50 |
51 | configurations {
52 | // There's an interaction between Gradle's resolution of dependencies with different types
53 | // (@jar, @aar) for `implementation` and `testImplementation` and with Android Studio's built-in
54 | // JUnit test runner. The runtime classpath in the built-in JUnit test runner gets the
55 | // dependency from the `implementation`, which is type @aar, and therefore the JNA dependency
56 | // doesn't provide the JNI dispatch libraries in the correct Java resource directories. I think
57 | // what's happening is that @aar type in `implementation` resolves to the @jar type in
58 | // `testImplementation`, and that it wins the dependency resolution battle.
59 | //
60 | // A workaround is to add a new configuration which depends on the @jar type and to reference
61 | // the underlying JAR file directly in `testImplementation`. This JAR file doesn't resolve to
62 | // the @aar type in `implementation`. This works when invoked via `gradle`, but also sets the
63 | // correct runtime classpath when invoked with Android Studio's built-in JUnit test runner.
64 | // Success!
65 | jnaForTest
66 | }
67 |
68 | dependencies {
69 | jnaForTest "net.java.dev.jna:jna:5.6.0@jar"
70 | implementation "net.java.dev.jna:jna:5.6.0@aar"
71 |
72 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
73 | exclude group: 'com.android.support', module: 'support-annotations'
74 | })
75 | implementation 'com.android.support:appcompat-v7:27.1.1'
76 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
77 | testImplementation 'junit:junit:4.12'
78 |
79 | // For reasons unknown, resolving the jnaForTest configuration directly
80 | // trips a nasty issue with the Android-Gradle plugin 3.2.1, like `Cannot
81 | // change attributes of configuration ':PROJECT:kapt' after it has been
82 | // resolved`. I think that the configuration is being made a
83 | // super-configuration of the testImplementation and then the `.files` is
84 | // causing it to be resolved. Cloning first dissociates the configuration,
85 | // avoiding other configurations from being resolved. Tricky!
86 | testImplementation files(configurations.jnaForTest.copyRecursive().files)
87 | // testImplementation "androidx.test.ext:junit:$versions.androidx_junit"
88 | testImplementation "org.robolectric:robolectric:4.2.1"
89 | }
90 |
91 | afterEvaluate {
92 | // The `cargoBuild` task isn't available until after evaluation.
93 | android.applicationVariants.all { variant ->
94 | def productFlavor = ""
95 | variant.productFlavors.each {
96 | productFlavor += "${it.name.capitalize()}"
97 | }
98 | def buildType = "${variant.buildType.name.capitalize()}"
99 | tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"])
100 |
101 | // Don't merge the jni lib folders until after the Rust libraries have been built.
102 | tasks["merge${productFlavor}${buildType}JniLibFolders"].dependsOn(tasks["cargoBuild"])
103 |
104 | // For unit tests.
105 | tasks["process${productFlavor}${buildType}UnitTestJavaRes"].dependsOn(tasks["cargoBuild"])
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/samples/unittest/gradle.properties:
--------------------------------------------------------------------------------
1 | android.useAndroidX=true
2 |
--------------------------------------------------------------------------------
/samples/unittest/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/samples/unittest/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/samples/unittest/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/samples/unittest/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/samples/unittest/settings.gradle:
--------------------------------------------------------------------------------
1 | includeBuild('../..') {
2 | dependencySubstitution {
3 | // As required.
4 | substitute module('org.mozilla.rust-android-gradle:rust-android') with project(':plugin')
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/samples/unittest/src/androidTest/java/com/nishtahir/androidrust/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.nishtahir.androidrust", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/java/com/nishtahir/androidrust/JNACallback.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | import com.sun.jna.Callback;
4 |
5 | public interface JNACallback extends Callback {
6 | public void invoke(String string);
7 | }
8 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/java/com/nishtahir/androidrust/JNICallback.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | public interface JNICallback {
4 | public void callback(String string);
5 | }
6 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/java/com/nishtahir/androidrust/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.nishtahir.androidrust;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.util.Log;
6 | import android.widget.TextView;
7 |
8 | import com.sun.jna.Library;
9 | import com.sun.jna.Native;
10 | import com.sun.jna.NativeLibrary;
11 |
12 | public class MainActivity extends AppCompatActivity implements JNACallback, JNICallback {
13 |
14 | private static final String TAG = "MainActivity";
15 |
16 | public interface RustLibrary extends Library {
17 | RustLibrary INSTANCE = (RustLibrary)
18 | Native.load("rust", RustLibrary.class);
19 |
20 | int invokeCallbackViaJNA(JNACallback callback);
21 | }
22 |
23 | static {
24 | // On Android, this can be just:
25 | // System.loadLibrary("rust");
26 | // But when running as a unit test, we need to fish the libraries from
27 | // Java resources and configure the classpath. We use JNA for that.
28 | NativeLibrary LIBRARY = NativeLibrary.getInstance("rust");
29 | System.load(LIBRARY.getFile().getPath());
30 | }
31 |
32 | TextView textView;
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 | textView = (TextView) findViewById(R.id.sample_text);
39 |
40 | invokeCallbackViaJNA(this);
41 | invokeCallbackViaJNI(this);
42 | }
43 |
44 | /**
45 | * A native method that is implemented by the 'rust' native library,
46 | * which is packaged with this application.
47 | */
48 | public static native void invokeCallbackViaJNI(JNICallback callback);
49 |
50 | public static void invokeCallbackViaJNA(JNACallback callback) {
51 | RustLibrary.INSTANCE.invokeCallbackViaJNA(callback);
52 | }
53 |
54 | @Override
55 | public void invoke(String string) {
56 | textView.append("From JNA: " + string + "\n");
57 | }
58 |
59 | @Override
60 | public void callback(String string) {
61 | textView.append("From JNI: " + string + "\n");
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/rust-android-gradle/898d9fd4fc4e2b25ad1c84cbed38962d6ad5e0b2/samples/unittest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidRust
3 |
4 |
--------------------------------------------------------------------------------
/samples/unittest/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/samples/unittest/src/test/java/com/nishtahir/androidrust/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | package com.nishtahir.androidrust;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.robolectric.RobolectricTestRunner;
10 | import org.junit.Assert;
11 |
12 | @RunWith(RobolectricTestRunner.class)
13 | public class ExampleUnitTest implements JNACallback, JNICallback {
14 | private String callbackValue;
15 |
16 | @Override
17 | public void invoke(String value) {
18 | // Here's the JNA version.
19 | callbackValue = "From JNA: " + value;
20 | }
21 |
22 | @Override
23 | public void callback(String value) {
24 | // Here's the JNI version.
25 | callbackValue = "From JNI: " + value;
26 | }
27 |
28 | @Test
29 | public void testViaJNI() {
30 | MainActivity.invokeCallbackViaJNI(this);
31 | Assert.assertEquals("From JNI: Hello from Rust", callbackValue);
32 | }
33 |
34 | @Test
35 | public void testViaJNA() {
36 | MainActivity.invokeCallbackViaJNA(this);
37 | Assert.assertEquals("From JNA: Hello from Rust", callbackValue);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'rust-android'
2 | include ':plugin'
3 |
--------------------------------------------------------------------------------
/version.properties:
--------------------------------------------------------------------------------
1 | version=0.9.6
2 |
--------------------------------------------------------------------------------