Compose Desktop Shared Library 포함하여 패키징하기

aosamesan·2022년 12월 14일
0

오늘의 삽질은 Compose Desktop 패키징 할 때 Shared Library를 추가해보는 것입니다.

사실 OpenCV가 재밌어서 최근에 40시간동안 안자고 삽질 + 업무까지 했는데
먼가 데스크탑 프로그램으로 만들고 싶어서 처음에는 C# MAUI를 쓰려고 했으나...
C# OpenCV Wrapper로 유명한 OpenCVSharp이 M1 맥에서 안된다는 것을 깨닫고 Multiplatform Compose로 급선회했다.

저번에 어디까지 했드라...
여튼 OpenCV 자바 빌드하고 System.loadLibrary(Core.NATIVE_LIBRARY_NAME)를 할 때 에러가나면 System.setProperty("java.library.path", "~~~~~~")를 먼저 해야한다고 했는데, 이걸 gradle이나 IntelliJ에서 실행할 때 java.library.path를 설정해서 실행하고, 실제 코드에서는 loadLibrary만 하도록 수정해봅시다.

저번에 OpenCV 컴파일을 했을 때 디렉터리로 가보면 libopencv_javaXXX.dylib 라는 공유 라이브러리가 jar파일과 같이 있는데, 이걸 복사해서 프로젝트 디렉터리에 libs라는 디렉터리를 만들고 거기에 넣는다.

build.gradle.kts 수정

이제 build.gradle.kts 의 디펜던시를 추가할 때 다음과 같이 한다.

implementation(files("libs/opencv-XXX.jar"))

IntelliJ 테스트를 실행할 때는 gradle의 test를 이용하는 것으로 보인다.
실제로는 :cleanJvmTest :jvmTest --tests "테스트 경로"를 실행시키므로 gradle 설정에 다음과 같이 추가해놓으면 java.library.path를 알아서 테스트때마다 넣어준다.

tasks.withType<Test> {
	useJUnitPlatform()
    jvmArgs("-Djava.library.path=./libs")
}

gradle을 이용하여 실행할 때에 (run 등) 위와 같이 넣어주기 위해 다음을 추가한다.

tasks.withType<JavaExec> {
	jvmArgs("-Djava.library.path=./libs")
}

다음은 macOS에서 dmg로 패키징할 때 lib에 추가하기 위한 것인데... 이게 좀 오래걸렸다.
일단 packageDmg나 packageReleaseDmg를 실행하면 다음과 같은 Task들이 실행되는데

DMG로 패키징하기 전에 dylib파일을 적절한 위치에 복사를 하고 패키징하도록 만들어야한다.
그럼 언제, 어느 경로에 해야하나?

위에 실행된 Task들을 살펴보면, 맨 마지막에 packageDmg를 하는 것을 볼 수 있다. 이 작업을 하기 직전에 하면 된다. 물론 나중에 Compose Multiplatform이 업데이트가되어서 실행되는 작업이 바뀌면 또 수정해야하지만...
DMG를 패키징할때는 일단 빌드를 해놓고 빌드된 것들을 가지고 DMG를 만들기 때문에 요 사이에 dylib를 복사하는 작업을 한다.
이제 어디에 복사하는 지 알아봅시다.

대충 빌드해놓고 빌드된 .app 내부를 보면 다음과 같은데

~~~.app/Contents/app 아래에 놓으면 되고, 이제 프로젝트로 돌아와서 build디렉터리를 보면,

요런 식이다. 저기에 복사해두면 된다.
일단 compose-opencv-app.app은 gradle 설정 파일의

compose.desktop {
	application {
    	mainClass = "MainKt
        nativeDistributions {
        	targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            pakcageName = "compose-opencv-app"
            packageVersion = "1.0.0"
        }
    }
}

요런식으로 되어있는 부분에서 packageName 부분이다. 이 부분을 따로 변수로 빼놓는다.

val myAppPackageName = "compose-opencv-app"

compose.desktop {
	// 중략
    packageName = myAppPackageName
    // 후략
}

그리고 위 build 디렉터리 내 binaries/main-release 의 main-release 부분의 경우 packageReleaseDmg를 실행하면 main-class가 되고, packageDmg를 실행하면 그냥 main이 된다.

이제 알건 다 알았으니 package(Release)Dmg를 실행하기 전에 파일을 복사하도록 합시다.

이게 개 삽질을 했는데...
일단 package(Release)Dmg는 따로 타입으로 정의되어있는 작업이 아니다. 또, 이름으로 바로 가져올 수 없으므로 실행하다가 추가되는 작업이다. ㅅㅂ
그러므로 좀 더러운데 내가 찾은 방법은 다음이다.

tasks.whenTaskAdded {
	if (Regex("^package(Release)?Dmg$").matches(name)) {
    	doFirst {
        	copy {
            	val mainName = if (name.contains("release", true)) {
                	"main-release"
                } else {
                	"main"
                }
                val source = layout.projectDirectory.dir("libs")
                val target = layout.buildDirectory.dir("compose/binaries/$mainName/app/$myAppPackageName.app/Contents/app")
                from (source)
                include ("*.dylib") // libs/*.dylib만
                into (target)
            }
        }
    }
}

이렇게 하면 위 app파일 깠을 때 app에 dylib가 포함되게 되고, 실행해도 잘 실행된다.

main을 바로 실행했을 때 java.library.path 설정


위와 같이 VM 옵션에 -Djava.library.path=./libs 를 추가한다.

일단은 macOS 기준으로 했는데 윈도도 비슷하게 하고 타겟 플랫폼에 따라 분기하면 된다.
시간나면 우분투로도 테스트 해봐야지...

profile
재미로 개발 하는 사람

0개의 댓글