├── .github ├── actions │ ├── androidTest │ │ └── action.yml │ └── commonSetup │ │ └── action.yml └── workflows │ ├── buildtestapks.yml │ ├── main.yml │ └── run-device-tests.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── build.gradle.kts ├── buildPlugin ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── birbit │ └── ksqlite │ └── build │ ├── CollectNativeLibrariesTask.kt │ ├── CreateDefFileWithLibraryPathTask.kt │ ├── KSqliteBuildExtension.kt │ ├── KSqliteBuildPlugin.kt │ ├── SqliteCompilationConfig.kt │ └── internal │ ├── AndroidSetup.kt │ ├── BuildOnServer.kt │ ├── DownloadTask.kt │ ├── JniSetup.kt │ ├── KonanTargetExt.kt │ ├── NativeSetup.kt │ ├── Publishing.kt │ ├── SqliteCompilation.kt │ ├── Util.kt │ └── clang │ ├── ClangCompileTask.kt │ ├── KonanBuildService.kt │ └── LlvmArchiveTask.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jnigenerator ├── build.gradle.kts ├── libs │ └── kotlin-grammar-tools-0.1-43.jar └── src │ └── main │ └── kotlin │ └── com │ └── birbit │ └── jnigen │ ├── ClassNames.kt │ ├── FunctionPair.kt │ ├── JniWriter.kt │ ├── Main.kt │ ├── ParseTreeExt.kt │ └── Type.kt ├── keystore └── debug.keystore ├── ksqlite3 ├── build.gradle.kts └── src │ ├── androidInstrumentedTest │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ ├── OsSpecificTestUtils.kt │ │ └── PlatformTestUtils.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ ├── Row.kt │ │ ├── SqliteConnection.kt │ │ └── SqliteStmt.kt │ ├── commonTest │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ ├── OsSpecificTestUtils.kt │ │ ├── PlatformTestUtils.kt │ │ ├── SqliteConnectionTest.kt │ │ └── StatementTest.kt │ ├── jvmTest │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ ├── OsSpecificTestUtils.kt │ │ └── PlatformTestUtils.kt │ ├── linuxTest │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ └── OsSpecificTestUtils.kt │ ├── macTest │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ └── OsSpecificTestUtils.kt │ ├── main │ └── AndroidManifest.xml │ ├── nativeTest │ └── kotlin │ │ └── com │ │ └── birbit │ │ └── sqlite3 │ │ └── PlatformTestUtils.kt │ └── windowsTest │ └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── OsSpecificTestUtils.kt ├── scripts └── copyright.txt ├── settings.gradle.kts ├── sqlitebindings-api ├── README.md ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ ├── AuthResult.kt │ ├── AuthorizationParams.kt │ ├── Authorizer.kt │ ├── ColumnType.kt │ ├── ResultCode.kt │ └── SqliteException.kt └── sqlitebindings ├── build.gradle.kts └── src ├── androidInstrumentedTest └── kotlin │ └── com │ └── birbit │ └── internal │ └── AndroidTest.kt ├── androidJniWrapperMain └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ ├── GeneratedJni.kt │ ├── GeneratedJniUtils.kt │ ├── JniCommonUtils.kt │ └── JniEnv.kt ├── androidMain └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ └── AndroidSqliteApi.kt ├── commonJvmMain └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ └── JvmCommonSqliteApi.kt ├── commonMain └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ └── SqliteApi.kt ├── commonTest └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ └── AuthorizerTest.kt ├── jvmJniWrapperMain └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ ├── GeneratedJni.kt │ ├── GeneratedJniUtils.kt │ ├── JniCommonUtils.kt │ └── JniEnv.kt ├── jvmMain └── kotlin │ └── com │ └── birbit │ └── sqlite3 │ └── internal │ └── JvmSqliteApi.kt ├── main └── AndroidManifest.xml ├── nativeInterop └── cinterop │ ├── jni.def │ └── sqlite.def └── nativeMain └── kotlin └── com └── birbit └── sqlite3 └── internal └── SqliteApi.kt /.github/actions/androidTest/action.yml: -------------------------------------------------------------------------------- 1 | name: 'AndroidTest action' 2 | description: 'Runs android test for the given api level' 3 | inputs: 4 | apiLevel: 5 | description: "Android API level" 6 | required: true 7 | runs: 8 | using: "composite" 9 | steps: 10 | - name: Run Integration Tests ${{ inputs.apiLevel }} 11 | uses: ReactiveCircus/android-emulator-runner@v2.24.0 12 | env: 13 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 14 | with: 15 | api-level: ${{ inputs.apiLevel }} 16 | emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 17 | disable-animations: true 18 | arch: x86_64 19 | script: ./gradlew connectedCheck --stacktrace -------------------------------------------------------------------------------- /.github/actions/commonSetup/action.yml: -------------------------------------------------------------------------------- 1 | name: 'common machine setup' 2 | description: 'Prepares the machine' 3 | inputs: 4 | gradleCacheKey: 5 | description: "Service account for gradle cache" 6 | required: false 7 | gradleCachePush: 8 | description: "True if we'll also push to the cache" 9 | required: false 10 | default: "true" 11 | runs: 12 | using: "composite" 13 | steps: 14 | - name: Set up JDK 17 15 | uses: actions/setup-java@v2 16 | with: 17 | java-version: '17' 18 | distribution: 'zulu' 19 | java-package: jdk 20 | - name: Cache konan 21 | uses: actions/cache@v3 22 | with: 23 | path: ~/.konan 24 | key: ${{ runner.os }}-konan-cache-${{ hashFiles('buildSrc/build.gradle.kts') }} 25 | # An ordered list of keys to use for restoring the cache if no cache hit occurred for key 26 | restore-keys: | 27 | ${{ runner.os }}-konan-cache- 28 | - name: Own sdk dir 29 | if: runner.os == 'Linux' 30 | shell: bash 31 | run: sudo chown $USER:$USER $ANDROID_HOME -R 32 | - name: "Setup Gradle" 33 | uses: gradle/gradle-build-action@v2 34 | with: 35 | # Don't reuse cache entries from any other Job. 36 | gradle-home-cache-strict-match: true 37 | 38 | # Limit the size of the cache entry. 39 | # These directories contain instrumented/transformed dependency jars which can be reconstructed relatively quickly. 40 | gradle-home-cache-excludes: | 41 | caches/jars-9 42 | caches/transforms-3 43 | - name: Set android sdk path in env context 44 | id: obtainSdkDir 45 | shell: bash 46 | run: | 47 | set -x 48 | echo "::set-output name=sdkDir::$ANDROID_SDK_ROOT" 49 | - name: Cache NDK 50 | uses: actions/cache@v3 51 | id: ndkCache 52 | with: 53 | path: ${{ steps.obtainSdkDir.outputs.sdkDir }}/ndk/21.3.6528147 54 | key: ${{ runner.os }}-android-ndk-cache-21.3.6528147 55 | - name: Download ndk mac/linux 56 | if: steps.ndkCache.outputs.cache-hit != 'true' && (runner.os == 'Linux' || runner.os == 'macOS') 57 | shell: bash 58 | run: | 59 | set -x 60 | echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --install "ndk;21.3.6528147" 61 | - name: Download ndk windows 62 | if: steps.ndkCache.outputs.cache-hit != 'true' && runner.os == 'Windows' 63 | shell: bash 64 | run: echo "yes" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager.bat --install "ndk;21.3.6528147" 65 | -------------------------------------------------------------------------------- /.github/workflows/buildtestapks.yml: -------------------------------------------------------------------------------- 1 | name: Build Test Apks 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | env: 11 | # Allow precise monitoring of the save/restore of Gradle User Home by `gradle-build-action` 12 | # See https://github.com/marketplace/actions/gradle-build-action?version=v2.1.1#cache-debugging-and-analysis 13 | GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Setup machine 22 | uses: ./.github/actions/commonSetup 23 | with: 24 | gradleCacheKey: ${{ secrets.GCP_ACCOUNT }} 25 | gradleCachePush: true 26 | - name: build test artifacts 27 | id: gradle-build-test-artifacts 28 | run: ./gradlew packageDebugAndroidTest --info --stacktrace --scan 29 | env: 30 | GRADLE_CACHE_KEY: ${{ secrets.GCP_ACCOUNT }} 31 | GRADLE_CACHE_PUSH: true 32 | - name: Build Scan for Test 33 | shell: bash 34 | run: echo "::notice title=Build and Test scan::${{ steps.gradle-build-test-artifacts.outputs.build-scan-url }}" 35 | - name: Upload apks 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: test-apks-${{ runner.os }} 39 | path: ./**/*.apk 40 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | env: 13 | # Allow precise monitoring of the save/restore of Gradle User Home by `gradle-build-action` 14 | # See https://github.com/marketplace/actions/gradle-build-action?version=v2.1.1#cache-debugging-and-analysis 15 | GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ubuntu-latest, macos-latest, windows-latest] 24 | 25 | # The type of runner that the job will run on 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Setup machine 30 | uses: ./.github/actions/commonSetup 31 | with: 32 | gradleCacheKey: ${{ secrets.GCP_ACCOUNT }} 33 | gradleCachePush: true 34 | - name: build and test 35 | id: gradle-build-and-test 36 | run: ./gradlew allTests --stacktrace --scan 37 | env: 38 | GRADLE_CACHE_KEY: ${{ secrets.GCP_ACCOUNT }} 39 | GRADLE_CACHE_PUSH: true 40 | - name: Build Scan for Test 41 | shell: bash 42 | run: echo "::notice title=Build and Test scan::${{ steps.gradle-build-and-test.outputs.build-scan-url }}" 43 | - name: run spotless on build plugin 44 | run: ./gradlew -p buildPlugin spotlessCheck --stacktrace 45 | env: 46 | GRADLE_CACHE_KEY: ${{ secrets.GCP_ACCOUNT }} 47 | GRADLE_CACHE_PUSH: true 48 | - name: run spotless 49 | run: ./gradlew spotlessCheck --stacktrace 50 | env: 51 | GRADLE_CACHE_KEY: ${{ secrets.GCP_ACCOUNT }} 52 | GRADLE_CACHE_PUSH: true 53 | - name: stop daemon 54 | run: ./gradlew --stop 55 | - name: build dist 56 | id: gradle-build-dist 57 | run: ./gradlew buildOnServer packageDebugAndroidTest --stacktrace --scan --no-configuration-cache 58 | env: 59 | GRADLE_CACHE_KEY: ${{ secrets.GCP_ACCOUNT }} 60 | GRADLE_CACHE_PUSH: true 61 | - name: Build Scan for Dist 62 | shell: bash 63 | run: echo "::notice title=Dist Scan::${{ steps.gradle-build-dist.outputs.build-scan-url }}" 64 | - name: Upload test results for sqlitebindings 65 | if: always() 66 | uses: actions/upload-artifact@v2 67 | with: 68 | name: sqlitebindings-test-reports 69 | path: sqlitebindings/build/reports 70 | - name: Upload test results for ksqlite3 71 | if: always() 72 | uses: actions/upload-artifact@v2 73 | with: 74 | name: ksqlite3-test-reports 75 | path: ksqlite3/build/reports 76 | - name: Upload JNI failures 77 | if: always() 78 | uses: actions/upload-artifact@v2 79 | with: 80 | name: ksqlite3-jni-errors 81 | path: ksqlite3/hs_err* 82 | - name: Upload Dist 83 | if: always() 84 | uses: actions/upload-artifact@v2 85 | with: 86 | name: ${{ matrix.os }}-dist 87 | path: build/dist 88 | - name: Upload Build Reports 89 | if: always() 90 | uses: actions/upload-artifact@v2 91 | with: 92 | name: ${{ matrix.os }}-build-reports 93 | path: build/reports 94 | - name: Upload apks 95 | if: runner.os == 'Linux' 96 | uses: actions/upload-artifact@v2 97 | with: 98 | name: test apks of ${{ runner.os }} 99 | path: ./**/*.apk 100 | -------------------------------------------------------------------------------- /.github/workflows/run-device-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Integration Tests 2 | on: 3 | workflow_run: 4 | workflows: ["Build Test Apks"] 5 | types: [completed] 6 | 7 | jobs: 8 | run_integration_tests: 9 | runs-on: ubuntu-latest 10 | if: ${{ !github.event.repository.fork && github.event.workflow_run.conclusion == 'success' }} 11 | name: Run integration tests on FTL 12 | steps: 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v2 15 | with: 16 | java-version: '17' 17 | distribution: 'zulu' 18 | - name: "set output directory" 19 | run: | 20 | echo "::set-output name=output-dir::$(readlink -f .)/outputs" 21 | echo "::set-output name=ftl-logs::$(readlink -f .)/ftl-log.txt" 22 | id: dirs 23 | - id: run_tests 24 | uses: yigit/androidx-ci-action@dist-v0.08-2 25 | with: 26 | target-run-id: ${{ github.event.workflow_run.id }} 27 | gcp-token: ${{ secrets.GCP_SA_KEY }} 28 | github-token: ${{ secrets.GITHUB_TOKEN }} 29 | output-folder: ${{ steps.dirs.outputs.output-dir }} 30 | device-specs: "oriole:31, redfin:30, sailfish:25, Nexus4:21, Nexus5:23, Nexus6P:27" 31 | log-file: ${{ steps.dirs.outputs.ftl-logs }} 32 | artifact-name-filter-regex: ".*apk.*" 33 | gcp-bucket-name: "ftl-test-artifacts" 34 | gcp-bucket-path: "github-ci" 35 | - uses: actions/upload-artifact@v2 36 | if: always() 37 | with: 38 | name: outputs 39 | path: ${{ steps.dirs.outputs.output-dir }} 40 | - uses: actions/upload-artifact@v2 41 | if: always() 42 | with: 43 | name: logs 44 | path: ${{ steps.dirs.outputs.ftl-logs }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /build 3 | .gradle 4 | buildPlugin/build 5 | sqlitebindings/build 6 | sqlitebindings-api/build 7 | jnigenerator/build 8 | konan-warmup/build 9 | #ignore core dump files 10 | sqlitebindings/core 11 | sqlitebindings/hs_err_pid* 12 | ksqlite3/core 13 | ksqlite3/hs_err_pid* 14 | ksqlite3/build 15 | .DS_Store 16 | **/*.iml 17 | local.properties 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Sqlite Bindings 2 | This project provides kotlin multiplatform bindings for Sqlite3. (Win, Linux, Mac, Android, and iOS) 3 | 4 | By using it, you can write platform independent Sqlite code in your Kotlin common code. 5 | 6 | **For now, it is more of a playground then a real project so do not use it on production.** 7 | 8 | ## Disclaimer 9 | This is **not** an official Google product. 10 | 11 | ## Project Structure 12 | The project contains of two main modules. 13 | 14 | ### sqlitebindings 15 | This module provides a very small shim over Sqlite APIs as a KMP project. 16 | 17 | The only real implementation is on the native sourceSets (linux, windows, mac, android) while the JVM / Android ART 18 | implementation simply delegates to native via JNI. The JNI is generated in the `jnigenerator` based on the `actual` 19 | implementation for `SqliteApi` in the `jvm/art` source. 20 | 21 | It also bundles Sqlite as a static lib (for native) and dynamic lib (for JVM). 22 | 23 | ### ksqlite3 24 | This module aims to provide higher level APIs for SQLite and internally depends on `sqlitebindings` module. 25 | 26 | ## Artifacts 27 | There are no release artifacts right now but the CI builds a `repo` folder that can serve as a 28 | maven repository. If you want to try, you can download the `combined-repo` artifact from the 29 | latest Github Action and add it as a maven repo to your project. 30 | ## Disclaimer 31 | In case you've not read it above, this project is really a playground at this point. I'm trying to learn KMP so there is 32 | probably lots of memory handling etc mistakes. That being said, I hope to grow this into a proper project eventually. 33 | 34 | Copyright: 35 | 36 | Copyright 2020 Google LLC 37 | 38 | Licensed under the Apache License, Version 2.0 (the "License"); 39 | you may not use this file except in compliance with the License. 40 | You may obtain a copy of the License at 41 | 42 | https://www.apache.org/licenses/LICENSE-2.0 43 | 44 | Unless required by applicable law or agreed to in writing, software 45 | distributed under the License is distributed on an "AS IS" BASIS, 46 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | See the License for the specific language governing permissions and 48 | limitations under the License. -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.diffplug.gradle.spotless.SpotlessExtension 18 | 19 | plugins { 20 | alias(libs.plugins.spotless) 21 | alias(libs.plugins.ktlint) 22 | alias(libs.plugins.kotlinMp) apply false 23 | } 24 | 25 | buildscript { 26 | dependencies { 27 | // workaround for KMP plugin to find android classes 28 | classpath(libs.androidGradlePlugin) 29 | } 30 | } 31 | 32 | allprojects { 33 | repositories { 34 | google() 35 | mavenCentral() 36 | gradlePluginPortal() 37 | } 38 | 39 | tasks.withType().all { 40 | kotlinOptions.freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn") 41 | } 42 | if (this.path != ":konan-warmup") { 43 | apply(plugin = "com.diffplug.spotless") 44 | this.extensions.getByType(SpotlessExtension::class).apply { 45 | kotlin { 46 | target("src/**/*.kt") 47 | // ktlint fails to parse this 48 | targetExclude("src/**/JniEnv.kt") 49 | ktlint().userData( 50 | mapOf( 51 | "max_line_length" to "120" 52 | ) 53 | ) 54 | licenseHeaderFile(project.rootProject.file("scripts/copyright.txt")) 55 | } 56 | kotlinGradle { 57 | ktlint() 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /buildPlugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.diffplug.gradle.spotless.SpotlessExtension 2 | 3 | /* 4 | * Copyright 2020 Google, LLC. 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 | * http://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 | plugins { 20 | alias(libs.plugins.ktlint) 21 | alias(libs.plugins.spotless) 22 | alias(libs.plugins.kotlinJvm) 23 | `java-gradle-plugin` 24 | } 25 | 26 | dependencies { 27 | implementation(gradleApi()) 28 | implementation(gradleKotlinDsl()) 29 | implementation(libs.kotlinGradlePlugin) 30 | implementation(libs.kotlinReflect) 31 | implementation(libs.kotlinNativeUtils) 32 | implementation(libs.kotlinGradlePluginApi) 33 | implementation(libs.kotlinStdlibJdk) 34 | // workaround for KMP plugin to find android classes 35 | implementation(libs.agpApi) 36 | testImplementation(libs.truth) 37 | } 38 | 39 | configure { 40 | plugins { 41 | create("ksqliteBuild") { 42 | id = "ksqlite-build" 43 | implementationClass = "com.birbit.ksqlite.build.KSqliteBuildPlugin" 44 | } 45 | } 46 | } 47 | 48 | tasks.withType().configureEach { 49 | kotlinOptions.freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn") 50 | } 51 | 52 | extensions.getByType(SpotlessExtension::class).apply { 53 | kotlin { 54 | target("**/*.kt") 55 | ktlint().userData( 56 | mapOf( 57 | "max_line_length" to "120" 58 | ) 59 | ) 60 | licenseHeaderFile(project.rootProject.file("../scripts/copyright.txt")) 61 | } 62 | kotlinGradle { 63 | ktlint() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /buildPlugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pluginManagement { 17 | repositories { 18 | mavenCentral() 19 | gradlePluginPortal() 20 | google() 21 | } 22 | dependencyResolutionManagement { 23 | versionCatalogs { 24 | create("libs") { 25 | from(files("../gradle/libs.versions.toml")) 26 | } 27 | } 28 | } 29 | } 30 | 31 | dependencyResolutionManagement { 32 | repositories { 33 | mavenCentral() 34 | google() 35 | } 36 | } 37 | include("buildPlugin") 38 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/CollectNativeLibrariesTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build 17 | 18 | import com.birbit.ksqlite.build.internal.Publishing 19 | import com.birbit.ksqlite.build.internal.isBuiltOnThisMachine 20 | import com.birbit.ksqlite.build.internal.titleCase 21 | import org.gradle.api.DefaultTask 22 | import org.gradle.api.GradleException 23 | import org.gradle.api.Project 24 | import org.gradle.api.file.Directory 25 | import org.gradle.api.file.DirectoryProperty 26 | import org.gradle.api.provider.Provider 27 | import org.gradle.api.tasks.Input 28 | import org.gradle.api.tasks.InputFiles 29 | import org.gradle.api.tasks.Internal 30 | import org.gradle.api.tasks.OutputDirectory 31 | import org.gradle.api.tasks.TaskAction 32 | import org.gradle.api.tasks.TaskProvider 33 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 34 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 35 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 36 | import org.jetbrains.kotlin.konan.target.Architecture 37 | import org.jetbrains.kotlin.konan.target.Family 38 | import org.jetbrains.kotlin.konan.target.KonanTarget 39 | import java.io.File 40 | 41 | data class SoInput( 42 | val folderName: String, 43 | val soFile: File 44 | ) : java.io.Serializable { 45 | companion object { 46 | fun folderName(konanTarget: KonanTarget): String { 47 | // see https://github.com/scijava/native-lib-loader/blob/master/src/main/java/org/scijava/nativelib/NativeLibraryUtil.java 48 | return when (konanTarget.family) { 49 | Family.LINUX -> { 50 | "linux" + when (konanTarget.architecture) { 51 | Architecture.ARM32 -> "_arm" 52 | Architecture.ARM64 -> "_arm64" 53 | Architecture.X86 -> "_32" 54 | Architecture.X64 -> "_64" 55 | else -> throw GradleException("unexpected architecture ${konanTarget.architecture}") 56 | } 57 | } 58 | Family.MINGW -> "windows_${konanTarget.architecture.bitness}" 59 | Family.OSX -> { 60 | when (konanTarget.architecture) { 61 | Architecture.ARM64 -> "osx_arm64" 62 | Architecture.X64 -> "osx_64" 63 | else -> error("Unsupported architecture for mac: ${konanTarget.architecture}") 64 | } 65 | } 66 | Family.ANDROID -> when (konanTarget.architecture) { 67 | Architecture.X86 -> "x86" 68 | Architecture.X64 -> "x86_64" 69 | Architecture.ARM32 -> "armeabi-v7a" 70 | Architecture.ARM64 -> "arm64-v8a" 71 | else -> throw GradleException("add this architecture for android ${konanTarget.architecture}") 72 | } 73 | Family.IOS -> "ios" + when (konanTarget.architecture) { 74 | Architecture.ARM64 -> "_arm64" 75 | Architecture.X64 -> "_x64" 76 | else -> throw GradleException("unsupported arch ${konanTarget.architecture} for IOS family") 77 | } 78 | else -> throw GradleException("unsupported architecture family ${konanTarget.family}") 79 | } 80 | } 81 | } 82 | } 83 | 84 | abstract class CollectNativeLibrariesTask : DefaultTask() { 85 | @Internal 86 | lateinit var soInputs: List 87 | 88 | @get:OutputDirectory 89 | val outputDir: DirectoryProperty = project.objects.directoryProperty() 90 | 91 | @Input 92 | fun getFolderNames() = soInputs.map { it.folderName } 93 | 94 | @InputFiles 95 | fun getFilePaths() = soInputs.map { it.soFile } 96 | 97 | @Input 98 | var forAndroid: Boolean = false 99 | 100 | @TaskAction 101 | fun doIt() { 102 | val outputDir = outputDir.get().asFile 103 | outputDir.deleteRecursively() 104 | outputDir.mkdirs() 105 | val nativeDir = if (forAndroid) { 106 | outputDir 107 | } else { 108 | outputDir.resolve("natives") 109 | } 110 | soInputs.forEach { 111 | nativeDir.resolve(it.folderName).resolve(it.soFile.name).let { outFile -> 112 | outFile.parentFile.mkdirs() 113 | outFile.writeBytes(it.soFile.readBytes()) 114 | } 115 | } 116 | } 117 | 118 | companion object { 119 | 120 | @Suppress("unused") 121 | fun create( 122 | project: Project, 123 | namePrefix: String, 124 | outFolder: Provider, 125 | forAndroid: Boolean 126 | ): TaskProvider { 127 | val suffix = if (forAndroid) { 128 | "ForAndroid" 129 | } else { 130 | "ForJvm" 131 | } 132 | return project.tasks.register( 133 | "collectSharedLibsFor${namePrefix.titleCase()}$suffix", 134 | CollectNativeLibrariesTask::class.java 135 | ) { 136 | configure(it, namePrefix, outFolder, forAndroid) 137 | } 138 | } 139 | 140 | fun configure( 141 | task: CollectNativeLibrariesTask, 142 | namePrefix: String, 143 | outFolder: Provider, 144 | forAndroid: Boolean 145 | ) { 146 | val kotlin = 147 | task.project.extensions.findByType(KotlinMultiplatformExtension::class.java) 148 | checkNotNull(kotlin) { 149 | "cannot find kotlin extension" 150 | } 151 | val soFiles = mutableListOf() 152 | val distOutputsFolder = Publishing.getDistOutputs() 153 | var requireAndroidTarget = false 154 | if (distOutputsFolder == null || forAndroid) { 155 | // obtain from compilations 156 | kotlin.targets.withType(KotlinNativeTarget::class.java).filter { 157 | requireAndroidTarget = requireAndroidTarget || 158 | it.konanTarget.family == Family.ANDROID 159 | it.konanTarget.family != Family.IOS && 160 | it.konanTarget.isBuiltOnThisMachine() && 161 | forAndroid == (it.konanTarget.family == Family.ANDROID) 162 | }.forEach { 163 | val sharedLib = it.binaries.findSharedLib( 164 | namePrefix = namePrefix, 165 | buildType = NativeBuildType.RELEASE 166 | ) 167 | checkNotNull(sharedLib) { 168 | "cannot find shared lib in $it" 169 | } 170 | soFiles.add( 171 | SoInput( 172 | folderName = SoInput.folderName(it.konanTarget), 173 | soFile = sharedLib.outputFile 174 | ) 175 | ) 176 | task.dependsOn(sharedLib.linkTask) 177 | } 178 | } else { 179 | requireAndroidTarget = true 180 | // collect from dist output 181 | val nativesFolders = distOutputsFolder.walkTopDown().filter { 182 | it.isDirectory && it.name == "natives" 183 | } 184 | println("native folders: ${nativesFolders.toList()}") 185 | val foundSoFiles = nativesFolders.flatMap { 186 | it.listFiles().asSequence().filter { it.isDirectory }.map { target -> 187 | SoInput( 188 | folderName = target.name, 189 | soFile = target.listFiles().firstOrNull() 190 | ?: error("cannot find so file ${nativesFolders.toList()}") 191 | ) 192 | } 193 | } 194 | soFiles.addAll(foundSoFiles) 195 | } 196 | // soFiles shouldn't be empty unless we are targeting android and have no android 197 | // targets 198 | check(soFiles.isNotEmpty() || (forAndroid && !requireAndroidTarget)) { 199 | "found no SO files for ${task.name}" 200 | } 201 | println("found so files:$soFiles, for android $forAndroid") 202 | task.soInputs = soFiles 203 | task.outputDir.set(outFolder) 204 | task.forAndroid = forAndroid 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/CreateDefFileWithLibraryPathTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build 17 | 18 | import org.gradle.api.DefaultTask 19 | import org.gradle.api.file.DirectoryProperty 20 | import org.gradle.api.file.RegularFileProperty 21 | import org.gradle.api.tasks.CacheableTask 22 | import org.gradle.api.tasks.InputFile 23 | import org.gradle.api.tasks.Internal 24 | import org.gradle.api.tasks.OutputFile 25 | import org.gradle.api.tasks.PathSensitive 26 | import org.gradle.api.tasks.PathSensitivity 27 | import org.gradle.api.tasks.TaskAction 28 | 29 | @CacheableTask 30 | abstract class CreateDefFileWithLibraryPathTask : DefaultTask() { 31 | @get:InputFile 32 | @get:PathSensitive(PathSensitivity.RELATIVE) 33 | abstract val original: RegularFileProperty 34 | 35 | @get:InputFile 36 | @get:PathSensitive(PathSensitivity.RELATIVE) 37 | abstract val soFile: RegularFileProperty 38 | 39 | @get:OutputFile 40 | abstract val target: RegularFileProperty 41 | 42 | @get:Internal 43 | abstract val projectDir: DirectoryProperty 44 | 45 | @TaskAction 46 | fun doIt() { 47 | println("will copy from $original to $target") 48 | val target = target.asFile.get() 49 | target.parentFile.mkdirs() 50 | // use relative path to the owning project so it can be cached. 51 | val soLocalPath = soFile.asFile.get().parentFile.relativeTo(projectDir.get().asFile) 52 | val content = original.asFile.get() 53 | .readText(Charsets.UTF_8) + System.lineSeparator() + "libraryPaths = \"$soLocalPath\"" + 54 | System.lineSeparator() 55 | println("new content: $content") 56 | target.writeText(content, Charsets.UTF_8) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/KSqliteBuildExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build 17 | 18 | import com.birbit.ksqlite.build.internal.AndroidSetup 19 | import com.birbit.ksqlite.build.internal.BuildOnServer 20 | import com.birbit.ksqlite.build.internal.JniSetup 21 | import com.birbit.ksqlite.build.internal.Publishing 22 | import com.birbit.ksqlite.build.internal.SqliteCompilation 23 | import com.birbit.ksqlite.build.internal.setupCommon 24 | import org.gradle.api.Project 25 | import org.gradle.process.ExecOperations 26 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 27 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 28 | import javax.inject.Inject 29 | 30 | open class KSqliteBuildExtension 31 | @Inject constructor( 32 | private val project: Project, 33 | private val execOperations: ExecOperations 34 | ) { 35 | fun android() { 36 | AndroidSetup.configure(project) 37 | } 38 | 39 | fun publish() { 40 | Publishing.setup(project) 41 | } 42 | 43 | fun native( 44 | includeAndroidNative: Boolean = false, 45 | includeJni: Boolean = false, 46 | configure: KotlinNativeTarget.() -> Unit = {} 47 | ) { 48 | val kmpExt = project.extensions.findByType(KotlinMultiplatformExtension::class.java) 49 | ?: error("Must apply KMP plugin") 50 | kmpExt.setupCommon( 51 | includeAndroidNative = includeAndroidNative, 52 | configure = { 53 | if (includeJni) { 54 | JniSetup.configure(this) 55 | } 56 | this.configure() 57 | } 58 | ) 59 | } 60 | 61 | fun includeSqlite(config: SqliteCompilationConfig) { 62 | SqliteCompilation.setup(project, config) 63 | } 64 | 65 | fun buildOnServer() { 66 | BuildOnServer.register(project) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/KSqliteBuildPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build 17 | 18 | import com.birbit.ksqlite.build.internal.BuildOnServer 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.api.internal.TaskInternal 22 | import org.gradle.api.specs.AndSpec 23 | import org.gradle.kotlin.dsl.create 24 | import org.gradle.kotlin.dsl.withType 25 | import org.jetbrains.kotlin.gradle.tasks.CInteropProcess 26 | import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile 27 | 28 | class KSqliteBuildPlugin : Plugin { 29 | override fun apply(target: Project) { 30 | BuildOnServer.initIfNecessary(target) 31 | target.extensions.create( 32 | "ksqliteBuild" 33 | ) 34 | target.disableCinteropUpToDateChecks() 35 | target.tasks.withType().configureEach { 36 | it.kotlinOptions.jvmTarget = "1.8" 37 | } 38 | target.tasks.withType().configureEach { 39 | it.compilerOptions.freeCompilerArgs.add("-opt-in=kotlinx.cinterop.ExperimentalForeignApi") 40 | } 41 | } 42 | 43 | private fun Project.disableCinteropUpToDateChecks() { 44 | tasks.withType().configureEach { 45 | // workaround for https://youtrack.jetbrains.com/issue/KT-52243/ 46 | it.clearUpToDateChecks() 47 | } 48 | } 49 | 50 | /** 51 | * some KMP tasks have unreasonable up to date checks that completely blocks the caching. 52 | */ 53 | private fun CInteropProcess.clearUpToDateChecks() { 54 | val outputClass = outputs::class.java 55 | outputClass.getDeclaredField("upToDateSpec").let { field -> 56 | check(field.trySetAccessible()) 57 | field.set(outputs, AndSpec.empty()) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/SqliteCompilationConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build 17 | 18 | data class SqliteCompilationConfig( 19 | val version: String 20 | ) 21 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/AndroidSetup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import com.android.build.api.dsl.LibraryExtension 19 | import com.android.build.api.variant.AndroidComponentsExtension 20 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 21 | import org.gradle.api.Project 22 | import org.gradle.api.tasks.Copy 23 | import org.gradle.api.tasks.Exec 24 | import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform 25 | import java.io.File 26 | 27 | internal object AndroidSetup { 28 | private const val DOWNLOAD_CMD_LINE_TOOLS_TASK = "downloadAndroidCommandLineTools" 29 | private const val DOWNLOAD_NDK_TASK = "downloadNdk" 30 | fun configure(project: Project) { 31 | val androidLibrary: LibraryExtension = project.extensions 32 | .findByType(LibraryExtension::class.java) 33 | ?: error("cannot find library extension on $project") 34 | val androidComponents = project.extensions 35 | .findByType(AndroidComponentsExtension::class.java) 36 | ?: error("cannot find android components extension") 37 | androidComponents.beforeVariants { 38 | it.enableUnitTest = false 39 | } 40 | androidLibrary.compileSdk = 29 41 | androidLibrary.defaultConfig.let { 42 | it.minSdk = 21 43 | it.targetSdk = 29 44 | it.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 45 | it.ndk.abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")) 46 | } 47 | androidLibrary.sourceSets { 48 | getByName("androidTest").java 49 | .srcDir(project.file("src/androidInstrumented/kotlin")) 50 | } 51 | androidLibrary.ndkVersion = "23.1.7779620" 52 | val debugSigningConfig = androidLibrary.signingConfigs.getByName("debug") 53 | // Use a local debug keystore to have reproducible test apks 54 | debugSigningConfig.storeFile = project.getDebugKeystore() 55 | androidLibrary.buildTypes.all { buildType -> 56 | // Sign all the builds (including release) with debug key 57 | buildType.signingConfig = debugSigningConfig 58 | } 59 | 60 | createInstallNdkTask(project) 61 | } 62 | 63 | fun createInstallNdkTask(project: Project) { 64 | // find a reference to android 65 | val exists = project.rootProject.tasks.findByName(DOWNLOAD_NDK_TASK) 66 | if (exists != null) { 67 | return 68 | } 69 | val androidComponents = project.extensions.findByType(LibraryAndroidComponentsExtension::class.java) 70 | ?: return 71 | val androidLibraryExt = project.extensions.findByType(LibraryExtension::class.java) 72 | ?: return 73 | 74 | val rootProject = project.rootProject 75 | val buildDir = rootProject.layout.buildDirectory.dir("android-cmd-line-tools") 76 | val toolsZip = buildDir.map { it.file("tools.zip") } 77 | val downloadTask = rootProject.tasks.register("downloadAndroidCmdLineTools", DownloadTask::class.java) { 78 | it.downloadUrl.set(buildCommandLineToolsUrl()) 79 | it.downloadTargetFile.set(toolsZip) 80 | } 81 | val cmdLineToolsFolder = buildDir.map { it.dir("tools") } 82 | val unzipCommandLineToolsTask = rootProject.tasks.register("unzipCommandLineTools", Copy::class.java) { 83 | it.from(project.zipTree(toolsZip)) 84 | it.into(cmdLineToolsFolder) 85 | it.dependsOn(downloadTask) 86 | } 87 | rootProject.tasks.register("downloadNdk", Exec::class.java) { 88 | val os = DefaultNativePlatform.getCurrentOperatingSystem() 89 | val ext = if (os.isWindows) { 90 | ".bat" 91 | } else { 92 | "" 93 | } 94 | val sdkPath = androidComponents.sdkComponents.sdkDirectory.get().asFile.absolutePath 95 | if (os.isLinux) { 96 | it.doFirst { 97 | Runtime.getRuntime().exec("sudo chown \$USER:\$USER $sdkPath -R") 98 | } 99 | } 100 | it.executable(cmdLineToolsFolder.map { it.file("tools/bin/sdkmanager$ext") }) 101 | it.args("--install", "ndk;${androidLibraryExt.ndkVersion}", "--verbose") 102 | it.args("--sdk_root=$sdkPath") 103 | // pass y to accept licenses 104 | it.standardInput = "y".byteInputStream(Charsets.UTF_8) 105 | it.dependsOn(unzipCommandLineToolsTask) 106 | } 107 | } 108 | 109 | private fun Project.getDebugKeystore(): File { 110 | return rootProject.rootDir.resolve("keystore/debug.keystore").also { 111 | check(it.exists()) { 112 | "Cannot find keystore file in path: ${it.absolutePath}" 113 | } 114 | } 115 | } 116 | 117 | private fun buildCommandLineToolsUrl(): String { 118 | val os = DefaultNativePlatform.getCurrentOperatingSystem() 119 | val osKey = when { 120 | os.isWindows -> "win" 121 | os.isLinux -> "linux" 122 | os.isMacOsX -> "mac" 123 | else -> error("unsupported build OS: ${os.displayName}") 124 | } 125 | return "https://dl.google.com/android/repository/commandlinetools-$osKey-6514223_latest.zip" 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/BuildOnServer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import com.birbit.ksqlite.build.CollectNativeLibrariesTask 19 | import org.gradle.api.Project 20 | import org.gradle.api.Task 21 | import org.gradle.api.tasks.Copy 22 | import org.gradle.api.tasks.TaskProvider 23 | import java.io.File 24 | 25 | object BuildOnServer { 26 | private lateinit var distDir: File 27 | val TASK_NAME = "buildOnServer" 28 | private fun buildOnServerTask(project: Project): TaskProvider { 29 | val rootProject = project.rootProject 30 | if (rootProject.tasks.findByPath(TASK_NAME) == null) { 31 | distDir = rootProject.buildDir.resolve("dist") 32 | rootProject.tasks.register(TASK_NAME) 33 | configureCopyNativeLibraries(rootProject) 34 | } 35 | return rootProject.tasks.named(TASK_NAME) 36 | } 37 | 38 | fun initIfNecessary(project: Project) { 39 | buildOnServerTask(project) 40 | } 41 | 42 | fun register(project: Project) { 43 | val rootTask = buildOnServerTask(project) 44 | rootTask.configure { 45 | it.dependsOn(project.tasks.named("spotlessCheck")) 46 | it.dependsOn(project.tasks.named("allTests")) 47 | if (project.pluginManager.hasPlugin("maven-publish")) { 48 | it.dependsOn(project.tasks.named("publish")) 49 | } 50 | } 51 | } 52 | 53 | fun getOutRepo(): File { 54 | return distDir.resolve("repo") 55 | } 56 | 57 | private fun configureCopyNativeLibraries(rootProject: Project) { 58 | val copyNativeLibsTask = rootProject.tasks.register("copyNativeLibs", Copy::class.java) { copyTask -> 59 | rootProject.childProjects["sqlitebindings"]!!.tasks.withType(CollectNativeLibrariesTask::class.java) 60 | .all { 61 | copyTask.from(it.outputDir) 62 | copyTask.dependsOn(it) 63 | } 64 | copyTask.destinationDir = distDir.resolve("nativeLibs") 65 | } 66 | rootProject.tasks.named(TASK_NAME).configure { 67 | it.dependsOn(copyNativeLibsTask) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/DownloadTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import org.gradle.api.DefaultTask 19 | import org.gradle.api.file.RegularFileProperty 20 | import org.gradle.api.provider.Property 21 | import org.gradle.api.tasks.CacheableTask 22 | import org.gradle.api.tasks.Input 23 | import org.gradle.api.tasks.OutputFile 24 | import org.gradle.api.tasks.TaskAction 25 | import java.io.FileOutputStream 26 | import java.net.URL 27 | 28 | @CacheableTask 29 | abstract class DownloadTask : DefaultTask() { 30 | @get:Input 31 | abstract val downloadUrl: Property 32 | 33 | @get:OutputFile 34 | abstract val downloadTargetFile: RegularFileProperty 35 | 36 | @TaskAction 37 | fun doIt() { 38 | val downloadTargetFile = downloadTargetFile.asFile.get() 39 | downloadTargetFile.delete() 40 | downloadTargetFile.parentFile.mkdirs() 41 | URL(downloadUrl.get()).openStream().use { inputStream -> 42 | FileOutputStream(downloadTargetFile).use { outputStream -> 43 | inputStream.copyTo(outputStream) 44 | } 45 | } 46 | println("downloaded $downloadUrl to $downloadTargetFile") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/JniSetup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | import org.gradle.api.GradleException 18 | import org.gradle.kotlin.dsl.get 19 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 20 | import org.jetbrains.kotlin.konan.target.Family 21 | import java.io.File 22 | 23 | internal object JniSetup { 24 | // we don't include android here since it already has the jni APIs available. 25 | private val jniFamilies = listOf( 26 | Family.LINUX, 27 | Family.MINGW, 28 | Family.OSX, 29 | ) 30 | fun configure(target: KotlinNativeTarget) { 31 | // jni already exists on android so we don't need it there 32 | if (target.konanTarget.family in jniFamilies) { 33 | target.compilations["main"].cinterops.create("jni") { 34 | // JDK is required here, JRE is not enough 35 | val javaHome = File(System.getenv("JAVA_HOME") ?: System.getProperty("java.home")) 36 | var include = File(javaHome, "include") 37 | if (!include.exists()) { 38 | // look upper 39 | include = File(javaHome, "../include") 40 | } 41 | if (!include.exists()) { 42 | throw GradleException("cannot find include: $javaHome") 43 | } 44 | it.includeDirs( 45 | include, 46 | File(include, "darwin"), 47 | File(include, "linux"), 48 | File(include, "win32"), 49 | ) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/KonanTargetExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import org.jetbrains.kotlin.konan.target.HostManager 19 | import org.jetbrains.kotlin.konan.target.KonanTarget 20 | 21 | // check if the target can be built on this machine 22 | internal fun KonanTarget.isBuiltOnThisMachine() = HostManager().isEnabled(this) 23 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/NativeSetup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import org.gradle.kotlin.dsl.get 19 | import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform 20 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 21 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 22 | 23 | internal fun KotlinMultiplatformExtension.setupNative( 24 | includeAndroidNative: Boolean, 25 | configure: KotlinNativeTarget.() -> Unit 26 | ) { 27 | val os = DefaultNativePlatform.getCurrentOperatingSystem() 28 | mingwX64(configure = configure) 29 | if (os.isWindows) { 30 | // don't configure others on windows. Hits inconsistent type problems with JNI 31 | return 32 | } 33 | if (includeAndroidNative) { 34 | androidNativeArm32(configure = configure) 35 | androidNativeArm64(configure = configure) 36 | androidNativeX64(configure = configure) 37 | androidNativeX86(configure = configure) 38 | } 39 | linuxX64(configure = configure) 40 | linuxArm64(configure = configure) 41 | macosX64(configure = configure) 42 | macosArm64(configure = configure) 43 | if (os.isMacOsX) { 44 | iosX64(configure = configure) 45 | iosArm64(configure = configure) 46 | iosSimulatorArm64(configure = configure) 47 | } 48 | } 49 | 50 | internal fun KotlinMultiplatformExtension.setupCommon( 51 | includeAndroidNative: Boolean, 52 | configure: KotlinNativeTarget.() -> Unit 53 | ) { 54 | val nativeMain = sourceSets.create("nativeMain") { 55 | it.dependsOn(sourceSets["commonMain"]) 56 | } 57 | val nativeTest = sourceSets.create("nativeTest") { 58 | it.dependsOn(sourceSets["commonTest"]) 59 | } 60 | setupNative(includeAndroidNative) { 61 | sourceSets[this.targetName + "Main"].dependsOn(nativeMain) 62 | sourceSets[this.targetName + "Test"].dependsOn(nativeTest) 63 | this.configure() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/Publishing.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import com.android.build.api.dsl.LibraryExtension 19 | import org.gradle.api.Project 20 | import org.gradle.api.publish.PublishingExtension 21 | import org.gradle.kotlin.dsl.findByType 22 | import org.gradle.kotlin.dsl.maven 23 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 24 | import java.io.File 25 | 26 | internal object Publishing { 27 | 28 | fun setup(project: Project) { 29 | val publishing = project.extensions.findByType() 30 | ?: error("cannot find publishing extension") 31 | publishing.repositories { 32 | it.maven("file://${BuildOnServer.getOutRepo().absolutePath}") 33 | } 34 | val buildId = System.getenv("GITHUB_RUN_ID")?.padStart(10, '0') 35 | project.group = "com.birbit.ksqlite3" 36 | project.version = if (buildId != null) { 37 | "0.1.0.$buildId" 38 | } else { 39 | "0.1.0-SNAPSHOT" 40 | } 41 | // if it is android, enable publishing for debug & release 42 | val android = project.extensions.findByType() 43 | if (android != null) { 44 | val kotlin = project.extensions.findByType() 45 | ?: error("must apply KMP extension") 46 | kotlin.androidTarget { 47 | publishLibraryVariants("debug", "release") 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * outputs of other builds in CI 54 | */ 55 | fun getDistOutputs(): File? { 56 | return System.getenv(DIST_OUTPUTS_ENV_VAR)?.let { 57 | File(it).let { 58 | check(it.exists()) { 59 | "invalid $DIST_OUTPUTS_ENV_VAR param" 60 | } 61 | it.normalize() 62 | } 63 | } 64 | } 65 | 66 | private const val DIST_OUTPUTS_ENV_VAR = "DIST_OUTPUTS" 67 | } 68 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/SqliteCompilation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import com.birbit.ksqlite.build.CreateDefFileWithLibraryPathTask 19 | import com.birbit.ksqlite.build.SqliteCompilationConfig 20 | import com.birbit.ksqlite.build.internal.clang.ClangCompileTask 21 | import com.birbit.ksqlite.build.internal.clang.KonanBuildService 22 | import com.birbit.ksqlite.build.internal.clang.LlvmArchiveTask 23 | import org.gradle.api.Project 24 | import org.gradle.api.Task 25 | import org.gradle.api.tasks.Copy 26 | import org.gradle.api.tasks.TaskProvider 27 | import org.gradle.configurationcache.extensions.capitalized 28 | import org.gradle.kotlin.dsl.get 29 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 30 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 31 | import org.jetbrains.kotlin.konan.target.presetName 32 | 33 | internal object SqliteCompilation { 34 | fun setup(project: Project, config: SqliteCompilationConfig) { 35 | val downloadTask = project.tasks.register("downloadSqlite", DownloadTask::class.java) { 36 | it.downloadUrl.set(computeDownloadUrl(config.version)) 37 | it.downloadTargetFile.set( 38 | project.layout.buildDirectory.dir("sqlite-download").map { 39 | it.file("download/amalgamation.zip") 40 | } 41 | ) 42 | } 43 | 44 | val unzipTask = project.tasks.register("unzipSqlite", Copy::class.java) { 45 | it.from(project.zipTree(downloadTask.map { it.downloadTargetFile })) 46 | it.into( 47 | project.layout.buildDirectory.dir("sqlite-src") 48 | ) 49 | it.eachFile { 50 | // get rid of the amalgamation folder in output dir 51 | it.path = it.path.replaceFirst("sqlite-amalgamation-[\\d]+/".toRegex(), "") 52 | } 53 | } 54 | val kotlinExt = project.extensions.getByType(KotlinMultiplatformExtension::class.java) 55 | val compileTasks = mutableListOf>() 56 | kotlinExt.targets.withType(KotlinNativeTarget::class.java).filter { nativeTarget -> 57 | nativeTarget.konanTarget.isBuiltOnThisMachine() 58 | }.forEach { 59 | val konanTarget = it.konanTarget 60 | val konanBuildServiceProvider = KonanBuildService.obtain(project) 61 | val compileSQLiteTask = project.tasks.register( 62 | "compileSQLite${konanTarget.presetName.capitalized()}", 63 | ClangCompileTask::class.java 64 | ) { compileTask -> 65 | val srcDir = project.objects.directoryProperty().fileProvider( 66 | unzipTask.map { it.destinationDir } 67 | ) 68 | 69 | compileTask.usesService(konanBuildServiceProvider) 70 | val sourceFile = unzipTask.map { it.destinationDir.resolve("sqlite3.c") } 71 | val objFileDir = project.layout.buildDirectory.dir( 72 | "sqlite-compilation-output/${konanTarget.name}" 73 | ) 74 | compileTask.clangParameters.let { 75 | it.konanTarget.set(konanTarget) 76 | it.sources.from( 77 | sourceFile 78 | ) 79 | it.includes.set(srcDir) 80 | it.output.set(objFileDir) 81 | it.freeArgs.addAll(SQLITE_ARGS) 82 | } 83 | } 84 | val archiveSQLiteTask = project.tasks.register( 85 | "archiveSQLite${konanTarget.presetName.capitalized()}", 86 | LlvmArchiveTask::class.java 87 | ) { archiveTask -> 88 | archiveTask.llvmArParameters.let { 89 | it.konanTarget.set(konanTarget) 90 | it.objectFiles.from( 91 | compileSQLiteTask.flatMap { it.clangParameters.output } 92 | ) 93 | it.outputFile.set( 94 | project.layout.buildDirectory.file("static-lib-files/${konanTarget.name}/libsqlite3.a") 95 | ) 96 | } 97 | archiveTask.usesService(konanBuildServiceProvider) 98 | } 99 | compileTasks.add(archiveSQLiteTask) 100 | it.compilations["main"].cinterops.create("sqlite") { 101 | // JDK is required here, JRE is not enough 102 | val generatedDefFileFolder = project.layout.buildDirectory.dir("sqlite-def-files") 103 | it.packageName = "sqlite3" 104 | val cInteropTask = project.tasks[it.interopProcessingTaskName] 105 | it.includeDirs(unzipTask.map { it.destinationDir }) 106 | val original = it.defFile 107 | val newDefFile = generatedDefFileFolder.map { 108 | it.dir(konanTarget.presetName).file("sqlite-generated.def") 109 | } 110 | val createDefFileTask = project.tasks.register( 111 | "createDefFileForSqlite${konanTarget.presetName.titleCase()}", 112 | CreateDefFileWithLibraryPathTask::class.java 113 | ) { task -> 114 | task.original.set(original) 115 | task.target.set(newDefFile) 116 | task.soFile.set(archiveSQLiteTask.flatMap { it.llvmArParameters.outputFile }) 117 | task.projectDir.set(project.layout.projectDirectory) 118 | } 119 | // create def file w/ library paths. couldn't figure out how else to add it :/ :) 120 | it.defFile = newDefFile.get().asFile 121 | cInteropTask.dependsOn(createDefFileTask) 122 | } 123 | } 124 | } 125 | 126 | private fun computeDownloadUrl(version: String): String { 127 | // see https://www.sqlite.org/download.html 128 | // The version is encoded so that filenames sort in order of increasing version number 129 | // when viewed using "ls". 130 | // For version 3.X.Y the filename encoding is 3XXYY00. 131 | // For branch version 3.X.Y.Z, the encoding is 3XXYYZZ. 132 | val sections = version.split('.') 133 | check(sections.size >= 3) { // TODO add support for branch versions 134 | "invalid sqlite version $version" 135 | } 136 | val major = sections[0].toInt() 137 | val minor = sections[1].toInt() 138 | val patch = sections[2].toInt() 139 | val branch = if (sections.size >= 4) sections[3].toInt() else 0 140 | val fileName = String.format("%d%02d%02d%02d.zip", major, minor, patch, branch) 141 | println("filename: $fileName") 142 | return "$BASE_URL$fileName" 143 | } 144 | 145 | private const val BASE_URL = "https://www.sqlite.org/2022/sqlite-amalgamation-" 146 | 147 | internal val SQLITE_ARGS = listOf( 148 | "-DSQLITE_ENABLE_COLUMN_METADATA=1", 149 | "-DSQLITE_ENABLE_NORMALIZE=1", 150 | // "-DSQLITE_ENABLE_EXPLAIN_COMMENTS=1", 151 | // "-DSQLITE_ENABLE_DBSTAT_VTAB=1", 152 | "-DSQLITE_ENABLE_LOAD_EXTENSION=1", 153 | // "-DSQLITE_HAVE_ISNAN=1", 154 | "-DHAVE_USLEEP=1", 155 | // "-DSQLITE_CORE=1", 156 | "-DSQLITE_ENABLE_FTS3=1", 157 | "-DSQLITE_ENABLE_FTS3_PARENTHESIS=1", 158 | "-DSQLITE_ENABLE_FTS4=1", 159 | "-DSQLITE_ENABLE_FTS5=1", 160 | "-DSQLITE_ENABLE_JSON1=1", 161 | "-DSQLITE_ENABLE_RTREE=1", 162 | "-DSQLITE_ENABLE_STAT4=1", 163 | "-DSQLITE_THREADSAFE=1", 164 | "-DSQLITE_DEFAULT_MEMSTATUS=0", 165 | "-DSQLITE_OMIT_PROGRESS_CALLBACK=0", 166 | "-DSQLITE_ENABLE_RBU=1" 167 | ) 168 | } 169 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal 17 | 18 | import java.util.Locale 19 | 20 | internal fun String.titleCase() = 21 | this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() } 22 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/clang/ClangCompileTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal.clang 17 | 18 | import org.gradle.api.DefaultTask 19 | import org.gradle.api.file.ConfigurableFileCollection 20 | import org.gradle.api.file.DirectoryProperty 21 | import org.gradle.api.provider.ListProperty 22 | import org.gradle.api.provider.Property 23 | import org.gradle.api.services.ServiceReference 24 | import org.gradle.api.tasks.CacheableTask 25 | import org.gradle.api.tasks.Input 26 | import org.gradle.api.tasks.InputDirectory 27 | import org.gradle.api.tasks.InputFiles 28 | import org.gradle.api.tasks.Nested 29 | import org.gradle.api.tasks.OutputDirectory 30 | import org.gradle.api.tasks.PathSensitive 31 | import org.gradle.api.tasks.PathSensitivity 32 | import org.gradle.api.tasks.TaskAction 33 | import org.gradle.workers.WorkAction 34 | import org.gradle.workers.WorkParameters 35 | import org.gradle.workers.WorkerExecutor 36 | import org.jetbrains.kotlin.konan.target.KonanTarget 37 | import javax.inject.Inject 38 | 39 | abstract class ClangCompileParameters { 40 | @get:Input 41 | abstract val konanTarget: Property 42 | 43 | @get:InputFiles 44 | @get:PathSensitive(PathSensitivity.RELATIVE) 45 | abstract val sources: ConfigurableFileCollection 46 | 47 | @get:OutputDirectory 48 | abstract val output: DirectoryProperty 49 | 50 | @get:InputDirectory 51 | @get:PathSensitive(PathSensitivity.RELATIVE) 52 | abstract val includes: DirectoryProperty 53 | 54 | @get:Input 55 | abstract val freeArgs: ListProperty 56 | } 57 | 58 | abstract class CompileWorker : WorkAction { 59 | interface Params : WorkParameters { 60 | val clangParameters: Property 61 | val buildService: Property 62 | } 63 | 64 | override fun execute() { 65 | val buildService = parameters.buildService.get() 66 | buildService.compile( 67 | parameters.clangParameters.get() 68 | ) 69 | } 70 | } 71 | 72 | @CacheableTask 73 | abstract class ClangCompileTask @Inject constructor( 74 | private val workerExecutor: WorkerExecutor 75 | ) : DefaultTask() { 76 | @Suppress("UnstableApiUsage") 77 | @get:ServiceReference(KonanBuildService.KEY) 78 | abstract val konanBuildService: Property 79 | 80 | @get:Nested 81 | abstract val clangParameters: ClangCompileParameters 82 | 83 | @TaskAction 84 | fun compile() { 85 | workerExecutor.noIsolation().submit( 86 | CompileWorker::class.java 87 | ) { 88 | it.buildService.set(konanBuildService) 89 | it.clangParameters.set(clangParameters) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/clang/KonanBuildService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal.clang 17 | 18 | import org.gradle.api.Project 19 | import org.gradle.api.file.DirectoryProperty 20 | import org.gradle.api.provider.Provider 21 | import org.gradle.api.services.BuildService 22 | import org.gradle.api.services.BuildServiceParameters 23 | import org.gradle.process.ExecOperations 24 | import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader 25 | import org.jetbrains.kotlin.konan.target.Distribution 26 | import org.jetbrains.kotlin.konan.target.KonanTarget 27 | import org.jetbrains.kotlin.konan.target.Platform 28 | import org.jetbrains.kotlin.konan.target.PlatformManager 29 | import javax.inject.Inject 30 | 31 | abstract class KonanBuildService @Inject constructor( 32 | private val execOperations: ExecOperations 33 | ) : BuildService { 34 | private val dist = Distribution( 35 | konanHome = parameters.konanHome.get().asFile.absolutePath, 36 | onlyDefaultProfiles = false, 37 | ) 38 | 39 | private val platformManager = PlatformManager( 40 | distribution = dist 41 | ) 42 | 43 | fun compile(clangCompileParameters: ClangCompileParameters) { 44 | val platform = resolveKonanTarget(clangCompileParameters.konanTarget.get()) 45 | val outputDir = clangCompileParameters.output.get().asFile 46 | outputDir.deleteRecursively() 47 | outputDir.mkdirs() 48 | val clang = platform.clang 49 | execOperations.exec { 50 | it.executable = "clang" 51 | it.args(clang.clangArgs.toList()) 52 | it.args(clangCompileParameters.freeArgs.get()) 53 | it.workingDir = clangCompileParameters.output.get().asFile 54 | it.args( 55 | "-I${clangCompileParameters.includes.get().asFile.absolutePath}", 56 | "--compile", 57 | "-v", 58 | ) 59 | it.args(clangCompileParameters.sources.files.map { it.absolutePath }) 60 | } 61 | } 62 | 63 | fun archive(clangArchiveParameters: LlvmArchiveParameters) { 64 | val platform = resolveKonanTarget(clangArchiveParameters.konanTarget.get()) 65 | clangArchiveParameters.outputFile.get().asFile.also { 66 | it.delete() 67 | it.parentFile.mkdirs() 68 | } 69 | execOperations.exec { 70 | it.executable = platform.clang.llvmAr().single() 71 | it.args("rc") 72 | it.args(clangArchiveParameters.outputFile.get().asFile.absolutePath) 73 | it.args( 74 | clangArchiveParameters.objectFiles.files.flatMap { 75 | it.walkTopDown().filter { 76 | it.isFile 77 | }.map { it.canonicalPath } 78 | }.distinct() 79 | ) 80 | } 81 | } 82 | 83 | private fun resolveKonanTarget(konanTarget: KonanTarget): Platform { 84 | val wantedTargetName = konanTarget.name 85 | // for some reason, equals don't work between these, might be classpath issue, hence find it 86 | // again 87 | val wantedTarget = platformManager.enabled.find { 88 | it.name == wantedTargetName 89 | } ?: error("cannot find enabled target with name $wantedTargetName") 90 | val platform = platformManager.platform(wantedTarget) 91 | platform.downloadDependencies() 92 | return platform 93 | } 94 | 95 | interface Params : BuildServiceParameters { 96 | val konanHome: DirectoryProperty 97 | } 98 | 99 | companion object { 100 | const val KEY = "konanBuildService" 101 | fun obtain( 102 | project: Project 103 | ): Provider { 104 | return project.gradle.sharedServices.registerIfAbsent( 105 | KEY, 106 | KonanBuildService::class.java 107 | ) { 108 | // see: 109 | // https://github.com/JetBrains/kotlin/blob/d97e8ae456364d90f0ffbd7f20535983d4d62c5d/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/nativeToolRunners.kt#L33 110 | // maybe use that instead 111 | val nativeCompilerDownloader = NativeCompilerDownloader(project) 112 | nativeCompilerDownloader.downloadIfNeeded() 113 | 114 | it.parameters.konanHome.set( 115 | nativeCompilerDownloader.compilerDirectory 116 | ) 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /buildPlugin/src/main/kotlin/com/birbit/ksqlite/build/internal/clang/LlvmArchiveTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.ksqlite.build.internal.clang 17 | 18 | import org.gradle.api.DefaultTask 19 | import org.gradle.api.file.ConfigurableFileCollection 20 | import org.gradle.api.file.RegularFileProperty 21 | import org.gradle.api.provider.Property 22 | import org.gradle.api.services.ServiceReference 23 | import org.gradle.api.tasks.CacheableTask 24 | import org.gradle.api.tasks.Input 25 | import org.gradle.api.tasks.InputFiles 26 | import org.gradle.api.tasks.Nested 27 | import org.gradle.api.tasks.OutputFile 28 | import org.gradle.api.tasks.PathSensitive 29 | import org.gradle.api.tasks.PathSensitivity 30 | import org.gradle.api.tasks.TaskAction 31 | import org.gradle.workers.WorkAction 32 | import org.gradle.workers.WorkParameters 33 | import org.gradle.workers.WorkerExecutor 34 | import org.jetbrains.kotlin.konan.target.KonanTarget 35 | import javax.inject.Inject 36 | 37 | abstract class LlvmArchiveParameters { 38 | @get:Input 39 | abstract val konanTarget: Property 40 | @get:InputFiles 41 | @get:PathSensitive(PathSensitivity.RELATIVE) 42 | abstract val objectFiles: ConfigurableFileCollection 43 | @get:OutputFile 44 | abstract val outputFile: RegularFileProperty 45 | } 46 | 47 | private abstract class LlvmArchiveWorker : WorkAction { 48 | interface Params : WorkParameters { 49 | val llvmArParameters: Property 50 | val buildService: Property 51 | } 52 | 53 | override fun execute() { 54 | val buildService = parameters.buildService.get() 55 | buildService.archive( 56 | parameters.llvmArParameters.get() 57 | ) 58 | } 59 | } 60 | @CacheableTask 61 | abstract class LlvmArchiveTask @Inject constructor( 62 | private val workerExecutor: WorkerExecutor 63 | ) : DefaultTask() { 64 | @Suppress("UnstableApiUsage") 65 | @get:ServiceReference(KonanBuildService.KEY) 66 | abstract val konanBuildService: Property 67 | 68 | @get:Nested 69 | abstract val llvmArParameters: LlvmArchiveParameters 70 | 71 | @TaskAction 72 | fun archive() { 73 | workerExecutor.noIsolation().submit( 74 | LlvmArchiveWorker::class.java 75 | ) { 76 | it.llvmArParameters.set(llvmArParameters) 77 | it.buildService.set(konanBuildService) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Google, LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | kotlin.code.style=official 18 | org.gradle.daemon=true 19 | # breaks cinterop in IDE 20 | org.gradle.configureondemand=false 21 | org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 22 | org.gradle.caching=true 23 | kotlin.native.ignoreDisabledTargets=true 24 | android.useAndroidX=true 25 | android.enableJetifier=false 26 | kotlin.mpp.stability.nowarn=true 27 | # We can use this only in non windows platforms because jint type changes. This is also we don't 28 | # build anything but windows on windows. 29 | kotlin.mpp.enableCInteropCommonization=true 30 | kotlin.native.binary.memoryModel=experimental 31 | org.gradle.configuration-cache=true 32 | org.gradle.configuration-cache.problems=warn 33 | otlin.mpp.androidSourceSetLayoutVersion=2 34 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidGradlePlugin="8.1.1" 3 | spotless="6.0.5" 4 | ktlint="10.2.0" 5 | kotlin="1.9.10" 6 | 7 | [libraries] 8 | androidGradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin"} 9 | nativeLibLoader = { module = "org.scijava:native-lib-loader", version = "2.4.0" } 10 | kotlinPoet = { module = "com.squareup:kotlinpoet", version = "1.5.0" } 11 | androidxTestJunit = { module = "androidx.test.ext:junit", version = "1.1.1" } 12 | androidxTestRunner = { module = "androidx.test:runner", version = "1.2.0" } 13 | kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin"} 14 | kotlinGradlePluginApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin"} 15 | kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } 16 | kotlinNativeUtils = { module = "org.jetbrains.kotlin:kotlin-native-utils", version.ref = "kotlin" } 17 | kotlinStdlibJdk = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } 18 | kotlinStdlibCommon = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "kotlin" } 19 | kotlinTestCommon = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" } 20 | kotlinTestJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 21 | kotlinTestAnnotationsCommon = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" } 22 | 23 | 24 | truth = { module = "com.google.truth:truth", version = "1.1.3" } 25 | agpApi = { module = "com.android.tools.build:gradle-api", version.ref = "androidGradlePlugin"} 26 | 27 | [bundles] 28 | androidInstrumentedTest = ["androidxTestJunit", "androidxTestRunner"] 29 | 30 | 31 | [plugins] 32 | kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 33 | kotlinMp = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 34 | spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } 35 | ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } 36 | agpLibrary = { id = "com.android.library", version.ref = "androidGradlePlugin"} 37 | 38 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/kotlin-sqlite-bindings/77ef32a3503175d039099069a6cc5512cbd16d1d/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-8.1.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /jnigenerator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | plugins { 17 | id(libs.plugins.kotlinJvm.get().pluginId) 18 | } 19 | 20 | dependencies { 21 | implementation(libs.kotlinStdlibJdk) 22 | implementation( 23 | project.fileTree("libs") { 24 | include("*.jar") 25 | } 26 | ) 27 | implementation(libs.kotlinPoet) 28 | } 29 | 30 | tasks.withType().configureEach { 31 | kotlinOptions.jvmTarget = "17" 32 | } 33 | -------------------------------------------------------------------------------- /jnigenerator/libs/kotlin-grammar-tools-0.1-43.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/kotlin-sqlite-bindings/77ef32a3503175d039099069a6cc5512cbd16d1d/jnigenerator/libs/kotlin-grammar-tools-0.1-43.jar -------------------------------------------------------------------------------- /jnigenerator/src/main/kotlin/com/birbit/jnigen/ClassNames.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.jnigen 17 | 18 | import com.squareup.kotlinpoet.ClassName 19 | 20 | // match android for JNI types so that we can use the same native code in both JVM and Android 21 | private inline fun jniType(value: String) = ClassName("platform.android", value) 22 | private inline fun internalType(value: String) = ClassName("com.birbit.sqlite3.internal", value) 23 | private inline fun bindingApiType(value: String) = ClassName("com.birbit.sqlite3", value) 24 | 25 | private inline fun interopType(value: String) = ClassName("kotlinx.cinterop", value) 26 | 27 | object ClassNames { 28 | val JBYTEARRAY = jniType("jbyteArray") 29 | val RESULT_CODE = bindingApiType("ResultCode") 30 | val JINT = jniType("jint") 31 | val JLONG = jniType("jlong") 32 | val JDOUBLE = jniType("jdouble") 33 | val JBOOLEAN = jniType("jboolean") 34 | val JSTRING = jniType("jstring") 35 | val JOBJECT = jniType("jobject") 36 | val CNAME = ClassName("kotlin.native", "CName") 37 | val CPOINTER = interopType("CPointer") 38 | val CPOINTER_VGAR_OF = interopType("CPointerVarOf") 39 | val JNI_ENV_VAR = jniType("JNIEnvVar") 40 | val JCLASS = jniType("jclass") 41 | val SQLITE_API = internalType("SqliteApi") 42 | val DB_REF = internalType("DbRef") 43 | val STMT_REF = internalType("StmtRef") 44 | val AUTHORIZER = bindingApiType("Authorizer") 45 | val COLUMN_TYPE = bindingApiType("ColumnType") 46 | } 47 | -------------------------------------------------------------------------------- /jnigenerator/src/main/kotlin/com/birbit/jnigen/FunctionPair.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.jnigen 17 | 18 | data class FunctionPair( 19 | val actualFun: FunctionDeclaration, 20 | val nativeFun: FunctionDeclaration 21 | ) { 22 | val jniSignature: String 23 | // TODO this is probably more complicated 24 | get() = "Java_com_birbit_sqlite3_internal_SqliteApi_${nativeFun.name}" 25 | } 26 | -------------------------------------------------------------------------------- /jnigenerator/src/main/kotlin/com/birbit/jnigen/JniWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.jnigen 17 | 18 | import com.squareup.kotlinpoet.AnnotationSpec 19 | import com.squareup.kotlinpoet.FileSpec 20 | import com.squareup.kotlinpoet.FunSpec 21 | import com.squareup.kotlinpoet.ParameterSpec 22 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 23 | import java.io.File 24 | import java.util.Locale 25 | 26 | internal fun String.titleCase() = 27 | this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() } 28 | class JniWriter( 29 | val copyright: String, 30 | val pairs: List 31 | ) { 32 | fun write(file: File) { 33 | val spec = FileSpec.builder("com.birbit.sqlite3.internal", "GeneratedJni").apply { 34 | indent(" ") 35 | addAnnotation( 36 | AnnotationSpec.builder(Suppress::class).apply { 37 | addMember("%S, %S, %S", "unused", "UNUSED_PARAMETER", "UnnecessaryVariable") 38 | }.build() 39 | ) 40 | addComment("Generated by JniWriter, do not edit!") 41 | pairs.forEach { 42 | addFunction(generate(it)) 43 | } 44 | }.build() 45 | val output = StringBuilder() 46 | output.appendLine(copyright) 47 | spec.writeTo(output) 48 | println("will output to ${file.absolutePath}") 49 | file.writeText(output.toString(), Charsets.UTF_8) 50 | } 51 | 52 | fun generate(pair: FunctionPair) = FunSpec.builder(pair.actualFun.name).apply { 53 | addAnnotation( 54 | AnnotationSpec.builder(ClassNames.CNAME).apply { 55 | this.addMember("%S", pair.jniSignature) 56 | }.build() 57 | ) 58 | val envParam = ParameterSpec.builder( 59 | "env", 60 | ClassNames.CPOINTER.parameterizedBy(ClassNames.JNI_ENV_VAR) 61 | ).build() 62 | addParameter( 63 | envParam 64 | ) 65 | addParameter( 66 | ParameterSpec.builder( 67 | "clazz", 68 | ClassNames.JCLASS 69 | ).build() 70 | ) 71 | val params = mutableListOf>() 72 | pair.nativeFun.paramTypes.forEachIndexed { index, type -> 73 | val param = ParameterSpec.builder( 74 | "p$index", 75 | type.nativeClass 76 | ).build() 77 | params.add(pair.actualFun.paramTypes[index] to param) 78 | addParameter( 79 | param 80 | ) 81 | } 82 | // TODO add only for entry methods 83 | addStatement("initPlatform()") 84 | 85 | /** 86 | return runWithJniExceptionConversion(env, 0) { 87 | val localP0 = DbRef.fromJni(p0) 88 | val localP1 = checkNotNull(p1.toKString(env)) 89 | val callResult = SqliteApi.prepareStmt(localP0, localP1) 90 | val localCallResult = callResult.toJni() 91 | localCallResult 92 | } 93 | */ 94 | beginControlFlow( 95 | "return runWithJniExceptionConversion(%N, %L)", 96 | envParam, 97 | pair.nativeFun.returnType.defaultValue() 98 | ) 99 | val argumentNames = params.map { 100 | if (it.first.hasConvertFromJni()) { 101 | val localVarName = "local${it.second.name.titleCase()}}" 102 | val convert = it.first.convertFromJni(envParam, it.second, localVarName) 103 | addCode(convert) 104 | localVarName 105 | } else { 106 | it.second.name 107 | } 108 | } 109 | addStatement( 110 | "val %L = %T.%L(%L)", 111 | RETURN_VALUE_NAME, 112 | ClassNames.SQLITE_API, 113 | pair.actualFun.name, 114 | argumentNames.joinToString(", ") 115 | ) 116 | // now convert back if necessary 117 | // addComment("return type: %L , %L", pair.actualFun.returnType, convertToJni == null) 118 | if (pair.actualFun.returnType.hasConvertToJni()) { 119 | val localResultName = "local${RETURN_VALUE_NAME.titleCase()}}" 120 | addCode( 121 | pair.actualFun.returnType.convertToJni( 122 | envParam, 123 | RETURN_VALUE_NAME, localResultName 124 | ) 125 | ) 126 | addStatement("%L", localResultName) 127 | } else { 128 | addStatement("%L", RETURN_VALUE_NAME) 129 | } 130 | returns(pair.nativeFun.returnType.nativeClass) 131 | endControlFlow() 132 | }.build() 133 | 134 | companion object { 135 | val RETURN_VALUE_NAME = "callResult" 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /jnigenerator/src/main/kotlin/com/birbit/jnigen/Main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.jnigen 17 | 18 | import org.jetbrains.kotlin.spec.grammar.tools.parseKotlinCode 19 | import java.io.File 20 | import java.io.IOException 21 | import java.util.Calendar 22 | import java.util.concurrent.TimeUnit 23 | 24 | /** 25 | * TODO: should we make this part of the build? 26 | */ 27 | fun main() { 28 | val srcFile = File("./sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt") 29 | val targetFiles = listOf( 30 | File("./sqlitebindings/src/androidJniWrapperMain/kotlin/com/birbit/sqlite3/internal/GeneratedJni.kt"), 31 | File("./sqlitebindings/src/jvmJniWrapperMain/kotlin/com/birbit/sqlite3/internal/GeneratedJni.kt") 32 | ) 33 | val jvmMethodNames = findMethodNamesFromClassFile("./sqlitebindings/build") 34 | val invalidMethods = jvmMethodNames.filter { 35 | it.suffix.isNotBlank() && it.originalName.startsWith("native") 36 | } 37 | if (invalidMethods.isNotEmpty()) { 38 | val methodNames = invalidMethods.joinToString("\n") { 39 | it.fullName 40 | } 41 | error("""These methods cannot be called from native: $methodNames""") 42 | } 43 | println("hello ${File(".").absolutePath}") 44 | val tokens = parseKotlinCode(srcFile.readText(Charsets.UTF_8)) 45 | val sqliteApiObject = tokens.objectDeclarations().first { 46 | it.name == "SqliteApi" 47 | } 48 | 49 | val methods = sqliteApiObject.functions.filterNot { 50 | false && it.name in listOf("Suppress", "JvmName") 51 | }.groupBy { 52 | it.external 53 | } 54 | val externalMethods = methods[true] ?: error("no external method?") 55 | val actualMethods = methods[false]?.associateBy { 56 | "native${it.name.titleCase()}}" 57 | } ?: error("no actual methods?") 58 | val pairs = externalMethods.map { native -> 59 | val actualMethod = checkNotNull(actualMethods[native.name]) { 60 | "cannot find actual method for $native" 61 | } 62 | FunctionPair( 63 | actualFun = actualMethod, 64 | nativeFun = native 65 | ) 66 | } 67 | println(pairs) 68 | val copyright = File("./scripts/copyright.txt") 69 | .readText(Charsets.UTF_8) 70 | .replace("\$YEAR", Calendar.getInstance().get(Calendar.YEAR).toString()) 71 | targetFiles.forEach { 72 | JniWriter(copyright, pairs).write(it) 73 | } 74 | } 75 | 76 | fun findMethodNamesFromClassFile(folder: String): List { 77 | val javaHome = System.getenv("JAVA_HOME") ?: error("cannot find java home") 78 | val javap = File(javaHome, "bin/javap") 79 | val classFile = File("$folder/classes/kotlin/jvm/main/com/birbit/sqlite3/internal/SqliteApi.class") 80 | if (!classFile.exists()) { 81 | throw IllegalStateException("compile the project first, we need to validate mangled names") 82 | } 83 | val cmd = javap.absolutePath + " -s $classFile" 84 | val output = cmd.runCommand(File(".")) ?: "could not run javap" 85 | return output.lines().mapNotNull { 86 | it.parseJavapMethodName() 87 | } 88 | } 89 | 90 | fun String.runCommand(workingDir: File): String? { 91 | try { 92 | val parts = this.split("\\s".toRegex()) 93 | val proc = ProcessBuilder(*parts.toTypedArray()) 94 | .directory(workingDir) 95 | .redirectOutput(ProcessBuilder.Redirect.PIPE) 96 | .redirectError(ProcessBuilder.Redirect.PIPE) 97 | .start() 98 | 99 | proc.waitFor(60, TimeUnit.MINUTES) 100 | return proc.inputStream.bufferedReader().readText() 101 | } catch (e: IOException) { 102 | e.printStackTrace() 103 | return null 104 | } 105 | } 106 | 107 | fun String.parseJavapMethodName(): JvmClassName? { 108 | val openParan = indexOf('(') 109 | if (openParan < 0) return null 110 | val firstSpace = substring(0, openParan).lastIndexOf(" ") 111 | val methodName = substring(firstSpace + 1, openParan).let { 112 | if (it.isBlank()) null 113 | else it 114 | } ?: return null 115 | // native methods gets a `-` in their name, cleanup 116 | return if (methodName.contains("-")) { 117 | methodName.split("-").let { 118 | JvmClassName(it[0], it[1]) 119 | } 120 | } else { 121 | JvmClassName(methodName, "") 122 | } 123 | } 124 | 125 | data class JvmClassName( 126 | val originalName: String, 127 | val suffix: String 128 | ) { 129 | val fullName: String 130 | get() = if (suffix.isBlank()) { 131 | originalName 132 | } else { 133 | "$originalName-$suffix" 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /jnigenerator/src/main/kotlin/com/birbit/jnigen/ParseTreeExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.jnigen 17 | 18 | import org.jetbrains.kotlin.spec.grammar.tools.KotlinParseTree 19 | object Declarations { 20 | const val FUN_DECLARATION = "functionDeclaration" 21 | const val OBJ_DECLARATION = "objectDeclaration" 22 | const val SIMPLE_IDENTIFIER = "simpleIdentifier" 23 | const val IDENTIFIER = "Identifier" 24 | const val MODIFIERS = "modifiers" 25 | const val MODIFIER = "modifier" 26 | const val FUN = "FUN" 27 | const val FUN_VALUE_PARAMETERS = "functionValueParameters" 28 | const val FUN_VALUE_PARAMETER = "functionValueParameter" 29 | const val TYPE_REFERENCE = "typeReference" 30 | const val TYPE = "type" 31 | const val NULLABLE_TYPE = "nullableType" 32 | } 33 | 34 | object Modifiers { 35 | const val EXTERNAL = "external" 36 | const val ACTUAL = "actual" 37 | } 38 | 39 | val nameFiled = KotlinParseTree::class.java.declaredFields.first { 40 | it.name == "name" 41 | }.also { 42 | it.isAccessible = true 43 | } 44 | 45 | val textField = KotlinParseTree::class.java.declaredFields.first { 46 | it.name == "text" 47 | }.also { 48 | it.isAccessible = true 49 | } 50 | 51 | fun KotlinParseTree.name() = nameFiled.get(this) as? String 52 | 53 | fun KotlinParseTree.text() = textField.get(this) as? String 54 | 55 | fun KotlinParseTree.asSequence(): Sequence = sequence { 56 | yield(this@asSequence) 57 | children.forEach { 58 | it.asSequence().forEach { 59 | yield(it) 60 | } 61 | } 62 | } 63 | 64 | fun KotlinParseTree.findFirst(filter: (KotlinParseTree) -> Boolean): KotlinParseTree? { 65 | return this.asSequence().firstOrNull(filter) 66 | } 67 | 68 | fun KotlinParseTree.objectDeclarations() = asSequence().filter { 69 | it.name() == Declarations.OBJ_DECLARATION 70 | }.map { 71 | ObjectDeclaration(it) 72 | } 73 | 74 | fun KotlinParseTree.skipFindPath(target: String, sections: List): List { 75 | val index = children.indexOfFirst { 76 | it.name() == target 77 | } 78 | if (index < 0) { 79 | error("cannot find $target") 80 | } 81 | return children.subList(index + 1, children.size).flatMap { 82 | it.findPath(sections) 83 | } 84 | } 85 | fun KotlinParseTree.findPath(sections: List): List { 86 | if (sections.isEmpty()) { 87 | return listOf(this) 88 | } 89 | val section = sections.first() 90 | val subSections = if (name() == section) { 91 | sections.subList(1, sections.size) 92 | } else { 93 | sections 94 | } 95 | if (subSections.isEmpty()) { 96 | return listOf(this) 97 | } 98 | val nextSection = subSections.first() 99 | return children.sortedBy { 100 | if (it.name() == nextSection) { 101 | 0 102 | } else { 103 | 1 104 | } 105 | }.flatMap { 106 | it.findPath(subSections) 107 | } 108 | } 109 | 110 | class ObjectDeclaration( 111 | val parseTree: KotlinParseTree 112 | ) { 113 | val name by lazy { 114 | parseTree.findPath( 115 | listOf( 116 | Declarations.SIMPLE_IDENTIFIER, 117 | Declarations.IDENTIFIER 118 | ) 119 | ).first().text() 120 | } 121 | 122 | val functions by lazy { 123 | parseTree.findPath(listOf(Declarations.FUN_DECLARATION)) 124 | .map { 125 | FunctionDeclaration(it) 126 | } 127 | } 128 | } 129 | 130 | class FunctionDeclaration( 131 | val parseTree: KotlinParseTree 132 | ) { 133 | val name by lazy { 134 | checkNotNull( 135 | parseTree.skipFindPath( 136 | Declarations.FUN, 137 | listOf( 138 | Declarations.SIMPLE_IDENTIFIER, 139 | Declarations.IDENTIFIER 140 | ) 141 | ).firstOrNull()?.text() 142 | ) { 143 | "cannot find name for $parseTree" 144 | } 145 | } 146 | val modifiers by lazy { 147 | parseTree.findPath( 148 | listOf( 149 | Declarations.MODIFIERS, 150 | Declarations.MODIFIER 151 | ) 152 | ) 153 | .map { 154 | it.children[0].children[0].text() 155 | } 156 | } 157 | val paramTypes by lazy { 158 | val parameters = parseTree.findPath( 159 | listOf( 160 | Declarations.FUN_VALUE_PARAMETERS, 161 | Declarations.FUN_VALUE_PARAMETER 162 | ) 163 | ) 164 | parameters.map { 165 | val name = it.findPath( 166 | listOf( 167 | Declarations.TYPE_REFERENCE, 168 | Declarations.SIMPLE_IDENTIFIER, 169 | Declarations.IDENTIFIER 170 | ) 171 | ).first().text() 172 | val nullable = it.findPath( 173 | listOf( 174 | Declarations.TYPE, 175 | Declarations.NULLABLE_TYPE 176 | ) 177 | ).isNotEmpty() 178 | checkNotNull(name) { 179 | "name cannot be null" 180 | } 181 | name.resolveType(nullable) 182 | } 183 | } 184 | val returnType by lazy { 185 | val typeTree = parseTree.children.firstOrNull { 186 | it.name() == Declarations.TYPE 187 | } 188 | checkNotNull(typeTree) { 189 | "cannot find type tree ${parseTree.children.map { it.name() }}" 190 | } 191 | val nullable = typeTree.findPath( 192 | listOf( 193 | Declarations.TYPE, 194 | Declarations.NULLABLE_TYPE 195 | ) 196 | ).isNotEmpty() 197 | checkNotNull( 198 | typeTree.findPath( 199 | listOf( 200 | Declarations.TYPE_REFERENCE, 201 | Declarations.SIMPLE_IDENTIFIER, 202 | Declarations.IDENTIFIER 203 | ) 204 | ).first().text() 205 | ) { 206 | "cannot find return type for $parseTree" 207 | }.resolveType(nullable) 208 | } 209 | val external: Boolean 210 | get() = modifiers.contains(Modifiers.EXTERNAL) 211 | 212 | override fun toString(): String { 213 | return "$name(${paramTypes.joinToString()}):$returnType" 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /jnigenerator/src/main/kotlin/com/birbit/jnigen/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.jnigen 17 | 18 | import com.squareup.kotlinpoet.ClassName 19 | import com.squareup.kotlinpoet.CodeBlock 20 | import com.squareup.kotlinpoet.ParameterSpec 21 | import com.squareup.kotlinpoet.TypeName 22 | import com.squareup.kotlinpoet.asClassName 23 | import com.squareup.kotlinpoet.buildCodeBlock 24 | 25 | typealias ToJniFun = (type: Type, envParam: ParameterSpec, inVar: String, outVar: String) -> (CodeBlock) 26 | typealias FronJniFun = (type: Type, envParam: ParameterSpec, inParam: ParameterSpec, outVar: String) -> (CodeBlock) 27 | 28 | open class Type( 29 | val kotlinDecl: String, 30 | val kotlinClass: TypeName, 31 | val nativeClass: TypeName, 32 | private val convertToJni: ToJniFun? = null, 33 | private val convertFromJni: FronJniFun? = null, 34 | val nullable: Boolean, 35 | private val defaultValue: String 36 | ) { 37 | constructor( 38 | kotlinClass: ClassName, 39 | nativeClass: TypeName, 40 | convertToJni: ToJniFun? = null, 41 | convertFromJni: FronJniFun? = null, 42 | defaultValue: String 43 | ) : this( 44 | kotlinDecl = kotlinClass.simpleName, 45 | kotlinClass = kotlinClass, 46 | nativeClass = nativeClass, 47 | convertToJni = convertToJni, 48 | convertFromJni = convertFromJni, 49 | nullable = false, 50 | defaultValue = defaultValue 51 | ) 52 | 53 | fun defaultValue() = if (nullable) "null" else defaultValue 54 | 55 | fun hasConvertToJni() = convertToJni != null 56 | fun hasConvertFromJni() = convertFromJni != null 57 | 58 | fun convertToJni(envParam: ParameterSpec, inVar: String, outVar: String) = 59 | convertToJni!!(this, envParam, inVar, outVar) 60 | 61 | fun convertFromJni(envParam: ParameterSpec, inParam: ParameterSpec, outVar: String) = 62 | convertFromJni!!(this, envParam, inParam, outVar) 63 | 64 | fun copy(nullable: Boolean) = Type( 65 | kotlinDecl = kotlinDecl, 66 | kotlinClass = kotlinClass.copy(nullable = nullable), 67 | nativeClass = nativeClass.copy(nullable = nullable), 68 | convertFromJni = convertFromJni, 69 | convertToJni = convertToJni, 70 | nullable = nullable, 71 | defaultValue = defaultValue 72 | ) 73 | 74 | class BridgeType(kotlinClass: ClassName) : Type( 75 | kotlinClass = kotlinClass, 76 | nativeClass = ClassNames.JLONG, 77 | convertFromJni = { type, envParam, inParam, outVar -> 78 | CodeBlock.builder().apply { 79 | addStatement("val %L = ${kotlinClass.simpleName.titleCase()}FromJni(%N)", outVar, inParam) 80 | }.build() 81 | }, 82 | convertToJni = { type, envParam, inVar, outVar -> 83 | CodeBlock.builder().apply { 84 | addStatement("val %L = %L.toJni()", outVar, inVar) 85 | }.build() 86 | }, 87 | defaultValue = "0" 88 | ) 89 | 90 | class BytesBackedType( 91 | kotlinClass: ClassName, 92 | nativeClass: ClassName, 93 | toKMethod: String, 94 | toJMethod: String, 95 | defaultValue: String 96 | ) : Type( 97 | kotlinClass = kotlinClass, 98 | nativeClass = nativeClass, 99 | convertToJni = { type, envParam, inVar, outVar -> 100 | CodeBlock.builder().apply { 101 | if (type.nullable) { 102 | addStatement("val %L = %L?.$toJMethod(%N)", outVar, inVar, envParam) 103 | } else { 104 | addStatement("val %L = checkNotNull(%L.$toJMethod(%N))", outVar, inVar, envParam) 105 | } 106 | }.build() 107 | }, 108 | convertFromJni = { type, envParam, inParam, outVar -> 109 | CodeBlock.builder().apply { 110 | if (type.nullable) { 111 | addStatement("val %L = %N.$toKMethod(%N)", outVar, inParam, envParam) 112 | } else { 113 | addStatement("val %L = checkNotNull(%N.$toKMethod(%N))", outVar, inParam, envParam) 114 | } 115 | }.build() 116 | }, 117 | defaultValue = defaultValue 118 | ) 119 | 120 | companion object { 121 | private val types: List by lazy { 122 | val self = this 123 | this::class.java.methods.filter { 124 | Type::class.java.isAssignableFrom(it.returnType) && it.name.startsWith("get") 125 | }.map { 126 | it.invoke(self) as Type 127 | } 128 | } 129 | 130 | fun resolve(kotlinType: String, nullable: Boolean) = types.firstOrNull { 131 | it.kotlinDecl == kotlinType 132 | }?.let { 133 | it.copy(nullable = nullable) 134 | } ?: error("cannot resolve type $kotlinType") 135 | 136 | val INT = 137 | Type(Int::class.asClassName(), ClassNames.JINT, defaultValue = "0") 138 | val STRING = BytesBackedType( 139 | kotlinClass = String::class.asClassName(), 140 | nativeClass = ClassNames.JSTRING, 141 | toKMethod = "toKString", 142 | toJMethod = "toJString", 143 | defaultValue = "\"\".toJString(env)!!" // TODO fix this 144 | ) 145 | val DBREF = BridgeType(ClassNames.DB_REF) 146 | val STMTREF = BridgeType(ClassNames.STMT_REF) 147 | val LONG = 148 | Type(Long::class.asClassName(), ClassNames.JLONG, defaultValue = "0L") 149 | val DOUBLE = Type( 150 | Double::class.asClassName(), 151 | ClassNames.JDOUBLE, 152 | defaultValue = "0.0" 153 | ) 154 | val BOOLEAN = Type( 155 | Boolean::class.asClassName(), 156 | ClassNames.JBOOLEAN, 157 | defaultValue = "JFALSE", 158 | convertFromJni = { type, envParam, inParam, outVar -> 159 | buildCodeBlock { 160 | addStatement("val %L = %N.toKBoolean()", outVar, inParam) 161 | } 162 | }, 163 | convertToJni = { type, envParam, inVar, outVar -> 164 | buildCodeBlock { 165 | addStatement("val %L = %L.toJBoolean()", outVar, inVar) 166 | } 167 | } 168 | ) 169 | val RESULT_CODE = Type( 170 | ClassNames.RESULT_CODE, 171 | ClassNames.RESULT_CODE, 172 | defaultValue = "ResultCode(-1)" 173 | ) 174 | val BYTE_ARRAY = BytesBackedType( 175 | kotlinClass = ByteArray::class.asClassName(), 176 | nativeClass = ClassNames.JBYTEARRAY, 177 | toKMethod = "toKByteArray", 178 | toJMethod = "toJByteArray", 179 | defaultValue = "emptyByteArray()" 180 | ) 181 | val AUTHORIZER = Type( 182 | kotlinClass = ClassNames.AUTHORIZER, 183 | nativeClass = ClassNames.JOBJECT, 184 | convertFromJni = { type, envParam, inParam, outVar -> 185 | buildCodeBlock { 186 | addStatement("val %N = %N.toKAuthorizer(%N)", outVar, inParam, envParam) 187 | } 188 | }, 189 | defaultValue = "null" 190 | ) 191 | val COLUMN_TYPE = Type( 192 | kotlinClass = ClassNames.COLUMN_TYPE, 193 | nativeClass = ClassNames.COLUMN_TYPE, 194 | defaultValue = "ColumnType(-1)" 195 | ) 196 | } 197 | } 198 | 199 | fun String.resolveType(nullable: Boolean) = Type.resolve(this, nullable) 200 | -------------------------------------------------------------------------------- /keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigit/kotlin-sqlite-bindings/77ef32a3503175d039099069a6cc5512cbd16d1d/keystore/debug.keystore -------------------------------------------------------------------------------- /ksqlite3/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id(libs.plugins.agpLibrary.get().pluginId) 19 | alias(libs.plugins.kotlinMp) 20 | id("maven-publish") 21 | id("ksqlite-build") 22 | } 23 | 24 | ksqliteBuild { 25 | native(includeAndroidNative = false) 26 | android() 27 | publish() 28 | buildOnServer() 29 | } 30 | 31 | kotlin { 32 | sourceSets { 33 | val commonMain by getting { 34 | dependencies { 35 | implementation(libs.kotlinStdlibCommon) 36 | implementation(project(":sqlitebindings")) 37 | api(project(":sqlitebindings-api")) 38 | } 39 | } 40 | val commonTest by getting { 41 | dependencies { 42 | implementation(libs.kotlinTestCommon) 43 | implementation(libs.kotlinTestAnnotationsCommon) 44 | } 45 | } 46 | 47 | val androidInstrumentedTest by getting { 48 | dependsOn(commonTest) 49 | dependencies { 50 | implementation(libs.kotlinTestJunit) 51 | implementation(libs.bundles.androidInstrumentedTest) 52 | } 53 | } 54 | val androidUnitTest by getting { 55 | dependsOn(commonTest) 56 | dependencies { 57 | implementation(libs.kotlinTestJunit) 58 | implementation(libs.bundles.androidInstrumentedTest) 59 | } 60 | } 61 | // JVM-specific tests and their dependencies: 62 | jvm().compilations["test"].defaultSourceSet { 63 | dependencies { 64 | implementation(libs.kotlinTestJunit) 65 | } 66 | } 67 | val linuxTest by creating 68 | val windowsTest by creating 69 | val macTest by creating 70 | this.forEach { ss -> 71 | if (ss.name.endsWith("Test")) { 72 | val osSourceSet = when { 73 | ss.name.startsWith("mac") -> macTest 74 | ss.name.startsWith("ios") -> macTest 75 | ss.name.startsWith("linux") -> linuxTest 76 | ss.name.startsWith("mingw") -> windowsTest 77 | else -> null 78 | } 79 | osSourceSet?.let { 80 | if (it.name != ss.name) { 81 | ss.dependsOn(it) 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | android { 90 | namespace = "com.birbit.sqlite3" 91 | } 92 | -------------------------------------------------------------------------------- /ksqlite3/src/androidInstrumentedTest/kotlin/com/birbit/sqlite3/OsSpecificTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import java.io.File 19 | 20 | actual object OsSpecificTestUtils { 21 | internal actual fun mkdirForTest(path: String) { 22 | File(path).mkdirs() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ksqlite3/src/androidInstrumentedTest/kotlin/com/birbit/sqlite3/PlatformTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import android.content.Context 19 | import androidx.test.platform.app.InstrumentationRegistry 20 | import java.io.File 21 | import java.util.UUID 22 | import java.util.concurrent.atomic.AtomicReference 23 | import kotlin.concurrent.thread 24 | 25 | actual object PlatformTestUtils { 26 | private val context: Context 27 | get() = InstrumentationRegistry.getInstrumentation().context 28 | 29 | actual fun getTmpDir(): String = context.filesDir.resolve( 30 | UUID.randomUUID().toString().substring(0, 6) 31 | ).let { 32 | it.mkdirs() 33 | return it.absolutePath 34 | } 35 | 36 | actual fun fileExists(path: String): Boolean { 37 | return File(path).exists() 38 | } 39 | 40 | actual fun fileSeparator(): Char { 41 | return File.separatorChar 42 | } 43 | 44 | actual fun deleteDir(tmpDir: String) { 45 | File(tmpDir).deleteRecursively() 46 | } 47 | 48 | actual fun runInAnotherThread(block: () -> T): T { 49 | val result = AtomicReference() 50 | thread { 51 | result.set(block()) 52 | }.join() 53 | return result.get() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ksqlite3/src/commonMain/kotlin/com/birbit/sqlite3/Row.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import com.birbit.sqlite3.internal.SqliteApi 19 | import com.birbit.sqlite3.internal.StmtRef 20 | 21 | class Row internal constructor( 22 | private val stmt: StmtRef 23 | ) { 24 | fun isNull(index: Int) = SqliteApi.columnIsNull(stmt, index) 25 | fun readString(index: Int) = SqliteApi.columnText(stmt, index) 26 | fun readInt(index: Int) = SqliteApi.columnInt(stmt, index) 27 | fun readByteArray(index: Int): ByteArray? = SqliteApi.columnBlob(stmt, index) 28 | fun readDouble(index: Int): Double = SqliteApi.columnDouble(stmt, index) 29 | fun readLong(index: Int): Long = SqliteApi.columnLong(stmt, index) 30 | } 31 | -------------------------------------------------------------------------------- /ksqlite3/src/commonMain/kotlin/com/birbit/sqlite3/SqliteConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import com.birbit.sqlite3.internal.DbRef 19 | import com.birbit.sqlite3.internal.SqliteApi 20 | 21 | class SqliteConnection private constructor( 22 | private val dbRef: DbRef 23 | ) { 24 | fun prepareStmt(stmt: String): SqliteStmt { 25 | // TODO we should track these to ensure we release them if closed as they keep a StableRef 26 | return SqliteStmt(this, SqliteApi.prepareStmt(dbRef, stmt)) 27 | } 28 | 29 | fun query(query: String, args: List = emptyList(), callback: (Sequence) -> T): T { 30 | return prepareStmt(query).use { stmt -> 31 | stmt.bindValues(args) 32 | callback(stmt.query()) 33 | } 34 | } 35 | 36 | fun exec(query: String) = SqliteApi.exec(dbRef, query) 37 | 38 | fun lastErrorMessage() = SqliteApi.errorMsg(dbRef) 39 | 40 | fun lastErrorCode() = SqliteApi.errorCode(dbRef) 41 | 42 | fun use(block: (SqliteConnection) -> T): T { 43 | return try { 44 | block(this) 45 | } finally { 46 | close() 47 | } 48 | } 49 | 50 | fun close() { 51 | check(SqliteApi.close(dbRef) == ResultCode.OK) { 52 | "failed to close database" 53 | } 54 | dbRef.dispose() 55 | } 56 | 57 | fun setAuthCallback( 58 | callback: (AuthorizationParams) -> AuthResult 59 | ) { 60 | // TODO we should track these to ensure we release them if closed as they keep a StableRef 61 | // this object can disappear once we move Authorizer to fun interface 62 | SqliteApi.setAuthorizer( 63 | dbRef, 64 | object : Authorizer { 65 | override fun invoke(params: AuthorizationParams): AuthResult { 66 | return callback(params) 67 | } 68 | } 69 | ) 70 | } 71 | 72 | fun clearAuthCallback() { 73 | SqliteApi.setAuthorizer(dbRef, null) 74 | } 75 | 76 | companion object { 77 | fun openConnection(path: String) = SqliteConnection( 78 | SqliteApi.openConnection(path) 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ksqlite3/src/commonMain/kotlin/com/birbit/sqlite3/SqliteStmt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import com.birbit.sqlite3.internal.SqliteApi 19 | import com.birbit.sqlite3.internal.StmtRef 20 | 21 | class SqliteStmt( 22 | val connection: SqliteConnection, 23 | private val stmtRef: StmtRef 24 | ) { 25 | fun close() { 26 | check(SqliteApi.finalize(stmtRef) == ResultCode.OK) { 27 | "failed to close result code" 28 | } 29 | stmtRef.dispose() 30 | } 31 | 32 | fun obtainResultMetadata(): ResultMetadata { 33 | val columnCount = SqliteApi.columnCount(stmtRef) 34 | val columns = (0 until columnCount).map { columnIndex -> 35 | ResultMetadata.ColumnInfo( 36 | databaseName = SqliteApi.columnDatabaseName(stmtRef, columnIndex), 37 | tableName = SqliteApi.columnTableName(stmtRef, columnIndex), 38 | originName = SqliteApi.columnOriginName(stmtRef, columnIndex), 39 | declaredType = SqliteApi.columnDeclType(stmtRef, columnIndex), 40 | name = SqliteApi.columnName(stmtRef, columnIndex) 41 | ) 42 | } 43 | return ResultMetadata(columns) 44 | } 45 | 46 | fun obtainBindMetadata(): BindParameterMetadata { 47 | val bindParamCount = SqliteApi.bindParameterCount(stmtRef) 48 | return BindParameterMetadata( 49 | params = (1..bindParamCount).map { 50 | BindParameterMetadata.BindParameter( 51 | index = it, 52 | name = SqliteApi.bindParameterName(stmtRef, it) 53 | ) 54 | } 55 | ) 56 | } 57 | 58 | fun use(block: (SqliteStmt) -> T): T { 59 | return try { 60 | block(this) 61 | } finally { 62 | close() 63 | } 64 | } 65 | 66 | fun bind(index: Int, value: ByteArray) { 67 | val resultCode = SqliteApi.bindBlob(stmtRef, index, value) 68 | check(ResultCode.OK == resultCode) { 69 | "unable to bind bytes: $resultCode" 70 | } 71 | } 72 | 73 | fun bind(index: Int, value: String) { 74 | val resultCode = SqliteApi.bindText(stmtRef, index, value) 75 | check(ResultCode.OK == resultCode) { 76 | "unable to bind value $value to index $index. Result code: $resultCode" 77 | } 78 | } 79 | 80 | fun bind(index: Int, value: Int) { 81 | val resultCode = SqliteApi.bindInt(stmtRef, index, value) 82 | check(ResultCode.OK == resultCode) { 83 | "unable to bind value $value to index $index" 84 | } 85 | } 86 | 87 | fun bind(index: Int, value: Long) { 88 | val resultCode = SqliteApi.bindLong(stmtRef, index, value) 89 | check(ResultCode.OK == resultCode) { 90 | "unable to bind value $value to index $index" 91 | } 92 | } 93 | 94 | fun bindNull(index: Int) { 95 | val resultCode = SqliteApi.bindNull(stmtRef, index) 96 | check(ResultCode.OK == resultCode) { 97 | "unable to bind null to index $index" 98 | } 99 | } 100 | 101 | fun bind(index: Int, value: Double) { 102 | val resultCode = SqliteApi.bindDouble(stmtRef, index, value) 103 | check(ResultCode.OK == resultCode) { 104 | "unable to bind value $value to index $index" 105 | } 106 | } 107 | 108 | // TODO provide an API where we can enforce closing 109 | // maybe sth like `use` which will give APIs like query during the time `use` is called. 110 | // might be better to call it `acquire` or `obtain` if we won't close afterwards though. 111 | fun query(): Sequence = sequence { 112 | SqliteApi.reset(stmtRef) 113 | val row = Row(stmtRef) 114 | val stepResultCode: ResultCode = ResultCode.OK 115 | while (SqliteApi.step(stmtRef).also { stepResultCode == it } == ResultCode.ROW) { 116 | yield(row) 117 | } 118 | check(stepResultCode == ResultCode.OK || stepResultCode == ResultCode.DONE) { 119 | "querying rows ended prematurely $stepResultCode" 120 | } 121 | } 122 | 123 | fun execute(): ResultCode { 124 | val rc = SqliteApi.step(stmtRef) 125 | SqliteApi.reset(stmtRef) 126 | return rc 127 | } 128 | 129 | fun bindValue(index: Int, value: Any?) { 130 | // should we delgate to sqlite? might be tricky w/ all type casting 131 | when (value) { 132 | null -> bindNull(index) 133 | is Int -> bind(index, value) 134 | is Long -> bind(index, value) 135 | is Float -> bind(index, value.toDouble()) 136 | is Double -> bind(index, value) 137 | is String -> bind(index, value) 138 | is ByteArray -> bind(index, value) 139 | is Number -> bind(index, value.toDouble()) 140 | else -> throw SqliteException(ResultCode.FORMAT, "cannot bind $value") 141 | } 142 | } 143 | 144 | fun bindValues(args: List) { 145 | args.forEachIndexed { index, value -> 146 | bindValue(index + 1, value) 147 | } 148 | } 149 | 150 | fun columnType(index: Int) = SqliteApi.columnType(stmtRef, index) 151 | fun normalizedQuery() = SqliteApi.normalizedSql(stmtRef) 152 | fun expandedQuery() = SqliteApi.expandedSql(stmtRef) 153 | fun sql() = SqliteApi.sql(stmtRef) 154 | 155 | data class ResultMetadata( 156 | val columns: List 157 | ) { 158 | data class ColumnInfo( 159 | val name: String?, 160 | val databaseName: String?, 161 | val tableName: String?, 162 | val originName: String?, 163 | val declaredType: String? 164 | ) 165 | } 166 | 167 | data class BindParameterMetadata( 168 | val params: List 169 | ) { 170 | data class BindParameter( 171 | val index: Int, 172 | val name: String? 173 | ) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /ksqlite3/src/commonTest/kotlin/com/birbit/sqlite3/OsSpecificTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | expect object OsSpecificTestUtils { 19 | // mkdir in posix is different in all OSs so lets use this instead 20 | internal fun mkdirForTest(path: String) 21 | } 22 | -------------------------------------------------------------------------------- /ksqlite3/src/commonTest/kotlin/com/birbit/sqlite3/PlatformTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | expect object PlatformTestUtils { 19 | fun getTmpDir(): String 20 | fun fileExists(path: String): Boolean 21 | fun fileSeparator(): Char 22 | fun deleteDir(tmpDir: String) 23 | fun runInAnotherThread(block: () -> T): T 24 | } 25 | 26 | fun withTmpFolder(block: TmpFolderScope.() -> T) { 27 | val tmpDir = PlatformTestUtils.getTmpDir() 28 | val scope = object : TmpFolderScope { 29 | override fun getFilePath(name: String) = tmpDir + PlatformTestUtils.fileSeparator() + name 30 | } 31 | try { 32 | scope.block() 33 | } finally { 34 | PlatformTestUtils.deleteDir(tmpDir) 35 | } 36 | } 37 | 38 | interface TmpFolderScope { 39 | fun getFilePath(name: String): String 40 | } 41 | -------------------------------------------------------------------------------- /ksqlite3/src/commonTest/kotlin/com/birbit/sqlite3/SqliteConnectionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import com.birbit.sqlite3.PlatformTestUtils.runInAnotherThread 19 | import kotlin.test.Test 20 | import kotlin.test.assertEquals 21 | import kotlin.test.assertTrue 22 | 23 | class SqliteConnectionTest { 24 | @Test 25 | fun openInMemory() { 26 | val conn = SqliteConnection.openConnection(":memory:") 27 | conn.close() 28 | } 29 | 30 | @Test 31 | fun openInvalidPath() { 32 | val result = kotlin.runCatching { 33 | SqliteConnection.openConnection("/::") 34 | } 35 | assertEquals( 36 | result.exceptionOrNull(), 37 | SqliteException( 38 | ResultCode.CANTOPEN, 39 | "could not open database at path /::" 40 | ) 41 | ) 42 | } 43 | 44 | @Test 45 | fun openOnDisk() { 46 | withTmpFolder { 47 | val dbPath = getFilePath("testdb.db") 48 | val connection = SqliteConnection.openConnection(dbPath) 49 | connection.close() 50 | assertTrue(PlatformTestUtils.fileExists(dbPath), "no file in $dbPath") 51 | } 52 | } 53 | 54 | @Test 55 | fun readErrors() { 56 | SqliteConnection.openConnection(":memory:").use { 57 | val result = kotlin.runCatching { 58 | it.prepareStmt("SELECT * FROM nonExistingTable") 59 | } 60 | 61 | assertEquals( 62 | result.exceptionOrNull(), 63 | SqliteException( 64 | resultCode = ResultCode.ERROR, 65 | msg = "no such table: nonExistingTable" 66 | ) 67 | ) 68 | assertEquals(it.lastErrorCode(), ResultCode.ERROR) 69 | assertEquals(it.lastErrorMessage(), "no such table: nonExistingTable") 70 | } 71 | } 72 | 73 | @Test 74 | fun oneTimeQuery() { 75 | val res = SqliteConnection.openConnection(":memory:").use { 76 | it.query("SELECT ?, ?", listOf(3, "a")) { 77 | it.first().let { 78 | it.readInt(0) to it.readString(1) 79 | } 80 | } 81 | } 82 | assertEquals(res, 3 to "a") 83 | } 84 | 85 | @Test 86 | fun authCallback() { 87 | val auth1 = LoggingAuthCallback() 88 | val auth2 = LoggingAuthCallback() 89 | SqliteConnection.openConnection(":memory:").use { conn -> 90 | conn.setAuthCallback(auth1::invoke) 91 | conn.prepareStmt("SELECT * from sqlite_master").use { } 92 | auth1.assertParam("sqlite_master") 93 | auth1.clear() 94 | conn.setAuthCallback(auth2::invoke) 95 | conn.prepareStmt("SELECT * from sqlite_master").use { } 96 | auth2.assertParam("sqlite_master") 97 | auth1.assertEmpty() 98 | conn.clearAuthCallback() 99 | auth2.clear() 100 | conn.prepareStmt("SELECT * from sqlite_master").use { } 101 | auth1.assertEmpty() 102 | auth2.assertEmpty() 103 | } 104 | } 105 | 106 | @Test 107 | fun readEmptyTable() { 108 | SqliteConnection.openConnection(":memory:").use { 109 | it.exec("CREATE TABLE Test(id Int)") 110 | val result = it.prepareStmt("SELECT * FROM Test").use { 111 | it.query().toList() 112 | } 113 | assertEquals(result, emptyList()) 114 | } 115 | } 116 | 117 | @Test 118 | fun multiThreadedAccess() { 119 | SqliteConnection.openConnection(":memory:").use { conn -> 120 | conn.exec("CREATE TABLE Foo(name)") 121 | val rc = runInAnotherThread { 122 | conn.exec("INSERT INTO Foo VALUES('bar')") 123 | } 124 | assertEquals(rc, ResultCode.OK) 125 | val read = conn.query("SELECT * FROM Foo") { 126 | it.first().readString(0) 127 | } 128 | assertEquals(read, "bar") 129 | } 130 | } 131 | 132 | private class LoggingAuthCallback { 133 | val allParams = mutableSetOf() 134 | fun invoke(params: AuthorizationParams): AuthResult { 135 | allParams.addAll(listOfNotNull(params.param1, params.param2, params.param3, params.param4)) 136 | return AuthResult.OK 137 | } 138 | 139 | fun assertParam(param: String) { 140 | assertTrue(allParams.contains(param), "missing $param in $allParams") 141 | } 142 | 143 | fun assertEmpty() { 144 | assertEquals(allParams, emptySet(), "this should be empty but have $allParams") 145 | } 146 | 147 | fun clear() { 148 | allParams.clear() 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /ksqlite3/src/jvmTest/kotlin/com/birbit/sqlite3/OsSpecificTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import java.io.File 19 | 20 | actual object OsSpecificTestUtils { 21 | internal actual fun mkdirForTest(path: String) { 22 | File(path).mkdirs() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ksqlite3/src/jvmTest/kotlin/com/birbit/sqlite3/PlatformTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import java.io.File 19 | import java.nio.file.Paths 20 | import java.util.UUID 21 | import java.util.concurrent.atomic.AtomicReference 22 | import kotlin.concurrent.thread 23 | 24 | actual object PlatformTestUtils { 25 | actual fun getTmpDir(): String { 26 | val tmpDirPath = System.getProperty("java.io.tmpdir") ?: error("cannot find java tmp dir") 27 | val fullPath = Paths.get(tmpDirPath, "ksqlite", UUID.randomUUID().toString().substring(0, 20)) 28 | val file = fullPath.toFile() 29 | file.mkdirs() 30 | return file.absolutePath 31 | } 32 | 33 | actual fun fileExists(path: String): Boolean { 34 | return File(path).exists() 35 | } 36 | 37 | actual fun fileSeparator(): Char { 38 | return File.separatorChar 39 | } 40 | 41 | actual fun deleteDir(tmpDir: String) { 42 | val file = File(tmpDir) 43 | if (!file.exists()) return 44 | file.deleteRecursively() 45 | } 46 | 47 | actual fun runInAnotherThread(block: () -> T): T { 48 | val result = AtomicReference() 49 | thread { 50 | result.set(block()) 51 | }.join() 52 | return result.get() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ksqlite3/src/linuxTest/kotlin/com/birbit/sqlite3/OsSpecificTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import kotlinx.cinterop.convert 19 | import platform.posix.S_IRUSR 20 | import platform.posix.S_IWUSR 21 | import platform.posix.S_IXUSR 22 | import platform.posix.mkdir 23 | 24 | actual object OsSpecificTestUtils { 25 | internal actual fun mkdirForTest(path: String) { 26 | // TODO both linux and mac can use convert() 27 | mkdir(path, S_IRUSR.or(S_IWUSR).or(S_IXUSR).convert()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ksqlite3/src/macTest/kotlin/com/birbit/sqlite3/OsSpecificTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:OptIn(ExperimentalForeignApi::class) 17 | 18 | package com.birbit.sqlite3 19 | 20 | import kotlinx.cinterop.ExperimentalForeignApi 21 | import kotlinx.cinterop.convert 22 | import platform.posix.S_IRUSR 23 | import platform.posix.S_IWUSR 24 | import platform.posix.S_IXUSR 25 | import platform.posix.mkdir 26 | 27 | actual object OsSpecificTestUtils { 28 | internal actual fun mkdirForTest(path: String) { 29 | mkdir(path, S_IRUSR.or(S_IWUSR).or(S_IXUSR).convert()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ksqlite3/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /ksqlite3/src/nativeTest/kotlin/com/birbit/sqlite3/PlatformTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:OptIn(ExperimentalForeignApi::class) 17 | 18 | package com.birbit.sqlite3 19 | 20 | import kotlinx.cinterop.ExperimentalForeignApi 21 | import kotlinx.cinterop.memScoped 22 | import kotlinx.cinterop.staticCFunction 23 | import kotlinx.cinterop.toKStringFromUtf8 24 | import platform.posix.FTW_DEPTH 25 | import platform.posix.FTW_PHYS 26 | import platform.posix.F_OK 27 | import platform.posix.access 28 | import platform.posix.nftw 29 | import platform.posix.remove 30 | import kotlin.experimental.ExperimentalNativeApi 31 | import kotlin.native.concurrent.TransferMode 32 | import kotlin.native.concurrent.Worker 33 | import kotlin.random.Random 34 | 35 | @OptIn(ExperimentalNativeApi::class) 36 | actual object PlatformTestUtils { 37 | private fun randomFolderName(): String { 38 | return (0..20).map { 39 | 'a' + Random.nextInt(0, 26) 40 | }.joinToString("") 41 | } 42 | 43 | actual fun getTmpDir(): String { 44 | val tmpDir = memScoped { 45 | val tmpName = "ksqlite_tmp${randomFolderName()}" 46 | // for some reason, mkdtemp does not work on command line tests :/ 47 | // second param to mkdir is UShort on mac and UInt on linux :/ 48 | OsSpecificTestUtils.mkdirForTest(tmpName) 49 | tmpName 50 | } 51 | return checkNotNull(tmpDir) { 52 | "mkdtemp failed environment variable" 53 | } 54 | } 55 | 56 | actual fun fileExists(path: String): Boolean { 57 | return access(path, F_OK) != -1 58 | } 59 | 60 | actual fun fileSeparator(): Char { 61 | return when (Platform.osFamily) { 62 | OsFamily.WINDOWS -> '\\' 63 | else -> '/' 64 | } 65 | } 66 | 67 | actual fun deleteDir(tmpDir: String) { 68 | nftw( 69 | tmpDir, 70 | staticCFunction { path, stat, typeFlag, ftw -> 71 | memScoped { 72 | remove(path!!.toKStringFromUtf8()) 73 | } 74 | }, 75 | 64, FTW_DEPTH.or(FTW_PHYS) 76 | ) 77 | } 78 | 79 | actual fun runInAnotherThread(block: () -> T): T { 80 | // we don't care about being unsafe here, it is just for tests 81 | val worker = Worker.start(name = "one-off") 82 | val resultFuture = worker.execute(TransferMode.UNSAFE, { block }) { 83 | it() 84 | } 85 | return resultFuture.result 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ksqlite3/src/windowsTest/kotlin/com/birbit/sqlite3/OsSpecificTestUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | actual object OsSpecificTestUtils { 19 | internal actual fun mkdirForTest(path: String) { 20 | platform.posix.mkdir(path) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/copyright.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright $YEAR Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pluginManagement { 17 | repositories { 18 | mavenCentral() 19 | gradlePluginPortal() 20 | google() 21 | } 22 | } 23 | 24 | dependencyResolutionManagement { 25 | repositories { 26 | mavenCentral() 27 | google() 28 | } 29 | } 30 | 31 | plugins { 32 | id("androidx.build.gradle.gcpbuildcache") version("1.0.0-beta01") 33 | id("com.gradle.enterprise") version("3.10") 34 | } 35 | 36 | gradleEnterprise { 37 | buildScan { 38 | termsOfServiceUrl = "https://gradle.com/terms-of-service" 39 | termsOfServiceAgree = "yes" 40 | capture { 41 | isTaskInputFiles = true 42 | } 43 | } 44 | } 45 | 46 | val gcpKey = providers.environmentVariable("GRADLE_CACHE_KEY").orNull 47 | ?: providers.environmentVariable("GRADLE_CACHE_FILE").orNull?.let { 48 | File(it).readText() 49 | } 50 | val cacheIsPush = providers.environmentVariable("GRADLE_CACHE_PUSH").orNull?.toBoolean() ?: false 51 | if (gcpKey != null) { 52 | println("setting up remote build cache with push: $cacheIsPush") 53 | buildCache { 54 | remote(androidx.build.gradle.gcpbuildcache.GcpBuildCache::class) { 55 | projectId = "kotlin-sqlite-bindings" 56 | bucketName = "kotlin-sqlite-bindings-cache" 57 | credentials = androidx.build.gradle.gcpbuildcache.ExportedKeyGcpCredentials { 58 | gcpKey 59 | } 60 | isPush = cacheIsPush 61 | } 62 | } 63 | } else { 64 | println("not using remote build cache") 65 | } 66 | includeBuild("buildPlugin") 67 | include("sqlitebindings", "sqlitebindings-api", "jnigenerator", "ksqlite3") 68 | -------------------------------------------------------------------------------- /sqlitebindings-api/README.md: -------------------------------------------------------------------------------- 1 | Module to expose common SQLite related classes from the sqlitebindings artifact. 2 | 3 | This allows ksqlite to have an internal dependency on sqlitebindings while 4 | still exposing some parts as APIs (like ResultCode) -------------------------------------------------------------------------------- /sqlitebindings-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | alias(libs.plugins.kotlinMp) 19 | id("maven-publish") 20 | id("ksqlite-build") 21 | } 22 | 23 | ksqliteBuild { 24 | native(includeAndroidNative = true) 25 | publish() 26 | buildOnServer() 27 | } 28 | kotlin { 29 | jvm() 30 | sourceSets { 31 | val commonMain by getting { 32 | dependencies { 33 | implementation(libs.kotlinStdlibCommon) 34 | } 35 | } 36 | // studio is looking for it when syncing 37 | val main by creating { 38 | dependsOn(commonMain) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sqlitebindings-api/src/commonMain/kotlin/com/birbit/sqlite3/AuthResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import kotlin.jvm.JvmInline 19 | 20 | // see https://www.sqlite.org/c3ref/c_deny.html 21 | @JvmInline 22 | value class AuthResult(val value: Int) { 23 | companion object { 24 | val OK = AuthResult(0) 25 | val DENY = AuthResult(1) 26 | val IGNORE = AuthResult(2) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sqlitebindings-api/src/commonMain/kotlin/com/birbit/sqlite3/AuthorizationParams.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | class AuthorizationParams( 19 | val actionCode: Int, 20 | val param1: String?, 21 | val param2: String?, 22 | val param3: String?, 23 | val param4: String? 24 | ) { 25 | override fun toString(): String { 26 | return "AuthorizationParams(actionCode=$actionCode, param1=$param1, param2=$param2, param3=$param3, " + 27 | "param4=$param4)" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sqlitebindings-api/src/commonMain/kotlin/com/birbit/sqlite3/Authorizer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | interface Authorizer { 19 | operator fun invoke(params: AuthorizationParams): AuthResult 20 | fun dispose() { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sqlitebindings-api/src/commonMain/kotlin/com/birbit/sqlite3/ColumnType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import kotlin.jvm.JvmInline 19 | 20 | // see https://www.sqlite.org/c3ref/c_blob.html 21 | @JvmInline 22 | value class ColumnType(val value: Int) { 23 | companion object { 24 | val INTEGER = ColumnType(1) 25 | val FLOAT = ColumnType(2) 26 | val STRING = ColumnType(3) 27 | val BLOB = ColumnType(4) 28 | val NULL = ColumnType(5) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlitebindings-api/src/commonMain/kotlin/com/birbit/sqlite3/ResultCode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | import kotlin.jvm.JvmInline 19 | 20 | // commonized sqlite APIs to build the rest in common, or most at least 21 | @JvmInline 22 | value class ResultCode(val value: Int) { 23 | companion object { 24 | val OK = ResultCode(0) /* Successful result */ 25 | val ERROR = ResultCode(1) /* Generic error */ 26 | val INTERNAL = ResultCode(2) /* Internal logic error in SQLite */ 27 | val PERM = ResultCode(3) /* Access permission denied */ 28 | val ABORT = ResultCode(4) /* Callback routine requested an abort */ 29 | val BUSY = ResultCode(5) /* The database file is locked */ 30 | val LOCKED = ResultCode(6) /* A table in the database is locked */ 31 | val NOMEM = ResultCode(7) /* A malloc() failed */ 32 | val READONLY = ResultCode(8) /* Attempt to write a readonly database */ 33 | val INTERRUPT = 34 | ResultCode(9) /* Operation terminated by sqlite3_interrupt()*/ 35 | val IOERR = ResultCode(10) /* Some kind of disk I/O error occurred */ 36 | val CORRUPT = ResultCode(11) /* The database disk image is malformed */ 37 | val NOTFOUND = 38 | ResultCode(12) /* Unknown opcode in sqlite3_file_control() */ 39 | val FULL = ResultCode(13) /* Insertion failed because database is full */ 40 | val CANTOPEN = ResultCode(14) /* Unable to open the database file */ 41 | val PROTOCOL = ResultCode(15) /* Database lock protocol error */ 42 | val EMPTY = ResultCode(16) /* Internal use only */ 43 | val SCHEMA = ResultCode(17) /* The database schema changed */ 44 | val TOOBIG = ResultCode(18) /* String or BLOB exceeds size limit */ 45 | val CONSTRAINT = ResultCode(19) /* Abort due to constraint violation */ 46 | val MISMATCH = ResultCode(20) /* Data type mismatch */ 47 | val MISUSE = ResultCode(21) /* Library used incorrectly */ 48 | val NOLFS = ResultCode(22) /* Uses OS features not supported on host */ 49 | val AUTH = ResultCode(23) /* Authorization denied */ 50 | val FORMAT = ResultCode(24) /* Not used */ 51 | val RANGE = 52 | ResultCode(25) /* 2nd parameter to sqlite3_bind out of range */ 53 | val NOTADB = ResultCode(26) /* File opened that is not a database file */ 54 | val NOTICE = ResultCode(27) /* Notifications from sqlite3_log() */ 55 | val WARNING = ResultCode(28) /* Warnings from sqlite3_log() */ 56 | val ROW = ResultCode(100) /* sqlite3_step() has another row ready */ 57 | val DONE = ResultCode(101) /* sqlite3_step() has finished executing */ 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sqlitebindings-api/src/commonMain/kotlin/com/birbit/sqlite3/SqliteException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3 17 | 18 | class SqliteException( 19 | val resultCode: ResultCode, 20 | val msg: String 21 | ) : Throwable(msg) { 22 | override fun toString(): String { 23 | return "[ResultCode: $resultCode] $msg" 24 | } 25 | 26 | override fun equals(other: Any?): Boolean { 27 | if (this === other) return true 28 | if (other == null || this::class != other::class) return false 29 | 30 | other as SqliteException 31 | 32 | if (resultCode != other.resultCode) return false 33 | if (msg != other.msg) return false 34 | 35 | return true 36 | } 37 | 38 | override fun hashCode(): Int { 39 | var result = resultCode.hashCode() 40 | result = 31 * result + msg.hashCode() 41 | return result 42 | } 43 | 44 | companion object 45 | } 46 | -------------------------------------------------------------------------------- /sqlitebindings/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.birbit.ksqlite.build.SqliteCompilationConfig 18 | import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType 19 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 20 | import org.jetbrains.kotlin.konan.target.Family 21 | 22 | plugins { 23 | id(libs.plugins.agpLibrary.get().pluginId) 24 | alias(libs.plugins.kotlinMp) 25 | id("maven-publish") 26 | id("ksqlite-build") 27 | } 28 | ksqliteBuild { 29 | native( 30 | includeAndroidNative = true, 31 | includeJni = true 32 | ) { 33 | binaries { 34 | sharedLib(namePrefix = "sqlite3jni") 35 | } 36 | compilations["main"].defaultSourceSet { 37 | dependencies { 38 | project(":sqlitebindings-api") 39 | implementation(libs.kotlinStdlibCommon) 40 | } 41 | } 42 | } 43 | android() 44 | includeSqlite( 45 | SqliteCompilationConfig( 46 | version = "3.38.5" 47 | ) 48 | ) 49 | publish() 50 | buildOnServer() 51 | } 52 | 53 | kotlin { 54 | 55 | val combineSharedLibsTask = 56 | com.birbit.ksqlite.build.CollectNativeLibrariesTask 57 | .create( 58 | project = project, 59 | namePrefix = "sqlite3jni", 60 | outFolder = project.layout.buildDirectory.dir("combinedSharedLibs"), 61 | forAndroid = false 62 | ) 63 | 64 | val combineAndroidSharedLibsTask = 65 | com.birbit.ksqlite.build.CollectNativeLibrariesTask 66 | .create( 67 | project = project, 68 | namePrefix = "sqlite3jni", 69 | outFolder = project.layout.buildDirectory.dir("combinedAndroidSharedLibs"), 70 | forAndroid = true 71 | ) 72 | project.android.sourceSets { 73 | this["main"].jniLibs { 74 | srcDir( 75 | combineAndroidSharedLibsTask.flatMap { 76 | it.outputDir 77 | } 78 | ) 79 | } 80 | } 81 | // TODO we shouldn't need this but srcDir thing above doesn't seem to work 82 | 83 | val androidExt = project.extensions.findByType(com.android.build.gradle.LibraryExtension::class) 84 | androidExt!!.libraryVariants.configureEach { 85 | // this is very ugly but seems like we don't have access to a provider that would give us this task. 86 | tasks.named("mergeDebugJniLibFolders").configure { 87 | dependsOn(combineAndroidSharedLibsTask) 88 | } 89 | tasks.named("mergeReleaseJniLibFolders").configure { 90 | dependsOn(combineAndroidSharedLibsTask) 91 | } 92 | } 93 | sourceSets { 94 | val commonMain by getting { 95 | dependencies { 96 | implementation(libs.kotlinStdlibCommon) 97 | api(project(":sqlitebindings-api")) 98 | } 99 | } 100 | val commonTest by getting { 101 | dependencies { 102 | implementation(libs.kotlinTestCommon) 103 | implementation(libs.kotlinTestAnnotationsCommon) 104 | } 105 | } 106 | val commonJvmMain = create("commonJvmMain") { 107 | dependsOn(commonMain) 108 | dependencies { 109 | implementation(libs.kotlinStdlibJdk) 110 | } 111 | } 112 | val nativeMain by getting 113 | // commonization of jni does not work across jvm-android anymore, hence we duplicate 114 | // the code for them. Using symlinks is not possible due to ide not liking it + windows 115 | // issues 116 | val jniWrapperComonMain by creating { 117 | dependsOn(commonMain) 118 | dependsOn(nativeMain) 119 | } 120 | val androidJniWrapperMain by creating { 121 | dependsOn(jniWrapperComonMain) 122 | } 123 | val jvmJniWrapperMain by creating { 124 | dependsOn(jniWrapperComonMain) 125 | } 126 | val androidMain by getting { 127 | dependsOn(commonJvmMain) 128 | dependencies { 129 | implementation(libs.kotlinStdlibJdk) 130 | } 131 | } 132 | val androidInstrumentedTest by getting { 133 | dependsOn(commonTest) 134 | dependencies { 135 | implementation(libs.kotlinTestJunit) 136 | implementation(libs.bundles.androidInstrumentedTest) 137 | } 138 | } 139 | val androidUnitTest by getting { 140 | dependsOn(commonTest) 141 | dependencies { 142 | implementation(libs.kotlinTestJunit) 143 | implementation(libs.bundles.androidInstrumentedTest) 144 | } 145 | } 146 | 147 | // Default source set for JVM-specific sources and dependencies: 148 | jvm().compilations["main"].defaultSourceSet { 149 | dependsOn(commonJvmMain) 150 | dependencies { 151 | implementation(libs.nativeLibLoader) 152 | } 153 | resources.srcDir( 154 | combineSharedLibsTask.map { 155 | it.outputDir 156 | } 157 | ) 158 | } 159 | // JVM-specific tests and their dependencies: 160 | jvm().compilations["test"].defaultSourceSet { 161 | dependencies { 162 | implementation(libs.kotlinTestJunit) 163 | } 164 | } 165 | targets.forEach { target -> 166 | if (target.platformType == KotlinPlatformType.native) { 167 | val family = (target as KotlinNativeTarget).konanTarget.family 168 | when (family) { 169 | Family.ANDROID -> target.compilations["main"].defaultSourceSet { 170 | dependsOn(androidJniWrapperMain) 171 | } 172 | Family.OSX, Family.MINGW, Family.LINUX -> target.compilations["main"].defaultSourceSet { 173 | dependsOn(jvmJniWrapperMain) 174 | } 175 | else -> { 176 | // skip, doesn't need JNI 177 | } 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | android { 185 | namespace = "com.birbit.sqlite3.internal" 186 | } 187 | -------------------------------------------------------------------------------- /sqlitebindings/src/androidInstrumentedTest/kotlin/com/birbit/internal/AndroidTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.internal 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertTrue 20 | 21 | // existence of this test prevents connectedCheck task from skipping 22 | class AndroidTest { 23 | @Test 24 | fun ensureGradleDoesNotSkipTestTask() { 25 | assertTrue(true) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sqlitebindings/src/androidJniWrapperMain/kotlin/com/birbit/sqlite3/internal/GeneratedJniUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE") 17 | 18 | package com.birbit.sqlite3.internal 19 | 20 | import com.birbit.sqlite3.Authorizer 21 | import com.birbit.sqlite3.SqliteException 22 | import kotlinx.cinterop.CPointer 23 | import kotlinx.cinterop.addressOf 24 | import kotlinx.cinterop.cstr 25 | import kotlinx.cinterop.getBytes 26 | import kotlinx.cinterop.invoke 27 | import kotlinx.cinterop.memScoped 28 | import kotlinx.cinterop.pointed 29 | import kotlinx.cinterop.readValues 30 | import kotlinx.cinterop.toKStringFromUtf8 31 | import kotlinx.cinterop.usePinned 32 | import platform.android.JNIEnvVar 33 | import platform.android.JNI_ABORT 34 | import platform.android.jboolean 35 | import platform.android.jbyteArray 36 | import platform.android.jobject 37 | import platform.android.jstring 38 | 39 | internal fun initPlatform() { 40 | initRuntimeIfNeeded() 41 | Platform.isMemoryLeakCheckerActive = false 42 | } 43 | 44 | internal inline fun jstring?.toKString(env: CPointer): String? { 45 | val chars = env.nativeInterface().GetStringUTFChars!!(env, this, null) 46 | try { 47 | return chars?.toKStringFromUtf8() 48 | } finally { 49 | env.nativeInterface().ReleaseStringUTFChars!!(env, this, chars) 50 | } 51 | } 52 | 53 | internal inline fun jobject?.toKAuthorizer(env: CPointer): Authorizer? { 54 | if (this == null) return null 55 | return JvmAuthorizerCallback.createFromJvmInstance(env, this) 56 | } 57 | 58 | internal inline fun String?.toJString(env: CPointer): jstring? = this?.let { 59 | memScoped { 60 | env.nativeInterface().NewStringUTF!!.invoke(env, this@toJString.cstr.ptr)!! 61 | } 62 | } 63 | 64 | internal val JFALSE = 0.toUByte() 65 | internal val JTRUE = 1.toUByte() 66 | 67 | internal inline fun Boolean.toJBoolean(): jboolean = if (this) JTRUE else JFALSE 68 | 69 | internal inline fun jboolean.toKBoolean(): Boolean = this != JFALSE 70 | 71 | internal inline fun ByteArray?.toJByteArray(env: CPointer): jbyteArray? = memScoped { 72 | // TODO there is a double copy here from both sqlite to knative and then knative to java, we should probably 73 | // avoid it in the future 74 | if (this@toJByteArray == null) { 75 | return@memScoped null 76 | } 77 | val nativeInterface = env.nativeInterface() 78 | val newByteArray = nativeInterface.NewByteArray!!(env, this@toJByteArray.size) 79 | checkNotNull(newByteArray) { 80 | "jvm didn't provide valid byte array" 81 | } 82 | val self = this@toJByteArray 83 | self.usePinned { 84 | nativeInterface.SetByteArrayRegion!!(env, newByteArray, 0, this@toJByteArray.size, it.addressOf(0)) 85 | } 86 | newByteArray 87 | } 88 | 89 | internal inline fun jbyteArray?.toKByteArray(env: CPointer): ByteArray? { 90 | if (this == null) return null 91 | val bytes = env.nativeInterface().GetByteArrayElements!!(env, this, null) 92 | checkNotNull(bytes) { 93 | "unable to get bytes from JNI" 94 | } 95 | return try { 96 | // TODO probably needs to be optimized 97 | val length = env.nativeInterface().GetArrayLength!!(env, this) 98 | bytes.pointed.readValues(length).getBytes() 99 | } finally { 100 | env.nativeInterface().ReleaseByteArrayElements!!(env, this, bytes, JNI_ABORT) 101 | } 102 | } 103 | 104 | // TODO we should wrap more exceptions 105 | internal inline fun runWithJniExceptionConversion( 106 | env: CPointer, 107 | dummy: T, 108 | block: () -> T 109 | ): T { 110 | return try { 111 | block() 112 | } catch (sqliteException: SqliteException) { 113 | JvmSqliteException.doThrow(env, sqliteException) 114 | dummy 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /sqlitebindings/src/androidJniWrapperMain/kotlin/com/birbit/sqlite3/internal/JniCommonUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3.internal 17 | 18 | import kotlinx.cinterop.COpaquePointer 19 | import kotlinx.cinterop.StableRef 20 | import kotlinx.cinterop.asStableRef 21 | import kotlinx.cinterop.toCPointer 22 | import kotlinx.cinterop.toLong 23 | import platform.android.jlong 24 | 25 | internal fun StmtRef.toJni() = nativeRef.stableRef.toJni() 26 | internal fun DbRef.toJni() = nativeRef.stableRef.toJni() 27 | 28 | internal fun dbRefFromJni(jlong: jlong): DbRef = jlong.castFromJni() 29 | internal fun stmtRefFromJni(jlong: jlong): StmtRef = jlong.castFromJni() 30 | internal inline fun jlong.castFromJni(): T { 31 | val ptr: COpaquePointer = this.toCPointer()!! 32 | return ptr.asStableRef().get() 33 | } 34 | 35 | internal inline fun StableRef.toJni() = this.asCPointer().toLong() 36 | -------------------------------------------------------------------------------- /sqlitebindings/src/androidMain/kotlin/com/birbit/sqlite3/internal/AndroidSqliteApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3.internal 17 | internal actual fun loadNativeLibrary() { 18 | System.loadLibrary("sqlite3jni") 19 | } 20 | -------------------------------------------------------------------------------- /sqlitebindings/src/commonJvmMain/kotlin/com/birbit/sqlite3/internal/JvmCommonSqliteApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3.internal 17 | 18 | import com.birbit.sqlite3.Authorizer 19 | import com.birbit.sqlite3.ColumnType 20 | import com.birbit.sqlite3.ResultCode 21 | 22 | open class JvmObjRef( 23 | ptr: Long 24 | ) : ObjRef { 25 | var ptr: Long = ptr 26 | private set 27 | 28 | override fun dispose() { 29 | ptr = 0 30 | } 31 | 32 | override fun isDisposed() = ptr == 0L 33 | } 34 | 35 | actual class DbRef(ptr: Long) : JvmObjRef(ptr), ObjRef 36 | 37 | actual class StmtRef(actual val dbRef: DbRef, ptr: Long) : JvmObjRef(ptr), ObjRef 38 | 39 | @Suppress("INAPPLICABLE_JVM_NAME") 40 | actual object SqliteApi { 41 | // for all native jvm name methods, see: https://youtrack.jetbrains.com/issue/KT-28135 42 | init { 43 | loadNativeLibrary() 44 | } 45 | 46 | actual fun openConnection(path: String): DbRef { 47 | return DbRef(nativeOpenConnection(path)) 48 | } 49 | 50 | external fun nativeOpenConnection(path: String): Long 51 | 52 | actual fun prepareStmt( 53 | dbRef: DbRef, 54 | stmt: String 55 | ): StmtRef { 56 | return StmtRef(dbRef, nativePrepareStmt(dbRef.ptr, stmt)) 57 | } 58 | 59 | external fun nativePrepareStmt(ptr: Long, stmt: String): Long 60 | 61 | actual fun step(stmtRef: StmtRef): ResultCode { 62 | return nativeStep(stmtRef.ptr) 63 | } 64 | 65 | @JvmName("nativeStep") 66 | external fun nativeStep(stmtPtr: Long): ResultCode 67 | 68 | actual fun columnText(stmtRef: StmtRef, index: Int): String? { 69 | return nativeColumnText(stmtRef.ptr, index) 70 | } 71 | 72 | external fun nativeColumnText(stmtPtr: Long, index: Int): String? 73 | actual fun columnInt(stmtRef: StmtRef, index: Int): Int { 74 | return nativeColumnInt(stmtRef.ptr, index) 75 | } 76 | 77 | external fun nativeColumnInt(stmtPtr: Long, index: Int): Int 78 | 79 | actual fun columnIsNull(stmtRef: StmtRef, index: Int): Boolean { 80 | return nativeColumnIsNull(stmtRef.ptr, index) 81 | } 82 | 83 | external fun nativeColumnIsNull(stmtPtr: Long, index: Int): Boolean 84 | actual fun reset(stmtRef: StmtRef): ResultCode { 85 | return nativeReset(stmtRef.ptr) 86 | } 87 | 88 | @JvmName("nativeReset") 89 | external fun nativeReset(stmtPtr: Long): ResultCode 90 | actual fun close(dbRef: DbRef): ResultCode { 91 | return nativeClose(dbRef.ptr) 92 | } 93 | 94 | @JvmName("nativeClose") 95 | external fun nativeClose(ptr: Long): ResultCode 96 | 97 | actual fun finalize(stmtRef: StmtRef): ResultCode { 98 | return nativeFinalize(stmtRef.ptr) 99 | } 100 | 101 | @JvmName("nativeFinalize") 102 | external fun nativeFinalize(stmtPtr: Long): ResultCode 103 | 104 | actual fun columnBlob(stmtRef: StmtRef, index: Int): ByteArray? { 105 | return nativeColumnBlob(stmtRef.ptr, index) 106 | } 107 | 108 | external fun nativeColumnBlob(stmtPtr: Long, index: Int): ByteArray? 109 | 110 | actual fun columnDouble(stmtRef: StmtRef, index: Int): Double { 111 | return nativeColumnDouble(stmtRef.ptr, index) 112 | } 113 | 114 | external fun nativeColumnDouble(stmtPtr: Long, index: Int): Double 115 | actual fun columnLong(stmtRef: StmtRef, index: Int): Long { 116 | return nativeColumnLong(stmtRef.ptr, index) 117 | } 118 | 119 | external fun nativeColumnLong(stmtPtr: Long, index: Int): Long 120 | actual fun bindBlob(stmtRef: StmtRef, index: Int, bytes: ByteArray): ResultCode { 121 | return nativeBindBlob(stmtRef.ptr, index, bytes) 122 | } 123 | 124 | @JvmName("nativeBindBlob") 125 | external fun nativeBindBlob(stmtPtr: Long, index: Int, bytes: ByteArray): ResultCode 126 | 127 | actual fun bindText(stmtRef: StmtRef, index: Int, value: String): ResultCode { 128 | return nativeBindText(stmtRef.ptr, index, value) 129 | } 130 | 131 | @JvmName("nativeBindText") 132 | external fun nativeBindText(stmtPtr: Long, index: Int, value: String): ResultCode 133 | actual fun bindInt(stmtRef: StmtRef, index: Int, value: Int): ResultCode { 134 | return nativeBindInt(stmtRef.ptr, index, value) 135 | } 136 | 137 | @JvmName("nativeBindInt") 138 | external fun nativeBindInt(stmtPtr: Long, index: Int, value: Int): ResultCode 139 | 140 | actual fun bindLong(stmtRef: StmtRef, index: Int, value: Long): ResultCode { 141 | return nativeBindLong(stmtRef.ptr, index, value) 142 | } 143 | 144 | @JvmName("nativeBindLong") 145 | external fun nativeBindLong(stmtPtr: Long, index: Int, value: Long): ResultCode 146 | 147 | actual fun bindNull(stmtRef: StmtRef, index: Int): ResultCode { 148 | return nativeBindNull(stmtRef.ptr, index) 149 | } 150 | 151 | @JvmName("nativeBindNull") 152 | external fun nativeBindNull(stmtPtr: Long, index: Int): ResultCode 153 | 154 | actual fun errorMsg(dbRef: DbRef): String? { 155 | return nativeErrorMsg(dbRef.ptr) 156 | } 157 | 158 | external fun nativeErrorMsg(dbPtr: Long): String? 159 | 160 | actual fun errorCode(dbRef: DbRef): ResultCode { 161 | return nativeErrorCode(dbRef.ptr) 162 | } 163 | 164 | @JvmName("nativeErrorCode") 165 | external fun nativeErrorCode(dbPtr: Long): ResultCode 166 | 167 | actual fun errorString(code: ResultCode): String? { 168 | return nativeErrorString(code) 169 | } 170 | 171 | @JvmName("nativeErrorString") 172 | external fun nativeErrorString(code: ResultCode): String? 173 | 174 | actual fun bindDouble(stmtRef: StmtRef, index: Int, value: Double): ResultCode { 175 | return nativeBindDouble(stmtRef.ptr, index, value) 176 | } 177 | 178 | @JvmName("nativeBindDouble") 179 | external fun nativeBindDouble(stmtPtr: Long, index: Int, value: Double): ResultCode 180 | actual fun setAuthorizer( 181 | dbRef: DbRef, 182 | authorizer: Authorizer? 183 | ): ResultCode { 184 | return nativeSetAuthorizer(dbRef.ptr, authorizer) 185 | } 186 | 187 | @JvmName("nativeSetAuthorizer") 188 | external fun nativeSetAuthorizer(dbPtr: Long, authorizer: Authorizer?): ResultCode 189 | actual fun columnType( 190 | stmtRef: StmtRef, 191 | index: Int 192 | ): ColumnType { 193 | return nativeColumnType(stmtRef.ptr, index) 194 | } 195 | 196 | @JvmName("nativeColumnType") 197 | external fun nativeColumnType(stmtPtr: Long, index: Int): ColumnType 198 | actual fun exec( 199 | dbRef: DbRef, 200 | query: String 201 | ): ResultCode { 202 | return nativeExec(dbRef.ptr, query) 203 | } 204 | 205 | @JvmName("nativeExec") 206 | external fun nativeExec(dbPtr: Long, query: String): ResultCode 207 | actual fun columnDeclType(stmtRef: StmtRef, index: Int): String? { 208 | return nativeColumnDeclType(stmtRef.ptr, index) 209 | } 210 | 211 | external fun nativeColumnDeclType(stmtPtr: Long, index: Int): String? 212 | 213 | actual fun columnDatabaseName(stmtRef: StmtRef, index: Int): String? { 214 | return nativeColumnDatabaseName(stmtRef.ptr, index) 215 | } 216 | 217 | external fun nativeColumnDatabaseName(stmtPtr: Long, index: Int): String? 218 | 219 | actual fun columnTableName(stmtRef: StmtRef, index: Int): String? { 220 | return nativeColumnTableName(stmtRef.ptr, index) 221 | } 222 | 223 | external fun nativeColumnTableName(stmtPtr: Long, index: Int): String? 224 | 225 | actual fun columnOriginName(stmtRef: StmtRef, index: Int): String? { 226 | return nativeColumnOriginName(stmtRef.ptr, index) 227 | } 228 | 229 | external fun nativeColumnOriginName(stmtPtr: Long, index: Int): String? 230 | actual fun columnCount(stmtRef: StmtRef): Int { 231 | return nativeColumnCount(stmtRef.ptr) 232 | } 233 | 234 | external fun nativeColumnCount(ptr: Long): Int 235 | 236 | actual fun columnName(stmtRef: StmtRef, index: Int): String? { 237 | return nativeColumnName(stmtRef.ptr, index) 238 | } 239 | 240 | external fun nativeColumnName(stmtPtr: Long, index: Int): String? 241 | actual fun expandedSql(stmtRef: StmtRef): String { 242 | return nativeExpandedSql(stmtRef.ptr) 243 | } 244 | 245 | external fun nativeExpandedSql(stmtPtr: Long): String 246 | 247 | actual fun normalizedSql(stmtRef: StmtRef): String { 248 | return nativeNormalizedSql(stmtRef.ptr) 249 | } 250 | 251 | external fun nativeNormalizedSql(ptr: Long): String 252 | actual fun sql(stmtRef: StmtRef): String { 253 | return nativeSql(stmtRef.ptr) 254 | } 255 | 256 | external fun nativeSql(stmtPtr: Long): String 257 | actual fun bindParameterCount(stmtRef: StmtRef): Int { 258 | return nativeBindParameterCount(stmtRef.ptr) 259 | } 260 | 261 | external fun nativeBindParameterCount(stmtPtr: Long): Int 262 | 263 | actual fun bindParameterName(stmtRef: StmtRef, index: Int): String? { 264 | return nativeBindParameterName(stmtRef.ptr, index) 265 | } 266 | 267 | external fun nativeBindParameterName(stmtPtr: Long, index: Int): String? 268 | 269 | actual fun bindParameterIndex(stmtRef: StmtRef, name: String): Int { 270 | return nativeBindParameterIndex(stmtRef.ptr, name) 271 | } 272 | 273 | external fun nativeBindParameterIndex(stmtPtr: Long, name: String): Int 274 | } 275 | -------------------------------------------------------------------------------- /sqlitebindings/src/commonMain/kotlin/com/birbit/sqlite3/internal/SqliteApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3.internal 17 | 18 | import com.birbit.sqlite3.Authorizer 19 | import com.birbit.sqlite3.ColumnType 20 | import com.birbit.sqlite3.ResultCode 21 | import com.birbit.sqlite3.SqliteException 22 | 23 | // only needed jvm and android 24 | internal expect fun loadNativeLibrary() 25 | // TODO trying to use ResultCode here crashed the compiler, hence using Ints 26 | internal inline fun checkResultCode( 27 | dbRef: DbRef, 28 | received: Int, 29 | expected: Int 30 | ) { 31 | if (received != expected) { 32 | throw SqliteException.buildFromConnection(dbRef, received) 33 | } 34 | } 35 | 36 | internal fun SqliteException.Companion.buildFromConnection(dbRef: DbRef, errorCode: Int?): SqliteException { 37 | return SqliteException( 38 | resultCode = errorCode?.let { ResultCode(it) } ?: SqliteApi.errorCode(dbRef), 39 | msg = SqliteApi.errorMsg(dbRef) ?: errorCode?.let { SqliteApi.errorString(ResultCode(it)) } 40 | ?: "unknown error" 41 | ) 42 | } 43 | fun ResultCode.errorString() = SqliteApi.errorString(this) 44 | 45 | interface ObjRef { 46 | fun dispose() 47 | fun isDisposed(): Boolean 48 | } 49 | 50 | expect class DbRef : ObjRef 51 | 52 | expect class StmtRef : ObjRef { 53 | val dbRef: DbRef 54 | } 55 | 56 | /** 57 | * Common API for all calls. 58 | * 59 | * Native implements this by directly calling SQLite. 60 | * JVM implements this via JNI which delegates to the native implementation. 61 | */ 62 | expect object SqliteApi { 63 | fun openConnection(path: String): DbRef 64 | fun prepareStmt(dbRef: DbRef, stmt: String): StmtRef 65 | fun step(stmtRef: StmtRef): ResultCode 66 | fun columnIsNull(stmtRef: StmtRef, index: Int): Boolean 67 | fun columnText(stmtRef: StmtRef, index: Int): String? 68 | fun columnInt(stmtRef: StmtRef, index: Int): Int 69 | fun columnBlob(stmtRef: StmtRef, index: Int): ByteArray? 70 | fun columnDouble(stmtRef: StmtRef, index: Int): Double 71 | fun columnLong(stmtRef: StmtRef, index: Int): Long 72 | fun reset(stmtRef: StmtRef): ResultCode 73 | fun close(dbRef: DbRef): ResultCode 74 | fun finalize(stmtRef: StmtRef): ResultCode 75 | fun bindBlob(stmtRef: StmtRef, index: Int, bytes: ByteArray): ResultCode 76 | fun bindText(stmtRef: StmtRef, index: Int, value: String): ResultCode 77 | fun bindInt(stmtRef: StmtRef, index: Int, value: Int): ResultCode 78 | fun bindLong(stmtRef: StmtRef, index: Int, value: Long): ResultCode 79 | fun bindDouble(stmtRef: StmtRef, index: Int, value: Double): ResultCode 80 | fun bindNull(stmtRef: StmtRef, index: Int): ResultCode 81 | fun errorMsg(dbRef: DbRef): String? 82 | fun errorCode(dbRef: DbRef): ResultCode 83 | fun errorString(code: ResultCode): String? 84 | fun setAuthorizer(dbRef: DbRef, authorizer: Authorizer?): ResultCode 85 | fun columnType(stmtRef: StmtRef, index: Int): ColumnType 86 | fun exec(dbRef: DbRef, query: String): ResultCode 87 | fun columnDeclType(stmtRef: StmtRef, index: Int): String? 88 | fun columnDatabaseName(stmtRef: StmtRef, index: Int): String? 89 | fun columnTableName(stmtRef: StmtRef, index: Int): String? 90 | fun columnOriginName(stmtRef: StmtRef, index: Int): String? 91 | fun columnCount(stmtRef: StmtRef): Int 92 | fun columnName(stmtRef: StmtRef, index: Int): String? 93 | fun expandedSql(stmtRef: StmtRef): String 94 | fun normalizedSql(stmtRef: StmtRef): String 95 | fun sql(stmtRef: StmtRef): String 96 | fun bindParameterCount(stmtRef: StmtRef): Int 97 | fun bindParameterName(stmtRef: StmtRef, index: Int): String? 98 | fun bindParameterIndex(stmtRef: StmtRef, name: String): Int 99 | } 100 | -------------------------------------------------------------------------------- /sqlitebindings/src/commonTest/kotlin/com/birbit/sqlite3/internal/AuthorizerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3.internal 17 | 18 | import com.birbit.sqlite3.AuthResult 19 | import com.birbit.sqlite3.AuthorizationParams 20 | import com.birbit.sqlite3.Authorizer 21 | import kotlin.test.Test 22 | import kotlin.test.assertFalse 23 | import kotlin.test.assertTrue 24 | 25 | class AuthorizerTest { 26 | private class TestAuthorizer : Authorizer { 27 | var disposed = false 28 | var invoked = false 29 | override fun invoke(params: AuthorizationParams): AuthResult { 30 | check(!disposed) { 31 | "got invocation call after dispose" 32 | } 33 | invoked = true 34 | return AuthResult.OK 35 | } 36 | 37 | override fun dispose() { 38 | disposed = true 39 | } 40 | } 41 | 42 | @Test 43 | fun disposePreviousAuthorizer() { 44 | val db = SqliteApi.openConnection(":memory:") 45 | fun runQuery() { 46 | val stmt = SqliteApi.prepareStmt(db, "SELECT * FROM sqlite_master") 47 | SqliteApi.finalize(stmt) 48 | } 49 | try { 50 | val auth1 = TestAuthorizer() 51 | val auth2 = TestAuthorizer() 52 | SqliteApi.setAuthorizer(db, auth1) 53 | runQuery() 54 | assertTrue(auth1.invoked, "should have invoked authorizer") 55 | assertFalse(auth1.disposed, "should not be disposed yet") 56 | auth1.invoked = false 57 | SqliteApi.setAuthorizer(db, auth2) 58 | assertTrue(auth1.disposed, "should've disposed auth 1") 59 | runQuery() 60 | assertTrue(auth2.invoked, "should have invoked authorizer") 61 | assertFalse(auth2.disposed, "should not be disposed yet") 62 | assertFalse(auth1.invoked, "should not call previous authorizer") 63 | } finally { 64 | SqliteApi.close(db) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sqlitebindings/src/jvmJniWrapperMain/kotlin/com/birbit/sqlite3/internal/GeneratedJniUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE") 17 | 18 | package com.birbit.sqlite3.internal 19 | 20 | import com.birbit.sqlite3.Authorizer 21 | import com.birbit.sqlite3.SqliteException 22 | import kotlinx.cinterop.CPointer 23 | import kotlinx.cinterop.addressOf 24 | import kotlinx.cinterop.cstr 25 | import kotlinx.cinterop.getBytes 26 | import kotlinx.cinterop.invoke 27 | import kotlinx.cinterop.memScoped 28 | import kotlinx.cinterop.pointed 29 | import kotlinx.cinterop.readValues 30 | import kotlinx.cinterop.toKStringFromUtf8 31 | import kotlinx.cinterop.usePinned 32 | import platform.android.JNIEnvVar 33 | import platform.android.JNI_ABORT 34 | import platform.android.jboolean 35 | import platform.android.jbyteArray 36 | import platform.android.jobject 37 | import platform.android.jstring 38 | 39 | internal fun initPlatform() { 40 | initRuntimeIfNeeded() 41 | Platform.isMemoryLeakCheckerActive = false 42 | } 43 | 44 | internal inline fun jstring?.toKString(env: CPointer): String? { 45 | val chars = env.nativeInterface().GetStringUTFChars!!(env, this, null) 46 | try { 47 | return chars?.toKStringFromUtf8() 48 | } finally { 49 | env.nativeInterface().ReleaseStringUTFChars!!(env, this, chars) 50 | } 51 | } 52 | 53 | internal inline fun jobject?.toKAuthorizer(env: CPointer): Authorizer? { 54 | if (this == null) return null 55 | return JvmAuthorizerCallback.createFromJvmInstance(env, this) 56 | } 57 | 58 | internal inline fun String?.toJString(env: CPointer): jstring? = this?.let { 59 | memScoped { 60 | env.nativeInterface().NewStringUTF!!.invoke(env, this@toJString.cstr.ptr)!! 61 | } 62 | } 63 | 64 | internal val JFALSE = 0.toUByte() 65 | internal val JTRUE = 1.toUByte() 66 | 67 | internal inline fun Boolean.toJBoolean(): jboolean = if (this) JTRUE else JFALSE 68 | 69 | internal inline fun jboolean.toKBoolean(): Boolean = this != JFALSE 70 | 71 | internal inline fun ByteArray?.toJByteArray(env: CPointer): jbyteArray? = memScoped { 72 | // TODO there is a double copy here from both sqlite to knative and then knative to java, we should probably 73 | // avoid it in the future 74 | if (this@toJByteArray == null) { 75 | return@memScoped null 76 | } 77 | val nativeInterface = env.nativeInterface() 78 | val newByteArray = nativeInterface.NewByteArray!!(env, this@toJByteArray.size) 79 | checkNotNull(newByteArray) { 80 | "jvm didn't provide valid byte array" 81 | } 82 | val self = this@toJByteArray 83 | self.usePinned { 84 | nativeInterface.SetByteArrayRegion!!(env, newByteArray, 0, this@toJByteArray.size, it.addressOf(0)) 85 | } 86 | newByteArray 87 | } 88 | 89 | internal inline fun jbyteArray?.toKByteArray(env: CPointer): ByteArray? { 90 | if (this == null) return null 91 | val bytes = env.nativeInterface().GetByteArrayElements!!(env, this, null) 92 | checkNotNull(bytes) { 93 | "unable to get bytes from JNI" 94 | } 95 | return try { 96 | // TODO probably needs to be optimized 97 | val length = env.nativeInterface().GetArrayLength!!(env, this) 98 | bytes.pointed.readValues(length).getBytes() 99 | } finally { 100 | env.nativeInterface().ReleaseByteArrayElements!!(env, this, bytes, JNI_ABORT) 101 | } 102 | } 103 | 104 | // TODO we should wrap more exceptions 105 | internal inline fun runWithJniExceptionConversion( 106 | env: CPointer, 107 | dummy: T, 108 | block: () -> T 109 | ): T { 110 | return try { 111 | block() 112 | } catch (sqliteException: SqliteException) { 113 | JvmSqliteException.doThrow(env, sqliteException) 114 | dummy 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /sqlitebindings/src/jvmJniWrapperMain/kotlin/com/birbit/sqlite3/internal/JniCommonUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:OptIn(ExperimentalForeignApi::class) 17 | 18 | package com.birbit.sqlite3.internal 19 | 20 | import kotlinx.cinterop.COpaquePointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.StableRef 23 | import kotlinx.cinterop.asStableRef 24 | import kotlinx.cinterop.toCPointer 25 | import kotlinx.cinterop.toLong 26 | import platform.android.jlong 27 | 28 | internal fun StmtRef.toJni() = nativeRef.stableRef.toJni() 29 | internal fun DbRef.toJni() = nativeRef.stableRef.toJni() 30 | 31 | internal fun dbRefFromJni(jlong: jlong): DbRef = jlong.castFromJni() 32 | internal fun stmtRefFromJni(jlong: jlong): StmtRef = jlong.castFromJni() 33 | internal inline fun jlong.castFromJni(): T { 34 | val ptr: COpaquePointer = this.toCPointer()!! 35 | return ptr.asStableRef().get() 36 | } 37 | 38 | internal inline fun StableRef.toJni() = this.asCPointer().toLong() 39 | -------------------------------------------------------------------------------- /sqlitebindings/src/jvmMain/kotlin/com/birbit/sqlite3/internal/JvmSqliteApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Google, LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.birbit.sqlite3.internal 17 | 18 | import org.scijava.nativelib.NativeLoader 19 | internal actual fun loadNativeLibrary() { 20 | NativeLoader.loadLibrary("sqlite3jni") 21 | } 22 | -------------------------------------------------------------------------------- /sqlitebindings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /sqlitebindings/src/nativeInterop/cinterop/jni.def: -------------------------------------------------------------------------------- 1 | headers = jni.h 2 | # match android so we can have shared code between android and jvm jni 3 | package=platform.android -------------------------------------------------------------------------------- /sqlitebindings/src/nativeInterop/cinterop/sqlite.def: -------------------------------------------------------------------------------- 1 | package=sqlite3 2 | headers=sqlite3.h 3 | headerFilter = sqlite3*.h 4 | noStringConversion = sqlite3_prepare_v2 sqlite3_prepare_v3 5 | #TODO move this to build per target? 6 | staticLibraries=libsqlite3.a 7 | #libraryPaths=sqlite 8 | #libraryPaths = /home/yboyar/src/kotlin-jni-test/build/sqlite-compilation/output/linuxX64 9 | linkerOpts.linux_x64 = -lpthread -ldl 10 | linkerOpts.macos_x64 = -lpthread -ldl 11 | # TODO we may want to copy all opts from sqlite compilation to here for consistency. 12 | # after all, cintrop looks into this file, not how we compile sqlite. 13 | compilerOpts = -DSQLITE_ENABLE_NORMALIZE=1 --------------------------------------------------------------------------------