Skip to content

Commit 749118f

Browse files
committed
Refactor migration guide and plugin configuration for StringCare: update migration instructions for version 5.x, including changes to plugin coordinates and Gradle DSL usage. Enhance build scripts with new plugin management and dependency configurations, including detekt and ktlint integration. Improve native library handling and update internal task management for better performance and clarity.
1 parent 0912b10 commit 749118f

38 files changed

Lines changed: 1446 additions & 831 deletions

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-java@v4
16+
with:
17+
distribution: temurin
18+
java-version: 17
19+
20+
- name: Grant execute permission for gradlew
21+
run: chmod +x gradlew
22+
23+
- name: Plugin unit tests
24+
run: ./gradlew :plugin:test :plugin:detekt --no-daemon
25+
26+
- name: Sample app assembleDebug
27+
run: ./gradlew :app:assembleDebug --no-daemon

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
3+
## [Unreleased] — refactor / 6.x groundwork
4+
5+
### Changed
6+
7+
- **Gradle**: `StringCareBuildService` (shared build service) replaces mutable static state on `StringCarePlugin` for paths, temp dir, and variant applicationIds.
8+
- **Dependencies**: Removed Guava and Gson; added `kotlinx-serialization-json` for task JSON list inputs.
9+
- **Execution**: Shell commands use `ProcessBuilder` with a 60s timeout and structured `ExecutionResult` (`Success` / `Failure`).
10+
- **Native host libs**: SHA-256 verification before `System.load`, retries, optional verbose logging tied to `debug` in tasks.
11+
- **XML / scan**: Faster attribute iteration in `parseXML`; `walkTopDown` skips `build/`, `.gradle/`, `.git/`, `node_modules/`; `mapNotNull` for resource/asset discovery; idempotent `StringCareConfiguration.normalize()`.
12+
- **Tooling**: Detekt + baseline, ktlint (non-failing), JaCoCo hook, Develocity build scan terms in root `settings.gradle.kts`.
13+
14+
### Migration notes
15+
16+
- Public plugin id and DSL block name are unchanged (`dev.vyp.stringcare.plugin`, `stringcare { }`).
17+
- If you relied on internal APIs (`StringCarePlugin.absoluteProjectPath`, etc.), migrate to the build service or task inputs only.
18+
19+
See [MIGRATION.md](MIGRATION.md).

MIGRATION.md

Lines changed: 14 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,24 @@
1-
# Migration from StringCare 4.x to 5.0
1+
# Migration guide (5.x → current refactor)
22

3-
## GroupId and artifact IDs
3+
## For consumers of the published plugin
44

5-
- **Old:** `io.github.stringcare` (or `com.stringcare`)
6-
- **New:** `dev.vyp.stringcare`
7-
8-
Update dependencies and plugin coordinates:
9-
10-
| 4.x | 5.0 |
11-
|-----|-----|
12-
| `io.github.stringcare:library` | `dev.vyp.stringcare:library` |
13-
| `com.stringcare` plugin ID | `dev.vyp.stringcare.plugin` |
14-
15-
## Gradle (Kotlin DSL)
16-
17-
**Before (4.x, Groovy):**
18-
19-
```groovy
20-
buildscript {
21-
dependencies {
22-
classpath 'com.stringcare:gradle-plugin:4.x'
23-
}
24-
}
25-
apply plugin: 'com.stringcare'
26-
dependencies {
27-
implementation 'io.github.stringcare:library:4.x'
28-
}
29-
```
30-
31-
**After (5.0, Kotlin DSL):**
5+
No changes are required in `build.gradle.kts` if you only use the public DSL:
326

337
```kotlin
34-
plugins {
35-
id("dev.vyp.stringcare.plugin")
36-
}
37-
dependencies {
38-
implementation("dev.vyp.stringcare:library:5.0.0")
8+
stringcare {
9+
debug = true
10+
// ...
3911
}
4012
```
4113

42-
Resolve the plugin from Maven Central or a local `includeBuild`; see [README](README.md#installation-kotlin-dsl).
14+
## For forks or code that used internal statics
4315

44-
## Plugin configuration
45-
46-
- Extension and task names are unchanged (`stringcare { ... }`, `stringFiles`, `assetsFiles`, `srcFolders`, `debug`, `skip`).
47-
- AGP 8.x and Gradle 8.x are required; the plugin uses the Variant API.
48-
49-
## Library package
50-
51-
- **Old:** `com.stringcare.library`
52-
- **New:** `dev.vyp.stringcare.library`
53-
54-
Update imports in your app:
55-
56-
```kotlin
57-
// Before
58-
import com.stringcare.library.SC
59-
import com.stringcare.library.SCTextView
60-
61-
// After
62-
import dev.vyp.stringcare.library.SC
63-
import dev.vyp.stringcare.library.SCTextView
64-
```
65-
66-
## API compatibility
67-
68-
Public API (e.g. `SC.reveal()`, `SC.obfuscate()`, `SCTextView`, resources usage) is unchanged. Only package and Maven coordinates differ.
16+
| Old | New |
17+
|-----|-----|
18+
| `StringCarePlugin.absoluteProjectPath` | Resolved per build via `StringCareBuildService.absoluteProjectPath()` (internal) |
19+
| `StringCarePlugin.variantMap` | `StringCareBuildService` variant map (internal) |
20+
| `StringCarePlugin.tempFolder` / `resetFolder()` | Service `getOrCreateTempFolder()` / `resetTempFolder()`; tests use `StringCareSession` + `StringCarePlugin.resetFolder()` |
6921

70-
## Build / CI
22+
## Native libraries
7123

72-
This repo uses the JNI native library as a Git submodule. Clone with `git clone --recurse-submodules` or `git submodule update --init --recursive`. In GitHub Actions use `checkout` with `submodules: true`.
24+
When updating bundled `.dylib` / `.so` / `.dll`, refresh SHA-256 entries in `Stark.kt` (`EXPECTED_SHA256`).

SECURITY.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Security
2+
3+
## Native binaries
4+
5+
Host-side JNI libraries shipped in the plugin JAR are verified with SHA-256 before load. If you replace prebuilts, update the expected hashes in `plugin/.../internal/Stark.kt`.
6+
7+
## Signing fingerprints
8+
9+
The plugin derives keys from `signingReport` output. Treat CI logs as sensitive if debug logging prints certificate details.
10+
11+
## Reporting
12+
13+
Report security issues via the repository’s issue tracker or maintainer contact in the published POM.

plugin/build.gradle.kts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
plugins {
22
kotlin("jvm") version "2.0.21"
3+
kotlin("plugin.serialization") version "2.0.21"
34
id("java-gradle-plugin")
45
id("maven-publish")
56
id("signing")
7+
id("io.gitlab.arturbosch.detekt") version "1.23.7"
8+
id("org.jlleitschuh.gradle.ktlint") version "12.1.2"
9+
jacoco
610
}
711

812
gradlePlugin {
@@ -32,17 +36,48 @@ repositories {
3236
dependencies {
3337
implementation(gradleApi())
3438
implementation("com.android.tools.build:gradle:8.7.3")
35-
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.21")
36-
implementation("com.google.guava:guava:33.3.1-jre")
37-
implementation("com.google.code.gson:gson:2.11.0")
39+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
3840
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
3941
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
42+
testImplementation(gradleTestKit())
4043
}
4144

4245
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
4346
kotlinOptions.jvmTarget = "17"
4447
}
4548

49+
jacoco {
50+
toolVersion = "0.8.12"
51+
}
52+
53+
tasks.test {
54+
finalizedBy(tasks.jacocoTestReport)
55+
}
56+
57+
tasks.jacocoTestReport {
58+
dependsOn(tasks.test)
59+
reports {
60+
xml.required.set(true)
61+
html.required.set(true)
62+
}
63+
}
64+
65+
detekt {
66+
buildUponDefaultConfig = true
67+
allRules = false
68+
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
69+
baseline = file("$rootDir/config/detekt/baseline.xml")
70+
parallel = true
71+
}
72+
73+
ktlint {
74+
version.set("1.3.1")
75+
ignoreFailures.set(true)
76+
filter {
77+
exclude("**/build/**")
78+
}
79+
}
80+
4681
publishing {
4782
publications {
4883
create<MavenPublication>("plugin") {
@@ -141,7 +176,7 @@ tasks.register<Sync>("syncDistNativesToPluginJni") {
141176
if (!stringcareJniDist.asFile.exists()) {
142177
throw GradleException(
143178
"stringcare-jni/dist not found. Init submodule: git submodule update --init --recursive " +
144-
"then build natives in stringcare-android-c (see that repo's docs)."
179+
"then build natives in stringcare-android-c (see that repo's docs).",
145180
)
146181
}
147182
}

plugin/config/detekt/baseline.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" ?>
2+
<SmellBaseline>
3+
<ManuallySuppressedIssues></ManuallySuppressedIssues>
4+
<CurrentIssues>
5+
<ID>TooGenericExceptionCaught:Stark.kt$Stark.Companion$e: Throwable</ID>
6+
<ID>UnusedParameter:VariantApi.kt$extension: StringCareExtension</ID>
7+
<ID>UnusedPrivateProperty:PrintUtils.kt$PrintUtils.Companion$private val logger = LoggerFactory.getLogger(StringCarePlugin::class.java)</ID>
8+
<ID>UnusedPrivateProperty:VariantApi.kt$val beforeStrings = tasks.register( "stringcareBeforeMergeResources$variantCapitalized", ObfuscateStringsTask::class.java, ) { it.projectPath = projectPath it.moduleName = moduleName it.variantName = variantName it.applicationId = applicationId it.skip = config.skip it.debug = config.debug it.mockedFingerprint = config.mockedFingerprint it.srcFoldersJson = gson.toJson(config.srcFolders) it.stringFilesJson = gson.toJson(config.stringFiles) it.assetsFilesJson = gson.toJson(config.assetsFiles) }</ID>
9+
</CurrentIssues>
10+
</SmellBaseline>

plugin/config/detekt/detekt.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Detekt baseline for StringCare plugin — strict but pragmatic for Gradle plugin code
2+
build:
3+
# Tighten to 0 after phase7-18 (detekt cleanup)
4+
maxIssues: 999
5+
config:
6+
validation: true
7+
style:
8+
active: true
9+
WildcardImport:
10+
active: false
11+
UtilityClassWithPublicConstructor:
12+
active: false
13+
MaxLineLength:
14+
active: true
15+
maxLineLength: 140
16+
excludes: ['**/test/**']
17+
ReturnCount:
18+
active: true
19+
max: 4
20+
complexity:
21+
LongMethod:
22+
active: true
23+
threshold: 120
24+
LongParameterList:
25+
active: true
26+
functionThreshold: 8
27+
TooManyFunctions:
28+
active: true
29+
thresholdInFiles: 50
30+
thresholdInObjects: 20
31+
CyclomaticComplexMethod:
32+
active: true
33+
threshold: 40
34+
ComplexCondition:
35+
active: true
36+
threshold: 6
37+
NestedBlockDepth:
38+
active: true
39+
threshold: 8
40+
empty-blocks:
41+
active: true
42+
exceptions:
43+
active: true
44+
SwallowedException:
45+
active: false
46+
naming:
47+
active: true
48+
FunctionNaming:
49+
active: true
50+
ignoreAnnotated: ['Composable']
51+
excludes: ['**/PrintUtils.kt', '**/test/**']
52+
TopLevelPropertyNaming:
53+
active: false
54+
potential-bugs:
55+
active: true
56+
performance:
57+
active: true
58+
coroutines:
59+
active: false

plugin/src/main/kotlin/dev/vyp/stringcare/plugin/StringCarePlugin.kt

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,72 @@
11
package dev.vyp.stringcare.plugin
22

33
import dev.vyp.stringcare.plugin.internal.PrintUtils
4+
import dev.vyp.stringcare.plugin.internal.StringCareSession
45
import dev.vyp.stringcare.plugin.internal.absolutePath
56
import dev.vyp.stringcare.plugin.internal.config
6-
import dev.vyp.stringcare.plugin.internal.defaultConfig
77
import dev.vyp.stringcare.plugin.internal.registerTask
88
import dev.vyp.stringcare.plugin.internal.registerVariantObfuscationTasks
9-
import dev.vyp.stringcare.plugin.internal.tempPath
9+
import dev.vyp.stringcare.plugin.services.StringCareBuildService
10+
import org.gradle.api.Action
1011
import org.gradle.api.Plugin
1112
import org.gradle.api.Project
13+
import org.gradle.api.provider.Provider
14+
import org.gradle.api.services.BuildServiceSpec
1215

1316
/**
1417
* StringCare Gradle plugin entry point. Applies string/asset obfuscation for Android application projects.
1518
*/
1619
class StringCarePlugin : Plugin<Project> {
17-
1820
companion object {
21+
/** Test hook: clears the in-memory temp root used by [dev.vyp.stringcare.plugin.internal.tempPath]. */
1922
@JvmStatic
20-
internal lateinit var absoluteProjectPath: String
21-
22-
private var internalTempDir: String? = null
23-
@JvmStatic
24-
var tempFolder: String
25-
get() = internalTempDir ?: tempPath()
26-
set(value) {
27-
internalTempDir = value
28-
}
29-
3023
fun resetFolder() {
31-
internalTempDir = null
24+
StringCareSession.clearTestTempRoot()
3225
}
33-
34-
@JvmStatic
35-
internal var configuration: StringCareConfiguration = defaultConfig()
36-
37-
@JvmStatic
38-
internal var variantMap = mutableMapOf<String, String>()
3926
}
4027

4128
override fun apply(target: Project) {
4229
val extension = target.extensions.create("stringcare", StringCareExtension::class.java)
43-
absoluteProjectPath = target.absolutePath()
30+
val buildService = registerStringCareService(target)
4431

4532
target.plugins.withId("com.android.application") {
4633
val config = target.config(extension)
47-
configuration = config
48-
target.registerVariantObfuscationTasks(extension, config)
34+
buildService.get().setResolvedConfiguration(config)
35+
target.registerVariantObfuscationTasks(config, buildService)
4936
target.afterEvaluate {
50-
if (configuration.debug) {
51-
PrintUtils.print("PATH", absoluteProjectPath)
37+
val resolved = buildService.get().getResolvedConfiguration()
38+
if (resolved.debug) {
39+
PrintUtils.print("PATH", buildService.get().absoluteProjectPath())
5240
}
53-
target.registerTask(configuration)
41+
target.registerTask(resolved, buildService)
5442
}
5543
}
5644

5745
if (!target.plugins.hasPlugin("com.android.application")) {
5846
target.afterEvaluate {
59-
configuration = target.config(extension)
60-
target.registerTask(configuration)
47+
val config = target.config(extension)
48+
buildService.get().setResolvedConfiguration(config)
49+
target.registerTask(config, buildService)
6150
}
6251
}
6352
}
53+
54+
private fun registerStringCareService(target: Project): Provider<StringCareBuildService> =
55+
target.gradle.sharedServices.registerIfAbsent(
56+
"stringCareBuildService",
57+
StringCareBuildService::class.java,
58+
Action<BuildServiceSpec<StringCareBuildService.Params>> { spec ->
59+
spec.parameters.absoluteProjectPath.set(target.provider { target.absolutePath() })
60+
},
61+
)
6462
}
6563

6664
/**
6765
* Internal resolved configuration (module name, applicationId, etc.).
6866
*/
69-
open class StringCareConfiguration(var name: String) {
67+
open class StringCareConfiguration(
68+
var name: String,
69+
) {
7070
var assetsFiles = mutableListOf<String>()
7171
var stringFiles = mutableListOf<String>()
7272
var srcFolders = mutableListOf<String>()

0 commit comments

Comments
 (0)