Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Run runtime stress checks
run: make stress-check

- name: Generate and validate runtime SBOM
run: make sbom

- name: Build debug APK
run: make assemble

Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ jobs:
distribution: temurin
java-version: '17'

- name: Set up Node.js 20
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '20'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@07d1f8453baaff67fb503883367f8b13e6158a54 # v6
with:
Expand All @@ -50,13 +55,21 @@ jobs:
GRAMMALECTE_SIGNING_KEY_PASSWORD: ${{ secrets.GRAMMALECTE_SIGNING_KEY_PASSWORD }}
run: make signed-release

- name: Record signed APK checksum
- name: Generate and validate runtime SBOM
run: make sbom

- name: Record release checksums
run: |
sha256sum \
app/build/outputs/apk/release/app-release-signed.apk \
> app/build/outputs/apk/release/app-release-signed.apk.sha256

sha256sum \
build/reports/sbom/grammalecte-android.cdx.json \
> build/reports/sbom/grammalecte-android.cdx.json.sha256

cat app/build/outputs/apk/release/app-release-signed.apk.sha256
cat build/reports/sbom/grammalecte-android.cdx.json.sha256

- name: Upload signed release APK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
Expand All @@ -65,6 +78,8 @@ jobs:
path: |
app/build/outputs/apk/release/app-release-signed.apk
app/build/outputs/apk/release/app-release-signed.apk.sha256
build/reports/sbom/grammalecte-android.cdx.json
build/reports/sbom/grammalecte-android.cdx.json.sha256
if-no-files-found: error
retention-days: 14

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- CycloneDX runtime SBOM generation and validation, including the vendored Grammalecte engine and release-workflow artifacts.
- Manual signed-release pipeline with protected GitHub environment secrets, release-certificate fingerprint verification and signed APK artifacts.
- Reproducible unsigned-release build documentation and a two-worktree byte-for-byte APK verification command.
- Real-engine performance regression tests covering QuickJS runtime reuse and tolerant cold-start, sentence and paragraph timing budgets.
Expand Down
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GRADLE := ./gradlew

.PHONY: bootstrap vendor check performance-check stress-check release-reproducibility signed-release test lint assemble instrumented refresh-verification-metadata clean
.PHONY: bootstrap vendor check performance-check stress-check release-reproducibility signed-release sbom test lint assemble instrumented refresh-verification-metadata clean

bootstrap:
./tools/bootstrap-gradle-wrapper.sh
Expand Down Expand Up @@ -41,6 +41,11 @@ signed-release:
./tools/sign-release-apk.sh
./tools/check-apk-permissions.sh app/build/outputs/apk/release/app-release-signed.apk

sbom:
$(GRADLE) --no-daemon cyclonedxBom
node tools/enrich-sbom.mjs
node tools/check-sbom.mjs

test:
$(GRADLE) --no-daemon :core:test :engine-grammalecte:test :spellchecker:test

Expand All @@ -63,7 +68,8 @@ refresh-verification-metadata:
:app:lintDebug \
:app:assembleDebug \
:spellchecker:assembleDebugAndroidTest \
:app:assembleDebugAndroidTest
:app:assembleDebugAndroidTest \
cyclonedxBom

clean:
$(GRADLE) --no-daemon clean
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ See [`docs/release.md`](docs/release.md) for the complete release procedure.

A configured release-signing environment can also build and verify the signed release artifact with `make signed-release`.

Generate and validate the runtime CycloneDX SBOM with `make sbom`.

See [`docs/sbom.md`](docs/sbom.md) for its scope and maintenance rules.

The debug APK is generated under:

```text
Expand Down Expand Up @@ -248,6 +252,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup, project conventi
- [`docs/testing.md`](docs/testing.md)
- [`docs/compatibility.md`](docs/compatibility.md)
- [`docs/release.md`](docs/release.md)
- [`docs/sbom.md`](docs/sbom.md)
- [`docs/adr/`](docs/adr/)

## Name and affiliation
Expand Down
16 changes: 16 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import org.cyclonedx.gradle.CyclonedxDirectTask

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.cyclonedx)
}


allprojects {
tasks.named<CyclonedxDirectTask>("cyclonedxDirectBom") {
includeConfigs =
listOf(
"runtimeClasspath",
"releaseRuntimeClasspath",
)
testConfigs = emptyList()
includeMetadataResolution = true
}
}

subprojects {
Expand Down
108 changes: 108 additions & 0 deletions docs/sbom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Software Bill of Materials

Grammalecte Android generates a machine-readable CycloneDX Software Bill of
Materials for the runtime contents of the Android release.

## Format

The authoritative generated SBOM is:

build/reports/sbom/grammalecte-android.cdx.json

It currently uses CycloneDX specification 1.6.

Generate and validate it with:

make sbom

## Scope

The SBOM is intentionally scoped to components required by the release
runtime.

It includes:

- the Android application and internal Gradle modules;
- QuickJS-KT and its Android runtime;
- Kotlin runtime dependencies;
- kotlinx.coroutines runtime dependencies;
- transitive runtime components selected by Gradle;
- the vendored Grammalecte engine and Graphspell dictionaries.

Test-only dependencies and build tooling are intentionally excluded from the
runtime SBOM.

Examples of excluded components include JUnit, AndroidX Test and the
CycloneDX Gradle plugin itself.

## Gradle dependency inventory

The CycloneDX Gradle plugin generates an aggregate SBOM from the resolved
runtime dependency graphs.

Only runtime configurations relevant to the application release are selected.

Gradle dependency verification remains enabled independently through:

gradle/verification-metadata.xml

## Vendored Grammalecte

Grammalecte is stored directly in the repository rather than resolved as a
Gradle dependency, so it does not naturally appear in the Gradle dependency
graph.

The project therefore enriches the generated CycloneDX document with the
vendored Grammalecte component.

Its version and immutable upstream commit are read from:

tools/grammalecte.env

The resulting dependency relationship explicitly records:

engine-grammalecte
-> Grammalecte

The SBOM also records the upstream VCS reference, GPL-3.0 license metadata,
vendored status and pinned upstream commit.

## Validation

The SBOM validator checks:

- CycloneDX format and specification version;
- presence of the application modules;
- presence of required runtime dependencies;
- absence of known test-only dependencies;
- presence of exactly one Grammalecte component;
- consistency with tools/grammalecte.env;
- the dependency relationship between engine-grammalecte and Grammalecte.

Validation failures cause make sbom to fail.

## Release artifacts

The manual signed-release workflow generates the SBOM from the same source
commit used for the signed APK.

The GitHub Actions release artifact contains:

- the signed APK;
- the signed APK SHA-256 checksum;
- the CycloneDX JSON SBOM;
- the SBOM SHA-256 checksum.

The SBOM does not replace THIRD_PARTY_NOTICES.md or the final third-party
license review. Those remain separate human-reviewed release requirements.

## Maintenance

Whenever a runtime dependency or vendored component changes:

1. update the project dependency or vendored metadata;
2. regenerate dependency verification metadata when required;
3. run make sbom;
4. inspect the generated dependency inventory;
5. update THIRD_PARTY_NOTICES.md when licensing information changes;
6. run the normal project checks.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
agp = "9.4.1"
kotlin = "2.4.20"
ktlint = "14.2.0"
cyclonedx = "3.5.0"
quickjs = "1.0.15"
coroutines = "1.11.0"
junit4 = "4.13.2"
Expand All @@ -26,3 +27,4 @@ android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
cyclonedx = { id = "org.cyclonedx.bom", version.ref = "cyclonedx" }
Loading
Loading