Skip to content

Commit 39f0a5c

Browse files
committed
Refactor plugin native library handling: update documentation for clarity on JNI integration and automatic packaging in the plugin JAR. Enhance build.gradle.kts to streamline native library synchronization and improve task dependencies. Add link to verify obfuscation in README.md.
1 parent 8aa5f90 commit 39f0a5c

8 files changed

Lines changed: 120 additions & 19 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ For **Groovy** use `buildscript` / `apply plugin: 'dev.vyp.stringcare.plugin'` a
7979

8080
**Build / CI:** This project uses the JNI native library as a Git submodule (`stringcare-jni`, e.g. stringcare-android-c). Clone with submodules: `git clone --recurse-submodules ...` or run `git submodule update --init --recursive` after clone. CI workflows must use `checkout` with `submodules: true`.
8181

82-
**Plugin host natives:** After building prebuilts in the submodule (`stringcare-jni/dist/{macos,linux,windows}/`), copy them into the plugin with **`./gradlew :plugin:syncPluginNativeLibraries`**. macOS ships a universal `libsignKey.dylib`; Linux and Windows ship x64 and arm64 variants (`*.so` / `*.dll`).
82+
**Plugin host natives:** With submodule `stringcare-jni` and `dist/` built, the plugin JAR picks up **`dist/{macos,linux,windows}/`** automatically on each **`preparePluginNativeLibraries`** (runs before `processResources`). Use **`./gradlew :plugin:syncPluginNativeLibraries`** only if you want to commit those binaries under `plugin/.../jni/`. macOS: universal `libsignKey.dylib`; Linux/Windows: x64 + arm64 `*.so` / `*.dll`.
8383

8484
**Publishing (release workflow):** See [CONTRIBUTING.md](CONTRIBUTING.md) for required secrets: `NEXUS_USERNAME`, `NEXUS_PASSWORD`, `GPG_KEY_ID`, `GPG_PASSPHRASE`, `PAT`. When dispatching the release workflow, set **Publish Maven** to `true` to run the publish job (publishes both `dev.vyp.stringcare:library` and `dev.vyp.stringcare:plugin`).
8585

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ StringCare Android provides **compile-time obfuscation** for strings and assets
1515
- [Architecture](architecture.md) — Mono-repo layout, library vs plugin, JNI, and Variant API.
1616
- [Contributing](contributing.md) — Release workflow secrets and local publish steps.
1717
- [Troubleshooting](troubleshooting.md) — Submodule, signing, plugin not found, publish job, JNI/NDK.
18+
- [Verify obfuscation](verify-obfuscation.md) — Comandos `gradlew` para comprobar que strings/assets se ofuscan (nativas del host, `syncPluginNativeLibraries`).
1819

1920
For the main project README and badges, see the [root README](../README.md).

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The **plugin** module:
2323

2424
- Implements the Gradle plugin entry point **`StringCarePlugin`** (implements `Plugin<Project>`), creates the **`stringcare`** extension (`StringCareExtension`) and registers tasks.
2525
- Uses the **AGP 8.7 Variant API**: it does **not** use `BuildListener`. It uses `project.plugins.withId("com.android.application")` and then `project.extensions.getByType(AndroidComponentsExtension::class.java)` and **`onVariants`** to register per-variant tasks (e.g. `stringcareBeforeMergeResourcesDebug`, `stringcareAfterMergeResourcesDebug`, and the same for Assets). Each variant gets before/after merge tasks for resources and for assets; the “before” tasks obfuscate, the “after” tasks restore originals from backup.
26-
- Contains **JNI** (`.dylib`, `.dll`, `.so`) in `src/main/kotlin/.../internal/jni` for the **host** (Gradle on macOS, Windows, or Linux). Prebuilts are built in **stringcare-jni** under `dist/{macos,linux,windows}/` and copied with `:plugin:syncPluginNativeLibraries`. macOS uses a universal `libsignKey.dylib`; Linux and Windows ship x64 and arm64 binaries. Packaged via `processResources`; separate from the app’s **sc-native-lib** in the library module.
26+
- Contains **JNI** (`.dylib`, `.dll`, `.so`) for the **host** (Gradle on macOS, Windows, or Linux). Prebuilts come from **stringcare-jni** `dist/{macos,linux,windows}/`; **`preparePluginNativeLibraries`** (before `processResources`) copies them into `build/generated/stringcare-plugin-natives/` and they are packaged in the JAR. Optional checked-in copies live under `internal/jni/`. macOS: universal `libsignKey.dylib`; Linux/Windows: x64 + arm64. Separate from the app’s **sc-native-lib** in the library module.
2727

2828
## Flow
2929

docs/build-and-ci.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ To run instrumented tests (requires an emulator or device):
3838
- **plugin** — Gradle plugin (JVM); included as a **composite build** via `includeBuild("plugin")` in the root `settings.gradle.kts`, not as `include(":plugin")`. So the plugin is built from the `plugin/` directory and applied to the app by ID `dev.vyp.stringcare.plugin`.
3939
- **stringcare-jni** — Git submodule containing the native C++ code used by the library and the **plugin host** prebuilts. The library’s `CMakeLists.txt` points to `../stringcare-jni/lib` for the Android JNI source. Plugin host binaries are produced under **`stringcare-jni/dist/`** (`macos/`, `linux/`, `windows/`).
4040

41-
### Syncing plugin host natives into the plugin JAR
41+
### Plugin host natives in the JAR
4242

43-
After building natives in the submodule (see that repo’s build output), run from the stringcare-android root:
43+
After building natives in the submodule (`stringcare-jni/dist/{macos,linux,windows}/`), **every** `:plugin:jar` / `:plugin:processResources` runs **`preparePluginNativeLibraries`**, which copies `dist/` into `plugin/build/generated/stringcare-plugin-natives/` and packages those files into the plugin JAR. No manual step is required for local builds as long as the submodule is present and `dist/` is populated.
44+
45+
Optional — copy prebuilts into Git-tracked `jni/` (for publishing without submodule on CI):
4446

4547
```bash
4648
./gradlew :plugin:syncPluginNativeLibraries
4749
```
4850

49-
This copies `dist/macos/*.dylib`, `dist/linux/*.so`, and `dist/windows/*.dll` into `plugin/src/main/kotlin/dev/vyp/stringcare/plugin/internal/jni/` so they are packaged with the plugin.
50-
5151
## CI
5252

5353
For **GitHub Actions** (or any CI), always checkout the repo **with submodules** so that the JNI code is available:

docs/troubleshooting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ See [Build and CI](build-and-ci.md).
6060

6161
- The **library** requires **minSdk 21** and a valid NDK/CMake setup. The **stringcare-jni** submodule must be present (see “Submodule not loaded” above).
6262
- **ABI filters:** The library builds for the default ABIs (e.g. armeabi-v7a, arm64-v8a, x86, x86_64). If you restrict ABIs in your app, ensure the library is compatible.
63-
- The **plugin** ships JNI for the **host** (Gradle runs on macOS, Windows, or Linux). Prebuilts are produced in **stringcare-jni** (stringcare-android-c): `dist/macos/libsignKey.dylib` (universal x86_64+arm64), `dist/linux/libsignKey.so` / `libsignKey-arm64.so`, `dist/windows/libsignKey.dll` / `libsignKey-arm64.dll`. After building there, sync into this repo with **`./gradlew :plugin:syncPluginNativeLibraries`** (requires submodule at `stringcare-jni/`). If the native library for your OS/arch is missing, set **`skip = true`** in the `stringcare` block or run the sync task. See [Build and CI](build-and-ci.md) and [Configuration](configuration.md).
63+
- The **plugin** ships JNI for the **host** (Gradle runs on macOS, Windows, or Linux). Prebuilts live in **stringcare-jni** (`dist/macos`, `dist/linux`, `dist/windows`). The plugin build **automatically** packs `dist/` into the JAR via **`preparePluginNativeLibraries`** (as long as `stringcare-jni/dist` exists after submodule init + native build). Optional: **`./gradlew :plugin:syncPluginNativeLibraries`** copies `dist/` into `plugin/.../jni/` for Git. If your OS/arch binary is still missing from `dist/`, set **`skip = true`** or complete the native build. See [Build and CI](build-and-ci.md) and [Configuration](configuration.md).

docs/verify-obfuscation.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Verificar ofuscación con Gradle
2+
3+
La ofuscación de strings y assets en el **módulo `app`** depende de que el plugin cargue la librería nativa del **host** (el mismo OS y arquitectura que ejecuta Gradle). Si no carga, verás en consola:
4+
5+
```text
6+
Skipping <variant> (native library not available for this architecture)
7+
```
8+
9+
## 1. Requisitos
10+
11+
1. Submódulo **`stringcare-jni`** inicializado y precompilados generados en `stringcare-jni/dist/` (tras el build en stringcare-android-c).
12+
2. **Automático:** al compilar el plugin (`:plugin:jar` o cualquier build que lo use), la tarea **`preparePluginNativeLibraries`** copia `stringcare-jni/dist/{macos,linux,windows}/` a `build/generated/stringcare-plugin-natives/` y **`processResources`** las empaqueta en el JAR. No hace falta `syncPluginNativeLibraries` salvo que quieras **commitear** las binarias en `src/.../jni/`.
13+
3. Si **no** existe `stringcare-jni/dist/`, se usan los ficheros ya presentes en `plugin/.../internal/jni/` (fallback).
14+
4. En el JAR del plugin deben estar, según tu máquina:
15+
- **macOS:** `libsignKey.dylib` (universal o la variante correcta).
16+
- **Linux:** `libsignKey.so` y/o `libsignKey-arm64.so`.
17+
- **Windows:** `libsignKey.dll` y/o `libsignKey-arm64.dll`.
18+
19+
Tras actualizar `dist/` en el submódulo, basta con **`./gradlew :plugin:jar`** o **`./gradlew :app:assemble...`** para volver a empaquetar las nativas.
20+
21+
## 2. Tareas útiles
22+
23+
Listar tareas del plugin en el app:
24+
25+
```bash
26+
./gradlew :app:tasks --all | grep -i stringcare
27+
```
28+
29+
Tareas típicas (por variante; ejemplo **prodDebug**):
30+
31+
| Tarea | Rol |
32+
|-------|-----|
33+
| `stringcareBeforeMergeResourcesProdDebug` | Ofusca `strings.xml` antes del merge de recursos |
34+
| `stringcareAfterMergeResourcesProdDebug` | Restaura strings desde backup temporal |
35+
| `stringcareBeforeMergeAssetsProdDebug` | Ofusca assets (p. ej. `*.json`) |
36+
| `stringcareAfterMergeAssetsProdDebug` | Restaura assets |
37+
| `stringcarePreview` | Vista previa / diagnóstico |
38+
| `stringcareTestObfuscate` | Prueba de ofuscación (tests del plugin) |
39+
40+
## 3. Comprobar que no se salta la ofuscación
41+
42+
Con salida detallada:
43+
44+
```bash
45+
./gradlew :app:stringcareBeforeMergeResourcesProdDebug --rerun-tasks --info 2>&1 | tee stringcare-verify.log
46+
```
47+
48+
**Esperado si la nativa carga y hay huella de firma:** mensajes del plugin con variante y clave SHA1, backup de recursos, etc. **No** debe aparecer la línea de *Skipping … native library*.
49+
50+
Comprobar también assets:
51+
52+
```bash
53+
./gradlew :app:stringcareBeforeMergeAssetsProdDebug --rerun-tasks --info 2>&1 | tee -a stringcare-verify.log
54+
```
55+
56+
## 4. Build completa
57+
58+
```bash
59+
./gradlew :app:clean :app:assembleProdDebug --rerun-tasks --info 2>&1 | tee stringcare-assemble.log
60+
```
61+
62+
Durante el merge, los recursos en disco pueden estar ofuscados de forma temporal; al final **`stringcareAfterMerge*`** restaura los fuentes del módulo. La comprobación fiable es el **log** de las tareas `BeforeMerge` (y que exista huella vía `signingReport` o configuración del plugin), no solo el `values.xml` final empaquetado.
63+
64+
## 5. Huella de firma (SHA1)
65+
66+
Si no hay `mockedFingerprint` en el bloque `stringcare { }`, el plugin usa `./gradlew signingReport` para la variante. Sin SHA1 válido, puede no ofuscar aunque la nativa cargue. Para depurar:
67+
68+
```bash
69+
./gradlew :app:signingReport
70+
```
71+
72+
---
73+
74+
**Resumen:** Si ves *Skipping … native library*, comprueba que exista `stringcare-jni/dist/` con las tres plataformas (o al menos la del host), ejecuta **`./gradlew :plugin:clean :plugin:jar`** y repite. Si el submódulo no está inicializado, haz `git submodule update --init --recursive` o usa `syncPluginNativeLibraries` + commit en `jni/` como respaldo.

plugin/build.gradle.kts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,50 @@ if (project.hasProperty("signing.gnupg.keyName")) {
9494
}
9595
}
9696

97+
/**
98+
* Prebuilt natives from stringcare-android-c (submodule `stringcare-jni`).
99+
* NOTE: With `includeBuild("plugin")`, this project's `rootProject` is the plugin dir, not stringcare-android —
100+
* use a path relative to this project (`../stringcare-jni/dist`).
101+
*/
102+
val stringcareJniDist = layout.projectDirectory.dir("../stringcare-jni/dist")
103+
val pluginJniSource = layout.projectDirectory.dir("src/main/kotlin/dev/vyp/stringcare/plugin/internal/jni")
104+
val pluginNativesGenerated = layout.buildDirectory.dir("generated/stringcare-plugin-natives")
105+
106+
/**
107+
* Packs all host natives into the plugin JAR on every `processResources` / `jar`.
108+
* - If `../stringcare-jni/dist/{macos,linux,windows}/` exists (after building in stringcare-android-c), copies from there.
109+
* - Otherwise falls back to checked-in files under `internal/jni/`.
110+
*/
111+
tasks.register<Sync>("preparePluginNativeLibraries") {
112+
into(pluginNativesGenerated)
113+
if (stringcareJniDist.asFile.exists()) {
114+
from(stringcareJniDist.dir("macos")) { include("*.dylib") }
115+
from(stringcareJniDist.dir("linux")) { include("*.so") }
116+
from(stringcareJniDist.dir("windows")) { include("*.dll") }
117+
} else {
118+
from(pluginJniSource) {
119+
include("*.dylib", "*.dll", "*.so")
120+
}
121+
}
122+
}
123+
97124
tasks.processResources {
98125
duplicatesStrategy = DuplicatesStrategy.INCLUDE
99-
from("src/main/kotlin/dev/vyp/stringcare/plugin/internal/jni") {
126+
dependsOn("preparePluginNativeLibraries")
127+
from(pluginNativesGenerated) {
100128
include("*.dylib", "*.dll", "*.so")
101129
}
102130
}
103131

104132
/**
105-
* Copy prebuilt plugin natives from the stringcare-jni submodule (stringcare-android-c dist/).
106-
* Run after building natives there: ./gradlew :plugin:syncPluginNativeLibraries
133+
* Optional: copy dist natives into `src/.../jni/` for committing prebuilts to Git (e.g. Maven publish without submodule on CI).
107134
*/
108135
tasks.register<Copy>("syncPluginNativeLibraries") {
109-
val dist = rootProject.layout.projectDirectory.dir("stringcare-jni/dist")
110-
val jni = layout.projectDirectory.dir("src/main/kotlin/dev/vyp/stringcare/plugin/internal/jni")
136+
val dist = stringcareJniDist
137+
into(pluginJniSource)
111138
from(dist.dir("macos")) { include("*.dylib") }
112139
from(dist.dir("linux")) { include("*.so") }
113140
from(dist.dir("windows")) { include("*.dll") }
114-
into(jni)
115141
doFirst {
116142
if (!dist.asFile.exists()) {
117143
throw GradleException(

plugin/src/main/kotlin/dev/vyp/stringcare/plugin/internal/Stark.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ open class Stark {
6868

6969
ZipFile(zipFile.absolutePath).use { zip ->
7070
zip.entries().asSequence().forEach { entry ->
71+
if (entry.isDirectory) return@forEach
72+
val matches = entry.name == fileName || entry.name.endsWith("/$fileName")
73+
if (!matches) return@forEach
7174
zip.getInputStream(entry).use { input ->
72-
if (entry.name == fileName) {
73-
74-
lib = File("${StringCarePlugin.tempFolder}${File.separator}${entry.name}").apply {
75-
this.outputStream().use { output ->
76-
input.copyTo(output)
77-
}
75+
lib = File("${StringCarePlugin.tempFolder}${File.separator}$fileName").apply {
76+
this.outputStream().use { output ->
77+
input.copyTo(output)
7878
}
7979
}
8080
}

0 commit comments

Comments
 (0)