프로그래밍/Android

[안드로이드] Push할 때마다 Auto-Formatting적용하기 : GitHook으로 코딩 스타일 맞추기

Lou Park 2023. 2. 4. 16:07

작업환경을 타지 않는 방법의 필요성

코딩 스타일의 일관성은 가독성, 협업에서 중요한 포인트이지만 개인적인 코딩 습관들, 빡빡한 일정들 등으로 인해 지키기 쉽지 않다. IDE에서 저장시에 코드 포맷팅을 해주는 기능들이 있기는 하지만, 작업환경마다 세팅해줘야한다는 단점이 있다. 따라서 어떤 환경에서 작업하더라도 코딩 스타일을 지켜줄 수 있는 방법이 필요했다.

(이 원문을 응용하여 작성된 글입니다.)

 

GitHook + ktlint

그것은 바로 GitHook을 이용하는 방법이다. git init을 하면 .git/hook 폴더에는 여러 GitHook 샘플들이 생긴다. 이 경로 안에 내가 Hook을 발생시키기 원하는 시점에 대한 파일을 “확장자 없이” 넣어주면 간단히 Hook 세팅을 할 수 있다. 샘플 파일들은 .sample 확장자가 있기에 실행이 안되는 것이다.

예로들면 Commit전에 할 일을 정의한다면 실행가능한 pre-commit이라는 파일을 여기에 넣어두면된다. 각각이 어떤 상황인지에 대한 설명은 Cutomizing GIt - Git Hooks를 참조하면 된다.

1 -rwxr-xr-x 1 ASUS 197121  478 11월 15 16:50 applypatch-msg.sample
4 -rwxr-xr-x 1 ASUS 197121  896 11월 15 16:50 commit-msg.sample
8 -rwxr-xr-x 1 ASUS 197121 4726 11월 15 16:50 fsmonitor-watchman.sample
1 -rwxr-xr-x 1 ASUS 197121  189 11월 15 16:50 post-update.sample
1 -rwxr-xr-x 1 ASUS 197121  424 11월 15 16:50 pre-applypatch.sample
4 -rwxr-xr-x 1 ASUS 197121 1643 11월 15 16:50 pre-commit.sample
1 -rwxr-xr-x 1 ASUS 197121  416 11월 15 16:50 pre-merge-commit.sample
4 -rwxr-xr-x 1 ASUS 197121 1492 11월 15 16:50 prepare-commit-msg.sample
4 -rwxr-xr-x 1 ASUS 197121 1374 11월 15 16:50 pre-push.sample
8 -rwxr-xr-x 1 ASUS 197121 4898 11월 15 16:50 pre-rebase.sample
1 -rwxr-xr-x 1 ASUS 197121  544 11월 15 16:50 pre-receive.sample
4 -rwxr-xr-x 1 ASUS 197121 2783 11월 15 16:50 push-to-checkout.sample
4 -rwxr-xr-x 1 ASUS 197121 3650 11월 15 16:50 update.sample

나는 이 중에서 pre-push를 이용하려한다. Commit은 많을땐 하루에도 몇 번씩 할 때가 있는데, gradle task 수행 속도가 느려서 Commit 마다 Formatting할 경우 생산성이 저하될 가능성이 있기 때문이다.

 

멀티모듈을 위한 Ktlint 세팅

KtlintKotlin 표준 코딩 스타일 가이드에 따라 코드 스타일을 검사하고 Formatting도 시켜주는 툴인데, 우리는 Ktlint Gradle Plugin인 https://github.com/jlleitschuh/ktlint-gradle 를사용해 볼 것이다. 모놀리틱 프로젝트에서 세팅하는 방법은 해당 Github 페이지에 존재하므로 참고하면되고, 나는 프로젝트 전체에 적용하는 방법을 소개한다.

// project/build.gradle

// [kts/Kotlin DSL]
plugins {
    id("org.jlleitschuh.gradle.ktlint") version "11.1.0"
}
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.jlleitschuh.gradle:ktlint-gradle:11.1.0")
  }
}
allprojects {
    apply(plugin = "org.jlleitschuh.gradle.ktlint")
}

// [Groovy]
plugins {
    id "org.jlleitschuh.gradle.ktlint" version "11.1.0"
}
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jlleitschuh.gradle.ktlint:11.1.0"
    }
}
allprojects {
    apply plugin: "org.jlleitschuh.gradle.ktlint"
}

이대로 빌드하고 나면 formatting/ktlintForamtverification/ktlintCheck Gradle Task가 생겨난 것을 볼 수 있을 것이다. 그러면 성공!

ktlintFormat 확인!

이제 명령행에 다음과 같이 입력하면 Formatting이 이루어진다.

$ ./gradlew ktlintFormat

 

GitHook 세팅

이를 pre-push마다 이루어지게 하기 위해서 프로젝트 Root 아래 pre-push 파일을 만들어주고, 내용은 다음과 같이 입력한다. 각 라인에 대한 설명은 주석으로 대체하겠다.

$ touch pre-push
#!/bin/sh
echo "Running git pre-push hook"

./gradlew ktlintFormat --daemon

// $? = ".gradlew ktlintFormat --daemon"에 대한 return 값
STATUS=$?

// 문제없이 끝났다면 exit 0, 아니면 1
// -ne: Not equals
[ $STATUS -ne 0 ] && exit 1
exit 0

 

이제 안드로이드 프로젝트를 Build하면, 이 pre-push 파일을 .git/hooks에 복사하여 넣어주는 Task를 만들어 준다. .git은 Git에 포함되지 않기때문에 어느 환경에서나 돌아가기 위해 해당 작업을 해주는 것이다.

tasks.register<Delete>("deletePreviousGitHook") {
    val prePush = "${rootProject.rootDir}/.git/hooks/pre-push"
    if (file(prePush).exists()) {
        delete(prePush)
    }
}

tasks.register<Copy>("installGitHook") {
    dependsOn("deletePreviousGitHook")
    from("${rootProject.rootDir}/pre-push")
    into("${rootProject.rootDir}/.git/hooks")
    eachFile {
        fileMode = 777
    }

    tasks.getByPath(":app:preBuild").dependsOn("installGitHook")
}

 

작성을 끝낸 후 Build를 수행하게 되면 .git/hooks/pre-push를 확인할 수 있을 것이다. 이제 코드를 아무렇게나 작성한 후 Push하면 Running git pre-push hook 메세지가 출력되며 자동으로 Formatting이 이루어지게 된다.

$ ls .git/hooks -al | grep "pre-push"
-rwxr-xr-x 1 ASUS 197121  131 Feb  4 15:51 pre-push
-rwxr-xr-x 1 ASUS 197121 1374 Aug 15 15:54 pre-push.sample