1 /* <lambda>null2 * 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 com.google.common.truth.Truth.assertThat 20 import org.gradle.api.file.ConfigurableFileCollection 21 import org.jetbrains.kotlin.konan.target.HostManager 22 import org.jetbrains.kotlin.konan.target.KonanTarget 23 import org.junit.AssumptionViolatedException 24 import org.junit.Test 25 26 class AndroidXClangTest : BaseClangTest() { 27 28 @Test 29 fun addJniHeaders() { 30 val multiTargetNativeCompilation = clangExtension.createNativeCompilation( 31 "mylib" 32 ) { 33 it.configureEachTarget { 34 it.addJniHeaders() 35 } 36 } 37 multiTargetNativeCompilation.configureTargets( 38 listOf(KonanTarget.LINUX_X64, KonanTarget.ANDROID_X64) 39 ) 40 // trigger configuration 41 multiTargetNativeCompilation.targetProvider(KonanTarget.LINUX_X64).get() 42 multiTargetNativeCompilation.targetProvider(KonanTarget.ANDROID_X64).get() 43 val compileTasks = project.tasks.withType(ClangCompileTask::class.java).toList() 44 val linuxCompileTask = compileTasks.first { 45 it.clangParameters.konanTarget.get().asKonanTarget == KonanTarget.LINUX_X64 46 } 47 // make sure it includes linux header 48 assertThat( 49 linuxCompileTask.clangParameters.includes.regularFileNames() 50 ).contains("jni.h") 51 val androidCompileTask = compileTasks.first { 52 it.clangParameters.konanTarget.get().asKonanTarget == KonanTarget.ANDROID_X64 53 } 54 // android has jni in sysroots, hence we shouldn't add that 55 assertThat( 56 androidCompileTask.clangParameters.includes.regularFileNames() 57 ).doesNotContain("jni.h") 58 } 59 60 @Test 61 fun configureTargets() { 62 val commonSourceFolders = tmpFolder.newFolder("src").also { 63 it.resolve("src1.c").writeText("") 64 it.resolve("src2.c").writeText("") 65 } 66 val commonIncludeFolders = listOf( 67 tmpFolder.newFolder("include1"), 68 tmpFolder.newFolder("include2"), 69 ) 70 val linuxSrcFolder = tmpFolder.newFolder("linuxOnlySrc").also { 71 it.resolve("linuxSrc1.c").writeText("") 72 it.resolve("linuxSrc2.c").writeText("") 73 } 74 val androidIncludeFolders = listOf( 75 tmpFolder.newFolder("androidInclude1"), 76 tmpFolder.newFolder("androidInclude2"), 77 ) 78 val multiTargetNativeCompilation = clangExtension.createNativeCompilation( 79 "mylib" 80 ) { 81 it.configureEachTarget { 82 it.sources.from(commonSourceFolders) 83 it.includes.from(commonIncludeFolders) 84 it.freeArgs.addAll("commonArg1", "commonArg2") 85 if (it.konanTarget == KonanTarget.LINUX_X64) { 86 it.freeArgs.addAll("linuxArg1") 87 } 88 if (it.konanTarget == KonanTarget.ANDROID_X64) { 89 it.freeArgs.addAll("androidArg1") 90 } 91 } 92 } 93 multiTargetNativeCompilation.configureTarget(KonanTarget.LINUX_X64) { 94 it.sources.from(linuxSrcFolder) 95 } 96 // multiple configure calls on the target 97 multiTargetNativeCompilation.configureTarget(KonanTarget.LINUX_X64) { 98 it.freeArgs.addAll("linuxArg2") 99 } 100 multiTargetNativeCompilation.configureTarget(KonanTarget.ANDROID_X64) { 101 it.includes.from(androidIncludeFolders) 102 it.freeArgs.addAll("androidArg2") 103 } 104 105 // Add this check if we can re-enable lazy evaluation b/325518502 106 // assertThat(project.tasks.withType( 107 // ClangCompileTask::class.java 108 // ).toList()).isEmpty() 109 110 // trigger configuration of targets 111 multiTargetNativeCompilation.targetProvider(KonanTarget.LINUX_X64).get() 112 multiTargetNativeCompilation.targetProvider(KonanTarget.ANDROID_X64).get() 113 114 // make sure it created tasks for it 115 project.tasks.withType(ClangCompileTask::class.java).let { compileTasks -> 116 // 2 compile tasks, 1 for linux, 1 for android 117 assertThat(compileTasks).hasSize(2) 118 val linuxTask = compileTasks.first { 119 it.clangParameters.konanTarget.get().asKonanTarget == KonanTarget.LINUX_X64 120 } 121 assertThat( 122 linuxTask.clangParameters.sources.regularFileNames() 123 ).containsExactly("src1.c", "src2.c", "linuxSrc1.c", "linuxSrc2.c") 124 assertThat( 125 linuxTask.clangParameters.includes.directoryNames() 126 ).containsExactly("include1", "include2") 127 assertThat( 128 linuxTask.clangParameters.freeArgs.get() 129 ).containsExactly("commonArg1", "commonArg2", "linuxArg1", "linuxArg2") 130 131 val androidTask = compileTasks.first { 132 it.clangParameters.konanTarget.get().asKonanTarget == KonanTarget.ANDROID_X64 133 } 134 assertThat( 135 androidTask.clangParameters.sources.regularFileNames() 136 ).containsExactly("src1.c", "src2.c") 137 assertThat( 138 androidTask.clangParameters.includes.directoryNames() 139 ).containsExactly( 140 "androidInclude1", "androidInclude2", "include1", "include2" 141 ) 142 assertThat( 143 androidTask.clangParameters.freeArgs.get() 144 ).containsExactly("commonArg1", "commonArg2", "androidArg1", "androidArg2") 145 } 146 // 2 archive tasks, 1 for each target 147 project.tasks.withType(ClangArchiveTask::class.java).let { archiveTasks -> 148 assertThat(archiveTasks).hasSize(2) 149 assertThat( 150 archiveTasks.map { it.llvmArchiveParameters.konanTarget.get().asKonanTarget } 151 ).containsExactly( 152 KonanTarget.LINUX_X64, 153 KonanTarget.ANDROID_X64 154 ) 155 archiveTasks.forEach { archiveTask -> 156 assertThat( 157 archiveTask.llvmArchiveParameters.outputFile.get().asFile.name 158 ).isEqualTo( 159 "libmylib.a" 160 ) 161 } 162 } 163 164 // 2 shared library tasks, 1 for each target 165 project.tasks.withType(ClangSharedLibraryTask::class.java).let { soTasks -> 166 assertThat( 167 soTasks.map { it.clangParameters.konanTarget.get().asKonanTarget } 168 ).containsExactly( 169 KonanTarget.LINUX_X64, 170 KonanTarget.ANDROID_X64 171 ) 172 soTasks.forEach { 173 assertThat( 174 it.clangParameters.outputFile.get().asFile.name 175 ).isEqualTo("libmylib.so") 176 } 177 } 178 } 179 180 @Test 181 fun configureDisabledTarget() { 182 if (HostManager.hostIsMac) { 183 throw AssumptionViolatedException( 184 """ 185 All targets are enabled on mac, hence we cannot end-to-end test disabled targets. 186 """.trimIndent() 187 ) 188 } 189 val multiTargetNativeCompilation = clangExtension.createNativeCompilation( 190 "mylib" 191 ) { 192 it.configureEachTarget { 193 it.sources.from(tmpFolder.newFolder()) 194 } 195 } 196 multiTargetNativeCompilation.configureTarget(KonanTarget.LINUX_X64) 197 multiTargetNativeCompilation.configureTarget(KonanTarget.MACOS_ARM64) 198 assertThat(multiTargetNativeCompilation.hasTarget( 199 KonanTarget.LINUX_X64 200 )).isTrue() 201 assertThat(multiTargetNativeCompilation.hasTarget( 202 KonanTarget.MACOS_ARM64 203 )).isFalse() 204 } 205 206 @Test 207 fun linking() { 208 val lib1Sources = tmpFolder.newFolder().also { 209 it.resolve("src1.c").writeText("") 210 } 211 val lib2Sources = tmpFolder.newFolder().also { 212 it.resolve("src2.c").writeText("") 213 } 214 val compilation1 = clangExtension.createNativeCompilation( 215 "lib1" 216 ) { 217 it.configureEachTarget { 218 it.sources.from(lib1Sources) 219 } 220 } 221 compilation1.configureTargets(listOf(KonanTarget.LINUX_X64, KonanTarget.ANDROID_X64)) 222 val compilation2 = clangExtension.createNativeCompilation( 223 "lib2" 224 ) { 225 it.configureEachTarget { 226 it.sources.from(lib2Sources) 227 it.linkWith(compilation1) 228 } 229 } 230 compilation2.configureTargets(listOf(KonanTarget.LINUX_X64, KonanTarget.ANDROID_X64)) 231 // trigger configuration 232 compilation2.targetProvider(KonanTarget.LINUX_X64).get() 233 compilation2.targetProvider(KonanTarget.ANDROID_X64).get() 234 val sharedLibrariesTasks = project.tasks.withType( 235 ClangSharedLibraryTask::class.java 236 ).toList().filter { 237 it.name.contains("lib2", ignoreCase = true) 238 } 239 assertThat(sharedLibrariesTasks).hasSize(2) 240 sharedLibrariesTasks.forEach { 241 assertThat( 242 it.clangParameters.linkedObjects.files.map { it.name } 243 ).containsExactly("liblib1.so") 244 } 245 } 246 247 private fun ConfigurableFileCollection.regularFileNames() = asFileTree.files.map { 248 it.name 249 } 250 251 private fun ConfigurableFileCollection.directoryNames() = files.flatMap { 252 it.walkTopDown() 253 }.filter { it.isDirectory }.map { it.name } 254 } 255