1 /* <lambda>null2 * Copyright 2022 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.importMaven 18 19 import androidx.build.importMaven.ArtifactResolver.resolveArtifacts 20 import androidx.build.importMaven.KmpConfig.SUPPORTED_KONAN_TARGETS 21 import java.net.URI 22 import org.apache.logging.log4j.kotlin.logger 23 import org.gradle.api.Named 24 import org.gradle.api.Project 25 import org.gradle.api.artifacts.Configuration 26 import org.gradle.api.artifacts.Dependency 27 import org.gradle.api.artifacts.component.ModuleComponentIdentifier 28 import org.gradle.api.artifacts.dsl.RepositoryHandler 29 import org.gradle.api.artifacts.result.ResolvedArtifactResult 30 import org.gradle.api.attributes.Attribute 31 import org.gradle.api.attributes.AttributeContainer 32 import org.gradle.api.attributes.Category 33 import org.gradle.api.attributes.LibraryElements 34 import org.gradle.api.attributes.Usage 35 import org.gradle.api.attributes.java.TargetJvmEnvironment 36 import org.gradle.api.attributes.java.TargetJvmVersion 37 import org.gradle.api.attributes.plugin.GradlePluginApiVersion 38 import org.gradle.api.internal.artifacts.verification.exceptions.DependencyVerificationException 39 import org.gradle.util.GradleVersion 40 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType 41 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 42 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages 43 import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute 44 import org.jetbrains.kotlin.gradle.targets.js.KotlinWasmTargetAttribute 45 import org.jetbrains.kotlin.konan.target.KonanTarget 46 47 /** 48 * Provides functionality to resolve and download artifacts. 49 * see: [resolveArtifacts] 50 * see: [LocalMavenRepoDownloader] 51 * see: [MavenRepositoryProxy] 52 */ 53 internal object ArtifactResolver { 54 internal val jetbrainsRepositories = listOf( 55 "https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev/", 56 "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev", 57 "https://maven.pkg.jetbrains.space/public/p/compose/dev", 58 "https://maven.pkg.jetbrains.space/kotlin/p/dokka/test" 59 ) 60 61 internal val gradlePluginPortalRepo = "https://plugins.gradle.org/m2/" 62 63 internal fun createAndroidXRepo( 64 buildId: Int 65 ) = "https://androidx.dev/snapshots/builds/$buildId/artifacts/repository" 66 67 internal fun createMetalavaRepo( 68 buildId: Int 69 ) = "https://androidx.dev/metalava/builds/$buildId/artifacts/repo/m2repository" 70 71 /** 72 * Resolves given set of [artifacts]. 73 * 74 * @param artifacts List of artifacts to resolve. 75 * @param additionalRepositories List of repositories in addition to mavenCentral and google 76 * @param localRepositories List of local repositories. If an artifact is found here, it won't 77 * be downloaded. 78 * @param explicitlyFetchInheritedDependencies If set to true, each discovered dependency will 79 * be fetched again. For instance: 80 * artifact1:v1 81 * artifact2:v2 82 * artifact3:v1 83 * artifact3:v3 84 * If this flag is `false`, we'll only fetch artifact1:v1, artifact2:v2, artifact3:v3. 85 * If this flag is `true`, we'll fetch `artifact3:v1` as well (because artifact2:v2 86 * declares a dependency on it even though it is overridden by the dependency of 87 * artifact1:v1 88 * @param downloadObserver An observer that will be notified each time a file is downloaded from 89 * a remote repository. 90 */ 91 fun resolveArtifacts( 92 artifacts: List<String>, 93 additionalRepositories: List<String> = emptyList(), 94 localRepositories: List<String> = emptyList(), 95 explicitlyFetchInheritedDependencies: Boolean = false, 96 downloadObserver: DownloadObserver?, 97 ): ArtifactsResolutionResult { 98 return SingleUseArtifactResolver( 99 project = ProjectService.createProject(), 100 artifacts = artifacts, 101 additionalPriorityRepositories = additionalRepositories, 102 localRepositories = localRepositories, 103 explicitlyFetchInheritedDependencies = explicitlyFetchInheritedDependencies, 104 downloadObserver = downloadObserver 105 ).resolveArtifacts() 106 } 107 108 /** 109 * see docs for [ArtifactResolver.resolveArtifacts] 110 */ 111 private class SingleUseArtifactResolver( 112 private val project: Project, 113 private val artifacts: List<String>, 114 private val additionalPriorityRepositories: List<String>, 115 private val localRepositories: List<String>, 116 private val explicitlyFetchInheritedDependencies: Boolean, 117 private val downloadObserver: DownloadObserver?, 118 ) { 119 private val logger = logger("ArtifactResolver") 120 fun resolveArtifacts(): ArtifactsResolutionResult { 121 logger.info { 122 """-------------------------------------------------------------------------------- 123 Resolving artifacts: 124 ${artifacts.joinToString(separator = "\n - ", prefix = " - ")} 125 Local repositories: 126 ${localRepositories.joinToString(separator = "\n - ", prefix = " - ")} 127 High priority repositories: 128 ${ 129 if (additionalPriorityRepositories.isEmpty()) 130 " - None" 131 else 132 additionalPriorityRepositories.joinToString(separator = "\n - ", prefix = " - ") 133 } 134 --------------------------------------------------------------------------------""" 135 } 136 return withProxyServer( 137 downloadObserver = downloadObserver 138 ) { 139 logger.trace { 140 "Initialized proxy servers" 141 } 142 var dependenciesPassedVerification = true 143 144 project.dependencies.apply { 145 components.all(CustomMetadataRules::class.java) 146 attributesSchema.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) 147 .compatibilityRules.add(JarAndAarAreCompatible::class.java) 148 } 149 val completedComponentIds = mutableSetOf<String>() 150 val pendingComponentIds = mutableSetOf<String>().also { 151 it.addAll(artifacts) 152 } 153 val allResolvedArtifacts = mutableSetOf<ResolvedArtifactResult>() 154 do { 155 val dependencies = pendingComponentIds.map { 156 project.dependencies.create(it) 157 } 158 val resolvedArtifacts = createConfigurationsAndResolve(dependencies) 159 if (!resolvedArtifacts.dependenciesPassedVerification) { 160 dependenciesPassedVerification = false 161 } 162 allResolvedArtifacts.addAll(resolvedArtifacts.artifacts) 163 completedComponentIds.addAll(pendingComponentIds) 164 pendingComponentIds.clear() 165 val newComponentIds = resolvedArtifacts.artifacts.mapNotNull { 166 (it.id.componentIdentifier as? ModuleComponentIdentifier)?.toString() 167 }.filter { 168 !completedComponentIds.contains(it) && pendingComponentIds.add(it) 169 } 170 logger.trace { 171 "New component ids:\n${newComponentIds.joinToString("\n")}" 172 } 173 pendingComponentIds.addAll(newComponentIds) 174 } while (explicitlyFetchInheritedDependencies && pendingComponentIds.isNotEmpty()) 175 ArtifactsResolutionResult( 176 allResolvedArtifacts.toList(), 177 dependenciesPassedVerification 178 ) 179 }.also { result -> 180 val artifacts = result.artifacts 181 logger.trace { 182 "Resolved files: ${artifacts.size}" 183 } 184 check(artifacts.isNotEmpty()) { 185 "Didn't resolve any artifacts from $artifacts. Try --verbose for more " + 186 "information" 187 } 188 artifacts.forEach { artifact -> 189 logger.trace { 190 artifact.id.toString() 191 } 192 } 193 } 194 } 195 196 /** 197 * Creates configurations with the given list of dependencies and resolves them. 198 */ 199 private fun createConfigurationsAndResolve( 200 dependencies: List<Dependency> 201 ): ArtifactsResolutionResult { 202 val configurations = dependencies.flatMap { dep -> 203 buildList { 204 addAll(createApiConfigurations(dep)) 205 addAll(createRuntimeConfigurations(dep)) 206 addAll(createGradlePluginConfigurations(dep)) 207 addAll(createKmpConfigurations(dep)) 208 } 209 } 210 val resolutionList = configurations.map { configuration -> 211 resolveArtifacts(configuration) 212 } 213 val artifacts = resolutionList.flatMap { resolution -> 214 resolution.artifacts 215 } 216 val dependenciesPassedVerification = resolutionList.all { resolution -> 217 resolution.dependenciesPassedVerification 218 } 219 return ArtifactsResolutionResult(artifacts, dependenciesPassedVerification) 220 } 221 222 /** 223 * Resolves the given configuration. 224 * @param configuration The configuration to resolve 225 */ 226 private fun resolveArtifacts( 227 configuration: Configuration, 228 ): ArtifactsResolutionResult { 229 val artifacts = configuration.incoming.artifactView { 230 // We need to be lenient because we are requesting files that might not exist. 231 // For example source.jar or .asc. 232 it.lenient(true) 233 }.artifacts.artifacts.toList() 234 return ArtifactsResolutionResult(artifacts.toList(), dependenciesPassedVerification = false) 235 } 236 237 /** 238 * Creates proxy servers for remote repositories, adds them to the project and invokes 239 * the block. Once the block is complete, all proxy servers will be closed. 240 */ 241 private fun <T> withProxyServer( 242 downloadObserver: DownloadObserver? = null, 243 block: () -> T 244 ): T { 245 val repoUrls = additionalPriorityRepositories + listOf( 246 RepositoryHandler.GOOGLE_URL, 247 RepositoryHandler.MAVEN_CENTRAL_URL, 248 gradlePluginPortalRepo 249 ) 250 return MavenRepositoryProxy.startAll( 251 repositoryUrls = repoUrls, 252 downloadObserver = downloadObserver 253 ) { repoUris -> 254 project.repositories.clear() 255 // add local repositories first, they are not tracked 256 localRepositories.map { localRepo -> 257 project.repositories.maven { 258 it.url = URI(localRepo) 259 } 260 } 261 repoUris.map { mavenUri -> 262 project.repositories.maven { 263 it.url = mavenUri 264 it.isAllowInsecureProtocol = true 265 } 266 } 267 block() 268 } 269 } 270 271 private fun createConfiguration( 272 vararg dependencies: Dependency, 273 configure: Configuration.() -> Unit 274 ): Configuration { 275 val configuration = project.configurations.detachedConfiguration(*dependencies) 276 configuration.configure() 277 return configuration 278 } 279 280 /** 281 * Creates a configuration that has the same attributes as java runtime configuration 282 */ 283 private fun createRuntimeConfigurations( 284 vararg dependencies: Dependency 285 ): List<Configuration> { 286 return listOf( 287 LibraryElements.JAR to TargetJvmEnvironment.STANDARD_JVM, 288 LibraryElements.JAR to TargetJvmEnvironment.ANDROID, 289 "aar" to TargetJvmEnvironment.ANDROID, 290 ).map { (libraryElement, jvmEnvironment) -> 291 createConfiguration(*dependencies) { 292 attributes.apply { 293 attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, libraryElement) 294 attribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME) 295 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 296 attribute( 297 TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, 298 jvmEnvironment 299 ) 300 } 301 } 302 } 303 } 304 305 @Suppress("UnstableApiUsage") 306 private fun createGradlePluginConfigurations( 307 vararg dependencies: Dependency 308 ): List<Configuration> { 309 return listOf( 310 GradleVersion.current().baseVersion, 311 GradleVersion.current() 312 ).map { version -> 313 // taken from DefaultScriptHandler in gradle 314 createConfiguration(*dependencies) { 315 attributes.apply { 316 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 317 attribute( 318 TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, 319 TargetJvmEnvironment.STANDARD_JVM 320 ) 321 322 attribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API) 323 324 attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, LibraryElements.JAR) 325 326 attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) 327 attribute( 328 GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, 329 version.version 330 ) 331 } 332 } 333 } 334 } 335 336 /** 337 * Creates a configuration that has the same attributes as java api configuration 338 */ 339 private fun createApiConfigurations( 340 vararg dependencies: Dependency 341 ): List<Configuration> { 342 return listOf( 343 LibraryElements.JAR, 344 "aar" 345 ).map { libraryElement -> 346 createConfiguration(*dependencies) { 347 attributes.apply { 348 attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, libraryElement) 349 attribute(Usage.USAGE_ATTRIBUTE, Usage.JAVA_API) 350 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 351 } 352 } 353 } 354 } 355 356 /** 357 * Creates configuration that resembles the ones created by KMP. 358 * Note that, the configurations built by KMP depends on flags etc so to account for all of 359 * them, we create all variations with different attribute values. 360 */ 361 private fun createKmpConfigurations( 362 vararg dependencies: Dependency, 363 ): List<Configuration> { 364 val konanTargetConfigurations = SUPPORTED_KONAN_TARGETS.flatMap { konanTarget -> 365 KOTlIN_USAGES.map { kotlinUsage -> 366 createKonanTargetConfiguration( 367 dependencies = dependencies, 368 konanTarget = konanTarget, 369 kotlinUsage = kotlinUsage 370 ) 371 } 372 } 373 // jvm and android configurations 374 val jvmAndAndroid = KOTlIN_USAGES.flatMap { kotlinUsage -> 375 listOf( 376 "jvm", 377 TargetJvmEnvironment.ANDROID 378 ).map { targetJvm -> 379 createConfiguration(*dependencies) { 380 attributes.apply { 381 attribute(Usage.USAGE_ATTRIBUTE, kotlinUsage) 382 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 383 attribute( 384 TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, 385 targetJvm 386 ) 387 } 388 } 389 } 390 } 391 392 val wasmJs = KOTlIN_USAGES.map { kotlinUsage -> 393 createConfiguration(*dependencies) { 394 attributes.apply { 395 attribute(KotlinPlatformType.attribute, KotlinPlatformType.wasm) 396 attribute(Usage.USAGE_ATTRIBUTE, kotlinUsage) 397 attribute( 398 KotlinWasmTargetAttribute.wasmTargetAttribute, 399 KotlinWasmTargetAttribute.js 400 ) 401 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 402 attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, "non-jvm") 403 } 404 } 405 } 406 407 val js = 408 KOTlIN_USAGES.map { kotlinUsage -> 409 createConfiguration(*dependencies) { 410 attributes.apply { 411 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 412 attribute( 413 TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, 414 "non-jvm" 415 ) 416 attribute(Usage.USAGE_ATTRIBUTE, kotlinUsage) 417 attribute( 418 KotlinJsCompilerAttribute.jsCompilerAttribute, 419 KotlinJsCompilerAttribute.ir 420 ) 421 attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) 422 } 423 } 424 } 425 426 val commonArtifacts = KOTlIN_USAGES.map { kotlinUsage -> 427 createConfiguration(*dependencies) { 428 attributes.apply { 429 attribute(Usage.USAGE_ATTRIBUTE, kotlinUsage) 430 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 431 attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) 432 } 433 } 434 } 435 return jvmAndAndroid + wasmJs + js + konanTargetConfigurations + commonArtifacts 436 } 437 438 private fun createKonanTargetConfiguration( 439 vararg dependencies: Dependency, 440 konanTarget: KonanTarget, 441 kotlinUsage: String 442 ): Configuration { 443 return createConfiguration(*dependencies) { 444 attributes.apply { 445 attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) 446 attribute(Usage.USAGE_ATTRIBUTE, kotlinUsage) 447 attribute(KotlinNativeTarget.konanTargetAttribute, konanTarget.name) 448 attribute(Category.CATEGORY_ATTRIBUTE, Category.LIBRARY) 449 attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, "non-jvm") 450 } 451 } 452 } 453 454 private fun <T : Named> AttributeContainer.attribute( 455 key: Attribute<T>, 456 value: String 457 ) = attribute( 458 key, project.objects.named( 459 key.type, 460 value 461 ) 462 ) 463 464 companion object { 465 /** 466 * Kotlin usage attributes that we want to pull. 467 */ 468 private val KOTlIN_USAGES = listOf( 469 KotlinUsages.KOTLIN_API, 470 KotlinUsages.KOTLIN_METADATA, 471 KotlinUsages.KOTLIN_CINTEROP, 472 KotlinUsages.KOTLIN_RUNTIME, 473 KotlinUsages.KOTLIN_SOURCES 474 ) 475 } 476 } 477 } 478