diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 098cb70e26..d50cbeecc2 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,9 +5,7 @@ module.exports = { es2021: true, node: true, }, - // TODO: we need to move to the following, but this gets us started with infrastructure without breaking our build - // extends: [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended"], - extends: ["plugin:@typescript-eslint/eslint-recommended"], + extends: [ "plugin:@typescript-eslint/eslint-recommended"], parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: "latest", diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..cd9921d963 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Always use LF for Dockerfile - otherwise HEREDOCs in Dockerfile +# will not work on Windows - we end up with a filename `foo\r` for +# the command +# ```Dockerfile +# RUN < foo +# echo "Hello world" +# EOF +# ``` +Dockerfile text eol=lf diff --git a/.github/actions/build-binary-packages/action.yml b/.github/actions/build-binary-packages/action.yml index 27f0246121..e764a26ac7 100644 --- a/.github/actions/build-binary-packages/action.yml +++ b/.github/actions/build-binary-packages/action.yml @@ -25,13 +25,13 @@ runs: using: 'composite' steps: - name: Download Artifacts - uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: keyman-srcpkg path: artifacts/keyman-srcpkg - name: Build - uses: sillsdev/gha-ubuntu-packaging@7b56f50d5d5537e9e9cafd3f6139ec6da69cfcda # v1.1 + uses: sillsdev/gha-ubuntu-packaging@556b268762be9bea0f39861a7392587211ef6a58 # v2.0.1 with: dist: "${{ inputs.dist }}" platform: "${{ inputs.arch }}" @@ -49,7 +49,7 @@ runs: echo '```' >> $GITHUB_STEP_SUMMARY - name: Store binary packages - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: keyman-binarypkgs-${{ inputs.dist }}_${{ inputs.arch }} path: | diff --git a/.github/labeler.yml b/.github/labeler.yml index 19c858cc81..fc37bf64b7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -11,51 +11,56 @@ docs: docs/** # Add labels based on changed files using actions/labeler # -android/: android/** +android/: + - android/** + - resources/teamcity/includes/** + - resources/teamcity/android/** android/app/: android/KMAPro/** android/engine/: android/KMEA/** android/samples/: android/Samples/** common/: - common/** - - resources/** - -common/models/templates/: web/src/engine/predictive-text/templates/** -common/models/wordbreakers/: web/src/engine/predictive-text/wordbreakers/** - -common/resources/: resources/** + - resources/teamcity/includes/** + - resources/teamcity/common/** common/web/: common/web/** core/: - core/** + - resources/teamcity/includes/** + - resources/teamcity/core/** developer/: - developer/** - + - resources/teamcity/includes/** + - resources/teamcity/developer/** developer/compilers/: - developer/src/kmc/** - developer/src/kmcmplib/** - developer/src/kmc-*/** - developer/ide/: - developer/src/server/** - developer/src/tike/** - -ios/: ios/** +ios/: + - ios/** + - resources/teamcity/includes/** + - resources/teamcity/ios/** ios/app/: ios/keyman/** ios/engine/: ios/engine/** ios/samples/: ios/samples/** -linux/: linux/** +linux/: + - linux/** + - resources/teamcity/includes/** + - resources/teamcity/linux/** linux/config/: linux/keyman-config/** -linux/engine/: - - linux/ibus-keyman/** - - linux/legacy/ibus-kmfl/** - - linux/legacy/kmflcomp/** - - linux/legacy/libkmfl/** +linux/engine/: linux/ibus-keyman/** -mac/: mac/** +mac/: + - mac/** + - resources/teamcity/includes/** + - resources/teamcity/mac/** # mac/config/: # mac/engine/: mac/** @@ -65,12 +70,21 @@ oem/fv/android/: oem/firstvoices/android/** oem/fv/ios/: oem/firstvoices/ios/** oem/fv/windows/: oem/firstvoices/windows/** -web/: web/** +resources/: resources/** + +web/: + - web/** + - resources/teamcity/includes/** + - resources/teamcity/web/** # web/bookmarklet/ web/engine/: web/source/** web/ui/: web/source/kmwui* web/samples/: web/samples/** +web/predictive-text/: web/src/engine/predictive-text/** -windows/: windows/** +windows/: + - windows/** + - resources/teamcity/includes/** + - resources/teamcity/windows/** windows/config/: windows/src/desktop/** windows/engine/: windows/src/engine/** diff --git a/.github/multi-labeler.yml b/.github/multi-labeler.yml index 5b494a8569..c7f2f408bc 100644 --- a/.github/multi-labeler.yml +++ b/.github/multi-labeler.yml @@ -33,6 +33,9 @@ labels: - label: 'fix' matcher: title: '^fix(\(|:)' + - label: 'maint' + matcher: + title: '^maint(\(|:)' - label: 'refactor' matcher: title: '^refactor(\(|:)' @@ -57,11 +60,6 @@ labels: matcher: title: '(🍒|:cherries:)' - # long-lived feature branches - - label: 'feature-branch' - matcher: - branch: '^feature-.+' - # # Scopes that we look for in the PR title # @@ -90,6 +88,9 @@ labels: - label: 'oem/' matcher: title: '\(.*oem.*\):' + - label: 'resources/' + matcher: + title: '\(.*resources.*\):' - label: 'web/' matcher: title: '\(.*web.*\):' diff --git a/.github/workflows/api-verification.yml b/.github/workflows/api-verification.yml index 8f37141c08..e8cc9aa95a 100644 --- a/.github/workflows/api-verification.yml +++ b/.github/workflows/api-verification.yml @@ -15,7 +15,7 @@ jobs: name: Verify API for libkeymancore*.so runs-on: ubuntu-latest outputs: - VERSION: ${{ steps.environment_step.outputs.VERSION }} + KEYMAN_VERSION: ${{ steps.environment_step.outputs.KEYMAN_VERSION }} PRERELEASE_TAG: ${{ steps.environment_step.outputs.PRERELEASE_TAG }} GIT_SHA: ${{ steps.environment_step.outputs.GIT_SHA }} GIT_BASE: ${{ steps.environment_step.outputs.GIT_BASE }} @@ -26,7 +26,7 @@ jobs: steps: - name: Restore artifacts - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | artifacts @@ -53,7 +53,7 @@ jobs: -f context="$STATUS_CONTEXT" - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: ref: '${{ steps.environment_step.outputs.GIT_SHA }}' fetch-depth: 0 @@ -69,7 +69,7 @@ jobs: run: | echo "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }}):" >> $GITHUB_STEP_SUMMARY - BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb") + BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.KEYMAN_VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb") cd ${{ github.workspace }}/keyman/linux ./scripts/deb-packaging.sh \ --gha \ @@ -79,7 +79,7 @@ jobs: verify 2>> $GITHUB_STEP_SUMMARY - name: Archive .symbols file - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: libkeymancore.symbols path: ${{ github.workspace }}/keyman/linux/debian/tmp/DEBIAN/symbols diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 7568a292cd..d5a25aacb6 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: crowdin action - uses: crowdin/github-action@1.3.2 + uses: crowdin/github-action@2.7.0 with: upload_sources: true diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 96ad55ca1a..aaeb1c6306 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -37,11 +37,11 @@ jobs: if: github.repository == 'keymanapp/keyman' runs-on: ubuntu-24.04 outputs: - VERSION: ${{ steps.version_step.outputs.VERSION }} + KEYMAN_VERSION: ${{ steps.version_step.outputs.KEYMAN_VERSION }} PRERELEASE_TAG: ${{ steps.prerelease_tag.outputs.PRERELEASE_TAG }} steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: ref: '${{ github.event.client_payload.buildSha }}' @@ -72,10 +72,10 @@ jobs: - name: Build source package id: build_source_package run: | - export TIER=$(cat TIER.md) + export KEYMAN_TIER=$(cat TIER.md) export GHA_TEST_BUILD="${{ github.event.client_payload.isTestBuild }}" export GHA_BRANCH="${{ github.event.client_payload.branch }}" - echo "TIER=$TIER" >> $GITHUB_ENV + echo "KEYMAN_TIER=$KEYMAN_TIER" >> $GITHUB_ENV echo "GHA_TEST_BUILD=${GHA_TEST_BUILD}" >> $GITHUB_ENV echo "GHA_BRANCH=${GHA_BRANCH}" >> $GITHUB_ENV cd linux @@ -87,7 +87,7 @@ jobs: run: | THIS_SCRIPT="$GITHUB_WORKSPACE/.github/workflows/deb-packaging.yml" . "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "KEYMAN_VERSION=${KEYMAN_VERSION:-${VERSION}}" >> $GITHUB_OUTPUT - name: Set prerelease tag as output parameter id: prerelease_tag @@ -103,16 +103,16 @@ jobs: - name: Output which branch or PR we're building plus name of .dsc file run: | if [ "${{ github.event.client_payload.isTestBuild }}" == "true" ]; then - echo ":checkered_flag: **Test build of version ${{ steps.version_step.outputs.VERSION }} for ${{ github.event.client_payload.branch }}**" >> $GITHUB_STEP_SUMMARY + echo ":checkered_flag: **Test build of version ${{ steps.version_step.outputs.KEYMAN_VERSION }} for ${{ github.event.client_payload.branch }}**" >> $GITHUB_STEP_SUMMARY else - echo ":ship: **Release build of ${{ github.event.client_payload.branch }} branch (${{ github.event.client_payload.branch}}), version ${{ steps.version_step.outputs.VERSION }}**" >> $GITHUB_STEP_SUMMARY + echo ":ship: **Release build of ${{ github.event.client_payload.branch }} branch (${{ github.event.client_payload.branch}}), version ${{ steps.version_step.outputs.KEYMAN_VERSION }}**" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY echo ":gift: Generated source package:" >> $GITHUB_STEP_SUMMARY echo "- $(find . -name keyman_\*.dsc)" >> $GITHUB_STEP_SUMMARY - name: Store source package - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: keyman-srcpkg path: | @@ -131,7 +131,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: ref: '${{ github.event.client_payload.buildSha }}' sparse-checkout: '.github/actions/' @@ -140,7 +140,7 @@ jobs: uses: ./.github/actions/build-binary-packages with: dist: ${{ matrix.dist }} - version: ${{ needs.sourcepackage.outputs.VERSION }} + version: ${{ needs.sourcepackage.outputs.KEYMAN_VERSION }} prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }} deb_fullname: ${{env.DEBFULLNAME}} deb_email: ${{env.DEBEMAIL}} @@ -157,7 +157,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: ref: '${{ github.event.client_payload.buildSha }}' sparse-checkout: '.github/actions/' @@ -166,7 +166,7 @@ jobs: uses: ./.github/actions/build-binary-packages with: dist: ${{ matrix.dist }} - version: ${{ needs.sourcepackage.outputs.VERSION }} + version: ${{ needs.sourcepackage.outputs.KEYMAN_VERSION }} prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }} deb_fullname: ${{env.DEBFULLNAME}} deb_email: ${{env.DEBEMAIL}} @@ -178,7 +178,7 @@ jobs: steps: # - name: Download Artifacts - # uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 + # uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 # with: # path: artifacts # merge-multiple: true @@ -198,7 +198,7 @@ jobs: # autopkgtest -B *.deb keyman_*.dsc -- qemu autopkgtest-jammy-amd64.img - name: Ignore run: | - echo "Ignored for now - until working solution is in place" + echo "Ignored for now - until working solution is in place (#13777)" deb_signing: name: Sign source and binary packages @@ -211,8 +211,8 @@ jobs: - name: Sign packages uses: sillsdev/gha-deb-signing@a38dbde6bc9afabede5e07609105acc83c760ad4 # v0.6 with: - src-pkg-name: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1_source.changes" - bin-pkg-name: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+" + src-pkg-name: "keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}-1_source.changes" + bin-pkg-name: "keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+" artifacts-prefix: "keyman-" artifacts-result-name: "keyman-signedpkgs" gpg-signing-key: "${{ secrets.GPG_SIGNING_KEY }}" @@ -247,7 +247,7 @@ jobs: echo "::endgroup::" - name: Download Artifacts - uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: keyman-signedpkgs @@ -293,7 +293,7 @@ jobs: echo "::endgroup::" echo -e "::group::${COLOR_GREEN}Uploading binary packages" - pattern="keyman_${{ needs.sourcepackage.outputs.VERSION }}.+\+(.*)[0-9]_[^.]+.changes" + pattern="keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}.+\+(.*)[0-9]_[^.]+.changes" for f in *_amd64.changes; do if [[ $f =~ $pattern ]]; then dist=${BASH_REMATCH[1]} @@ -303,20 +303,21 @@ jobs: echo "::endgroup::" prepare_api_verification: - name: Verify API for libkeymancore.so + name: Prepare API verification needs: [sourcepackage, binary_packages_released] runs-on: ubuntu-latest steps: - name: Download Artifacts - uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: artifacts + pattern: keyman-* merge-multiple: true - name: Save environment run: | - echo "VERSION=${{ needs.sourcepackage.outputs.VERSION }}" > artifacts/env + echo "KEYMAN_VERSION=${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}" > artifacts/env echo "PRERELEASE_TAG=${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}" >> artifacts/env echo "GIT_SHA=${{ github.event.client_payload.buildSha }}" >> artifacts/env echo "GIT_BASE=${{ github.event.client_payload.baseRef }}" >> artifacts/env @@ -326,7 +327,7 @@ jobs: echo "GIT_USER=${{ github.event.client_payload.user }}" >> artifacts/env - name: Cache artifacts - uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | artifacts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6216a80614..64e4dd454d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -384,10 +384,11 @@ group. | `auto` | [search][search-auto] | For PRs only: automatically-opened PRs, e.g. opened by CI. | | `bug` | [search][search-bug] | For issues only: confirmed bugs or reports that are very likely to be bugs. PRs use `fix` to mark a bug fix. | | `change` | [search][search-change] | Minor change in functionality, but not new. | -| `chore` | [search][search-chore] | Cleanup work, maintenance, without change in functionality. | +| `chore` | [search][search-chore] | Cleanup work, without change in functionality. | | `docs` | [search][search-docs] | Relating to any type of documentation. | | `feat` | [search][search-feat] | Feature requests. | | `fix` | [search][search-fix] | For PRs only: a bug fix, corresponds to issue label `bug`. | +| `maint` | [search][search-maint] | Maintenance work -- continuous integration, build scripts, infrastructure. | | `question` | [search][search-question] | Questions more than bug reports or feature requests (e.g. how do I do X). [**Usually better on the Keyman Community**](https://community.software.sil.org/c/keyman) | | `refactor` | [search][search-refactor] | Code reorganization and refactoring, without change in functionality. | | `spec` | [search][search-spec] | Issues which are specifications for a large scale feature. | @@ -443,6 +444,7 @@ These labels also include sublabels, such as `windows/config/`. [search-docs]: https://github.com/keymanapp/keyman/labels/docs [search-feat]: https://github.com/keymanapp/keyman/labels/feat [search-fix]: https://github.com/keymanapp/keyman/labels/fix +[search-maint]: https://github.com/keymanapp/keyman/labels/maint [search-question]: https://github.com/keymanapp/keyman/labels/question [search-spec]: https://github.com/keymanapp/keyman/labels/spec [search-style]: https://github.com/keymanapp/keyman/labels/style diff --git a/HISTORY.md b/HISTORY.md index 4278264505..daf9a20257 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,277 @@ # Keyman Version History +## 19.0.63 alpha 2025-06-12 + +* maint(web): add some more arguments for web release build script (#14167) +* maint(developer): use double quotes so that variable gets replaced (#14168) +* chore(deps): bump brace-expansion from 2.0.1 to 2.0.2 in /developer/src/server/src/win32/trayicon/addon-src (#14165) + +## 19.0.62 alpha 2025-06-11 + +* maint(developer): export environment variables (#14157) +* maint(web): use path when calling powershell script (#14156) +* maint(resources): fix regression on mac triggering builds (#14155) + +## 19.0.61 alpha 2025-06-10 + +* chore(developer): extend unit test timeout for getLastGitCommitDate (#14147) +* fix(common): Use zip.inc.sh for common test keyboards (#14145) +* maint(developer): add docker image to build developer node modules (#14058) + +## 19.0.60 alpha 2025-06-06 + +* maint(developer): allow to build locally on Linux (#14059) + +## 19.0.59 alpha 2025-06-05 + +* chore(deps): bump multer from 2.0.0 to 2.0.1 (#14127) +* maint(web): fix working directory for zipping artifacts for Web release (#14128) +* chore(linux): Update debian changelog (#14119) +* maint(web): improve TC build script for Keyman Web/Test (#14051) +* maint(developer): move TC configuration for Keyman Developer to scripts (#14052) + +## 19.0.58 alpha 2025-06-04 + +* feat(developer): update strs compiler to collect context (#14064) +* feat(developer): line numbers for UsetCompiler, others (#14090) +* feat(developer): remaining line numbers for tran/bksp compilers (#14097) +* feat(developer): ldml: update errors for unparseable reorder (#14098) +* chore(developer): ldml: messages cleanup (#14100) +* maint(android): Create "archive" builder step to prep release artifacts (#14093) +* maint(web): move TC configuration for Web to script (#14049) + +## 19.0.57 alpha 2025-06-03 + +* fix(windows): change from error to breadcrumb and log (#14069) +* maint(mac): make make-km-dmg.sh less quiet (#14105) + +## 19.0.56 alpha 2025-06-02 + +* maint(ios): Use zip.inc.sh to prep release artifacts (#13919) +* maint(linux): update to new version of gha-ubuntu-packaging (#14094) +* chore(linux): add Python TC test runner (#14062) + +## 19.0.55 alpha 2025-05-31 + +* maint(linux): `sudo` not required for removing temporary files (#14086) +* maint(linux): update to new version of gha-ubuntu-packaging (#14089) + +## 19.0.54 alpha 2025-05-30 + +* maint(linux): move TC build scripts to `resources/teamcity/linux` (#14061) + +## 19.0.53 alpha 2025-05-29 + +* fix(developer): ensure that kmc doesn't skip validate phase of kmc-ldml (#14068) + +## 19.0.52 alpha 2025-05-28 + +* feat(developer): line numbers for tran compiler (#14037) +* feat(developer): add line numbers to disp compiler (#14048) +* feat(developer): line numbers for layr compiler (#14050) +* feat(developer): line numbers for LocaCompiler (#14054) +* fix(common): properly use newly installed node version (#14057) +* maint(common): update build coordinator and labels for `resources/teamcity` changes (#14047) + +## 19.0.51 alpha 2025-05-27 + +* maint: add stable-19.0 triggers (#14043) +* maint: add empty 'scripts' tag to package.json to silence warning (#14044) +* fix(linux): fix version variable references in kmconfig (#14032) + +## 19.0.50 alpha 2025-05-26 + +* chore(web): mark async methods in UI components (#14034) + +## 19.0.49 alpha 2025-05-25 + +* feat(developer): revamp line numbers per design doc (#14016) +* docs(developer): add compiler message internal documentation (#14025) +* feat(developer): add line numbers to vars compiler (#14036) + +## 19.0.48 alpha 2025-05-23 + +* maint(web): upgrade Sentry to 8.55.0 (#14030) + +## 19.0.47 alpha 2025-05-22 + +* fix(windows): only breadcrumb unexpected state on first run (#13984) +* maint: change builder_completion script to a .inc.sh (#14007) +* fix(windows): skip sending handle kmshell events to upgrade state machine for keyboard install modes (#13989) +* fix(windows): fix sentry error message reporting (#14027) + +## 19.0.46 alpha 2025-05-21 + +* docs(common): update README with number of languages and keyboards (#14023) + +## 19.0.45 alpha 2025-05-21 + +* maint(linux): use correct syntax to define local variables in script (#14021) +* maint(developer): update multer to v2.0.0 (#14012) + +## 19.0.44 alpha 2025-05-21 + +* feat(android): log active keyboard, model IDs with reported errors (#13983) +* fix(windows): rename Enter and Exit methods TState (#13920) +* maint: update macOS environment setup, related documentation (#13979) +* chore(deps): bump multer from 1.4.5-lts.1 to 2.0.0 (#13982) +* feat(ios): log active keyboard, model IDs with reported errors (#13987) +* maint(linux): add quilt to release build configuration (#13986) +* chore(ios): update FirstVoices AppIcon (#13976) +* docs(developer): add security note to Upload documentation (#14015) +* fix(android): Check network access before trying to download keyboard (#13978) + +## 19.0.43 alpha 2025-05-20 + +* chore(ios): FirstVoices certificate update (#13991) +* fix(linux): configurable path in `.service` file when using basu (#13980) + +## 19.0.42 alpha 2025-05-20 + +* maint(linux): add `--no-werror` option to not report warnings as errors (#13988) + +## 19.0.41 alpha 2025-05-19 + +* fix(web): inspectable test-script mode should not be auto-enabled in local-env tests (#13895) +* maint(common): evaluate and apply npm audit fix (without forced changes) (#13897) +* maint: update mocha (#13899) +* maint: update @actions/github + @octokit references (#13900) +* maint(common): update fast-json-patch to ^3.1.1 (#13901) +* change(web): cancel flicks when both returning to and releasing at original tap location (#13683) +* fix(web): empty layout fontSize property should default to 1em (#13959) +* maint(linux): fix release build to run all tests except integration tests (#13968) + +## 19.0.40 alpha 2025-05-17 + +* maint(common): Add zip.inc.sh for cross-platform zipping (#13812) + +## 19.0.39 alpha 2025-05-16 + +* chore(mac,windows): Update Crowdin strings for Italian (#13871) +* refactor(web): rename generic type variables (#13930) +* maint(linux): properly fallback to `VERSION` in deb-packaging (#13961) +* maint(linux): update gha-ubuntu-packaging dependency (#13964) +* fix(linux): use correct header guard define in OrderedOutputDevice.h (#13962) +* maint(linux): don't include `dockerbuild` log files (#13965) +* maint(linux): update GHA dependencies for Linux package builds (#13966) + +## 19.0.38 alpha 2025-05-15 + +* chore(android): Update FirstVoices launcher icon and red theme (#13907) +* feat(developer): add more line number issues for KeysCompiler (#13915) +* feat(developer): add line number for MetaCompiler (#13916) +* maint(linux): address comments from Debian package review (#13904) +* maint(linux): add Questing to launchpad package builds (#13910) +* maint(linux): fix logging in `deb-packaging.sh` (#13912) +* maint(linux): remove temporary files created during dependency installation (#13913) +* maint(common): move TC build configs for Linux to scripts (#13754) +* fix(developer): remove ngrok binary at build time (#13847) +* chore(linux): Update debian changelog (#13889) +* refactor(web): rename `HardKeyboard` to `HardKeyboardBase` (#13929) +* refactor(web): rename base `KeymanEngine` to `KeymanEngineBase` (#13942) + +## 19.0.37 alpha 2025-05-14 + +* maint(linux): remove generated file that got accidentally committed (#13902) +* maint(linux): remove check for patched ibus from `meson.build` (#13909) +* maint(common): report builder errors to stderr (#13882) + +## 19.0.36 alpha 2025-05-13 + +* feat(developer): line numbers for XML parsing and kmc-ldml (#13375) +* maint(android): Update to Java 21 and Gradle dependencies (#13858) +* maint(linux): improve getting PR# in `upload-to-debian.sh` (#13890) +* fix(linux): fallback to `VERSION` if `KEYMAN_VERSION` isn't set during package build (#13891) +* maint(common): move and document `builder_is_ci_*` functions (#13892) +* maint(android): use new variable name `KEYMAN_TIER` (#13898) +* fix(mac): handle PackageInfo section in kmp.inf file (#13876) + +## 19.0.35 alpha 2025-05-12 + +* maint(common): add two more functions to detect if running on CI (#13869) +* maint(common): use unique names for Keyman version variables (#13854) +* maint(common): use new `builder_is_running_on_*` functions (#13875) +* maint(common): remove obsolete code from docker-images `run.sh` (#13879) +* chore(mac): remove obsolete key processing code (#13848) + +## 19.0.34 alpha 2025-05-08 + +* docs(common): update `builder_echo` documentation for TC blocks (#13855) +* fix(windows): add breadcrumb to download failures (#13826) +* fix(web): sustain active batched-layout mode when keyboard is switched (#13860) +* maint(common): remove obsolete code from docker-images `build.sh` (#13861) +* maint(common): output start/end messages if not running on TC (#13868) + +## 19.0.33 alpha 2025-05-07 + +* maint(common): automatically set --debug option for local-environment builder-script uses (#13827) +* docs(web): add a Web engine keystroke lifecycle internal doc (#13759) +* maint(common): use function to detect if running on TC (#13844) +* docs(common): document new `builder_is_running_on_teamcity` function (#13852) + +## 19.0.32 alpha 2025-05-06 + +* change(web): replace USVString with string in predictive-text engine (#13785) +* chore(web): replace --ci flag with builder_is_ci_build (#13819) +* maint(web): simplify worker-main test.sh (#13820) +* change(web): lint kmwstring web-utility (#13800) +* fix(web): fix extraction of gesture-engine fixture used in DOM-based unit tests (#13805) +* fix(web): add robustness for bad touch-layout fontsize specs (#13774) +* fix(web): fix var declaration affected by linter strictness + length-style fix (#13839) +* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13840) +* chore(common): override check when running on TC (#13779) +* change(linux): add ICU dependency needed to build on Arch Linux (#13842) +* maint(common): add support for TC blocks (#13810) + +## 19.0.31 alpha 2025-05-05 + +* fix(mac): add version metadata after building tests (#13818) +* maint(common): remove `eslint:recommended` configuration for .ts (#13825) +* maint(common): Update write_download_info() to be cross-platform (#13813) +* change(ios): limit reports about devices without predefined scaling entries (#13806) +* change(web): lint engine/common/web-utils (#13797) +* chore(web): replace kmwFromCharCode with its ES6 equivalent (#13716) +* chore(web): add unit tests for SMP-aware string-handling helper methods (#13719) +* refactor(web): refactor string-helper library to eliminate its module side-effect (#13726) + +## 19.0.30 alpha 2025-05-02 + +* chore(android): Use `builder_is_ci_build` rather than `--ci` flag (#13772) +* docs: add `--deps` flags and move builder.md into /docs (#13815) +* chore(web): improve clean actions (#13804) +* fix(ios): restore keyboard-scale settings for older, still-supported iOS devices (#13773) +* maint(web): merge LMLayer CI build config with standard Web CI build config (#13807) +* maint(core): batch build kmx in tests for performance (#13795) +* maint(common): cleanup meson versioning (#13796) +* maint(common): test imported resource versions (#13803) + +## 19.0.29 alpha 2025-04-30 + +* change(web): enable linting for Keyman Engine for Web modules (#13787) +* fix(web): reconnect unit tests for low-level web utility classes/methods (#13714) +* fix(android): Change Alert Dialog instead of Toast if package missing languages (#13786) +* maint(common): remove platform advocates from CODEOWNERS (#13801) +* chore(common): allow to run tests in virtual environment (#13793) +* chore: increase linter strictness (#13788) + +## 19.0.28 alpha 2025-04-29 + +* chore(common): Update dependencies in crowdin GitHub action (#13775) +* chore: additional tweaks to scopes/labels (#13765) +* chore(common): rename `chore` GH label to `maint` (#13780) +* change(linux): add link to issue (#13778) + +## 19.0.27 alpha 2025-04-28 + +* chore: update labels for v19 (#13744) + +## 19.0.26 alpha 2025-04-24 + +* chore: merge beta version 18.0.235 history to master (#13735) +* chore(linux): Update debian changelog (#13738) +* chore: comment out CODEOWNERS (#13739) +* chore(linux): add a stub unit test to allow successful coverage reporting (#13740) + ## 19.0.25 alpha 2025-04-22 * chore: merge beta to master (pre-release prep) (#13730) @@ -113,6 +385,49 @@ * refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168) * chore: increment to alpha 19.0 (#13187) +## 18.0.238 stable 2025-06-04 + +* fix(developer): ldml: make sure .run() calls compile() for validation (#14071) +* maint(linux): fix typo of temporary dependency package (#14085) +* maint(linux): update to new version of gha-ubuntu-packaging (#14096) +* maint(mac): make make-km-dmg.sh less quiet (#14106) + +## 18.0.237 stable 2025-05-30 + +* maint(android): Update FirstVoices launcher icon and red theme (#13917) +* fix(web): empty layout fontSize property should default to 1em (#13960) +* fix(linux): use correct header guard define in OrderedOutputDevice.h (#13963) +* chore(linux): Update debian changelog (#13888) +* fix(mac): handle PackageInfo section in kmp.inf file (#13957) +* feat(android): log active keyboard, model IDs with reported errors (#14003) +* chore(ios): FirstVoices certificate update (#14005) +* feat(ios): log active keyboard, model IDs with reported errors (#14006) +* fix(linux): configurable path in `.service` file when using basu (#13999) +* maint(linux): add `--no-werror` option to not report warnings as errors (#14000) +* fix(windows): use json file name against cache folder and ename Enter and Exit methods for TState Object (#14010) +* chore(ios): update FirstVoices AppIcon (#14011) +* maint: change builder_completion script to a .inc.sh (#14008) +* fix(windows): only breadcrumb unexpected state on first run (#14026) +* fix(windows): skip sending handle kmshell events to upgrade state machine for keyboard install modes (#14028) +* fix(windows): fix sentry error message reporting (#14029) + +## 18.0.236 stable 2025-05-12 + +* chore(linux): Update debian changelog (#13737) +* chore(linux): add a stub unit test to allow successful coverage reporting (#13741) +* fix(android): Use Alert Dialog if keyboard pkg missing languages (#13802) +* maint(web): merge LMLayer CI build config with standard Web CI build config (#13808) +* fix(ios): restore keyboard-scale settings for older, still-supported iOS devices (#13817) +* change(ios): limit reports about devices without predefined scaling entries (#13828) +* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13841) +* fix(web): add robustness for bad touch-layout fontsize specs (#13838) +* change(linux): add ICU dependency needed to build on Arch Linux (#13843) +* fix(mac): add version metadata after building tests (#13853) +* fix(web): sustain active batched-layout mode when keyboard is switched (#13863) +* fix(developer): remove ngrok binary at build time (#13873) +* maint(common): apply new codeowners from master (#13874) +* chore(mac,windows): Update Crowdin strings for Italian (#13872) + ## 18.0.234 beta 2025-04-22 * fix(android): Require RELEASE_OEM env vars to build FirstVoices Android (#13725) diff --git a/LICENSE.md b/LICENSE.md index 5b20376235..332e5a63ea 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,16 +1,14 @@ # License -Copyright (c) 2017-2024 SIL Global. All rights reserved. +Copyright (c) 2017-2025 SIL Global. All rights reserved. Licensed under the MIT License. -[Keyman for Linux](./linux) is licensed under the MIT License apart from [ibus-kmfl](./linux/ibus-kmfl) which is licensed under the GNU General Public License as published by the Free Software Foundation; either [version 2](./linux/ibus-kmfl/COPYING) of the License, or (at your option) any later version. - --- The MIT License -Copyright (c) 2017-2024 SIL Global +Copyright (c) 2017-2025 SIL Global Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28,4 +26,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index caab39c2f9..601fdcc696 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ ![Keyman](https://keyman.com/cdn/dev/img/logo2.png) -[Keyman](https://keyman.com) makes it possible for you to type in any language on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even instantly in your web browser. Create keyboard layouts with Keyman Developer and share them with the community in the [keyboards repository](https://github.com/keymanapp/keyboards). The [Keyman Community](https://community.software.sil.org/c/keyman) have already contributed keyboard layouts for over 2,000 languages! +[Keyman](https://keyman.com) makes it possible for you to type in any language +on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even +instantly in your web browser. Create keyboard layouts with Keyman Developer and +share them with the community in the [keyboards +repository](https://github.com/keymanapp/keyboards). The [Keyman +Community](https://community.software.sil.org/c/keyman) have so far contributed +well over 1,000 keyboard layouts for over 2,500 languages! # License diff --git a/VERSION.md b/VERSION.md index c966bc82e4..efcfb4b252 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.26 \ No newline at end of file +19.0.64 \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore index a5720bccc7..e822a25c2b 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -57,6 +57,9 @@ KMAPro/**/assets/*.kmp # Play Store release notes (copied from whatsnew.md) KMAPro/kMAPro/src/main/play/release-notes/**/*.txt +# Release artifacts +upload/ + # Eclipse files .project .settings/ diff --git a/android/KMAPro/build-play-store-notes.inc.sh b/android/KMAPro/build-play-store-notes.inc.sh index 56b9a92326..667a9eb90f 100755 --- a/android/KMAPro/build-play-store-notes.inc.sh +++ b/android/KMAPro/build-play-store-notes.inc.sh @@ -11,8 +11,8 @@ function generateReleaseNotes() { # Copy release notes for Gradle Play Publisher to upload # Reference: https://github.com/Triple-T/gradle-play-publisher#uploading-release-notes # - local PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/$TIER.txt" - if [ $TIER = "stable" ]; then + local PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/${KEYMAN_TIER}.txt" + if [ $KEYMAN_TIER = "stable" ]; then PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/default.txt" fi builder_heading "Generating Play Store release notes to $PLAY_RELEASE_NOTES" diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index 5614fcae6a..c644d44fca 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -8,10 +8,10 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.9.2' // sentry-android-gradle-plugin 2.1.5+ requires AGP 7.0 classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0' - classpath 'name.remal:gradle-plugins:1.5.0' + classpath 'name.remal:gradle-plugins:1.9.2' // From jcenter() which was deprecated August 2024 // https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh index 1eafe1e667..fd54215d1c 100755 --- a/android/KMAPro/build.sh +++ b/android/KMAPro/build.sh @@ -7,6 +7,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" . "$KEYMAN_ROOT/resources/build/build-help.inc.sh" . "$KEYMAN_ROOT/resources/build/build-download-resources.sh" @@ -28,7 +29,6 @@ builder_describe "Builds Keyman for Android app." \ "build" \ "test Runs lint and unit tests." \ "publish Publishes symbols to Sentry and the APK to the Play Store." \ - "--ci Don't start the Gradle daemon. For CI" \ "--upload-sentry Upload to sentry" # parse before describe_outputs to check debug flags @@ -37,32 +37,32 @@ builder_parse "$@" if builder_is_debug_build; then builder_heading "### Debug config ####" CONFIG="debug" - BUILD_FLAGS="assembleDebug -x lint -x test" + BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug" TEST_FLAGS="-x assembleDebug lintDebug testDebug" fi builder_describe_outputs \ configure /android/KMAPro/kMAPro/libs/keyman-engine.aar \ - build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${VERSION}.apk + build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk #### Build # Parse args -if builder_has_option --ci; then +if builder_is_ci_build; then DAEMON_FLAG=--no-daemon fi #### Build action definitions #### function makeLocalSentryRelease() { - echo "Making a Sentry release for tag $VERSION_GIT_TAG" + echo "Making a Sentry release for tag $KEYMAN_VERSION_GIT_TAG" sentry-cli upload-dif -p keyman-android --include-sources - sentry-cli releases -p keyman-android files $VERSION_GIT_TAG upload-sourcemaps ./ + sentry-cli releases -p keyman-android files $KEYMAN_VERSION_GIT_TAG upload-sourcemaps ./ - echo "Finalizing release tag $VERSION_GIT_TAG" - sentry-cli releases finalize "$VERSION_GIT_TAG" + echo "Finalizing release tag $KEYMAN_VERSION_GIT_TAG" + sentry-cli releases finalize "$KEYMAN_VERSION_GIT_TAG" } diff --git a/android/KMAPro/gradle.properties b/android/KMAPro/gradle.properties index 93aaf1410c..f56c02ac58 100644 --- a/android/KMAPro/gradle.properties +++ b/android/KMAPro/gradle.properties @@ -1,4 +1,7 @@ android.enableJetifier=true android.useAndroidX=true +# AGP 8.0 generates R classes for resources defined in the current module only. +android.nonTransitiveRClass=false + org.gradle.jvmargs=-Xmx1024m diff --git a/android/KMAPro/gradle/wrapper/gradle-wrapper.properties b/android/KMAPro/gradle/wrapper/gradle-wrapper.properties index 3994438e22..d6e308a637 100644 --- a/android/KMAPro/gradle/wrapper/gradle-wrapper.properties +++ b/android/KMAPro/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index 768d98d933..864fb36ee9 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -20,6 +20,15 @@ android { noCompress "kmp" } + buildFeatures { + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + defaultConfig { applicationId "com.tavultesoft.kmapro" minSdkVersion 21 @@ -29,11 +38,11 @@ android { //dumpProperties(project) // Use this to dump all external properties for debugging TeamCity integration //println "===END DUMP===" - // VERSION strings from version.gradle - versionCode VERSION_CODE as Integer - versionName VERSION_NAME - buildConfigField 'String', 'VERSION_ENVIRONMENT', '"'+VERSION_ENVIRONMENT+'"' - buildConfigField 'String', 'VERSION_GIT_TAG', '"'+VERSION_GIT_TAG+'"' + // KEYMAN_VERSION strings from version.gradle + versionCode KEYMAN_VERSION_CODE as Integer + versionName KEYMAN_VERSION_NAME + buildConfigField 'String', 'KEYMAN_VERSION_ENVIRONMENT', '"'+KEYMAN_VERSION_ENVIRONMENT+'"' + buildConfigField 'String', 'KEYMAN_VERSION_GIT_TAG', '"'+KEYMAN_VERSION_GIT_TAG+'"' } String env_release_store_file = System.getenv("release_store_file") @@ -77,7 +86,7 @@ android { // - PR builds also have "-test-1234" suffix (where 1234 is PR#) // Gradle sync will nullify these values, so have a fallback String appSuffix = '' - String tag = VERSION_TAG ? VERSION_TAG : '-' + tier; + String tag = KEYMAN_VERSION_TAG ? KEYMAN_VERSION_TAG : '-' + tier; if (tag.startsWithAny("-beta", "beta")) { appSuffix = ' Beta'; } else if (tag.startsWithAny("-alpha", "alpha")) { @@ -88,16 +97,16 @@ android { // -test-PR_NUMBER appSuffix = appSuffix + match.group(1) } else { - String environment = VERSION_ENVIRONMENT ? VERSION_ENVIRONMENT : 'local'; + String environment = KEYMAN_VERSION_ENVIRONMENT ? KEYMAN_VERSION_ENVIRONMENT : 'local'; if (environment.matches("local")) { appSuffix = appSuffix + '-' + environment; } } variant.resValue "string", "app_name", 'Keyman' + appSuffix; - // Adjust output name to "keyman-${VERSION_MD}.apk" + // Adjust output name to "keyman-${KEYMAN_VERSION_MD}.apk" variant.outputs.all { - outputFileName = "keyman-" + VERSION_MD + ".apk" + outputFileName = "keyman-" + KEYMAN_VERSION_MD + ".apk" } } lintOptions { @@ -156,7 +165,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' implementation 'com.stepstone.stepper:material-stepper:4.3.1' api(name: 'keyman-engine', ext: 'aar') diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index e6016202a0..2d03311d50 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -62,8 +62,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven Log.d(TAG, "Initializing Sentry"); SentryAndroid.init(getApplicationContext(), options -> { options.setEnableAutoSessionTracking(false); - options.setRelease(com.tavultesoft.kmapro.BuildConfig.VERSION_GIT_TAG); - options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.VERSION_ENVIRONMENT); + options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG); + options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT); }); } if (BuildConfig.DEBUG) { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java index e66d5e9ebc..09a8921e7b 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java @@ -114,7 +114,14 @@ public class KMPBrowserActivity extends BaseActivity { Intent intent = new Intent(context, MainActivity.class); intent.setData(downloadURI); KMLog.LogBreadcrumb(TAG, "Successful keyboard search now triggering package install", true); - startActivity(intent); + + // Verify network access before trying to download + if (KMManager.hasConnection(context)) { + startActivity(intent); + } else { + Toast.makeText(context, getString(R.string.no_internet_connection), Toast.LENGTH_LONG).show(); + return true; + } // Finish activity finish(); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 07ce5c4cbb..3eec444fbd 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -144,8 +144,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (KMManager.getMaySendCrashReport()) { SentryAndroid.init(context, options -> { options.setEnableAutoSessionTracking(false); - options.setRelease(com.tavultesoft.kmapro.BuildConfig.VERSION_GIT_TAG); - options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.VERSION_ENVIRONMENT); + options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG); + options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT); }); } @@ -440,41 +440,41 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_info: - showInfo(); - return true; - case R.id.action_share: - showShareDialog(); - return true; + // Android Gradle 8.0 no longer declares resources final, so can't use switch statement here + if (item.getItemId() == R.id.action_info) { + showInfo(); + return true; + } else if (item.getItemId() == R.id.action_share) { + showShareDialog(); + return true; /* Disable Web Browser to investigate Google sign-in - case R.id.action_web: + } else if ((item.getItemId() == R.id.action_web) { showWebBrowser(); return true;*/ - case R.id.action_text_size: - showTextSizeDialog(); - return true; - case R.id.action_clear_text: - showClearTextDialog(); - return true; - case R.id.action_get_started: - showGetStarted(); - return true; - case R.id.action_settings: - showSettings(); - return true; - case R.id.action_update_keyboards: - KMManager.getUpdateTool().executeOpenUpdates(); - // Dismiss icon - updateUpdateCountIndicator(0); - final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); - if (_keyboardupdate != null && _keyboardupdate.isVisible()) { - _keyboardupdate.setVisible(false); - } + } else if (item.getItemId() == R.id.action_text_size) { + showTextSizeDialog(); + return true; + } else if (item.getItemId() == R.id.action_clear_text){ + showClearTextDialog(); + return true; + } else if (item.getItemId() == R.id.action_get_started) { + showGetStarted(); + return true; + } else if (item.getItemId() == R.id.action_settings) { + showSettings(); + return true; + } else if (item.getItemId() == R.id.action_update_keyboards) { + KMManager.getUpdateTool().executeOpenUpdates(); + // Dismiss icon + updateUpdateCountIndicator(0); + final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards); + if (_keyboardupdate != null && _keyboardupdate.isVisible()) { + _keyboardupdate.setVisible(false); + } - return true; - default: - return super.onOptionsItemSelected(item); + return true; + } else { + return super.onOptionsItemSelected(item); } } @@ -632,7 +632,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Intent i = new Intent(this, InfoActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(i); - overridePendingTransition(android.R.anim.fade_in, R.anim.hold); + overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold); } private void showWebBrowser() { @@ -643,7 +643,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene Intent i = new Intent(this, WebBrowserActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(i); - overridePendingTransition(android.R.anim.fade_in, R.anim.hold); + overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold); } private void showShareDialog() { diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java index 161d966308..ee76265a97 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java @@ -116,12 +116,14 @@ public class PackageActivity extends AppCompatActivity implements kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0) : 0; // Sanity check for keyboard packages + // Not using showErrorToast to avoid cluttering Sentry with noise if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) { if (keyboardCount == 0) { showErrorDialog(context, pkgId, getString(R.string.no_new_touch_keyboards_to_install)); return; } else if (languageCount == 0) { - showErrorToast(getString(R.string.no_associated_languages)); + showErrorDialog(context, pkgId, getString(R.string.no_associated_languages)); + return; } } diff --git a/android/KMEA/app/build.gradle b/android/KMEA/app/build.gradle index c80a4fd66e..dd4251025b 100644 --- a/android/KMEA/app/build.gradle +++ b/android/KMEA/app/build.gradle @@ -1,4 +1,5 @@ plugins { + id 'base' id 'com.android.library' id 'name.remal.default-plugins' } @@ -6,20 +7,32 @@ plugins { ext.rootPath = '../../' apply from: "$rootPath/version.gradle" +base { + // Specify library filename keyman-engine-$CONFIG.aar + archivesName = "keyman-engine" +} + android { compileSdk 34 namespace "com.keyman.engine" + buildFeatures { + // needed for custom BuildConfigField values in defaultConfig + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + defaultConfig { minSdkVersion 21 targetSdkVersion 34 - // VERSION_CODE and VERSION_NAME from version.gradle but Gradle removes them for libraries - buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+VERSION_NAME+"\"" - buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\"" - - // Sepcify library filename keyman-engine-$CONFIG.aar - setProperty("archivesBaseName", "keyman-engine") + // KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle but Gradle removes them for libraries + buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+KEYMAN_VERSION_NAME+"\"" + buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\"" } buildTypes { @@ -36,10 +49,6 @@ android { } testOptions { - unitTests { - // Allows use of a simulated Android API for tests. (Thanks, roboelectric!) - includeAndroidResources = true - } unitTests.all { testLogging { // May also add "passed", "skipped". @@ -55,17 +64,17 @@ android { dependencies { // 1.6.0-rc01 needed to resolve https://issuetracker.google.com/issues/238425626 - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' // material:1.7.0 will need Gradle plugin 7.1.0+ implementation 'com.google.android.material:material:1.12.0' - implementation 'commons-io:commons-io:2.13.0' + implementation 'commons-io:commons-io:2.16.1' implementation 'io.sentry:sentry-android:7.8.0' implementation 'androidx.preference:preference:1.2.1' // Robolectric - testImplementation 'androidx.test:core:1.5.0' - testImplementation 'androidx.test.ext:junit:1.1.5' - testImplementation 'org.robolectric:robolectric:4.10.3' + testImplementation 'androidx.test.ext:junit:1.2.1' + testImplementation 'org.robolectric:robolectric:4.14.1' + testImplementation 'androidx.test:core:1.6.1' // Generate QR Codes implementation ('com.github.kenglxn.QRGen:android:3.0.1') { diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 8d9d2d67dc..38f5ad617e 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -9,7 +9,6 @@ Keyman - diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 76c73120e3..75b0443537 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -362,8 +362,7 @@ public final class KMManager { // Keyman files protected static final String KMFilename_KeyboardHtml = "keyboard.html"; protected static final String KMFilename_JSEngine = "keymanweb-webview.js"; - protected static final String KMFilename_JSSentry = "sentry.min.js"; - protected static final String KMFilename_JSSentryInit = "keyman-sentry.js"; + protected static final String KMFilename_JSSentry = "keyman-sentry.js"; protected static final String KMFilename_AndroidHost = "android-host.js"; protected static final String KMFilename_KmwCss = "kmwosk.css"; protected static final String KMFilename_KmwGlobeHintCss = "globe-hint.css"; @@ -982,7 +981,6 @@ public final class KMManager { copyAsset(context, KMFilename_JSEngine, "", true); copyAsset(context, KMFilename_JSSentry, "", true); - copyAsset(context, KMFilename_JSSentryInit, "", true); copyAsset(context, KMFilename_AndroidHost, "", true); copyAsset(context, KMFilename_KmwCss, "", true); copyAsset(context, KMFilename_KmwGlobeHintCss, "", true); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java index bb8ee190b6..ce32f2beeb 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java @@ -1,5 +1,6 @@ package com.keyman.engine.cloud; +import android.annotation.SuppressLint; import android.app.DownloadManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -60,6 +61,7 @@ public class CloudDownloadMgr{ * Append downloadreceiver to the main context. * @param aContext the context */ + @SuppressLint("UnspecifiedRegisterReceiverFlag") public synchronized void initialize(Context aContext) { if(isInitialized) @@ -73,6 +75,7 @@ public class CloudDownloadMgr{ aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED); } else { + // Needs @SuppressLint UnspecifiedRegisterReceiverFlag aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } } catch (IllegalArgumentException e) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java index cb13cc967e..1ce55bf0ce 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java @@ -4,15 +4,19 @@ package com.keyman.engine.util; +import static com.keyman.engine.KMManager.KMKey_LexicalModelID; + import android.util.Log; import android.widget.Toast; import com.keyman.engine.BaseActivity; import com.keyman.engine.BuildConfig; import com.keyman.engine.KMManager; -import com.keyman.engine.util.DependencyUtil; +import com.keyman.engine.data.Keyboard; import com.keyman.engine.util.DependencyUtil.LibraryType; +import java.util.Map; + import io.sentry.Breadcrumb; import io.sentry.Sentry; import io.sentry.SentryLevel; @@ -20,19 +24,67 @@ import io.sentry.SentryLevel; public final class KMLog { private static final String TAG = "KMLog"; + private static final String KEYBOARD_TAG = "keyboardId"; + private static final String KEYBOARD_COUNT_TAG = "installedKeyboardCount"; + private static final String MODEL_TAG = "modelId"; + private static final String LANGCODE_TAG = "languageCode"; + + // Some of the methods used to generate debug logging information can, themselves, + // trigger errors that can also trigger the same logging. We must not get + // caught in an infinite loop / stack-overflow; this field helps us avoid states + // that would otherwise cause error-looping, etc. + private static boolean isLogging = false; + + private static void tagDebugInfo() { + String kbdId = ""; + String lngCode = ""; + String modelId = ""; + int kbdCount = 0; + // Do not risk raising a new error while tagging info for another error. + try { + // Both take a context parameter... but don't actually need or use it! + Keyboard kbd = KMManager.getCurrentKeyboardInfo(null); + kbdCount = KMManager.getKeyboardsList(null).size(); + if (kbd != null) { + kbdId = kbd.getKeyboardID(); + lngCode = kbd.getLanguageCode(); + Map modelMap = KMManager.getAssociatedLexicalModel(kbd.getLanguageID()); + if (modelMap != null) { + modelId = modelMap.get(KMKey_LexicalModelID); + if (modelId == null) { + modelId = ""; + } + } + } + } catch (Exception ex) { + String msg = ex.getMessage() == null ? "" : ex.getMessage(); + Sentry.setExtra("debugLoggingError", msg); + } + Sentry.setExtra(KEYBOARD_TAG, kbdId); + Sentry.setExtra(KEYBOARD_COUNT_TAG, "" + kbdCount); + Sentry.setExtra(LANGCODE_TAG, lngCode); + Sentry.setExtra(MODEL_TAG, modelId); + } + /** * Utility to log info and send to Sentry * @param tag String of the caller * @param msg String of the info message */ public static void LogInfo(String tag, String msg) { + if(isLogging) { + return; + } + isLogging = true; if (msg != null && !msg.isEmpty()) { Log.i(tag, msg); if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) { + tagDebugInfo(); Sentry.captureMessage(msg, SentryLevel.INFO); } } + isLogging = false; } /** @@ -46,9 +98,15 @@ public final class KMLog { return; } + if(isLogging) { + return; + } + isLogging = true; + Log.i(tag, msg); if (!DependencyUtil.libraryExists(LibraryType.SENTRY) || !Sentry.isEnabled()) { + isLogging = false; return; } @@ -74,7 +132,9 @@ public final class KMLog { crumb.setData("stacktrace", trace); } } + tagDebugInfo(); Sentry.addBreadcrumb(crumb); + isLogging = false; } /** @@ -83,6 +143,10 @@ public final class KMLog { * @param msg String of the error message */ public static void LogError(String tag, String msg) { + if(isLogging) { + return; + } + isLogging = true; if (msg != null && !msg.isEmpty()) { Log.e(tag, msg); @@ -91,9 +155,11 @@ public final class KMLog { } if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) { + tagDebugInfo(); Sentry.captureMessage(msg, SentryLevel.ERROR); } } + isLogging = false; } /** @@ -103,6 +169,10 @@ public final class KMLog { * @param e Throwable exception */ public static void LogException(String tag, String msg, Throwable e) { + if(isLogging) { + return; + } + isLogging = true; String errorMsg = ""; if (msg != null && !msg.isEmpty()) { errorMsg = msg + "\n" + e; @@ -116,9 +186,11 @@ public final class KMLog { } if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) { + tagDebugInfo(); Sentry.addBreadcrumb(errorMsg); Sentry.captureException(e); } + isLogging = false; } /** @@ -131,7 +203,12 @@ public final class KMLog { */ public static void LogExceptionWithData(String tag, String msg, String objName, Object obj, Throwable e) { + if(isLogging) { + return; + } + isLogging = true; if (obj != null && DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) { + tagDebugInfo(); String objStr = null; try { objStr = obj.toString(); @@ -141,6 +218,10 @@ public final class KMLog { } // Report the original exception LogException(tag, msg, e); + // And remove the exception-specific tagged data, lest it also be + // tracked on subsequent errors not associated with the current call. + Sentry.removeExtra(objName); } + isLogging = false; } } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/VersionUtils.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/VersionUtils.java index 58e80399f5..7188280615 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/util/VersionUtils.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/VersionUtils.java @@ -12,11 +12,11 @@ public final class VersionUtils { public static final String TAG = "VersionUtils"; public static boolean isLocalBuild() { - return BuildConfig.VERSION_ENVIRONMENT.equalsIgnoreCase("local"); + return BuildConfig.KEYMAN_VERSION_ENVIRONMENT.equalsIgnoreCase("local"); } public static boolean isTestBuild() { - return BuildConfig.VERSION_ENVIRONMENT.equalsIgnoreCase("test"); + return BuildConfig.KEYMAN_VERSION_ENVIRONMENT.equalsIgnoreCase("test"); } // Utility for local and PR test builds - e.g. force check of keyboard updates diff --git a/android/KMEA/build.gradle b/android/KMEA/build.gradle index 9aa2d0dcc7..4ca999ea60 100644 --- a/android/KMEA/build.gradle +++ b/android/KMEA/build.gradle @@ -5,10 +5,10 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.9.2' // io.sentry:sentry-android-gradle-plugin not available for library project classpath 'io.sentry:sentry-android:7.8.0' - classpath 'name.remal:gradle-plugins:1.5.0' + classpath 'name.remal:gradle-plugins:1.9.2' } } diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh index 29cb519cf5..4d4ea7f8e3 100755 --- a/android/KMEA/build.sh +++ b/android/KMEA/build.sh @@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" # ################################ Main script ################################ @@ -28,8 +29,7 @@ builder_describe "Builds Keyman Engine for Android." \ "configure" \ "build" \ "test Runs lint and unit tests." \ - ":engine Builds Engine" \ - "--ci Don't start the Gradle daemon. For CI" + ":engine Builds Engine" # parse before describe_outputs to check debug flags builder_parse "$@" @@ -37,7 +37,7 @@ builder_parse "$@" if builder_is_debug_build; then builder_heading "### Debug config ####" CONFIG="debug" - BUILD_FLAGS="assembleDebug -x lint -x test" + BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug" TEST_FLAGS="-x assembleDebug lintDebug testDebug" JUNIT_RESULTS="##teamcity[importData type='junit' path='keyman\android\KMEA\app\build\test-results\testDebugUnitTest\']" fi @@ -51,7 +51,7 @@ builder_describe_outputs \ # Parse args DAEMON_FLAG= -if builder_has_option --ci; then +if builder_is_ci_build; then DAEMON_FLAG=--no-daemon fi @@ -82,8 +82,6 @@ if builder_start_action build:engine; then cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/kmwosk.css" cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/globe-hint.css" "$ENGINE_ASSETS/globe-hint.css" cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/keymanweb-osk.ttf" - - cp "$KEYMAN_ROOT/node_modules/@sentry/browser/build/bundle.min.js" "$ENGINE_ASSETS/sentry.min.js" cp "$KEYMAN_ROOT/web/src/engine/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js" echo "Copying es6-shim polyfill" @@ -98,7 +96,7 @@ fi if builder_start_action test:engine; then - if builder_has_option --ci; then + if builder_is_ci_build; then # Report JUnit test results to CI echo "$JUNIT_RESULTS" fi diff --git a/android/KMEA/gradle/wrapper/gradle-wrapper.properties b/android/KMEA/gradle/wrapper/gradle-wrapper.properties index 3994438e22..8330c6a158 100644 --- a/android/KMEA/gradle/wrapper/gradle-wrapper.properties +++ b/android/KMEA/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Apr 30 11:17:37 ICT 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/README.md b/android/README.md index 6abb94161d..db1eb592b0 100644 --- a/android/README.md +++ b/android/README.md @@ -141,6 +141,10 @@ android { aaptOptions { noCompress "kmp" } + + buildFeatures { + buildConfig = true + } ``` 5. After the `android {}` object, include the following: ````gradle @@ -154,7 +158,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' api (name:'keyman-engine', ext:'aar') implementation 'androidx.preference:preference:1.2.1' diff --git a/android/Samples/KMSample1/app/build.gradle b/android/Samples/KMSample1/app/build.gradle index 51490c5ab2..cc92dee298 100644 --- a/android/Samples/KMSample1/app/build.gradle +++ b/android/Samples/KMSample1/app/build.gradle @@ -36,7 +36,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' api(name: 'keyman-engine', ext: 'aar') implementation 'androidx.preference:preference:1.2.1' diff --git a/android/Samples/KMSample1/build.gradle b/android/Samples/KMSample1/build.gradle index 15824022e2..18d4c46cd5 100644 --- a/android/Samples/KMSample1/build.gradle +++ b/android/Samples/KMSample1/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.9.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/Samples/KMSample1/build.sh b/android/Samples/KMSample1/build.sh index 66a04b8d7c..172ef6c5e7 100755 --- a/android/Samples/KMSample1/build.sh +++ b/android/Samples/KMSample1/build.sh @@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" ################################ Main script ################################ @@ -22,8 +23,7 @@ builder_describe "Build KMSample1 app for Android." \ "configure" \ "build" \ "test" \ - ":app KMSample1" \ - "--ci Don't start the Gradle daemon. Use for CI" + ":app KMSample1" # parse before describe_outputs to check debug flags builder_parse "$@" @@ -46,7 +46,7 @@ builder_describe_outputs \ # Parse args -if builder_has_option --ci; then +if builder_is_ci_build; then SAMPLE_FLAGS="$SAMPLE_FLAGS -no-daemon" fi diff --git a/android/Samples/KMSample1/gradle.properties b/android/Samples/KMSample1/gradle.properties index 80f4cae5a8..f486cc756a 100644 --- a/android/Samples/KMSample1/gradle.properties +++ b/android/Samples/KMSample1/gradle.properties @@ -18,4 +18,8 @@ # org.gradle.parallel=true android.enableJetifier=true android.useAndroidX=true + +# AGP 8.0 generates R classes for resources defined in the current module only. +android.nonTransitiveRClass=false + org.gradle.jvmargs=-Xmx1024m diff --git a/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties b/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties index fb79de47c0..4fba274552 100644 --- a/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties +++ b/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/Samples/KMSample2/app/build.gradle b/android/Samples/KMSample2/app/build.gradle index 71ab1dab03..cd2f4d6225 100644 --- a/android/Samples/KMSample2/app/build.gradle +++ b/android/Samples/KMSample2/app/build.gradle @@ -35,7 +35,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' api (name:'keyman-engine', ext:'aar') implementation 'androidx.preference:preference:1.2.1' diff --git a/android/Samples/KMSample2/build.gradle b/android/Samples/KMSample2/build.gradle index 15824022e2..18d4c46cd5 100644 --- a/android/Samples/KMSample2/build.gradle +++ b/android/Samples/KMSample2/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.9.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/Samples/KMSample2/build.sh b/android/Samples/KMSample2/build.sh index 75f985aa1f..2f7a99c764 100755 --- a/android/Samples/KMSample2/build.sh +++ b/android/Samples/KMSample2/build.sh @@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" ################################ Main script ################################ @@ -22,8 +23,7 @@ builder_describe "Build KMSample2 app for Android." \ "configure" \ "build" \ "test" \ - ":app KMSample2" \ - "--ci Don't start the Gradle daemon. Use for CI" + ":app KMSample2" # parse before describe_outputs to check debug flags builder_parse "$@" @@ -47,7 +47,7 @@ builder_describe_outputs \ # Parse args -if builder_has_option --ci; then +if builder_is_ci_build; then SAMPLE_FLAGS="$SAMPLE_FLAGS -no-daemon" fi diff --git a/android/Samples/KMSample2/gradle.properties b/android/Samples/KMSample2/gradle.properties index 80f4cae5a8..f486cc756a 100644 --- a/android/Samples/KMSample2/gradle.properties +++ b/android/Samples/KMSample2/gradle.properties @@ -18,4 +18,8 @@ # org.gradle.parallel=true android.enableJetifier=true android.useAndroidX=true + +# AGP 8.0 generates R classes for resources defined in the current module only. +android.nonTransitiveRClass=false + org.gradle.jvmargs=-Xmx1024m diff --git a/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties b/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties index 4b0560a7a2..0c0a7aec7d 100644 --- a/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties +++ b/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/Tests/KeyboardHarness/app/build.gradle b/android/Tests/KeyboardHarness/app/build.gradle index 5462622cfa..ab8066393b 100644 --- a/android/Tests/KeyboardHarness/app/build.gradle +++ b/android/Tests/KeyboardHarness/app/build.gradle @@ -14,15 +14,19 @@ android { noCompress "kmp" } + buildFeatures { + buildConfig = true + } + defaultConfig { applicationId "com.keyman.android.tests.keyboardHarness" minSdkVersion 21 targetSdkVersion 34 - // VERSION_CODE and VERSION_NAME from version.gradle - versionCode VERSION_CODE as Integer - versionName VERSION_NAME - buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\"" + // KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle + versionCode KEYMAN_VERSION_CODE as Integer + versionName KEYMAN_VERSION_NAME + buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\"" } buildTypes { release { @@ -42,7 +46,7 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' api (name:'keyman-engine', ext:'aar') implementation 'androidx.preference:preference:1.2.1' diff --git a/android/Tests/KeyboardHarness/build.gradle b/android/Tests/KeyboardHarness/build.gradle index ce0b936509..8fdf2aa422 100644 --- a/android/Tests/KeyboardHarness/build.gradle +++ b/android/Tests/KeyboardHarness/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0' + classpath 'com.android.tools.build:gradle:8.9.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/Tests/KeyboardHarness/build.sh b/android/Tests/KeyboardHarness/build.sh index 71b2deddf2..c23f8ca0f5 100755 --- a/android/Tests/KeyboardHarness/build.sh +++ b/android/Tests/KeyboardHarness/build.sh @@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" ################################ Main script ################################ @@ -22,8 +23,7 @@ builder_describe "Build KeyboardHarness test app for Android." \ "configure" \ "build" \ "test" \ - ":app KeyboardHarness" \ - "--ci Don't start the Gradle daemon. Use for CI" + ":app KeyboardHarness" # parse before describe outputs to check debug flags builder_parse "$@" @@ -33,7 +33,7 @@ ARTIFACT="app-release-unsigned.apk" if builder_is_debug_build; then builder_heading "### Debug config ####" CONFIG="debug" - BUILD_FLAGS="assembleDebug -x lint -x test" + BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug" TEST_FLAGS="-x assembleDebug lintDebug testDebug" ARTIFACT="app-$CONFIG.apk" fi @@ -55,7 +55,7 @@ SHLVL=0 # Parse args # Build flags that apply to all targets -if builder_has_option --ci; then +if builder_is_ci_build; then BUILD_FLAGS="$BUILD_FLAGS -no-daemon" TEST_FLAGS="$TEST_FLAGS -no-daemon" fi diff --git a/android/Tests/KeyboardHarness/gradle.properties b/android/Tests/KeyboardHarness/gradle.properties index 80f4cae5a8..8bb139036f 100644 --- a/android/Tests/KeyboardHarness/gradle.properties +++ b/android/Tests/KeyboardHarness/gradle.properties @@ -19,3 +19,6 @@ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1024m + +# AGP 8.0 generates R classes for resources defined in the current module only. +android.nonTransitiveRClass=false diff --git a/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties index da62d22563..3b14c8260c 100644 --- a/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties +++ b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/Tests/keycode/app/build.gradle b/android/Tests/keycode/app/build.gradle index ffba4c0cc4..a0b24abb44 100644 --- a/android/Tests/keycode/app/build.gradle +++ b/android/Tests/keycode/app/build.gradle @@ -12,10 +12,10 @@ android { minSdkVersion 21 targetSdkVersion 34 - // VERSION_CODE and VERSION_NAME from version.gradle - versionCode VERSION_CODE as Integer - versionName VERSION_NAME - buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\"" + // KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle + versionCode KEYMAN_VERSION_CODE as Integer + versionName KEYMAN_VERSION_NAME + buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\"" } buildTypes { release { diff --git a/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties b/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties index 763217c50f..8357d84852 100644 --- a/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties +++ b/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/build.sh b/android/build.sh index bb4fa0637c..12017e2c6b 100755 --- a/android/build.sh +++ b/android/build.sh @@ -14,6 +14,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" +. "$KEYMAN_ROOT/resources/build/zip.inc.sh" ################################ Main script ################################ @@ -36,7 +38,8 @@ builder_describe \ build \ test \ "publish Publishes symbols to Sentry and the APKs to the Play Store." \ - --ci+ \ + "archive Copy release artifacts to upload/ and rsync to downloads.keyman" \ + "--ci+ Deprecated build option. Remove in 20.0" \ --upload-sentry+ \ ":engine=KMEA Keyman Engine for Android" \ ":app=KMAPro Keyman for Android" \ @@ -69,4 +72,51 @@ function do_test_help() { builder_run_action test:help do_test_help -builder_run_child_actions publish \ No newline at end of file +builder_run_child_actions publish + +# Copy release artifacts to upload/ and rsync to downloads.keyman.com +if builder_start_action archive; then + UPLOAD_PATH="$KEYMAN_ROOT/android/upload/${KEYMAN_VERSION}" + KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip" + KEYMAN_APK="keyman-${KEYMAN_VERSION}.apk" + FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION}.apk" + ZIP_FILE="${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}" + ZIP_FLAGS=("-q" "-r") # quiet, recursive + + mkdir -p "${UPLOAD_PATH}" + + # Create Keyman Engine for Android archive + builder_echo "Copying Keyman Engine for Android into ${UPLOAD_PATH}..." + cd "${UPLOAD_PATH}" + cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs/keyman-engine.aar" ./ + add_zip_files "${ZIP_FILE}" "${ZIP_FLAGS[@]}" "keyman-engine.aar" + rm -f "keyman-engine.aar" + + builder_echo "Copying Keyman Engine for Android Sample projects into ${UPLOAD_PATH}..." + cp -rf "${KEYMAN_ROOT}/android/Samples" ./ + add_zip_files "${ZIP_FILE}" "-x@../../zip-excludes" "${ZIP_FLAGS[@]}" "Samples" + rm -rf "Samples" + + # Copy release APK + cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" ./ + + # FirstVoices app + + if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then + cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" ./ + fi + + # + # Write download info files + # + + cd "${UPLOAD_PATH}" + write_download_info "Keyman Engine for Android" "${KEYMAN_ENGINE_ANDROID_ZIP}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android" + write_download_info "Keyman for Android" "${KEYMAN_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android" + + if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then + write_download_info "FirstVoices Keyboards" "${FIRSTVOICES_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android" + fi + + builder_finish_action success archive +fi diff --git a/android/version.gradle b/android/version.gradle index ebf68a94e0..ea7342166c 100644 --- a/android/version.gradle +++ b/android/version.gradle @@ -7,9 +7,9 @@ // def getVersionCode = { -> - String env_version_major = System.getenv("VERSION_MAJOR") - String env_version_minor = System.getenv("VERSION_MINOR") - String env_version_patch = System.getenv("VERSION_PATCH") + String env_version_major = System.getenv("KEYMAN_VERSION_MAJOR") + String env_version_minor = System.getenv("KEYMAN_VERSION_MINOR") + String env_version_patch = System.getenv("KEYMAN_VERSION_PATCH") if(env_version_patch != null && env_version_minor != null && env_version_patch != null) { // Version code is, for 17.1.33: 1810033. This supports up to 9999 // builds for a given major.minor version. We only support up to 9 minor @@ -36,10 +36,10 @@ def getVersionCode = { -> } def getVersionMD = { -> - String env_version = System.getenv("VERSION") + String env_version = System.getenv("KEYMAN_VERSION") if (env_version != null) { - // If building from script, we have build number in VERSION - println "Using build $env_version from VERSION" + // If building from script, we have build number in KEYMAN_VERSION + println "Using build $env_version from KEYMAN_VERSION" return "$env_version" } else { String version_md = file("$rootPath/../VERSION.md").text.trim() @@ -48,15 +48,15 @@ def getVersionMD = { -> } def getVersionName = { -> - String env_version = System.getenv("VERSION_WITH_TAG") + String env_version = System.getenv("KEYMAN_VERSION_WITH_TAG") if (env_version != null) { - // If building from script, we have build number in VERSION_WITH_TAG - println "Using build $env_version from VERSION_WITH_TAG" + // If building from script, we have build number in KEYMAN_VERSION_WITH_TAG + println "Using build $env_version from KEYMAN_VERSION_WITH_TAG" return "$env_version" } else { // Building probably from IDE, so let's use VERSION.md and TIER.md directly - // This is a close match with VERSION_WITH_TAG, except that 'stable' tier - // is not normally included with VERSION_WITH_TAG + // This is a close match with KEYMAN_VERSION_WITH_TAG, except that 'stable' tier + // is not normally included with KEYMAN_VERSION_WITH_TAG String version_md = file("$rootPath/../VERSION.md").text.trim() String tier_md = file("$rootPath/../TIER.md").text.trim() println "Using build $version_md-$tier_md-local from project VERSION.md/TIER.md" @@ -65,10 +65,10 @@ def getVersionName = { -> } def getVersionEnvironment = { -> - String env_environment = System.getenv("VERSION_ENVIRONMENT") + String env_environment = System.getenv("KEYMAN_VERSION_ENVIRONMENT") if (env_environment != null) { - // If building from script, we have build number in VERSION_ENVIRONMENT - println "Using $env_environment from VERSION_ENVIRONMENT" + // If building from script, we have build number in KEYMAN_VERSION_ENVIRONMENT + println "Using $env_environment from KEYMAN_VERSION_ENVIRONMENT" return "$env_environment" } else { // Building probably from IDE, so use "local" @@ -78,23 +78,23 @@ def getVersionEnvironment = { -> } def getVersionGitTag = { -> - String env_git_tag = System.getenv("VERSION_GIT_TAG") + String env_git_tag = System.getenv("KEYMAN_VERSION_GIT_TAG") if (env_git_tag != null) { - println "Using $env_git_tag from VERSION_GIT_TAG" + println "Using $env_git_tag from KEYMAN_VERSION_GIT_TAG" return "$env_git_tag" } else { // Building probably from IDE - println "Using release@$VERSION_NAME" - return "release@$VERSION_NAME" + println "Using release@$KEYMAN_VERSION_NAME" + return "release@$KEYMAN_VERSION_NAME" } } ext { - VERSION_CODE=getVersionCode() - VERSION_MD=getVersionMD() - VERSION_NAME=getVersionName() - VERSION_ENVIRONMENT=getVersionEnvironment() - VERSION_TAG=System.getenv("VERSION_TAG") - VERSION_GIT_TAG=getVersionGitTag() + KEYMAN_VERSION_CODE=getVersionCode() + KEYMAN_VERSION_MD=getVersionMD() + KEYMAN_VERSION_NAME=getVersionName() + KEYMAN_VERSION_ENVIRONMENT=getVersionEnvironment() + KEYMAN_VERSION_TAG=System.getenv("KEYMAN_VERSION_TAG") + KEYMAN_VERSION_GIT_TAG=getVersionGitTag() } -//println "version.gradle: VERSION_TAG: " + VERSION_TAG \ No newline at end of file +//println "version.gradle: KEYMAN_VERSION_TAG: " + KEYMAN_VERSION_TAG \ No newline at end of file diff --git a/android/zip-excludes b/android/zip-excludes new file mode 100644 index 0000000000..1c2b62205d --- /dev/null +++ b/android/zip-excludes @@ -0,0 +1 @@ +Samples/*/build.sh diff --git a/common/test/keyboards/build.sh b/common/test/keyboards/build.sh index 2c8cc19ff4..2d6a986b87 100755 --- a/common/test/keyboards/build.sh +++ b/common/test/keyboards/build.sh @@ -5,6 +5,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE +. "$KEYMAN_ROOT/resources/build/zip.inc.sh" + targets=() # Build list of available targets from subfolders, if none specified for d in */; do @@ -29,7 +31,7 @@ builder_parse "$@" function zipsource() { local target="$1" pushd "$1" > /dev/null - 7z a -r -x!build -x"!$target.kpj.user" "${target}_source.zip" . + add_zip_files "${target}_source.zip" -x@../zip-excludes -q -r . # -q quiet, -r recursive popd > /dev/null } @@ -40,10 +42,10 @@ function build_index() { - Test Keyboards - $VERSION_WITH_TAG + Test Keyboards - $KEYMAN_VERSION_WITH_TAG -

Test Keyboards - $VERSION_WITH_TAG

+

Test Keyboards - $KEYMAN_VERSION_WITH_TAG