주요 편집기에서 AOSP Java 소스 브라우징하기
VS Code, Neovim, Zed 편집기에서 AOSP Java 소스 브라우징을 위한 방법이다.
AOSP 소스는 여러 언어의 소스가 사용된 아주 방대한 소스 트리인데, Android 프레임워크와 관련된 부분들에는 여전히 Java 소스가 많은데, 이 소스들을 분석할 때는 당연히 Java LSP를 사용하는 것이 편리하다.
문제는 AOSP 소스 트리에서 그냥 Java LSP를 켜면 Gradle 빌드가 되어 빌드 시간과 인덱싱 시간이 오래 걸릴 뿐만 아니라, AOSP에 포함된 Gradle 관련 파일들의 변경이 발생하여 Git에서 소스 변경이 발생하는 문제가 발생한다.
이 글에서는 Gradle 등의 빌드를 하지 않으면서 VS Code, Neovim, Zed 편집기에서 AOSP 소스 브라우징 등의 Java LSP 사용을 위하여 내가 찾은 방법을 정리한다.
VS Code
- 먼저 Java LSP를 지원하는 Language Support for Java 익스텐션을 설치한다.
- Workspace (또는 global) 설정에서 아래 예와 같이 Java 관련 설정을 한다. (Gradle/Maven 빌드를 disable 시키고, Java LSP 관련을 설정함)
"java.autobuild.enabled": false, "java.compile.nullAnalysis.mode": "disabled", "java.configuration.updateBuildConfiguration": "disabled", "java.errors.incompleteClasspath.severity": "ignore", "java.format.enabled": false, "java.import.gradle.annotationProcessing.enabled": false, "java.import.gradle.enabled": false, "java.import.gradle.wrapper.enabled": false, "java.import.maven.enabled": false, "java.import.projectSelection": "manual", "java.inlayHints.parameterNames.enabled": "none", "java.jdt.ls.androidSupport.enabled": "auto", "java.project.importOnFirstTimeStartup": "disabled", "java.server.launchMode": "Standard", "java.symbols.includeSourceMethodDeclarations": true, - 검색에서 제외시킬 디렉토리는 Workspace 설정에서 아래 예와 같이 추가할 수 있다.
"java.import.exclusions": [ "**/art/**", "**/bindist/**", "**/bionic/**", "**/bootable/**", "**/build/**", "**/common/**", "**/cts/**", "**/dalvik/**", "**/developers/**", "**/development/**", "**/device/**", "**/hardware/**", "**/kernel/**", "**/lib/**", "**/libnativehelper/**", "**/ndk/**", "**/out/**", "**/pdk/**", "**/platform_testing/**", "**/prebuilts/**", "**/sdk/**", "**/system/**", "**/test/**", "**/toolchain/**", ], - AOSP의 Java 파일을 열면 아래와 같은 팝업이 뜬다. “Import All” 버튼을 누르면 디텍트 된 모든 Gradle/Maven 빌드가 임포트되고, “Let Me Select” 버튼을 누르면 디텍트 된 Gradle/Maven 빌드에서 임의 선택할 수 있다.

여기에서 1개 이상의 항목을 선택하면 Java LSP가 활성화 된다. - Workspace 설정 파일에서 아래 예와 같이 AOSP의 소스 경로를 추가한다. (브라우징하려는 소스에 따라서 수정 필요)
"java.project.sourcePaths": [ "frameworks/base/core/java", "frameworks/base/media/java", "libcore/ojluni/src/main", "libcore/ojluni/src/main/java", "libcore/ojluni/src/main/java/java/lang/annotation", "packages/apps/TV/common/src", "packages/apps/TV/src", ],참고로 VS Code에서
"Java: List All Java Source Paths"명령을 실행시키면 인식된 source path를 확인할 수 있다.
만약에 Java 소스가 없고 jar 파일만 있으면 아래 예와 같이 추가할 수 있다. (이 경우 decompile 하여 class 파일로 보여짐)"java.project.referencedLibraries": [ "out/soong/.intermediates/prebuilts/sdk/current/support/android-support-annotations/android_common/turbine-combined/android-support-annotations.jar" ], - 참고로 Java LSP 캐시를 제거하고 reload 시키려면, 커맨드 팔레트에서
"Java: Clean Java Language Server Workspace"명령을 실행하면 된다.
Neovim
- 먼저
:MasonInstall jdtls명령으로 Java LSP인 jdtls를 설치한다.
참고로 jdtls는 Java 21 이상을 요구하므로, 아직 JDK가 설치되지 않은 상태이면 아래 예와 같이 JDK를 설치한다.$ sudo apt install openjdk-21-jdk - AOSP(Android Open Source Project)에서 Java 소스를 브라우징하기 위하여
~/.config/nvim/lua/plugins/lsp_config.lua파일을 아래와 같이 작성하였다.
간단히 설명하면 AOSP 소스 브라우징을 위하여 프로젝트 root 경로에.project파일과.classpath파일을 만들 것인데, 이 경로를 LSP를 위한 root 경로로 설정하고 (단, 못 찾은 경우에는.git디렉토리가 있는 경로를 찾아서 설정), Gradle이나 Maven의 자동 빌드는 disable 시키고, jdtls LSP가 생성하는 파일들은 소스 트리 대신에 jdtls workspace의 metadata에 생성되게 한다.local function eclipse_root(fname) local root for dir in vim.fs.parents(fname) do if vim.uv.fs_stat(dir .. "/.project") and vim.uv.fs_stat(dir .. "/.classpath") then root = dir end end return root end return { "nvim-lspconfig", opts = function(_, opts) -- Generate .project/.classpath/.settings at workspace metadata instead of source tree vim.env.JDTLS_JVM_ARGS = "-Djava.import.generatesMetadataFilesAtProjectRoot=false" opts.servers.jdtls = { -- Find eclipse root directory (which have .project and .classpath files) root_dir = function(bufnr, on_dir) local fname = vim.api.nvim_buf_get_name(bufnr) if fname == "" then return end fname = vim.fs.normalize(fname) on_dir(eclipse_root_dir(fname) or vim.fs.root(fname, { ".git" })) end, -- Settings before init before_init = function(params, _) params.initializationOptions = vim.tbl_deep_extend("force", params.initializationOptions or {}, { settings = { java = { import = { gradle = { enabled = false }, maven = { enabled = false }, }, }, }, }) end, } end, } - AOSP root 경로에
.project파일을 아래 예와 같이 작성하였다. (filteredResources에 인덱싱이 필요없는 디렉토리를 지정함, 필요에 따라서 수정)<?xml version="1.0" encoding="UTF-8"?> <projectDescription> <name>aosp</name> <natures> <nature>org.eclipse.jdt.core.javanature</nature> </natures> <filteredResources> <filter> <id>1</id> <name></name> <type>14</type> <matcher> <id>org.eclipse.core.resources.regexFilterMatcher</id> <arguments>^art$|^bindist$|^bionic$|^bootable$|^build$|^common$|^cts$|^dalvik$|^developers$|^development$|^device$|^hardware$|^kernel$|^lib$|^libnativehelper$|^ndk$|^out$|^pdk$|^platform_testing$|^prebuilts$|^sdk$|^system$|^test$|^toolchain$</arguments> </matcher> </filter> </filteredResources> </projectDescription> - AOSP root 경로에
.classpath파일을 아래 예와 같이 작성하였다. (브라우징하려는 소스에 따라서 수정 필요)<?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="external/dagger2/java"/> <classpathentry kind="src" path="external/guava/android/guava/src"/> <classpathentry kind="src" path="external/jsr330/src"/> <classpathentry kind="src" path="frameworks/base/core/java"/> <classpathentry kind="src" path="frameworks/base/media/java"/> <classpathentry kind="src" path="libcore/json/src/main/java"/> <classpathentry kind="src" path="libcore/ojluni/src/main/java"/> <classpathentry kind="src" path="packages/apps/TV/common/src"/> <classpathentry kind="src" path="packages/apps/TV/src"/> <classpathentry kind="lib" path="/home/yrpark99/AOSP/prebuilts/sdk/current/public/android.jar"/> <classpathentry kind="lib" path="/home/yrpark99/AOSP/packages/apps/TV/libs/javax.annotation-api-1.2.jar"/> <classpathentry kind="lib" path="/home/yrpark99/AOSP/prebuilts/sdk/current/support/m2repository/com/android/support/support-annotations/28.0.0/support-annotations-28.0.0.jar"/> </classpath> - 이제 Neovim으로 AOSP의 Java 소스를 열면 자동으로 jdtls가 동작하여 인덱싱을 수행하고, 이후 소스 브라우징 등의 LSP 기능이 정상적으로 동작한다. (또한 AOSP 소스 트리에서는 자동 빌드가 되지 않게 설정하였으므로 소스 트리의 수정도 발생하지 않음)
참고로 jdtls의 workspace는~/.cache/nvim/jdtls/workspace/경로에 있으므로, 기존 jdtls의 cache 데이터를 삭제시키려면 아래와 같이 실행한다.$ rm -rf ~/.cache/nvim/jdtls/workspace/ - 참고로 base 경로의
.classpath,.project파일을 내 Git에서만 ignore 시키려면.git/info/exclude파일을 아래와 같이 작성하면 된다./.project /.classpath
Zed
- Zed는 Java 익스텐션을 설치하면 Java 언어가 지원되는데, 내부적으로 Java LSP인 jdtls를 사용하므로, Neovim에서의 설정과 유사하다.
- AOSP root 경로에
.project파일을 아래 예와 같이 작성하였다. (Neovim을 위한 파일과 동일, 필요에 따라서 수정)<?xml version="1.0" encoding="UTF-8"?> <projectDescription> <name>AOSP</name> <natures> <nature>org.eclipse.jdt.core.javanature</nature> </natures> <filteredResources> <filter> <id>1</id> <name></name> <type>14</type> <matcher> <id>org.eclipse.core.resources.regexFilterMatcher</id> <arguments>^art$|^bindist$|^bionic$|^bootable$|^build$|^common$|^cts$|^dalvik$|^developers$|^development$|^device$|^hardware$|^kernel$|^lib$|^libnativehelper$|^ndk$|^out$|^pdk$|^platform_testing$|^prebuilts$|^sdk$|^system$|^test$|^toolchain$</arguments> </matcher> </filter> <filter> <id>1789369426975</id> <name></name> <type>30</type> <matcher> <id>org.eclipse.core.resources.regexFilterMatcher</id> <arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments> </matcher> </filter> </filteredResources> </projectDescription> - AOSP root 경로에
.classpath파일을 아래 예와 같이 작성하였다. (Neovim을 위한 파일과 동일, 브라우징하려는 소스에 따라서 수정 필요)<?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="external/dagger2/java"/> <classpathentry kind="src" path="external/guava/android/guava/src"/> <classpathentry kind="src" path="external/jsr330/src"/> <classpathentry kind="src" path="frameworks/base/core/java"/> <classpathentry kind="src" path="frameworks/base/media/java"/> <classpathentry kind="src" path="libcore/json/src/main/java"/> <classpathentry kind="src" path="libcore/ojluni/src/main/java"/> <classpathentry kind="src" path="packages/apps/TV/common/src"/> <classpathentry kind="src" path="packages/apps/TV/src"/> <classpathentry kind="lib" path="/home/yrpark99/AOSP/prebuilts/sdk/current/public/android.jar"/> <classpathentry kind="lib" path="/home/yrpark99/AOSP/packages/apps/TV/libs/javax.annotation-api-1.2.jar"/> <classpathentry kind="lib" path="/home/yrpark99/AOSP/prebuilts/sdk/current/support/m2repository/com/android/support/support-annotations/28.0.0/support-annotations-28.0.0.jar"/> </classpath> - AOSP root 경로에서
.zed/settings.json파일을 아래와 같이 작성하였다. (즉, /home/yrpark99/AOSP/ 트리에서 root 경로를 제외한 모든 하위 디렉토리들을 Gradle 자동 탐색 대상에서 제외시킴, 결과로 root 경로의.project와.classpath파일만 이용하여 Java 소스 브라우징이 됨){ "lsp": { "jdtls": { "initialization_options": { "settings": { "java": { "autobuild": { "enabled": false }, "import": { "maven": { "enabled": false }, "gradle": { "enabled": false }, "exclusions": [ "/home/yrpark99/AOSP/*", "/home/yrpark99/AOSP/*/**" ] }, "configuration": { "updateBuildConfiguration": "disabled" } } } } } } } - 이제 Zed에서도 AOSP의 Java 소스들이 브라우징을 포함하여 LSP 기능들이 잘 동작한다. 만약에 소스 브라우징이 되지 않으면, Linux 서버에서 아래와 같이 확인시 값이 올바르게
1로 출력되는지 확인해 본다 (즉, root project 1개만 import 되어야 함).$ D=$(ls -d ~/.cache/jdtls-* | head -1) $ ls "$D/.metadata/.plugins/org.eclipse.core.resources/.projects/" | wc -l - 만약에 Zed를 종료했는데도 Linux 서버에서 jdtls 프로세스가 남아있으면, 아래와 같이 강제로 kill 시킬 수 있다.
$ pkill -f org.eclipse.jdt.ls.core - 참고로 Zed의 jdtls 캐시 디렉토리는
~/.cache/밑에 jdtls-XXX 이름으로 구성되므로, jdtls 캐시를 삭제하고 싶으면 해당 디렉토리를 삭제하면 된다.
맺음말
위와 같은 방법을 사용하면 VS Code, Neovim, Zed에서 Android의 Java 소스도 LSP를 이용하여 아주 편리하게 코드 분석 및 코딩 작업을 할 수 있다. 이런 환경 설정이 은근히 까다로워서 모르는 사람이 많아서 이번에 정리해 보았다.