From 3a49bfc829e3864da197ecb2ac80b0c8ebe7adbe Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:31:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20CI=20=ED=8C=8C=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EB=B0=8F=20detekt=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=8F=84=EA=B5=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GitHub Actions CI 워크플로우 추가 (develop PR / develop·main push / 수동 실행 트리거) - detekt, Android lint, unit test, release 빌드를 매트릭스로 병렬 검사 - detekt + detekt-formatting(ktlint 규칙) 전 모듈 적용 - detekt 설정 추가: Compose @Composable 네이밍 예외, formatting 규칙 활성화 - detekt SARIF를 병합해 GitHub 코드 스캐닝에 업로드 (업로드 실패는 게이트 비차단) --- .github/workflows/ci.yml | 73 +++++++++++++++++++ build.gradle.kts | 45 ++++++++++++ config/detekt/detekt.yml | 50 +++++++++++++ .../com/gamss/android/core/AppResult.kt | 2 + gradle/libs.versions.toml | 4 + 5 files changed, 174 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 config/detekt/detekt.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..528e2c5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,73 @@ +name: CI + +on: + pull_request: + branches: [develop] + push: + branches: [develop, main] + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + security-events: write # upload detekt SARIF to code scanning + +jobs: + check: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: detekt + task: detekt + - name: lint + task: lint + - name: test + task: test + - name: build + task: assembleRelease + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + validate-wrappers: true + + - name: Run ${{ matrix.task }} + run: ./gradlew ${{ matrix.task }} --stacktrace + + - name: Upload detekt SARIF to code scanning + # Runs even when detekt fails so findings still surface in the PR. + # continue-on-error: an upload failure (e.g. read-only token on a fork + # PR, or a transient code-scanning API error) must never fail the gate. + if: ${{ always() && matrix.name == 'detekt' }} + continue-on-error: true + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: build/reports/detekt/merged.sarif + category: detekt + + - name: Upload reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }}-reports + path: | + **/build/reports/** + **/build/test-results/** + if-no-files-found: ignore + retention-days: 7 diff --git a/build.gradle.kts b/build.gradle.kts index e32e2f4..bcec462 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,49 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.hilt) apply false + alias(libs.plugins.detekt) +} + +// The type-safe `libs` accessor is not available inside `subprojects {}`, +// so capture the dependency provider here in the root script scope. +val detektFormatting = libs.detekt.formatting + +// Merge every module's detekt SARIF into one file so CI can upload a single +// report to GitHub code scanning. +val detektReportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) { + output.set(rootProject.layout.buildDirectory.file("reports/detekt/merged.sarif")) +} + +subprojects { + apply(plugin = "io.gitlab.arturbosch.detekt") + + detekt { + buildUponDefaultConfig = true + config.setFrom(rootProject.files("config/detekt/detekt.yml")) + autoCorrect = false + } + + dependencies { + detektPlugins(detektFormatting) + } + + tasks.withType().configureEach { + jvmTarget = "17" + reports { + html.required.set(true) + sarif.required.set(true) + xml.required.set(false) + txt.required.set(false) + md.required.set(false) + } + // Feed this task's SARIF into the merged report even when detekt fails, + // so findings still reach code scanning. + finalizedBy(detektReportMerge) + } + detektReportMerge.configure { + input.from(tasks.withType().map { it.sarifReportFile }) + } + tasks.withType().configureEach { + jvmTarget = "17" + } } diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 0000000..65794d3 --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,50 @@ +build: + maxIssues: 0 + excludeCorrectable: false + +config: + validation: true + warningsAsErrors: false + +# ktlint wrapper rules provided by the detekt-formatting plugin. +# See https://detekt.dev/docs/rules/formatting +formatting: + active: true + android: true + autoCorrect: false + Indentation: + active: true + indentSize: 4 + MaximumLineLength: + active: true + maxLineLength: 120 + FinalNewline: + active: true + NoWildcardImports: + active: true + NoTrailingSpaces: + active: true + NoUnusedImports: + active: true + +naming: + FunctionNaming: + active: true + # Jetpack Compose @Composable functions use PascalCase by convention. + ignoreAnnotated: ['Composable'] + +complexity: + LongMethod: + active: true + threshold: 80 + TooManyFunctions: + active: true + thresholdInFiles: 30 + thresholdInClasses: 30 + +style: + MaxLineLength: + active: true + maxLineLength: 120 + MagicNumber: + active: false diff --git a/core/src/main/kotlin/com/gamss/android/core/AppResult.kt b/core/src/main/kotlin/com/gamss/android/core/AppResult.kt index 15d8bd1..e4eedbd 100644 --- a/core/src/main/kotlin/com/gamss/android/core/AppResult.kt +++ b/core/src/main/kotlin/com/gamss/android/core/AppResult.kt @@ -5,6 +5,8 @@ sealed interface AppResult { data class Failure(val throwable: Throwable) : AppResult companion object { + // A Result wrapper intentionally captures any failure from the block. + @Suppress("TooGenericExceptionCaught") inline fun of(block: () -> T): AppResult = try { Success(block()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e07b797..e7c0352 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ androidx-lifecycle = "2.9.4" compose-bom = "2026.04.01" navigation3 = "1.1.0-rc01" junit = "4.13.2" +detekt = "1.23.8" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } @@ -31,6 +32,8 @@ hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hil junit = { module = "junit:junit", version.ref = "junit" } +detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } + [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } @@ -39,3 +42,4 @@ composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "k kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } From 734385a00b777420b56e8bd8e343b293435474a3 Mon Sep 17 00:00:00 2001 From: soyeonLee <109227292+soyeonLee126@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:47:51 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20CI=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=ED=95=98?= =?UTF-8?q?=EB=93=9C=EC=BD=94=EB=94=A9=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 설명 주석 제거 (build.gradle.kts, ci.yml, detekt.yml, AppResult.kt) - detekt 플러그인 id를 버전 카탈로그에서 단일 소싱 - 미사용 DetektCreateBaselineTask 설정 제거 (jvmTarget 중복 해소) - detekt.yml에서 중복된 style.MaxLineLength 규칙 제거 --- .github/workflows/ci.yml | 5 +---- build.gradle.kts | 12 ++---------- config/detekt/detekt.yml | 6 ------ .../main/kotlin/com/gamss/android/core/AppResult.kt | 1 - 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 528e2c5..cd25f02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ concurrency: permissions: contents: read - security-events: write # upload detekt SARIF to code scanning + security-events: write jobs: check: @@ -51,9 +51,6 @@ jobs: run: ./gradlew ${{ matrix.task }} --stacktrace - name: Upload detekt SARIF to code scanning - # Runs even when detekt fails so findings still surface in the PR. - # continue-on-error: an upload failure (e.g. read-only token on a fork - # PR, or a transient code-scanning API error) must never fail the gate. if: ${{ always() && matrix.name == 'detekt' }} continue-on-error: true uses: github/codeql-action/upload-sarif@v3 diff --git a/build.gradle.kts b/build.gradle.kts index bcec462..3e4ffb6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,18 +8,15 @@ plugins { alias(libs.plugins.detekt) } -// The type-safe `libs` accessor is not available inside `subprojects {}`, -// so capture the dependency provider here in the root script scope. +val detektPluginId = libs.plugins.detekt.get().pluginId val detektFormatting = libs.detekt.formatting -// Merge every module's detekt SARIF into one file so CI can upload a single -// report to GitHub code scanning. val detektReportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) { output.set(rootProject.layout.buildDirectory.file("reports/detekt/merged.sarif")) } subprojects { - apply(plugin = "io.gitlab.arturbosch.detekt") + apply(plugin = detektPluginId) detekt { buildUponDefaultConfig = true @@ -40,14 +37,9 @@ subprojects { txt.required.set(false) md.required.set(false) } - // Feed this task's SARIF into the merged report even when detekt fails, - // so findings still reach code scanning. finalizedBy(detektReportMerge) } detektReportMerge.configure { input.from(tasks.withType().map { it.sarifReportFile }) } - tasks.withType().configureEach { - jvmTarget = "17" - } } diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 65794d3..86478e2 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -6,8 +6,6 @@ config: validation: true warningsAsErrors: false -# ktlint wrapper rules provided by the detekt-formatting plugin. -# See https://detekt.dev/docs/rules/formatting formatting: active: true android: true @@ -30,7 +28,6 @@ formatting: naming: FunctionNaming: active: true - # Jetpack Compose @Composable functions use PascalCase by convention. ignoreAnnotated: ['Composable'] complexity: @@ -43,8 +40,5 @@ complexity: thresholdInClasses: 30 style: - MaxLineLength: - active: true - maxLineLength: 120 MagicNumber: active: false diff --git a/core/src/main/kotlin/com/gamss/android/core/AppResult.kt b/core/src/main/kotlin/com/gamss/android/core/AppResult.kt index e4eedbd..5269fd1 100644 --- a/core/src/main/kotlin/com/gamss/android/core/AppResult.kt +++ b/core/src/main/kotlin/com/gamss/android/core/AppResult.kt @@ -5,7 +5,6 @@ sealed interface AppResult { data class Failure(val throwable: Throwable) : AppResult companion object { - // A Result wrapper intentionally captures any failure from the block. @Suppress("TooGenericExceptionCaught") inline fun of(block: () -> T): AppResult = try {