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