$30 off During Our Annual Pro Sale. View Details »

Kotlin KSP - Intro

Kotlin KSP - Intro

TaeHwan

May 14, 2022
Tweet

More Decks by TaeHwan

Other Decks in Programming

Transcript

  1. Kotlin KSP 활용하기 KSP로 뭘 할까? taehwan

  2. KSP? Kotlin Symbol Processing API • 기존 Java 기반의 kapt

    대용 • kapt 대비 2배 빠른 속도 • 장점 > code generate / 단점 > 리빌드 필요 • Google open source project • Apache-2.0 license 코드를 자동으로 코드를 만들어주긴 하지만 리빌드가 필요. 리빌드를 최소화시키는 구조르 KSP 개발이 필요
  3. KSP 버전 정보 • KSP의 버전은 Kotlin 버전에 밀접하다. •

    Kotlin Version - KSP Version • 현재 기준 1.0.5 최신 버전 적용은 3개를 배포했다. ◦ 1.6.20-1.0.5 ◦ 1.6.21-1.0.5 ◦ 1.7.0-Beta-1.0.5 KSP는 Kotlin 버전에 KSP 버전을 배포하는데, 현재 최신 버전은 Kotlin 1.6.21에 KSP 1.0.5 버전입니다. 사용 시 사용하는 Kotlin 버전 - KSP 버전 매칭이 필요합니다.
  4. KSP 어디서 활용할까? KSP는 어디에서 사용하고 있을까요?

  5. Room | Moshi Room/Moshi에서 사용하고 있고, 아직 변환 전이거나, 변환

    예정인 SDK 목록은 KSP 사이트에서 확인할 수 있습니다.
  6. CPU에게 일을 시키거나 KSP를 활용하는 것은 직접 코딩하는 것이 아닌

    CPU에게 일하도록 시키는 작업입니다.
  7. 사용 예 오픈 소스를 통해 KSP 사용법을 살펴보겠습니다.

  8. KSP - Compose @ProvidedComposeLocal GitHub - android-alatan/LifecycleComponents 포함된 _lifecycle-handler/_compose/provided-compose-local-ksp/ 경로

    위치 정승욱 님이 배포한 android-alatan에 포함되어 있는 compose local ksp를 통해 KSP 활용 방법을 습득해 보겠습니다.
  9. KSP - Compose @ProvidedComposeLocal - 목적 • Android Compose의 CompositionLocalProvider에

    viewModel 등록 • ComposeView 전에 ComposeLocalProvider {}로 시작 • 파라메터로 전달하던 viewModel을 위치와 관계없이 viewModel() 사용 가능 fun View() { val data = viewModel().data ... } 이 모듈의 목적은 Compose의 CompositionLocalProvider에 ViewModel을 등록하고, 위치와 관계없이 viewModel() 함수로 CompositionLocalProvider에 등록된 viewModel을 활용할 수 있도록 만들어주는 코드를 KSP를 활용하여 자동으로 만들어주게 됩니다.
  10. KSP - Compose - 직접 구현한다면? private val LocalComposeSampleViewModel: ProvidableCompositionLocal<ComposeSampleViewModel>

    = compositionLocalOf<ComposeSampleViewModel> { error("LocalComposeSampleViewModel isn't provided") } internal fun viewModelProviderValue(viewModel: ComposeSampleViewModel) = LocalComposeSampleViewModel provides viewModel @Composable internal fun viewModel() = LocalComposeSampleViewModel.current 먼저 직접 구현할 수 있어야 KSP로 자동화 시킬 수 있는데, 이와 같은 형태의 코드를 자동화 시켜줍니다. 여기서는 2개의 파일이 만들어지는데, Composition을 등록하는 부분
  11. KSP - Compose - 직접 구현한다면? 사용하는 위치에 아래 코드를

    등록하고, 사용 CompositionLocalProvider(viewModelProviderValue(viewModel)) {} 또는 만들어 사용 internal fun ComposeLifecycleSampleActivity.ComposeLocalProvider(content: @Composable () -> Unit) { CompositionLocalProvider(viewModelProviderValue(viewModel), content = content) } 이 코드는 View에서 활용할 부분의 코드입니다. 이 코드 호출 시 Composition을 등록하고, 사용할 수 있는 준비까지 모두 마치게 됩니다. 직접 만들어 사용하면 동일한 코드가 수십 개 만들어질 수 있지만, 이를 KSP 활용 시 자동으로 만들 수 있습니다.
  12. KSP - Compose 구현체는 https://github.com/android-alatan/LifecycleComponents/blob/main/_lifecycle- handler/_compose/provided-compose-local-ksp/src/main/java/io/androidalata n/compose/local/ksp/ComposeLocalProcessor.kt 코드 자세한 내용은

    위 링크를 통해 확인할 수 있습니다.
  13. 직접 만들어보자

  14. 작업 순서? 1. KSP 모듈 추가 2. 초기화 코드 작성

    3. Target 할 Annotation 정의 4. 사용할 모듈에 build.gradle 적용하고, Annotation 정의 5. Annotation을 찾고 6. 생성할 코드 정의 7. 예외 처리 이 발표에서 진행하기 위한 작업 순서를 정해보았습니다. 실제 적용과는 차이가 있으니 참고만 하세요
  15. KSP Module 추가 plugins { kotlin("jvm") } dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib:KotlinVersion")

    implementation("com.google.devtools.ksp:symbol-processing-api:KotlinVersion-KSPVers ion") } Releases · google/ksp · GitHub 먼저 KSP 모듈을 추가합니다. KSP 개발할 부분에 이 코드를 추가해 주면 됩니다. KSP 모듈은 JVM 기반의 코드로 작성하면 됩니다.
  16. Square - kotlinpoet도 추가 KSP Generate 쉽게 도와주는 kotlinpoet 추가

    implementation("com.squareup:kotlinpoet:LastVersion") KotlinPoet KSP만을 가지고 실제 적용 코드 작성은 복잡할 수 있습니다. 다행히도 Square에서 Kotlin poet을 배포하고 있고, 이를 활용하면 위 코드와 같이 코드 그대로 출력도 가능하고, 클래스, function, property 형태의 코드 만드는 걸 편하게 활용할 수 있습니다.
  17. 먼저 알아보자 • SymbolProcessorProvider - KSP에서 실행 ◦ KSP의 SymbolProcessor

    객체 생성과 options, codeGenerator, logger 접근이 가능 ◦ options : KSP에 arg 지정 정보를 가져올 수 있다. ◦ codeGenerator : 파일 생성을 위한 generator ◦ logger : KSP 진행 중 메시지, error, exception 등을 출력할 수 있다. • SymbolProcessor - 내부 구현 ◦ process(resolver: Resolver): List<KSAnnotated> : KSP의 시작점 ◦ finish() : KSP의 종료 지점 KSP는 크게 2개의 상속을 받아 구현해야 합니다. SymbolProcessorProvider는 KSP에서 실행을 하기 위해 상속 구현해 줘야 합니다. SymbolProcessor는 저희가 내부 구현을 할 부분입니다. 이 클래스는 process를 구현해 주고, finish 처리해 줄 수 있습니다.
  18. (중요) KSP ProcessorProvider 지정 • resources/META-INF/services 폴더를 생성 • com.google.devtools.ksp.processing.SymbolProcessorProvider

    파일 생성 • SymbolProcessorProvider 상속 받은 class 위치를 알려준다. ◦ ex) tech.thdev.ksp.sample.SampleProcessorProvider KSP 실행을 위해 services 등록을 해줘야 합니다. KSP 모듈의 하위 main 폴더 안에 resources 폴더를 생성하고 위와 같이 파일을 추가해 줍니다. 마지막으로 SymbolProcessorProvider를 상속받는 클래스를 해당 파일에 명시해 주면 됩니다.
  19. Annotation 추가 @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.CLASS) annotation class GenerateAnnotation 작업을 위한 Annotation을

    하나 만들어줘야 합니다. 이 어노테이션을 찾아서 필요한 코드를 작성할 수 있습니다.
  20. Test 가능한 환경 구축 - build.gradle buildscript { dependencies {

    classpath("com.android.tools.build:gradle:7.2.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21") classpath("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.6 .21-1.0.5") } } KSP 개발하기 위한 구조는 만들었으니, 이제 실제 테스트 및 사용이 가능한 환경을 구축해야 합니다. 먼저 root의 build.gradle에 devtools을 추가합니다.
  21. Test 가능한 환경 구축 - 모듈.build.gradle plugins { id("com.android.application") //

    또는 id("com.android.library") id("com.google.devtools.ksp") } android { sourceSets.create("debug") { kotlin.srcDir("build/generated/ksp/debug/kotlin") } sourceSets.create("release") { kotlin.srcDir("build/generated/ksp/release/kotlin") } } dependencies { ksp(project(":ksp-sample")) } 안드로이드 build.gradle.kts에서 Flavor 나눠진 경우 KSP 빌드 폴더를 flavor 별 sourceSets 설정 방법 사용할 모듈에 KSP에서 빌드 한 결과물에 대한 위치를 지정해 주면 되겠습니다. 마지막으로 ksp로 시작하는 import를 처리해 주면 되겠습니다.
  22. Test 가능한 환경 구축 - Annotation 적용 @GenerateAnnotation class SecondActivity

    : AppCompatActivity() 테스트할 activity를 하나 추가하고, 여기에서 GenerateAnnotaiton을 추가해 줍니다.
  23. SymbolProcessor에서 Annotation을 찾아보자 Annotation을 찾고, 이에 대한 로그를 출력해 보겠습니다.

    getSymbolWithAnnotation() 함수를 통해 모듈에 적용한 Annotation을 찾고, 이 클래스의 정보를 받아올 수 있습니다. 클래스에 정의한 함수와 클래스에 정의한 property 정보를 모두 불러올 수 있습니다.
  24. 동작 로그 확인 • w: [ksp] findKSAnnotated SecondActivity • w:

    [ksp] Function onCreate • w: [ksp] Function <init> • w: [ksp] Properties binding Annotation 하나만을 가지고, 몇 가지 출력한 결과는 위와 같습니다.
  25. 자동으로 만들어줄 코드는? 그럼 어떤 코드를 만들어볼까요? showXXXActivity() 함수를 자동으로

    만들어보겠습니다.
  26. 코드 그대로 파일로 출력해 보자 파일이 잘 출력되고, 원하는 코드가

    잘 나오는지를 먼저 확인할 필요성이 있습니다. 먼저 KSP를 활용은 하지만, 코드에 대한 generate는 동적이 아닌 정적으로 만들어보겠습니다.
  27. 코드 그대로 파일로 출력해 보자 여기서는 kotlinpoet을 활용해 파일을 생성하는

    것까지 진행합니다. 먼저 패키지와 새로운 파일 이름을 만들어주고, context를 kotlin extensions 형태로 만들었으니, receiver() 에 context를 추가합니다.
  28. 코드 그대로 파일로 출력해 보자 마지막으로 파일을 출력해 주면 됩니다.

  29. 코드 그대로 파일로 출력해 보자 코드 동작에 문제가 없다면 모듈

    위치의 build/generated/ksp/debug(또는 release) 아래에 지정한 패키지 경로와 파일이 만들어짐을 확인할 수 있습니다. 만약 정상적이라면 오른쪽과 같이 파일까지 모두 출력됩니다.(정렬은 무시하세요)
  30. 문제 1 하지만 이 코드는 문제가 있습니다. 문제를 하나씩 파악해

    보죠
  31. 문제 1 여기서 붉은 부분이 보이시나요? Intent가 import 안 되어있음을

    확인할 수 있습니다.
  32. 해결 - Import 가 없으니 추가한다 file.addImport("android.content", "Intent") 해결 방법은

    간단합니다. 앞에서 작성한 코드에 import 하나 추가하면 됩니다.
  33. 문제 2 2번째 문제를 찾아보겠습니다.

  34. 문제 2 2번째 문제로 이 코드에 있습니다. 샘플로 작성했던 코드를

    그대로 활용했기 때문에 @GenerateAnnotation을 몇 개를 부르던 모든 코드에서 SampleActivity라는 이름의 함수를 만들게 됩니다. 그럼 공용성이 없고, 그냥 만들기만 할 뿐…
  35. 해결 - SampleActivity 전용이므로 수정 필요 다행히 Kotlin poet에서는 string에서

    % 형태의 코드 작성이 가능합니다. 이 위치에 simpleName을 그대로 넣고 코드를 완성할 수 있습니다. 여기서는 %L을 활용해 완전한 형태의 코드 작성을 완료합니다.
  36. 문제 3 마지막 하나만 더 보겠습니다. 문제 3

  37. 문제 3 문제 3은 실제 Activity가 아니더라도 코드를 만들어준다는 것입니다.

    Activity 일 때만 활용되어야 하니 Activity 인지 체크가 필요합니다.
  38. 해결 - filter 현재 클래스의 상속을 파악하고, 상속된 클래스 정보가

    AppCompatActivity 상속받을 경우에 만 동작하도록 만들면 됩니다.
  39. 오류 발생시켜 진행을 막자 앞에는 코드 동작에 초점을 맞췄습니다. 하지만

    하지 않아야 할 경우도 있습니다.
  40. error를 내보자 if (targetClass.not()) { logger.error( "No activity class!!! ${ksClassDeclaration.simpleName.asString()}

    Remove @GenerateAnnotation", ksClassDeclaration ) } KSP logger를 활용해 error를 만들 수 있지만, 마지막의 클래스 정보를 넘겨주지 않을 경우에는 단순히 오류만 노출할 뿐 멈추지는 않습니다. 멈추게 하려면 class를 마지막 정보에 넘겨주면 됩니다.
  41. KSP 사용 TIP 몇 가지 Tip을 알아보겠습니다.

  42. Class 생성자 변수 정보를 알고 싶어요 클래스의 상속은 superclass를 불러올

    수 있었고, 생성자의 primary 생성자 역시 가져올 수 있습니다.
  43. 실제 Class가 위치하는 지 알고 싶어요? 내 모듈에 포함되어 있는

    정보가 실제 접근이 가능한지 파악할 필요가 있습니다. KSName을 지정해 주고, 이와 같은 형태의 코드를 활용하면 KSName이 패키지에 접근할 수 있고, 실제로 존재하는지도 파악할 수 있습니다. 파일이 위치에 없다면 null을 리턴합니다.
  44. Visitor는 언제 쓰나요? • Annotation을 찾을 때 이미 Visitor를 활용하고

    있다. 이미 클래스 내부정보를 알 수 있어 다시 할 필요는 없다. • 많은 예제에 보면 Visitor를 이용하는데, 이는 문서에도 나와있지만 Multiple round processing 에서 활용 많은 코드를 보다 보면 Visitor를 활용하고 있는데, 문서에 나와있지만 Multiple round processing 문서에 설명되어 있습니다. 만약 해당 코드 샘플처럼 validate까지 진행하고, multiple round processing을 활용해야 한다면 샘플을 찾아보시길…
  45. KSP의 전체 구조 전체 구조는 KSP의 파일 구조를 이해하는 데

    도움 됩니다. https://kotlinlang.org/docs/images/ksp-class-diagram.svg
  46. 여기까지

  47. 참고한 자료들 공식 문서 Kotlin Symbol Processing API 소스코드 google/ksp:

    Kotlin Symbol Processing API My first Kotlin Symbol Processing Tool for Android | by SeongUg Steve Jung KSP: Fact or kapt?. Fast and ergonomic annotation… | by David Rawson | ProAndroidDev KSP(Kotlin Symbol Processor) 톺아보기 KAPT보다 2배 더 빠르게, 코틀린을 위한 KSP