Unverified Commit a2fefe03 authored by Hubert Błaszczyk's avatar Hubert Błaszczyk Committed by GitHub

Control Skia symbols visibility across platforms (#1208)

To extract Skottie and other extensions from Skiko core, we need to
expose certain private symbols from skia. This requires creating tasks
to control the visibility of Skia symbols.

To control visibility of symbols the following things were applied:

### JVM
We are generating a lists of symbols that we want to export. To obtain
the symbols we're doing the following:

1. Collecting all globally defined symbols from the core Skiko/Skia
inputs: Skia static libraries plus core object files.
2. Collecting all undefined symbols from extension/module inputs. These
represent symbols that extension modules need the core library to
provide.
3. Adding JNI infrastructure symbols from the core exports, such as
`Java_`, `JNI`, and `jvm`, so the JVM can still find the native entry
points.
4. Intersecting the extension imports with the core exports. The result
is the list of core symbols that should stay exported.

Generated symbol list it then passed to the linker which hide symbols we
don't need to have visible.

### Native 
For Native targets, Skia is linked into the final produced binary rather
than exposed as a shared "core" library that extension modules link
against. Because of that, Skia symbols do not need to remain externally
visible for other Skiko modules to resolve against them.
Since we are going to build Skia from our fork with Skia symbols public
by default we need to hide them on native.

### WASM
This PR does not include wasm parser as we don't need it. We can first
create the wasm binary for extension functions and then provide them
when compiling the core. This will allow to export only symbols that are
needed by the extension modules automatically. This will be introduced
in the PR with Skottie extraction.

Closes [SKIKO-1142](https://youtrack.jetbrains.com/issue/SKIKO-1142)
parent f532abfc
......@@ -464,6 +464,39 @@ afterEvaluate {
}
}
fun configureSymbolsFor(os: OS, arch: Arch) {
val suffix = joinToTitleCamelCase(os.id, arch.id)
val skiaBindingsDir = skikoProjectContext.registerOrGetSkiaDirProvider(os, arch)
val coreCompile = tasks.named<CompileSkikoCppTask>("compileJvmBindings$suffix")
val coreObjcCompile = if (os.isMacOs) tasks.named<CompileSkikoObjCTask>("objcCompile$suffix") else null
skikoProjectContext.configureGenerateSymbolsList(
os, arch, skiaBindingsDir, coreCompile, coreObjcCompile
)
tasks.named("linkJvmBindings$suffix") {
dependsOn("generateSymbolsList$suffix")
}
}
if (supportAwt) {
afterEvaluate {
configureSymbolsFor(targetOs, targetArch)
if (targetOs == OS.MacOS && targetArch == Arch.Arm64) {
configureSymbolsFor(OS.MacOS, Arch.X64)
}
}
}
if (supportAndroid) {
afterEvaluate {
for (arch in arrayOf(Arch.X64, Arch.Arm64)) {
configureSymbolsFor(OS.Android, arch)
}
}
}
skikoProjectContext.declarePublications()
val mavenCentral = MavenCentralProperties(project)
......
......@@ -28,6 +28,15 @@ abstract class PatchSkiaSymbolsTask : DefaultTask() {
@get:InputFiles
abstract val skiaLibs: ListProperty<File>
/**
* Additional static libraries whose public symbols must be present in the
* rename map, but which should not be copied into [outputDir].
*
* We use it in extension modules so that skia core symbols will be also patched.
*/
@get:InputFiles
abstract val symbolSourceLibs: ListProperty<File>
/** Path to the skiko C++ native-bridges static library (.a). */
@get:InputFile
abstract val skikoBridge: Property<File>
......@@ -43,6 +52,7 @@ abstract class PatchSkiaSymbolsTask : DefaultTask() {
outDir.mkdirs()
val skiaLibFiles = skiaLibs.get()
val symbolSourceLibFiles = symbolSourceLibs.get()
val bridgeFile = skikoBridge.get()
val allLibs = skiaLibFiles + bridgeFile
......@@ -53,7 +63,7 @@ abstract class PatchSkiaSymbolsTask : DefaultTask() {
// renamed a second time.
logger.lifecycle("Extracting public symbols from Skia libraries …")
val allSymbols = mutableSetOf<String>()
for (lib in skiaLibFiles) {
for (lib in symbolSourceLibFiles + skiaLibFiles) {
val syms = extractGlobalDefinedSymbols(lib)
val newSyms = syms.filterTo(mutableSetOf()) { !it.endsWith("_skiko") }
logger.lifecycle(
......
......@@ -22,6 +22,9 @@ enum class OS(
val isMacOs
get() = this == MacOS
val isLinux
get() = this == Linux
fun idWithSuffix(isUikitSim: Boolean = false): String {
return id + if (isUikitSim) "Sim" else ""
}
......
package symbols
private val WHITESPACE = Regex("""\s+""")
private val STORAGE_CLASSES = setOf(
"External",
"Static",
"Label",
"Filename",
"WeakExternal",
"EndOfFunction",
"BeginFunction",
)
/**
* Helper symbols emitted by Windows COFF toolchains that are not valid .def exports.
*/
private fun isCompilerGeneratedName(name: String): Boolean =
name.startsWith("__imp_") ||
name.startsWith(".refptr") ||
name.startsWith("__real@") ||
name.startsWith("__xmm@") ||
name.startsWith("??_C@") ||
name.startsWith("\"")
/**
* Parser for `dumpbin /SYMBOLS` output (Windows COFF object files / static libs)
*
* Each symbol row uses the following layout:
* ```
* 000 00000000 SECT1 notype External | SymbolName
* 001 00000000 UNDEF notype () External | OtherSymbol
* ```
**/
internal fun parseDumpbinSymbols(output: String): Sequence<Symbol> = sequence {
for (rawLine in output.lineSequence()) {
val line = rawLine.trim()
if (line.isEmpty()) continue
val (header, symbolPart) = line
.split('|', limit = 2)
.takeIf { it.size == 2 }
?.let { (header, symbol) -> header to symbol }
?: continue
val headerTokens = header.trim().split(WHITESPACE)
if (headerTokens.size < 4) continue
val section = headerTokens.getOrNull(2) ?: continue
val storage = headerTokens.lastOrNull { it in STORAGE_CLASSES } ?: continue
if (storage != "External") continue
val name = symbolPart.trim().substringBefore(' ')
if (name.isEmpty() || isCompilerGeneratedName(name)) continue
val defined = section != "UNDEF"
yield(
Symbol(
name = name,
type = if (defined) SymbolType.DefinedGlobal else SymbolType.Undefined,
)
)
}
}
package symbols
import OS
import Arch
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import java.io.File
import javax.inject.Inject
abstract class GenerateSymbolsListTask : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:Input
abstract val targetOs: Property<OS>
@get:Input
abstract val targetArch: Property<Arch>
@get:Input
abstract val symbolExtractorCommand: ListProperty<String>
@get:InputFiles
abstract val coreObjectFiles: ConfigurableFileCollection
@get:InputFiles
abstract val moduleObjectFiles: ConfigurableFileCollection
@get:InputFiles
abstract val skiaLibs: ConfigurableFileCollection
@get:InputFiles
abstract val moduleLibs: ConfigurableFileCollection
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@TaskAction
fun generate() {
val os = targetOs.get()
val arch = targetArch.get()
val outDir = outputDir.get().asFile
outDir.mkdirs()
if (os == OS.IOS || os == OS.TVOS || os == OS.Wasm) {
throw IllegalStateException("generateSymbolsList does not support ${os.name} target")
}
logger.lifecycle(
"generateSymbolsList: targetOs=${os.name}, targetArch=${arch.name}, coreObjects=${coreObjectFiles.files.size}, moduleObjects=${moduleObjectFiles.files.size}, skiaLibs=${skiaLibs.files.size}, moduleLibs=${moduleLibs.files.size}"
)
val coreExports = outDir.resolve("core_exports.txt")
val extImports = outDir.resolve("ext_imports.txt")
val symbolsFiltered = outDir.resolve("symbols_filtered.txt")
val symbolsUnexported = outDir.resolve("symbols_unexported.txt")
// 1. core exports
val coreExportedList = extractSymbols(skiaLibs.files.toList() + coreObjectFiles.files.toList(), true)
coreExports.writeText(coreExportedList.sorted().joinToString("\n"))
// 2. all ext imports
val extImportedList =
extractSymbols(moduleObjectFiles.files.toList() + moduleLibs.files.toList(), false).toMutableList()
// Keep JVM/JNI infrastructure globals
extImportedList.addAll(coreExportedList.filter(::isJniInfrastructureSymbol))
extImports.writeText(extImportedList.distinct().sorted().joinToString("\n"))
// 3. initial keep list = intersection of ext imports + JNI with core exports
val coreExportsSet = coreExportedList.toSet()
val keepSet = extImportedList.filter { it in coreExportsSet }.toSet()
symbolsFiltered.writeText(keepSet.sorted().joinToString("\n"))
// 4. unexported = core exports minus what strip decided to keep
val unexportedSet = coreExportsSet - keepSet
symbolsUnexported.writeText(unexportedSet.sorted().joinToString("\n"))
// Create export files for Linux or Windows. MacOS uses the raw txt file
if (os.isLinux || os == OS.Android) {
val versionScript = outDir.resolve("symbols.map")
generateVersionScript(symbolsFiltered.toPath(), versionScript.toPath())
}
if (os.isWindows) {
val defFile = outDir.resolve("symbols.def")
generateDefFile(symbolsFiltered.toPath(), defFile.toPath())
}
logger.lifecycle("Symbols to keep: ${keepSet.size}, to hide: ${unexportedSet.size}")
}
private fun extractSymbols(files: List<File>, exported: Boolean): List<String> {
val os = targetOs.get()
logger.lifecycle(
"generateSymbolsList: extracting ${if (exported) "exported" else "undefined"} " +
"from ${files.size} files"
)
val type = if (exported) SymbolType.DefinedGlobal else SymbolType.Undefined
return SymbolExtractor(
execOperations = execOperations,
os = os,
command = symbolExtractorCommand.get(),
).extract(files, type).toList()
}
}
package symbols
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import javax.inject.Inject
import OS
abstract class HideSkiaSymbolsTask : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:Input
abstract val targetOs: Property<OS>
@get:Input
abstract val symbolExtractorCommand: ListProperty<String>
@get:InputFiles
abstract val symbolSourceLibraries: ConfigurableFileCollection
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun write() {
val os = targetOs.get()
val symbols = SymbolExtractor(
execOperations = execOperations,
os = os,
command = symbolExtractorCommand.get(),
).extract(symbolSourceLibraries.files, SymbolType.DefinedGlobal)
.filter { !isOrgJetbrainsSymbol(it) }
.sorted()
val output = outputFile.get().asFile
output.parentFile.mkdirs()
output.writeText(if (os.isLinux) versionScript(local = symbols) else symbols.joinToString("\n", postfix = "\n"))
}
}
package symbols
private val WHITESPACE = Regex("""\s+""")
/**
* Parser for `nm` output
*
* Each symbol row uses the following layout:
* ```
* <name> <type> [<value>]
* ```
**/
internal fun parseNmPosix(
output: String,
): Sequence<Symbol> =
output
.lineSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.filterNot { it.endsWith(':') } // file/archive headers
.mapNotNull(::parseNmPosixLine)
private fun parseNmPosixLine(line: String): Symbol? {
val columns = line.split(WHITESPACE, limit = 4)
if (columns.size < 2) return null
val name = columns[0]
val typeText = columns[1]
if (typeText.length != 1) return null
val typeLetter = typeText[0]
val type = classifyNmSymbolType(typeLetter) ?: return null
return Symbol(
name = name,
type = type,
)
}
private fun classifyNmSymbolType(
typeLetter: Char,
): SymbolType? =
when {
typeLetter == 'U' -> SymbolType.Undefined
typeLetter.isUpperCase() -> SymbolType.DefinedGlobal
typeLetter == 'u' -> SymbolType.DefinedGlobal
else -> null
}
package symbols
/**
* A linker-visible symbol parsed from a tool output (`nm`, `dumpbin`).
*/
internal data class Symbol(
val name: String,
val type: SymbolType,
)
internal enum class SymbolType {
DefinedGlobal,
Undefined,
}
package symbols
import OS
import org.gradle.process.ExecOperations
import java.io.ByteArrayOutputStream
import java.io.File
internal class SymbolExtractor(
private val execOperations: ExecOperations,
private val os: OS,
private val command: List<String>,
) {
fun extract(files: Collection<File>, type: SymbolType): Set<String> {
val actualFiles = files.filter { it.isFile }
if (actualFiles.isEmpty()) return emptySet()
require(type == SymbolType.DefinedGlobal || type == SymbolType.Undefined) {
"Symbol extraction does not support ${type.name} symbols"
}
return when (os) {
OS.Linux, OS.Android, OS.MacOS, OS.IOS, OS.TVOS -> {
val nmCommand = nmCommand(type)
val output = run(nmCommand.command, nmCommand.args + actualFiles.map { it.absolutePath })
parseNmPosix(output)
.filter { it.type == type }
.mapTo(mutableSetOf()) { it.name }
}
OS.Windows -> {
val output = run(
command,
listOf("/SYMBOLS") + actualFiles.map { it.absolutePath }
)
parseDumpbinSymbols(output)
.filter { it.type == type }
.mapTo(mutableSetOf()) { it.name }
}
OS.Wasm -> error("Symbol extraction does not support ${os.name} target")
}
}
private fun nmCommand(type: SymbolType): Command {
val args = when (type) {
SymbolType.Undefined -> listOf("-P", "-u")
SymbolType.DefinedGlobal -> when (os) {
OS.MacOS, OS.IOS, OS.TVOS -> listOf("-P", "-g", "-U")
OS.Linux, OS.Android -> listOf("-P", "-g", "--defined-only")
else -> error("nm symbol extraction does not support ${os.name} target")
}
}
return Command(command, args)
}
private fun run(command: List<String>, args: List<String>): String {
require(command.isNotEmpty()) { "Symbol extractor command must not be empty" }
val executable = command.first()
val allArgs = command.drop(1) + args
val stdout = ByteArrayOutputStream()
val stderr = ByteArrayOutputStream()
val result = execOperations.exec {
this.executable = executable
this.args = allArgs
standardOutput = stdout
errorOutput = stderr
isIgnoreExitValue = true
}
if (result.exitValue != 0) {
error(
"""
Command failed with exit code ${result.exitValue}: $executable ${allArgs.joinToString(" ")}
stderr:
$stderr
""".trimIndent()
)
}
return stdout.toString()
}
private data class Command(val command: List<String>, val args: List<String>)
}
package symbols
import java.nio.file.Path
import kotlin.io.path.readLines
import kotlin.io.path.writeText
internal fun generateDefFile(exportedTxt: Path, output: Path) {
val symbols = exportedTxt.readSymbolLines()
output.writeText(buildString {
appendLine("EXPORTS")
symbols.forEach { symbol ->
appendLine(" $symbol")
}
})
}
internal fun generateVersionScript(symbolsTxt: Path, output: Path) {
output.writeText(versionScript(global = symbolsTxt.readSymbolLines(), local = listOf("*")))
}
internal fun versionScript(global: List<String> = emptyList(), local: List<String> = emptyList()): String = buildString {
appendLine("{")
if (global.isNotEmpty()) {
appendLine(" global:")
global.forEach { symbol ->
appendLine(" $symbol;")
}
}
if (local.isNotEmpty()) {
appendLine(" local:")
local.forEach { symbol ->
appendLine(" $symbol;")
}
}
appendLine("};")
}
private fun Path.readSymbolLines(): List<String> =
readLines()
.map { it.trim() }
.filter { it.isNotEmpty() }
package symbols
internal fun isOrgJetbrainsSymbol(name: String): Boolean =
name.removePrefix("_").startsWith("org_jetbrains")
internal fun isJniInfrastructureSymbol(name: String): Boolean {
val symbol = name.removePrefix("_")
return symbol.startsWith("Java_") ||
symbol.startsWith("JNI") ||
symbol.startsWith("jvm")
}
......@@ -25,11 +25,14 @@ import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.withType
import org.gradle.kotlin.dsl.register
import projectDirs
import registerOrGetSkiaDirProvider
import registerSkikoTask
import runPkgConfig
import symbols.GenerateSymbolsListTask
import targetId
import java.io.File
......@@ -144,18 +147,7 @@ fun SkikoProjectContext.createCompileJvmBindingsTask(
fun Provider<String>.orEmpty(): Provider<String> =
orElse("")
fun Project.androidClangFor(targetArch: Arch, version: String = "30"): Provider<String> {
val androidArch = when (targetArch) {
Arch.Arm64 -> "aarch64"
Arch.X64 -> "x86_64"
else -> throw GradleException("unsupported $targetArch")
}
val hostOsArch = when (hostOs) {
OS.MacOS -> "darwin-x86_64"
OS.Linux -> "linux-x86_64"
OS.Windows -> "windows-x86_64"
else -> throw GradleException("unsupported $hostOs")
}
fun Project.androidNdkPath(): Provider<String> {
val ndkPathProvider = project.providers
.environmentVariable("ANDROID_NDK_HOME")
.orEmpty()
......@@ -171,12 +163,43 @@ fun Project.androidClangFor(targetArch: Arch, version: String = "30"): Provider<
"$androidHome/$ndkVersion"
}
}
return ndkPathProvider.map { ndkPath ->
return ndkPathProvider
}
fun Project.androidLlvmBinPath(): Provider<String> {
val hostOsArch = when (hostOs) {
OS.MacOS -> "darwin-x86_64"
OS.Linux -> "linux-x86_64"
OS.Windows -> "windows-x86_64"
else -> throw GradleException("unsupported $hostOs")
}
return androidNdkPath().map { ndkPath ->
"$ndkPath/toolchains/llvm/prebuilt/$hostOsArch/bin"
}
}
fun Project.androidClangFor(targetArch: Arch, version: String = "30"): Provider<String> {
val androidArch = when (targetArch) {
Arch.Arm64 -> "aarch64"
Arch.X64 -> "x86_64"
else -> throw GradleException("unsupported $targetArch")
}
return androidLlvmBinPath().map { llvmBinPath ->
var clangBinaryName = "$androidArch-linux-android$version-clang++"
if (hostOs.isWindows) {
clangBinaryName += ".cmd"
}
"$ndkPath/toolchains/llvm/prebuilt/$hostOsArch/bin/$clangBinaryName"
"$llvmBinPath/$clangBinaryName"
}
}
fun Project.androidLlvmNm(): Provider<String> {
return androidLlvmBinPath().map { llvmBinPath ->
var nmBinaryName = "llvm-nm"
if (hostOs.isWindows) {
nmBinaryName += ".exe"
}
"$llvmBinPath/$nmBinaryName"
}
}
......@@ -219,6 +242,49 @@ fun SkikoProjectContext.createObjcCompileTask(
)
}
fun SkikoProjectContext.configureGenerateSymbolsList(
targetOs: OS,
targetArch: Arch,
skiaJvmBindingsDir: Provider<File>,
coreCompile: TaskProvider<CompileSkikoCppTask>,
coreObjcCompile: TaskProvider<CompileSkikoObjCTask>?
) {
val suffix = joinToTitleCamelCase(targetOs.id, targetArch.id)
project.tasks.register<GenerateSymbolsListTask>("generateSymbolsList$suffix") {
this.targetOs.set(targetOs)
this.targetArch.set(targetArch)
this.symbolExtractorCommand.set(
when (targetOs) {
OS.Android -> project.androidLlvmNm().map { listOf(it) }
OS.Windows -> project.provider { listOf(windowsSdkPaths.dumpbin.absolutePath) }
else -> project.provider { listOf("nm") }
}
)
val target = targetId(targetOs, targetArch)
val maybeSignedDir = project.layout.buildDirectory.dir("maybe-signed-$target")
outputDir.set(maybeSignedDir)
dependsOn(coreCompile)
coreObjectFiles.from(coreCompile.map {
it.outDir.get().asFile.walk().filter { it.name.endsWith(".o") || it.name.endsWith(".obj") }.toList()
})
if (coreObjcCompile != null) {
dependsOn(coreObjcCompile)
coreObjectFiles.from(coreObjcCompile.map {
it.outDir.get().asFile.walk().filter { it.name.endsWith(".o") }.toList()
})
}
val skiaBinSubdir = "out/${buildType.id}-$target"
val skiaBinDirProvider = skiaJvmBindingsDir.map { it.resolve(skiaBinSubdir) }
val skiaBinDir = skiaBinDirProvider.get().absolutePath
val coreBinaryInputs = resolveBinaryInputs(targetOs, targetArch, TargetEnv.JVM, skiaBinDir)
skiaLibs.from(project.files(coreBinaryInputs.staticArchivePaths + coreBinaryInputs.directStaticArchivePaths))
moduleLibs.from(project.files(emptyList<File>()))
}
}
fun SkikoProjectContext.createLinkJvmBindings(
targetOs: OS,
......@@ -246,7 +312,7 @@ fun SkikoProjectContext.createLinkJvmBindings(
buildTargetArch.set(targetArch)
buildVariant.set(buildType)
linker.set(linkerForTarget(targetOs, targetArch))
val maybeSignedDir = project.layout.buildDirectory.dir("maybe-signed-$target").get().asFile
when (targetOs) {
OS.MacOS -> {
dependsOn(objcCompileTask!!)
......@@ -331,6 +397,46 @@ fun SkikoProjectContext.createLinkJvmBindings(
}
}
flags.set(listOf(*osFlags))
flags.addAll(project.provider {
val result = mutableListOf<String>()
val unexportedSymbols = maybeSignedDir.resolve("symbols_unexported.txt")
val exportedSymbols = maybeSignedDir.resolve("symbols_filtered.txt")
if (unexportedSymbols.exists()) {
when (targetOs) {
OS.MacOS -> {
result.add("-Wl,-exported_symbols_list,${exportedSymbols.absolutePath}")
}
OS.Linux, OS.Android -> {
val versionScript = maybeSignedDir.resolve("symbols.map")
if (versionScript.exists()) {
result.add("-Wl,--version-script=${versionScript.absolutePath}")
}
// The version script controls symbol visibility, but it does not make
// static archive members live. Add the kept symbols as undefined roots
// before the explicitly ordered Skia archives so Linux pulls members
// needed by extension modules, without resorting to --whole-archive.
if (exportedSymbols.exists()) {
exportedSymbols.readLines()
.map { it.trim() }
.filter { it.isNotEmpty() }
.forEach { result.add("-Wl,-u,$it") }
}
}
OS.Windows -> {
val defFile = maybeSignedDir.resolve("symbols.def")
if (defFile.exists()) {
result.add("/DEF:${defFile.absolutePath}")
}
}
else -> {}
}
}
result
})
}
private val Arch.darwinSignClientName: String
......@@ -420,7 +526,7 @@ fun SkikoProjectContext.createSkikoJvmJarTask(os: OS, arch: Arch, commonJar: Tas
createDownloadCodeSignClientDarwinTask(os, hostArch)
}
val maybeSign = maybeSignOrSealTask(os, arch, linkBindings)
val nativeLib = maybeSign.map { it.outputFiles.get().single() }
val nativeLib = maybeSign.map { it -> it.outputFiles.get().single { it.name.endsWith(os.dynamicLibExt) } }
val createChecksums = createChecksumsTask(os, arch, nativeLib)
val nativeFiles = mutableListOf(
nativeLib,
......@@ -440,7 +546,7 @@ fun SkikoProjectContext.createSkikoJvmJarTask(os: OS, arch: Arch, commonJar: Tas
val linkBindings2 =
createLinkJvmBindings(os, altArch, skiaBindingsDir2, compileBindings2, objcCompile2)
val maybeSign2 = maybeSignOrSealTask(os, altArch, linkBindings2)
val nativeLib2 = maybeSign2.map { it.outputFiles.get().single() }
val nativeLib2 = maybeSign2.map { it.outputFiles.get().single { f -> f.name.endsWith(os.dynamicLibExt) } }
val createChecksums2 = createChecksumsTask(os, altArch, nativeLib2)
nativeFiles.add(nativeLib2)
nativeFiles.add(createChecksums2.map { it.outputs.files.singleFile })
......
......@@ -25,7 +25,9 @@ import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import projectDirs
import registerOrGetSkiaDirProvider
import registerSkikoTask
import symbols.HideSkiaSymbolsTask
import java.io.File
import kotlin.collections.plus
fun String.withSuffix(isUikitSim: Boolean = false) =
this + if (isUikitSim) "Sim" else ""
......@@ -224,10 +226,34 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
nativeArchives + bridgesLibraryPath
}
val hiddenSymbolsFile = layout.buildDirectory.file(
"nativeHiddenSymbols/$targetString/${if (os.isLinux) "symbols.map" else "symbols.txt"}"
)
val hiddenSymbolSources = if (requiresSymbolPatching) {
val patchedBaseLibraries = nativeArchives.map { lib -> "${patchedLibsDir.absolutePath}/${File(lib).name}" }
val patchedBridgeLibrary = "${patchedLibsDir.absolutePath}/${nativeBridgesLibPrefix}-$targetString.a"
patchedBaseLibraries + patchedBridgeLibrary
} else {
nativeArchives
}
val hideSkiaSymbols = project.registerSkikoTask<HideSkiaSymbolsTask>(
"hideSkiaSymbols".withSuffix(isUikitSim = isUikitSim),
os,
arch
) {
targetOs.set(os)
symbolExtractorCommand.set(if (os == OS.IOS || os == OS.TVOS) listOf("xcrun", "nm") else listOf("nm"))
symbolSourceLibraries.from(hiddenSymbolSources.map { File(it) })
outputFile.set(hiddenSymbolsFile)
}
val linkerFlags = when (os) {
OS.MacOS -> {
configureCinterop(cinteropName, os, arch, target, targetString, resolvedBinaryInputs.frameworks)
mutableListOfLinkerOptions(resolvedBinaryInputs.frameworks + resolvedBinaryInputs.linkFlags)
mutableListOfLinkerOptions(resolvedBinaryInputs.frameworks + resolvedBinaryInputs.linkFlags + listOf(
"-unexported_symbols_list",
hiddenSymbolsFile.get().asFile.absolutePath
))
}
OS.IOS -> {
// list of linker options to be included into klib, which are needed for skiko consumers
......@@ -235,11 +261,17 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
// Important! Removing or renaming cinterop-uikit publication might cause compile error
// for projects depending on older Compose/Skiko transitively https://youtrack.jetbrains.com/issue/KT-60399
configureCinterop("uikit", os, arch, target, targetString, resolvedBinaryInputs.frameworks)
mutableListOfLinkerOptions(resolvedBinaryInputs.frameworks + resolvedBinaryInputs.linkFlags)
mutableListOfLinkerOptions(resolvedBinaryInputs.frameworks + resolvedBinaryInputs.linkFlags + listOf(
"-unexported_symbols_list",
hiddenSymbolsFile.get().asFile.absolutePath
))
}
OS.TVOS -> {
configureCinterop("uikit", os, arch, target, targetString, resolvedBinaryInputs.frameworks)
mutableListOfLinkerOptions(resolvedBinaryInputs.frameworks + resolvedBinaryInputs.linkFlags)
mutableListOfLinkerOptions(resolvedBinaryInputs.frameworks + resolvedBinaryInputs.linkFlags + listOf(
"-unexported_symbols_list",
hiddenSymbolsFile.get().asFile.absolutePath
))
}
OS.Linux -> {
val options = mutableListOf(
......@@ -255,6 +287,7 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
options.add(0, "-L/opt/arm-gnu-toolchain/aarch64-none-linux-gnu/libc/lib64")
options.add(1, "-L/opt/arm-gnu-toolchain/aarch64-none-linux-gnu/libc/usr/lib64")
}
options.add("--version-script=${hiddenSymbolsFile.get().asFile.absolutePath}")
mutableListOfLinkerOptions(options)
}
else -> mutableListOf()
......@@ -320,6 +353,7 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
dependsOn(unzipper)
dependsOn(linkTask)
skiaLibs.set(nativeArchives.map { File(it) })
symbolSourceLibs.set(emptyList())
skikoBridge.set(File(bridgesLibraryPath))
outputDir.set(patchedLibsDir)
}
......@@ -327,9 +361,14 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
linkTask
}
hideSkiaSymbols.configure {
dependsOn(unzipper)
dependsOn(compilationDependency)
}
target.compilations.all {
compileTaskProvider.configure {
dependsOn(compilationDependency)
dependsOn(hideSkiaSymbols)
}
}
}
......
......@@ -12,6 +12,7 @@ import kotlin.math.abs
data class WindowsSdkPaths(
val compiler: File,
val linker: File,
val dumpbin: File,
val includeDirs: Collection<File>,
val libDirs: Collection<File>,
val toolchainVersion: VersionNumber,
......@@ -31,9 +32,12 @@ fun findWindowsSdkPaths(gradle: Gradle, arch: Arch): WindowsSdkPaths {
val ucrt = finder.findUcrt()
val winrt = finder.findWinrt()
val systemLibraries = listOf(visualCpp, windowsSdk, ucrt, winrt)
val compiler = visualCpp.compilerExecutable.fixPathFor(arch)
val linker = visualCpp.linkerExecutable.fixPathFor(arch)
return WindowsSdkPaths(
compiler = visualCpp.compilerExecutable.fixPathFor(arch),
linker = visualCpp.linkerExecutable.fixPathFor(arch),
compiler = compiler,
linker = linker,
dumpbin = linker.parentFile.resolve("dumpbin.exe"),
includeDirs = systemLibraries.flatMap { it.includeDirs }.map { it.fixPathFor(arch) },
libDirs = systemLibraries.flatMap { it.libDirs }.map { it.fixPathFor(arch) },
toolchainVersion = visualCpp.implementationVersion,
......
......@@ -10,6 +10,7 @@ RUN yum install -y tar xz git wget curl zip unzip gzip && \
ln -sf /usr/bin/gcc10-gcc /usr/bin/gcc && \
ln -sf /usr/bin/gcc10-g++ /usr/bin/g++ && \
ln -sf /usr/bin/gcc10-ar /usr/bin/ar && \
ln -sf /usr/bin/gcc10-nm /usr/bin/nm && \
yum clean all
# Install Xvfb (and GLX module) for headless UI testing
......
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