1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.build.clang 18 19 import androidx.build.ProjectLayoutType 20 import java.io.File 21 import org.gradle.api.Named 22 import org.gradle.api.Project 23 import org.gradle.api.file.ConfigurableFileCollection 24 import org.gradle.api.provider.ListProperty 25 import org.gradle.api.tasks.TaskProvider 26 import org.jetbrains.kotlin.konan.target.Family 27 import org.jetbrains.kotlin.konan.target.KonanTarget 28 29 /** 30 * Represents a C compilation for a single [konanTarget]. 31 * 32 * @param konanTarget Target host for the compilation. 33 * @param compileTask The task that compiles the sources and build .o file for each source file. 34 * @param archiveTask The task that will archive the output of the [compileTask] into a single .a 35 * file. 36 * @param sharedLibTask The task that will created a shared library from the output of [compileTask] 37 * that also optionally links with [linkedObjects] 38 * @param sources List of source files for the compilation. 39 * @param includes List of include directories containing .h files for the compilation. 40 * @param linkedObjects List of object files that should be dynamically linked in the final shared 41 * object output. 42 * @param linkerArgs Arguments that will be passed into linker when creating a shared library. 43 * @param freeArgs Arguments that will be passed into clang for compilation. 44 */ 45 class NativeTargetCompilation 46 internal constructor( 47 val project: Project, 48 val konanTarget: KonanTarget, 49 internal val compileTask: TaskProvider<ClangCompileTask>, 50 internal val archiveTask: TaskProvider<ClangArchiveTask>, 51 internal val sharedLibTask: TaskProvider<ClangSharedLibraryTask>, 52 val sources: ConfigurableFileCollection, 53 val includes: ConfigurableFileCollection, 54 val linkedObjects: ConfigurableFileCollection, 55 @Suppress("unused") // used via build.gradle 56 val linkerArgs: ListProperty<String>, 57 @Suppress("unused") // used via build.gradle 58 val freeArgs: ListProperty<String> 59 ) : Named { getNamenull60 override fun getName(): String = konanTarget.name 61 62 /** 63 * Dynamically links the shared library output of this target with the given [dependency]'s 64 * object library output. 65 */ 66 @Suppress("unused") // used from build.gradle 67 fun linkWith(dependency: MultiTargetNativeCompilation) { 68 linkedObjects.from(dependency.sharedObjectOutputFor(konanTarget)) 69 } 70 71 /** 72 * Statically include the shared library output of this target with the given [dependency]'s 73 * archive library output. 74 */ 75 @Suppress("unused") // used from build.gradle includenull76 fun include(dependency: MultiTargetNativeCompilation) { 77 linkedObjects.from(dependency.sharedArchiveOutputFor(konanTarget)) 78 } 79 80 /** Convenience method to add jni headers to the compilation. */ 81 @Suppress("unused") // used from build.gradle addJniHeadersnull82 fun addJniHeaders() { 83 if (konanTarget.family == Family.ANDROID) { 84 // android already has JNI 85 return 86 } 87 88 includes.from(project.provider { findJniHeaderDirectories() }) 89 } 90 findJniHeaderDirectoriesnull91 private fun findJniHeaderDirectories(): List<File> { 92 // TODO b/306669673 add support for GitHub builds. 93 // we need to find 2 jni header files 94 // jni.h -> This is the same across all platforms 95 // jni_md.h -> Includes machine dependant definitions. 96 // Internal Devs: You can read more about it here: http://go/androidx-jni-cross-compilation 97 val javaHome = File(System.getProperty("java.home")) 98 if (ProjectLayoutType.isPlayground(project)) { 99 return findJniHeadersInPlayground(javaHome) 100 } 101 // for jni_md, we need to find the prebuilts because each jdk ships with jni_md only for 102 // its own target family. 103 val jdkPrebuiltsRoot = javaHome.parentFile 104 105 val relativeHeaderPaths = 106 when (konanTarget.family) { 107 Family.MINGW -> { 108 listOf("windows-x86/include", "windows-x86/include/win32") 109 } 110 Family.OSX -> { 111 // it is OK that we are using x86 here, they are the same files (openjdk only 112 // distinguishes between unix and windows). 113 listOf("darwin-x86/include", "darwin-x86/include/darwin") 114 } 115 Family.LINUX -> { 116 listOf( 117 "linux-x86/include", 118 "linux-x86/include/linux", 119 ) 120 } 121 else -> error("unsupported family ($konanTarget) for JNI compilation") 122 } 123 return relativeHeaderPaths 124 .map { jdkPrebuiltsRoot.resolve(it) } 125 .onEach { 126 check(it.exists()) { 127 "Cannot find header directory (${it.name}) in ${it.canonicalPath}" 128 } 129 } 130 } 131 132 /** 133 * JDK ships with JNI headers only for the current platform. As a result, we don't have access 134 * to cross-platform jni headers. They are mostly the same and we don't ship cross compiled code 135 * from GitHub so it is acceptable to use local JNI headers for cross platform compilation on 136 * GitHub. 137 */ findJniHeadersInPlaygroundnull138 private fun findJniHeadersInPlayground(javaHome: File): List<File> { 139 val include = File(javaHome, "include") 140 if (!include.exists()) { 141 error("Cannot find header directory in $javaHome") 142 } 143 return listOf( 144 include, 145 File(include, "darwin"), 146 File(include, "linux"), 147 File(include, "win32"), 148 ) 149 .filter { it.exists() } 150 } 151 } 152