프로젝트 생성과 세팅, 그리고 구조

guswls·2024년 8월 5일

플러그인 개발

목록 보기
2/7

1. 프로젝트 생성


어떤걸 사용해서 개발해야되지?

  • 보통 인텔리제이 플러그인 개발 관련해서 서칭을 하면 Plugin DevKit을 많이 사용하여 생성한다.

  • 2023.03버전 이후의 IntelliJ에선 해당 플러그인이 내장되어있지 않았기 때문에, 직접
    Settings > Plugins에 가서 다운로드를 받은 후 새 프로젝트를 생성해서 시작해야
    한다.

  • 하지만, 개발을 진행하던 중 쉽지 않은 문제가 발생했다.


테스트 코드 작성 시 알 수 없는 예외 발생

  • 그러나, DevKit을 사용해서 프로젝트를 생성하고 개발을 진행하다보니 문제가 발생했다.

  • 인텔리제이 측에선 개발자들이 쉽게 플러그인을 만들 수 있도록 IntelliJ Platform Plugin SDK를 제공해준다. 그리고 SDK에선 BasePlatformTestCase라는 통합테스트 관련 테스트 프레임워크 역시 지원해주었다.

  • 하지만 해당 프레임워크를 사용해서 테스트를 진행해보니, 아래 오류가 발생했다.

    java.lang.IllegalAccessError: class com.intellij.testFramework.UITestUtil (in unnamed module @0x5387f9e0) cannot access class sun.awt.AWTAutoShutdown (in module java.desktop) because module java.desktop does not export sun.awt to unnamed module @0x5387f9e0
  • 찾아본바로는 'awt클래스에 접근할 수 있는 권한이 없어서 그렇다'는 말이 가장 많았다.

  • 이 글의 상황이 나와 비슷해서 테스트 실행 시 권한 파라미터를 추가하는 쪽으로 해결을 시도했으나 해결하지 못했다.

  • 다른 해결책으로는 JDK의 버전을 낮추는 것도 제시되었지만, 플러그인 SDK에선 JDK 17이상이 강제되었기 때문에 그럴 수 없었다.


공식문서를 읽자

  • 위의 문제로 인해 시작부터 골머리를 썩혔다.

  • 중요한건 저 BasePlatformTestCase뿐만 아니라 비슷한 프레임워크들 모두 동작 하지 않았기 때문에, SDK의 테스트 도구를 사용하지 못한다고 봐도 무방했다.

  • 그러던 도중, 공식문서의 이 글을 통해 IntelliJ Platform Plugin Template의 존재를 알아챘다.

  • "깃헙에서 Use this template을 눌러라. 생성하고 나면 깃헙 액션 워크플로우가 초기 세팅으로 맞춰준다"와 같이 동작 하나하나가 매우 친절하게 나와있었다.

  • 비영어권인 내가 봐도 눈에 확 들어오는 친절한 설명이었고, 이 일을 기점으로 공식문서를 애용하기 시작했다. 정확히는 공식문서, 오픈소스, Claude긴 하다.

  • 아무튼, 이전의 경험과 가이드라인 덕분에 IntelliJ Platform Plugin Template을 성공적으로 적용할 수 있었다.

  • 그리고, 이전에 안됐던 Test코드 프레임워크 역시 성공적으로 돌아갔다.


IntelliJ Platform Plugin Template를 쓰자

  • 프로젝트 초반부터 고생했다보니 말이 장황해졌다.

  • 결론은 "IntelliJ 플러그인을 만들 땐 intellij-platform-plugin-template을 사용하자" 이다.

  • 앞서 느꼈던 사용성과 안정성 이외에도 해당 템플릿은 Gradle 기반의 여러 부가 기능들을 제공해준다.

  • 실제로 공식 문서에서도 Devkit에서 해당 시스템으로의 마이그레이션을 권장하고 있다.

  • 처음 봤을 땐 Kover, Qodana, CHANGELOG 등등해서 좀 복잡해보이긴 한다.

  • 하지만, 개인 프로젝트에서 이런 체계를 갖고 개발을 해볼 경험이 생각보다 적기 때문에 이번 기회에 한번씩 써보는 것도 좋은 기회라고 생각한다. (꼭 다 쓸 필요도 없기도 하고)



2. 설정


Build.gradle.kts와 gradle.properies

  • "템플릿"이라는 단어에 맞게 세팅 역시 잘 되어있었기 때문에, 개발 초기 단계에선 크게 이 부분을 건들 필요가 없었다.

  • 플러그인 개발을 처음 한다면 Build.gradle.kts는 웬만해선 안건드는걸 추천한다.

  • 아마 plugin을 만드려는 분들은 build.gradle파일을 숱하게 많이 보셨을 것이기 때문에 이해하는데 크게는 문제가 없으리라 생각이 든다.

  • 그리고, Build.gradle.kts에선 몇몇 정보들을 gradle.properties에서 임포트해서 값을 가져온다.

  • properties에선 몇몇 중요한 정보들이 있는데 그중 하나가 pluginSinceBuild이다.

  • pluginSinceBuild플러그인을 빌드하기 위한 인텔리제이의 하한 버전을 의미하는데, 구현과정에서 이 버전을 따로 업을 해줘야하는 경우가 생긴다.

  • 또한, pluginSinceBuild의 버전이 현재 개발중인 인텔리제이의 버전보다 높다면 당연하게도 빌드가 안되기 때문에 이 부분도 조심해야 한다.

  • 정리하자면 다음과 같다.

    • 초기 상태의 Build.gradle.kts는 마치 Spring initializer로 구워진 Build.gradle과 똑같다. 필요한 경우에만 수정하자.

    • Build.gradle.kts에선 외부에서 property값을 가져오는 경우가 있다.
      이중에선 버전 관련 정보도 있는데 충돌이 나지 않도록 잘 관리하자.

plugin.xml

  • plugin.xml의 경우, 플러그인을 개발함에 있어 반드시 알고 넘어가야하는 존재이다.

  • 이부분은 웬만해선 공식문서와 오픈소스를 잘 활용해서 이해하고 넘어가는 것을 추천한다. (중요한 내용이 만큼 공식문서도 상당히 길다. ChatGPT 활용 추천.)

  • plugin.xml에는 플러그인의 메타데이터(마켓에 노출되는 정보)부터 플러그인에 사용되는 extention, action, listener, window 등등이 정의된다.

  • 예를 들어 action에선 사용자가 액션을 수행했을 때 실행할 플러그인의 동작을 맵핑할 수 있고, extentions를 통해 Spring과 유사하게 구현체(서비스)를 등록할수도 있다. 자세한 설명은 생략한다.

  • 이처럼 프로젝트의 구조가 담기는 곳이 plugin.xml이다.

  • 정리하자면 다음과 같다.

    • plugin.xml에서는 플러그인의 메타데이터action, listener, extensions 등등을 등록할 수 있다. 즉, 플러그인의 구조가 이 파일에 담긴다.

    • 일반적으로 xml에 등록된 action이 어떤 기능에 대한 진입점이 되는 경우가 많다. 플러그인의 동작 방식에 대해선 미리 설계해놓자.


3. 플러그인의 동작방식


플러그인의 간단한 동작 방식

  • 위 그림은 플러그인의 동작과정을 간략하게 표현한 것이다.

  • 이것을 글로 풀어쓰면 다음과 같다.

    1. 사용자가 버튼을 누르거나 아이콘을 누르는 등의 동작을 일으킨다.

    2. 플러그인에선 plugin.xml에 등록된 Action 클래스의 actionPerformed()를 호출한다.

    3. actionPerformed에선 현재 시점의 컨텍스트AnActionEvent라는 객체에서 가지고 있다.

    4. AnActionEvent객체를 통해 상황을 파악하고 필요한 동작을 수행하거나, Service에 동작을 위임한다.

    5. 결과물이 생성되면 사용자가 알 수 있게 결과를 표시한다.
  • 이 외에도 Listener를 통해 시스템의 이벤트에 대해서도 반응하도록 만들 수 있고, Extension을 통해 플러그인의 동작을 확장할수도 있다.

  • 특정 상황에선 Notification을 통해 사용자에게 피드백을 줄수도 있고, 눈으로 보이는 결과가 아닌 어떠한 동작이 수행될수도 있다.


그렇다면 우리는 어떻게 개발을 해야되는거지?

  • 이처럼 요청 -> 응답(반환 or 수행)이라는 소프트웨어의 플로우가 플러그인에도 적용이 된다고 볼 수 있다.

  • 필요한 구성들(action, listener 등)은 plugin.xml에 등록을 한 후, 특정 클래스를 상속받아 구현하면 된다.

  • 우리는 Action을 진입점으로 생각하고, "사용자가 아이콘을 눌렀을 때 커밋메시지를 생성시키는 시스템의 책임"을 여러 작은 Service객체에 책임을 분배하면서 하나하나 개발해가면 된다.

    예제1: plugin.xml에 action 등록

    <actions>
        <action id="com.github.guswlsdl0121.messagemaker.actions.GenerateCommitMessageAction"
                class="com.github.guswlsdl0121.messagemaker.actions.GenerateCommitMessageAction"
                text="Generate Commit Message"
                description="Generate a commit message based on selected changes"
                icon="/icons/commitIcon.svg">
            <add-to-group group-id="Vcs.MessageActionGroup" anchor="first"/>
        </action>
    </actions>

    예제2. DumbAwareAction을 오버라이드하여 Action 구현

    //DumbAwareAction이라는 Action클래스를 상속받아 동작 정의
    class GenerateCommitMessageAction : DumbAwareAction() {
        override fun actionPerformed(e: AnActionEvent) {
            e.project?.let { project ->
                //직접 수행하거나
                doSomething(e)
                //누군가에게 넘기거나
                project.service<CommitMessageService>().generate(e)
            }
        }
    }
    


4. 마치며


  • 이 글을 쓰는 시점은 플러그인 개발을 시작한지 일주일이 다 되가는 시점이며, 개발 역시 순조롭게 되어가고 있는 중이다.

  • 개발이 너무 잘되고, 재밌다보니 벨로그 적는 것을 까먹었었다.

  • 이번 글은 본격적인 코드레벨로 들어가기 전의 튜토리얼 느낌으로 작성하였으며, 당연하겠지만 실제로는 이것보다 훨씬 복잡하며 SDK 역시 복잡하다.

  • 개발에 들어가기전에 공식문서의 Plugin Structure 파트와 만들고자하는 플러그인과 비슷한 오픈소스 코드를 찾아서 쭉 읽어보면 훨씬 도움이 될 것이라고 생각한다.

  • 다음 글 부터는 실제 코드를 다루며 내가 했던 고민들을 녹여보고자 한다.

profile
안녕하세요

0개의 댓글