├── .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
--------------------------------------------------------------------------------