Unverified Commit 907d7fa0 authored by Thomas Vos's avatar Thomas Vos Committed by GitHub

Add linuxArm64 target (#1051)

This PR adds support for the linuxArm64 target. It can be cross compiled
from a Linux x64 host.

The `multistrap` package is used for downloading some required arm64
dependencies.

# Testing

GitHub run: https://github.com/Thomas-Vos/skiko/actions/runs/15261348943

Also using it in a compose app.
parent b4f463b9
...@@ -164,7 +164,10 @@ jobs: ...@@ -164,7 +164,10 @@ jobs:
sudo apt-get install gcc-9 g++-9 -y sudo apt-get install gcc-9 g++-9 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9
sudo update-alternatives --config gcc sudo update-alternatives --config gcc
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip xvfb -y sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip multistrap xvfb -y
sudo apt-get install gcc-9-aarch64-linux-gnu g++-9-aarch64-linux-gnu -y
sudo update-alternatives --install /usr/bin/aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-gnu-gcc-9 60 --slave /usr/bin/aarch64-linux-gnu-g++ aarch64-linux-gnu-g++ /usr/bin/aarch64-linux-gnu-g++-9
sudo update-alternatives --config aarch64-linux-gnu-gcc
sudo Xvfb :0 -screen 0 1280x720x24 & sudo Xvfb :0 -screen 0 1280x720x24 &
- shell: bash - shell: bash
...@@ -173,6 +176,12 @@ jobs: ...@@ -173,6 +176,12 @@ jobs:
export DISPLAY=:0 export DISPLAY=:0
./gradlew --no-daemon --stacktrace --info -Pskiko.native.enabled=true -Pkotlin.native.cacheKind.linuxX64=none -Pskiko.test.onci=true :skiko:linuxX64Test ./gradlew --no-daemon --stacktrace --info -Pskiko.native.enabled=true -Pkotlin.native.cacheKind.linuxX64=none -Pskiko.test.onci=true :skiko:linuxX64Test
# Linux arm64 tests are executed on a separate runner
- shell: bash
name: 'Compile Linux arm64 tests'
run: |
./gradlew --no-daemon --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:linkDebugTestLinuxArm64
- shell: bash - shell: bash
name: 'Compile and run AWT tests' name: 'Compile and run AWT tests'
run: | run: |
...@@ -197,6 +206,79 @@ jobs: ...@@ -197,6 +206,79 @@ jobs:
path: ./skiko/build/reports/tests path: ./skiko/build/reports/tests
retention-days: 5 retention-days: 5
- uses: actions/upload-artifact@v4
name: 'Save linuxArm64 test binary as artifact'
with:
name: test-binary-linuxArm64
path: ./skiko/build/bin/linuxArm64
if-no-files-found: error
retention-days: 5
linux-arm:
needs: linux
runs-on: ubuntu-22.04-arm
steps:
- uses: actions/checkout@v3
name: 'Check out code'
- uses: actions/setup-java@v3
name: 'Set up JDK 11'
with:
distribution: 'adopt'
java-version: '11'
cache: 'gradle'
- uses: actions/download-artifact@v4
with:
name: test-binary-linuxArm64
path: ./skiko/build/bin/linuxArm64
- shell: bash
name: 'Set up Linux build environment'
run: |
# TODO: simplify installation of compilers.
sudo apt-get update -y
sudo apt-get install build-essential software-properties-common -y
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt-get update -y
sudo apt-get install build-essential software-properties-common -y
sudo apt-get update
sudo apt-get install clang-11 -y
sudo apt-get remove g++ -y
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-11 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-11 100
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip xvfb -y
sudo Xvfb :0 -screen 0 1280x720x24 &
- shell: bash
name: 'Run Linux arm64 tests'
working-directory: ./skiko
run: |
export DISPLAY=:0
chmod +x ./build/bin/linuxArm64/debugTest/test.kexe
./build/bin/linuxArm64/debugTest/test.kexe
- shell: bash
name: 'Compile and run AWT tests'
run: |
export DISPLAY=:0
./gradlew --no-daemon --stacktrace --info -Pskiko.test.onci=true :skiko:awtTest
timeout-minutes: 25
- shell: bash
name: 'Publish to Maven Local, check AWT sample'
run: |
./gradlew --no-daemon --stacktrace --info :skiko:publishToMavenLocal
./gradlew --no-daemon --stacktrace --info :SkiaAwtSample:installDist
- uses: actions/upload-artifact@v4
name: 'Save test results as artifact'
if: always()
with:
name: test-reports-linux-arm
path: ./skiko/build/reports/tests
retention-days: 5
windows: windows:
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
......
...@@ -37,7 +37,10 @@ jobs: ...@@ -37,7 +37,10 @@ jobs:
sudo apt-get install gcc-9 g++-9 -y sudo apt-get install gcc-9 g++-9 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9
sudo update-alternatives --config gcc sudo update-alternatives --config gcc
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip xvfb -y sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip multistrap xvfb -y
sudo apt-get install gcc-9-aarch64-linux-gnu g++-9-aarch64-linux-gnu -y
sudo update-alternatives --install /usr/bin/aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-gnu-gcc-9 60 --slave /usr/bin/aarch64-linux-gnu-g++ aarch64-linux-gnu-g++ /usr/bin/aarch64-linux-gnu-g++-9
sudo update-alternatives --config aarch64-linux-gnu-gcc
sudo Xvfb :0 -screen 0 1280x720x24 & sudo Xvfb :0 -screen 0 1280x720x24 &
- name: 'Build Dokka documentation' - name: 'Build Dokka documentation'
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* `macOs` Install Xcode Command Line Tools * `macOs` Install Xcode Command Line Tools
* `Linux` Install these tools: * `Linux` Install these tools:
``` ```
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip libx11-dev sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip multistrap libx11-dev
``` ```
* `Windows` * `Windows`
1. Download [Visual Studio Build Tools 2019](https://learn.microsoft.com/en-us/visualstudio/releases/2019/history) (search "BuildTools" on the page). 1. Download [Visual Studio Build Tools 2019](https://learn.microsoft.com/en-us/visualstudio/releases/2019/history) (search "BuildTools" on the page).
......
...@@ -142,6 +142,7 @@ kotlin { ...@@ -142,6 +142,7 @@ kotlin {
} }
if (supportNativeLinux) { if (supportNativeLinux) {
skikoProjectContext.configureNativeTarget(OS.Linux, Arch.X64, linuxX64()) skikoProjectContext.configureNativeTarget(OS.Linux, Arch.X64, linuxX64())
skikoProjectContext.configureNativeTarget(OS.Linux, Arch.Arm64, linuxArm64())
} }
if (supportNativeIosArm64) { if (supportNativeIosArm64) {
skikoProjectContext.configureNativeTarget(OS.IOS, Arch.Arm64, iosArm64()) skikoProjectContext.configureNativeTarget(OS.IOS, Arch.Arm64, iosArm64())
...@@ -292,6 +293,12 @@ kotlin { ...@@ -292,6 +293,12 @@ kotlin {
val linuxX64Test by getting { val linuxX64Test by getting {
dependsOn(linuxTest) dependsOn(linuxTest)
} }
val linuxArm64Main by getting {
dependsOn(linuxMain)
}
val linuxArm64Test by getting {
dependsOn(linuxTest)
}
} }
if (supportAnyNativeIos || supportNativeMac) { if (supportAnyNativeIos || supportNativeMac) {
val darwinMain by creating { val darwinMain by creating {
......
...@@ -35,11 +35,16 @@ val OS.isCompatibleWithHost: Boolean ...@@ -35,11 +35,16 @@ val OS.isCompatibleWithHost: Boolean
OS.Android -> true OS.Android -> true
} }
fun compilerForTarget(os: OS, arch: Arch): String = fun compilerForTarget(os: OS, arch: Arch, isJvm: Boolean = false): String =
when (os) { when (os) {
// TODO: Use clang++ for all Linux targets
OS.Linux -> when (arch) { OS.Linux -> when (arch) {
Arch.X64 -> "g++" Arch.X64 -> "g++"
Arch.Arm64 -> "clang++" Arch.Arm64 -> if (isJvm) {
"clang++"
} else {
if (hostArch == Arch.Arm64) "g++" else "aarch64-linux-gnu-g++"
}
Arch.Wasm -> "Unexpected combination: $os & $arch" Arch.Wasm -> "Unexpected combination: $os & $arch"
} }
OS.Android -> "clang++" OS.Android -> "clang++"
...@@ -48,8 +53,8 @@ fun compilerForTarget(os: OS, arch: Arch): String = ...@@ -48,8 +53,8 @@ fun compilerForTarget(os: OS, arch: Arch): String =
OS.Wasm -> "emcc" OS.Wasm -> "emcc"
} }
fun linkerForTarget(os: OS, arch: Arch): String = fun linkerForTarget(os: OS, arch: Arch, isJvm: Boolean = false): String =
if (os.isWindows) "lld-link.exe" else compilerForTarget(os, arch) if (os.isWindows) "lld-link.exe" else compilerForTarget(os, arch, isJvm)
val OS.dynamicLibExt: String val OS.dynamicLibExt: String
get() = when (this) { get() = when (this) {
......
...@@ -66,7 +66,7 @@ fun SkikoProjectContext.createCompileJvmBindingsTask( ...@@ -66,7 +66,7 @@ fun SkikoProjectContext.createCompileJvmBindingsTask(
includeHeadersNonRecursive(projectDir.resolve("src/jvmMain/cpp/include")) includeHeadersNonRecursive(projectDir.resolve("src/jvmMain/cpp/include"))
includeHeadersNonRecursive(projectDir.resolve("src/commonMain/cpp/common/include")) includeHeadersNonRecursive(projectDir.resolve("src/commonMain/cpp/common/include"))
compiler.set(compilerForTarget(targetOs, targetArch)) compiler.set(compilerForTarget(targetOs, targetArch, isJvm = true))
val osFlags: Array<String> val osFlags: Array<String>
when (targetOs) { when (targetOs) {
...@@ -237,7 +237,7 @@ fun SkikoProjectContext.createLinkJvmBindings( ...@@ -237,7 +237,7 @@ fun SkikoProjectContext.createLinkJvmBindings(
buildSuffix.set("jvm") buildSuffix.set("jvm")
buildTargetArch.set(targetArch) buildTargetArch.set(targetArch)
buildVariant.set(buildType) buildVariant.set(buildType)
linker.set(linkerForTarget(targetOs, targetArch)) linker.set(linkerForTarget(targetOs, targetArch, isJvm = true))
when (targetOs) { when (targetOs) {
OS.MacOS -> { OS.MacOS -> {
......
...@@ -7,6 +7,7 @@ import SkiaBuildType ...@@ -7,6 +7,7 @@ import SkiaBuildType
import SkikoProjectContext import SkikoProjectContext
import WriteCInteropDefFile import WriteCInteropDefFile
import compilerForTarget import compilerForTarget
import hostArch
import isCompatibleWithHost import isCompatibleWithHost
import joinToTitleCamelCase import joinToTitleCamelCase
import listOfFrameworks import listOfFrameworks
...@@ -51,13 +52,21 @@ fun SkikoProjectContext.compileNativeBridgesTask( ...@@ -51,13 +52,21 @@ fun SkikoProjectContext.compileNativeBridgesTask(
): TaskProvider<CompileSkikoCppTask> = with (this.project) { ): TaskProvider<CompileSkikoCppTask> = with (this.project) {
val skiaNativeDir = registerOrGetSkiaDirProvider(os, arch, isUikitSim = isUikitSim) val skiaNativeDir = registerOrGetSkiaDirProvider(os, arch, isUikitSim = isUikitSim)
val setupMultistrapTask = if (os == OS.Linux && arch == Arch.Arm64 && hostArch != Arch.Arm64) {
setupMultistrapTask(os, arch, isUikitSim = isUikitSim)
} else {
null
}
val actionName = "compileNativeBridges".withSuffix(isUikitSim = isUikitSim) val actionName = "compileNativeBridges".withSuffix(isUikitSim = isUikitSim)
return project.registerSkikoTask<CompileSkikoCppTask>(actionName, os, arch) { return project.registerSkikoTask<CompileSkikoCppTask>(actionName, os, arch) {
dependsOn(skiaNativeDir) dependsOn(skiaNativeDir)
val unpackedSkia = skiaNativeDir.get() val unpackedSkia = skiaNativeDir.get()
compiler.set(compilerForTarget(os, arch)) setupMultistrapTask?.let { dependsOn(it) }
compiler.set(compilerForTarget(os, arch, isJvm = false))
buildTargetOS.set(os) buildTargetOS.set(os)
if (isUikitSim) { if (isUikitSim) {
buildSuffix.set("sim") buildSuffix.set("sim")
...@@ -126,8 +135,9 @@ fun SkikoProjectContext.compileNativeBridgesTask( ...@@ -126,8 +135,9 @@ fun SkikoProjectContext.compileNativeBridgesTask(
)) ))
} }
OS.Linux -> { OS.Linux -> {
flags.set(listOf( flags.set(listOfNotNull(
*buildType.clangFlags, *buildType.clangFlags,
"-fPIC",
"-fno-rtti", "-fno-rtti",
"-fno-exceptions", "-fno-exceptions",
"-fvisibility=hidden", "-fvisibility=hidden",
...@@ -239,8 +249,9 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -239,8 +249,9 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
configureCinterop("uikit", os, arch, target, targetString, tvosFrameworks) configureCinterop("uikit", os, arch, target, targetString, tvosFrameworks)
mutableListOfLinkerOptions(tvosFrameworks) mutableListOfLinkerOptions(tvosFrameworks)
} }
OS.Linux -> mutableListOfLinkerOptions( OS.Linux -> {
"-L/usr/lib/x86_64-linux-gnu", val options = mutableListOf(
"-L/usr/lib/${if (arch == Arch.Arm64) "aarch64" else "x86_64"}-linux-gnu",
"-lfontconfig", "-lfontconfig",
"-lGL", "-lGL",
// TODO: an ugly hack, Linux linker searches only unresolved symbols. // TODO: an ugly hack, Linux linker searches only unresolved symbols.
...@@ -250,6 +261,12 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -250,6 +261,12 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
"$skiaBinDir/libskunicode_icu.a", "$skiaBinDir/libskunicode_icu.a",
"$skiaBinDir/libskia.a" "$skiaBinDir/libskia.a"
) )
if (arch == Arch.Arm64 && hostArch != Arch.Arm64) {
options.add(0, "-L$buildDir/multistrap-arm64/usr/lib")
options.add(1, "-L$buildDir/multistrap-arm64/usr/lib/aarch64-linux-gnu")
}
mutableListOfLinkerOptions(options)
}
else -> mutableListOf() else -> mutableListOf()
} }
if (skiko.includeTestHelpers) { if (skiko.includeTestHelpers) {
...@@ -308,6 +325,24 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -308,6 +325,24 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
} }
} }
/**
* This installs dependencies needed for cross compiling Skiko on Linux.
* https://youtrack.jetbrains.com/issue/KT-36871
*/
fun SkikoProjectContext.setupMultistrapTask(
os: OS, arch: Arch, isUikitSim: Boolean
): TaskProvider<Exec> = with (this.project) {
require(os == OS.Linux)
require(arch == Arch.Arm64)
val actionName = "setupMultistrap".withSuffix(isUikitSim = isUikitSim)
return project.registerSkikoTask<Exec>(actionName, os, arch) {
workingDir(projectDir)
commandLine("multistrap", "-f", "multistrap-config-${arch.id}")
}
}
fun KotlinMultiplatformExtension.configureIOSTestsWithMetal(project: Project) { fun KotlinMultiplatformExtension.configureIOSTestsWithMetal(project: Project) {
val metalTestTargets = listOf("iosX64", "iosSimulatorArm64") val metalTestTargets = listOf("iosX64", "iosSimulatorArm64")
metalTestTargets.forEach { target: String -> metalTestTargets.forEach { target: String ->
......
...@@ -41,7 +41,7 @@ fun SkikoProjectContext.createWasmLinkTasks(): LinkWasmTasks = with(this.project ...@@ -41,7 +41,7 @@ fun SkikoProjectContext.createWasmLinkTasks(): LinkWasmTasks = with(this.project
dependsOn(skiaWasmDir) dependsOn(skiaWasmDir)
compiler.set(compilerForTarget(OS.Wasm, Arch.Wasm)) compiler.set(compilerForTarget(OS.Wasm, Arch.Wasm, isJvm = false))
buildTargetOS.set(osArch.first) buildTargetOS.set(osArch.first)
buildTargetArch.set(osArch.second) buildTargetArch.set(osArch.second)
buildVariant.set(buildType) buildVariant.set(buildType)
...@@ -73,7 +73,7 @@ fun SkikoProjectContext.createWasmLinkTasks(): LinkWasmTasks = with(this.project ...@@ -73,7 +73,7 @@ fun SkikoProjectContext.createWasmLinkTasks(): LinkWasmTasks = with(this.project
dependsOn(skiaWasmDir) dependsOn(skiaWasmDir)
val unpackedSkia = skiaWasmDir.get() val unpackedSkia = skiaWasmDir.get()
linker.set(linkerForTarget(OS.Wasm, Arch.Wasm)) linker.set(linkerForTarget(OS.Wasm, Arch.Wasm, isJvm = false))
buildTargetOS.set(osArch.first) buildTargetOS.set(osArch.first)
buildTargetArch.set(osArch.second) buildTargetArch.set(osArch.second)
buildVariant.set(buildType) buildVariant.set(buildType)
......
...@@ -19,6 +19,7 @@ val skikoArtifactIds: List<String> = ...@@ -19,6 +19,7 @@ val skikoArtifactIds: List<String> =
SkikoArtifacts.jsArtifactId, SkikoArtifacts.jsArtifactId,
SkikoArtifacts.wasmArtifactId, SkikoArtifacts.wasmArtifactId,
SkikoArtifacts.nativeArtifactIdFor(OS.Linux, Arch.X64), SkikoArtifacts.nativeArtifactIdFor(OS.Linux, Arch.X64),
SkikoArtifacts.nativeArtifactIdFor(OS.Linux, Arch.Arm64),
SkikoArtifacts.nativeArtifactIdFor(OS.MacOS, Arch.Arm64), SkikoArtifacts.nativeArtifactIdFor(OS.MacOS, Arch.Arm64),
SkikoArtifacts.nativeArtifactIdFor(OS.MacOS, Arch.X64), SkikoArtifacts.nativeArtifactIdFor(OS.MacOS, Arch.X64),
SkikoArtifacts.nativeArtifactIdFor(OS.IOS, Arch.X64), SkikoArtifacts.nativeArtifactIdFor(OS.IOS, Arch.X64),
......
[General]
arch=arm64
directory=build/multistrap-arm64
cleanup=true
noauth=false
unpack=true
cleanup=true
aptsources=Ubuntu
bootstrap=Ubuntu
[Ubuntu]
packages=fontconfig libfontconfig1-dev libglu1-mesa-dev
source=https://ports.ubuntu.com/
keyring=ubuntu-keyring
suite=jammy
...@@ -2,8 +2,10 @@ package org.jetbrains.skia ...@@ -2,8 +2,10 @@ package org.jetbrains.skia
import org.jetbrains.skia.impl.interopScope import org.jetbrains.skia.impl.interopScope
import org.jetbrains.skia.impl.use import org.jetbrains.skia.impl.use
import org.jetbrains.skiko.Arch
import org.jetbrains.skiko.KotlinBackend import org.jetbrains.skiko.KotlinBackend
import org.jetbrains.skiko.OS import org.jetbrains.skiko.OS
import org.jetbrains.skiko.hostArch
import org.jetbrains.skiko.hostOs import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.kotlinBackend import org.jetbrains.skiko.kotlinBackend
import org.jetbrains.skiko.tests.TestGlContext import org.jetbrains.skiko.tests.TestGlContext
...@@ -113,6 +115,10 @@ class SurfaceTest { ...@@ -113,6 +115,10 @@ class SurfaceTest {
// TODO implement for other platforms and render targets // TODO implement for other platforms and render targets
return return
} }
if (hostArch == Arch.Arm64) {
// TODO implement and test EGL on arm64
return
}
val pixels = TestGlContext.run { val pixels = TestGlContext.run {
DirectContext.makeGL().useContext { ctx -> DirectContext.makeGL().useContext { ctx ->
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment