1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import groovy.json.JsonOutput 6import org.gradle.api.DefaultTask 7import org.gradle.api.tasks.TaskAction 8 9import java.util.regex.Pattern 10import java.util.concurrent.Executors 11import java.util.concurrent.TimeUnit 12 13/** 14 * Task to download dependencies specified in {@link ChromiumPlugin} and configure the 15 * Chromium build to integrate them. Used by declaring a new task in a {@code build.gradle} 16 * file: 17 * <pre> 18 * task myTaskName(type: BuildConfigGenerator) { 19 * repositoryPath 'build_files_and_repository_location/' 20 * } 21 * </pre> 22 */ 23class BuildConfigGenerator extends DefaultTask { 24 private static final BUILD_GN_TOKEN_START = "# === Generated Code Start ===" 25 private static final BUILD_GN_TOKEN_END = "# === Generated Code End ===" 26 private static final BUILD_GN_GEN_PATTERN = Pattern.compile( 27 "${BUILD_GN_TOKEN_START}(.*)${BUILD_GN_TOKEN_END}", 28 Pattern.DOTALL) 29 private static final BUILD_GN_GEN_REMINDER = "# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.\n" 30 private static final DEPS_TOKEN_START = "# === ANDROID_DEPS Generated Code Start ===" 31 private static final DEPS_TOKEN_END = "# === ANDROID_DEPS Generated Code End ===" 32 private static final DEPS_GEN_PATTERN = Pattern.compile( 33 "${DEPS_TOKEN_START}(.*)${DEPS_TOKEN_END}", 34 Pattern.DOTALL) 35 private static final DOWNLOAD_DIRECTORY_NAME = "libs" 36 37 // Some libraries are hosted in Chromium's //third_party directory. This is a mapping between 38 // them so they can be used instead of android_deps pulling in its own copy. 39 public static final def EXISTING_LIBS = [ 40 'com_ibm_icu_icu4j': '//third_party/icu4j:icu4j_java', 41 'com_almworks_sqlite4java_sqlite4java': '//third_party/sqlite4java:sqlite4java_java', 42 'com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework': 43 '//third_party/accessibility_test_framework:accessibility_test_framework_java', 44 'junit_junit': '//third_party/junit:junit', 45 'org_bouncycastle_bcprov_jdk15on': '//third_party/bouncycastle:bouncycastle_java', 46 'org_hamcrest_hamcrest_core': '//third_party/hamcrest:hamcrest_core_java', 47 'org_hamcrest_hamcrest_integration': '//third_party/hamcrest:hamcrest_integration_java', 48 'org_hamcrest_hamcrest_library': '//third_party/hamcrest:hamcrest_library_java', 49 ] 50 51 /** 52 * Directory where the artifacts will be downloaded and where files will be generated. 53 * Note: this path is specified as relative to the chromium source root, and must be normalised 54 * to an absolute path before being used, as Groovy would base relative path where the script 55 * is being executed. 56 */ 57 String repositoryPath 58 59 /** 60 * Relative path to the Chromium source root from the build.gradle file. 61 */ 62 String chromiumSourceRoot 63 64 /** 65 * Name of the cipd root package. 66 */ 67 String cipdBucket 68 69 /** 70 * Prefix of path to strip before uploading to CIPD. 71 */ 72 String stripFromCipdPath 73 74 /** 75 * Skips license file import. 76 */ 77 boolean skipLicenses 78 79 /** 80 * Only pull play services targets into BUILD.gn file. 81 * If the play services target depends on a non-play services target, it will use the target in 82 * //third_party/android_deps/BUILD.gn. 83 */ 84 boolean onlyPlayServices 85 86 /** 87 * Array with visibility for targets which are not listed in build.gradle 88 */ 89 String[] internalTargetVisibility 90 91 /** 92 * Whether to use dedicated directory for androidx dependencies. 93 */ 94 boolean useDedicatedAndroidxDir 95 96 @TaskAction 97 void main() { 98 skipLicenses = skipLicenses || project.hasProperty("skipLicenses") 99 useDedicatedAndroidxDir |= project.hasProperty("useDedicatedAndroidxDir") 100 101 def graph = new ChromiumDepGraph(project: project, skipLicenses: skipLicenses) 102 def normalisedRepoPath = normalisePath(repositoryPath) 103 104 // 1. Parse the dependency data 105 graph.collectDependencies() 106 107 // 2. Import artifacts into the local repository 108 def dependencyDirectories = [] 109 def downloadExecutor = Executors.newCachedThreadPool() 110 graph.dependencies.values().each { dependency -> 111 if (excludeDependency(dependency)) { 112 return 113 } 114 logger.debug "Processing ${dependency.name}: \n${jsonDump(dependency)}" 115 def depDir = "${DOWNLOAD_DIRECTORY_NAME}/${dependency.id}" 116 def absoluteDepDir = "${normalisedRepoPath}/${depDir}" 117 118 dependencyDirectories.add(depDir) 119 120 if (new File("${absoluteDepDir}/${dependency.fileName}").exists()) { 121 getLogger().quiet("${dependency.id} exists, skipping.") 122 return 123 } 124 125 project.copy { 126 from dependency.artifact.file 127 into absoluteDepDir 128 } 129 130 new File("${absoluteDepDir}/README.chromium").write(makeReadme(dependency)) 131 new File("${absoluteDepDir}/cipd.yaml").write(makeCipdYaml(dependency, cipdBucket, 132 stripFromCipdPath, 133 repositoryPath)) 134 new File("${absoluteDepDir}/OWNERS").write(makeOwners()) 135 if (!skipLicenses) { 136 if (!dependency.licensePath?.trim()?.isEmpty()) { 137 new File("${absoluteDepDir}/LICENSE").write( 138 new File("${normalisedRepoPath}/${dependency.licensePath}").text) 139 } else if (!dependency.licenseUrl?.trim()?.isEmpty()) { 140 File destFile = new File("${absoluteDepDir}/LICENSE") 141 downloadExecutor.submit { 142 downloadFile(dependency.id, dependency.licenseUrl, destFile) 143 if (destFile.text.contains("<html")) { 144 throw new RuntimeException("Found HTML in LICENSE file. Please add an " 145 + "override to ChromiumDepGraph.groovy for ${dependency.id}.") 146 } 147 } 148 } else { 149 getLogger().warn("Missing license for ${dependency.id}.") 150 getLogger().warn("License Name was: ${dependency.licenseName}") 151 } 152 } 153 } 154 downloadExecutor.shutdown() 155 downloadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); 156 157 // 3. Generate the root level build files 158 updateBuildTargetDeclaration(graph, repositoryPath, normalisedRepoPath) 159 updateDepsDeclaration(graph, cipdBucket, stripFromCipdPath, repositoryPath, 160 "${normalisedRepoPath}/../../DEPS") 161 dependencyDirectories.sort { path1, path2 -> return path1.compareTo(path2) } 162 updateReadmeReferenceFile(dependencyDirectories, 163 "${normalisedRepoPath}/additional_readme_paths.json") 164 } 165 166 private void updateBuildTargetDeclaration(ChromiumDepGraph depGraph, 167 String repositoryPath, String normalisedRepoPath) { 168 File buildFile = new File("${normalisedRepoPath}/BUILD.gn"); 169 def sb = new StringBuilder() 170 171 // Comparator to sort the dependency in alphabetical order, with the visible ones coming 172 // before all the internal ones. 173 def dependencyComparator = { dependency1, dependency2 -> 174 def visibilityResult = Boolean.compare(dependency1.visible, dependency2.visible) 175 if (visibilityResult != 0) return -visibilityResult 176 177 return dependency1.id.compareTo(dependency2.id) 178 } 179 180 depGraph.dependencies.values().sort(dependencyComparator).each { dependency -> 181 if (excludeDependency(dependency) || !dependency.generateTarget) { 182 return 183 } 184 185 def targetName = translateTargetName(dependency.id) + "_java" 186 if (useDedicatedAndroidxDir && targetName.startsWith("androidx_")) { 187 assert dependency.extension == 'jar' || dependency.extension == 'aar' 188 sb.append(""" 189 java_group("${targetName}") { 190 deps = [ \"//third_party/androidx:${targetName}\" ] 191 """.stripIndent()) 192 if (dependency.testOnly) sb.append(" testonly = true\n") 193 sb.append("}\n\n") 194 return 195 } 196 197 def depsStr = "" 198 if (!dependency.children.isEmpty()) { 199 dependency.children.each { childDep -> 200 def dep = depGraph.dependencies[childDep] 201 if (dep.exclude) { 202 return 203 } 204 // Special case: If a child dependency is an existing lib, rather than skipping 205 // it, replace the child dependency with the existing lib. 206 def existingLib = EXISTING_LIBS.get(dep.id) 207 def depTargetName = translateTargetName(dep.id) + "_java" 208 if (existingLib != null) { 209 depsStr += "\"${existingLib}\"," 210 } else if (excludeDependency(dep)) { 211 depsStr += "\"//third_party/android_deps:${depTargetName}\"," 212 } else if (dep.id == "com_google_android_material_material") { 213 // Material design is pulled in via doubledown, should 214 // use the variable instead of the real target. 215 depsStr += "\"\\\$material_design_target\"," 216 } else { 217 depsStr += "\":${depTargetName}\"," 218 } 219 } 220 } 221 222 def libPath = "${DOWNLOAD_DIRECTORY_NAME}/${dependency.id}" 223 sb.append(BUILD_GN_GEN_REMINDER) 224 if (dependency.extension == 'jar') { 225 sb.append("""\ 226 java_prebuilt("${targetName}") { 227 jar_path = "${libPath}/${dependency.fileName}" 228 output_name = "${dependency.id}" 229 """.stripIndent()) 230 if (dependency.supportsAndroid) { 231 sb.append(" supports_android = true\n") 232 } else { 233 // Save some time by not validating classpaths of desktop 234 // .jars. Also required to break a dependency cycle for 235 // errorprone. 236 sb.append(" enable_bytecode_checks = false\n") 237 } 238 } else if (dependency.extension == 'aar') { 239 sb.append("""\ 240 android_aar_prebuilt("${targetName}") { 241 aar_path = "${libPath}/${dependency.fileName}" 242 info_path = "${libPath}/${dependency.id}.info" 243 """.stripIndent()) 244 } else { 245 throw new IllegalStateException("Dependency type should be JAR or AAR") 246 } 247 248 sb.append(generateBuildTargetVisibilityDeclaration(dependency)) 249 250 if (dependency.testOnly) sb.append(" testonly = true\n") 251 if (!depsStr.empty) sb.append(" deps = [${depsStr}]\n") 252 addSpecialTreatment(sb, dependency.id, dependency.extension) 253 254 sb.append("}\n\n") 255 } 256 257 def out = "${BUILD_GN_TOKEN_START}\n${sb.toString()}\n${BUILD_GN_TOKEN_END}" 258 if (buildFile.exists()) { 259 def matcher = BUILD_GN_GEN_PATTERN.matcher(buildFile.getText()) 260 if (!matcher.find()) throw new IllegalStateException("BUILD.gn insertion point not found.") 261 out = matcher.replaceFirst(out) 262 } 263 buildFile.write(out) 264 } 265 266 public static String translateTargetName(String targetName) { 267 if (isPlayServicesTarget(targetName)) { 268 return targetName.replaceFirst("com_", "").replaceFirst("android_gms_", "") 269 } 270 return targetName 271 } 272 273 public static boolean isPlayServicesTarget(String dependencyId) { 274 // Firebase has historically been treated as a part of play services, so it counts here for 275 // backwards compatibility. Datatransport is new as of 2019 and is used by many play 276 // services libraries. 277 return Pattern.matches(".*google.*(play_services|firebase|datatransport).*", dependencyId) 278 } 279 280 public String generateBuildTargetVisibilityDeclaration( 281 ChromiumDepGraph.DependencyDescription dependency) { 282 def sb = new StringBuilder() 283 switch (dependency.id) { 284 case 'com_google_android_material_material': 285 sb.append(' # Material Design is pulled in via Doubledown, thus this target should not\n') 286 sb.append(' # be directly depended on. Please use :material_design_java instead.\n') 287 sb.append(generateInternalTargetVisibilityLine()) 288 return sb.toString() 289 case 'com_google_protobuf_protobuf_javalite': 290 sb.append(' # Protobuf runtime is pulled in via Doubledown, thus this target should not\n') 291 sb.append(' # be directly depended on. Please use :protobuf_lite_runtime_java instead.\n') 292 sb.append(generateInternalTargetVisibilityLine()) 293 return sb.toString() 294 } 295 296 if (!dependency.visible) { 297 sb.append(' # To remove visibility constraint, add this dependency to\n') 298 sb.append(" # //${repositoryPath}/build.gradle.\n") 299 sb.append(generateInternalTargetVisibilityLine()) 300 } 301 return sb.toString() 302 } 303 304 private String generateInternalTargetVisibilityLine() { 305 return 'visibility = ' + makeGnArray(internalTargetVisibility) + '\n' 306 } 307 308 private static void addSpecialTreatment(StringBuilder sb, String dependencyId, String dependencyExtension) { 309 if (isPlayServicesTarget(dependencyId)) { 310 if (Pattern.matches(".*cast_framework.*", dependencyId)) { 311 sb.append(' # Removing all resources from cast framework as they are unused bloat.\n') 312 sb.append(' # Can only safely remove them when R8 will strip the path that accesses them.\n') 313 sb.append(' strip_resources = !is_java_debug\n') 314 } else { 315 sb.append(' # Removing drawables from GMS .aars as they are unused bloat.\n') 316 sb.append(' strip_drawables = true\n') 317 } 318 } 319 if (dependencyId.startsWith('org_robolectric')) { 320 // Skip platform checks since it depends on 321 // accessibility_test_framework_java which requires_android. 322 sb.append(' bypass_platform_checks = true\n') 323 } 324 if (dependencyExtension == "aar" && 325 (dependencyId.startsWith('androidx') || 326 dependencyId.startsWith('com_android_support'))) { 327 // androidx and com_android_support libraries have duplicate resources such as 'primary_text_default_material_dark'. 328 sb.append(' resource_overlay = true\n') 329 } 330 switch(dependencyId) { 331 case 'androidx_annotation_annotation': 332 sb.append(' # https://crbug.com/989505\n') 333 sb.append(' jar_excluded_patterns = ["META-INF/proguard/*"]\n') 334 break 335 case 'androidx_core_core': 336 sb.append('\n') 337 sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') 338 sb.append(' ignore_aidl = true\n') 339 sb.append('\n') 340 sb.append(' # Manifest and proguard config have just one entry: Adding (and -keep\'ing\n') 341 sb.append(' # android:appComponentFactory="androidx.core.app.CoreComponentFactory"\n') 342 sb.append(' # Chrome does not use this feature and it causes a scary stack trace to be\n') 343 sb.append(' # shown when incremental_install=true.\n') 344 sb.append(' ignore_manifest = true\n') 345 sb.append(' ignore_proguard_configs = true\n') 346 break 347 case 'androidx_fragment_fragment': 348 sb.append("""\ 349 | deps += [ "//third_party/android_deps/local_modifications/androidx_fragment_fragment:androidx_fragment_fragment_prebuilt_java" ] 350 | # Omit this file since we use our own copy, included above. 351 | # We can remove this once we migrate to AndroidX master for all libraries. 352 | jar_excluded_patterns = [ 353 | "androidx/fragment/app/DialogFragment*", 354 | ] 355 | 356 | ignore_proguard_configs = true 357 | 358 | bytecode_rewriter_target = "//build/android/bytecode:fragment_activity_replacer" 359 |""".stripMargin()) 360 break 361 case 'androidx_media_media': 362 case 'androidx_versionedparcelable_versionedparcelable': 363 case 'com_android_support_support_media_compat': 364 sb.append('\n') 365 sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') 366 sb.append(' ignore_aidl = true\n') 367 break 368 case 'androidx_test_uiautomator_uiautomator': 369 sb.append(' deps = [":androidx_test_runner_java"]\n') 370 break 371 case 'androidx_mediarouter_mediarouter': 372 sb.append(' # https://crbug.com/1000382\n') 373 sb.append(' proguard_configs = ["androidx_mediarouter.flags"]\n') 374 break 375 case 'androidx_transition_transition': 376 // Not specified in the POM, compileOnly dependency not supposed to be used unless 377 // the library is present: b/70887421 378 sb.append(' deps += [":androidx_fragment_fragment_java"]\n') 379 break 380 case 'androidx_vectordrawable_vectordrawable': 381 case 'com_android_support_support_vector_drawable': 382 // Target has AIDL, but we don't support it yet: http://crbug.com/644439 383 sb.append(' create_srcjar = false\n') 384 break 385 case 'android_arch_lifecycle_runtime': 386 case 'android_arch_lifecycle_viewmodel': 387 case 'androidx_lifecycle_lifecycle_runtime': 388 case 'androidx_lifecycle_lifecycle_viewmodel': 389 sb.append('\n') 390 sb.append(' # https://crbug.com/887942#c1\n') 391 sb.append(' ignore_proguard_configs = true\n') 392 break 393 case 'com_android_support_coordinatorlayout': 394 case 'androidx_coordinatorlayout_coordinatorlayout': 395 sb.append('\n') 396 sb.append(' # Reduce binary size. https:crbug.com/954584\n') 397 sb.append(' ignore_proguard_configs = true\n') 398 break 399 case 'com_google_android_material_material': 400 sb.append('\n') 401 sb.append(' # Reduce binary size. https:crbug.com/954584\n') 402 sb.append(' ignore_proguard_configs = true\n') 403 break 404 case 'com_android_support_support_annotations': 405 sb.append(' # https://crbug.com/989505\n') 406 sb.append(' jar_excluded_patterns = ["META-INF/proguard/*"]\n') 407 break 408 case 'com_android_support_support_compat': 409 sb.append('\n') 410 sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') 411 sb.append(' ignore_aidl = true\n') 412 sb.append(' ignore_manifest = true\n') 413 // Necessary to not have duplicate classes after jetification. 414 // They can be removed when we no longer jetify targets 415 // that depend on com_android_support_support_compat. 416 sb.append("""\ 417 | jar_excluded_patterns = [ 418 | "android/support/v4/graphics/drawable/IconCompatParcelizer.class", 419 | "android/support/v4/os/ResultReceiver*", 420 | "androidx/core/graphics/drawable/IconCompatParcelizer.class", 421 | "androidx/core/internal/package-info.class", 422 | "android/support/v4/app/INotificationSideChannel*", 423 | "android/support/v4/os/IResultReceiver*", 424 | ] 425 | 426 |""".stripMargin()) 427 break 428 case 'com_android_support_transition': 429 // Not specified in the POM, compileOnly dependency not supposed to be used unless 430 // the library is present: b/70887421 431 sb.append(' deps += [":com_android_support_support_fragment_java"]\n') 432 break 433 case 'com_android_support_versionedparcelable': 434 sb.append('\n') 435 sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') 436 sb.append(' ignore_aidl = true\n') 437 // Necessary to not have identical classes after jetification. 438 // They can be removed when we no longer jetify targets 439 // that depend on com_android_support_versionedparcelable. 440 sb.append("""\ 441 | jar_excluded_patterns = [ 442 | "android/support/v4/graphics/drawable/IconCompat.class", 443 | "androidx/*", 444 | ] 445 | 446 |""".stripMargin()) 447 break 448 case 'com_google_ar_core': 449 // Target .aar file contains .so libraries that need to be extracted, 450 // and android_aar_prebuilt template will fail if it's not set explictly. 451 sb.append(' extract_native_libraries = true\n') 452 break 453 case 'com_google_guava_guava': 454 sb.append('\n') 455 sb.append(' # Need to exclude class and replace it with class library as\n') 456 sb.append(' # com_google_guava_listenablefuture has support_androids=true.\n') 457 sb.append(' deps += [":com_google_guava_listenablefuture_java"]\n') 458 sb.append(' jar_excluded_patterns = ["*/ListenableFuture.class"]\n') 459 break 460 case 'com_google_code_findbugs_jsr305': 461 case 'com_google_errorprone_error_prone_annotations': 462 case 'com_google_guava_failureaccess': 463 case 'com_google_j2objc_j2objc_annotations': 464 case 'com_google_guava_listenablefuture': 465 case 'com_googlecode_java_diff_utils_diffutils': 466 case 'org_codehaus_mojo_animal_sniffer_annotations': 467 sb.append('\n') 468 sb.append(' # Needed to break dependency cycle for errorprone_plugin_java.\n') 469 sb.append(' enable_bytecode_checks = false\n') 470 break 471 case 'androidx_test_rules': 472 // Target needs Android SDK deps which exist in third_party/android_sdk. 473 sb.append("""\ 474 | deps += [ 475 | "//third_party/android_sdk:android_test_base_java", 476 | "//third_party/android_sdk:android_test_mock_java", 477 | "//third_party/android_sdk:android_test_runner_java", 478 | ] 479 | 480 |""".stripMargin()) 481 break 482 case 'androidx_test_espresso_espresso_contrib': 483 case 'androidx_test_espresso_espresso_web': 484 case 'androidx_window_window': 485 sb.append(' enable_bytecode_checks = false\n') 486 break 487 case 'net_sf_kxml_kxml2': 488 sb.append(' # Target needs to exclude *xmlpull* files as already included in Android SDK.\n') 489 sb.append(' jar_excluded_patterns = [ "*xmlpull*" ]\n') 490 break 491 case 'androidx_preference_preference': 492 sb.append("""\ 493 | deps += [ "//third_party/android_deps/local_modifications/androidx_preference_preference:androidx_preference_preference_prebuilt_java" ] 494 | # Omit these files since we use our own copy from AndroidX master, included above. 495 | # We can remove this once we migrate to AndroidX master for all libraries. 496 | jar_excluded_patterns = [ 497 | "androidx/preference/PreferenceDialogFragmentCompat*", 498 | "androidx/preference/PreferenceFragmentCompat*", 499 | ] 500 | 501 | bytecode_rewriter_target = "//build/android/bytecode:fragment_activity_replacer" 502 |""".stripMargin()) 503 // Replace broad library -keep rules with a more limited set in 504 // chrome/android/java/proguard.flags instead. 505 sb.append(' ignore_proguard_configs = true\n') 506 break 507 case 'com_google_android_gms_play_services_basement': 508 sb.append(' # https://crbug.com/989505\n') 509 sb.append(' jar_excluded_patterns = ["META-INF/proguard/*"]\n') 510 // Deprecated deps jar but still needed by play services basement. 511 sb.append(' input_jars_paths=["\\$android_sdk/optional/org.apache.http.legacy.jar"]\n') 512 sb.append(' bytecode_rewriter_target = "//build/android/bytecode:fragment_activity_replacer"\n') 513 break 514 case 'com_google_android_gms_play_services_maps': 515 sb.append(' # Ignore the dependency to org.apache.http.legacy. See crbug.com/1084879.\n') 516 sb.append(' ignore_manifest = true\n') 517 break 518 case 'com_google_protobuf_protobuf_javalite': 519 sb.append(' # Prebuilt protos in the runtime library.\n') 520 sb.append(' # If you want to use these protos, you should create a proto_java_library\n') 521 sb.append(' # target for them. See crbug.com/1103399 for discussion.\n') 522 sb.append(' jar_excluded_patterns = [\n') 523 sb.append(' "com/google/protobuf/Any*",\n') 524 sb.append(' "com/google/protobuf/Api*",\n') 525 sb.append(' "com/google/protobuf/Duration*",\n') 526 sb.append(' "com/google/protobuf/Empty*",\n') 527 sb.append(' "com/google/protobuf/FieldMask*",\n') 528 sb.append(' "com/google/protobuf/SourceContext*",\n') 529 sb.append(' "com/google/protobuf/Struct\\\\\\$1.class",\n') 530 sb.append(' "com/google/protobuf/Struct\\\\\\$Builder.class",\n') 531 sb.append(' "com/google/protobuf/Struct.class",\n') 532 sb.append(' "com/google/protobuf/StructOrBuilder.class",\n') 533 sb.append(' "com/google/protobuf/StructProto.class",\n') 534 sb.append(' "com/google/protobuf/Timestamp*",\n') 535 sb.append(' "com/google/protobuf/Type*",\n') 536 sb.append(' "com/google/protobuf/Wrappers*",\n') 537 sb.append(' ]') 538 break 539 case 'androidx_webkit_webkit': 540 sb.append(' visibility = [\n') 541 sb.append(' "//android_webview/tools/system_webview_shell:*",\n') 542 sb.append(' "//third_party/android_deps:*"\n') 543 sb.append(' ]') 544 break 545 case 'com_android_tools_desugar_jdk_libs_configuration': 546 sb.append(' enable_bytecode_checks = false\n') 547 break 548 } 549 } 550 551 private void updateDepsDeclaration(ChromiumDepGraph depGraph, String cipdBucket, 552 String stripFromCipdPath, String repoPath, 553 String depsFilePath) { 554 File depsFile = new File(depsFilePath) 555 def sb = new StringBuilder() 556 // Note: The string we're inserting is nested 1 level, hence the 2 leading spaces. Same 557 // applies to the multiline package declaration string below. 558 sb.append(" # Generated by //third_party/android_deps/fetch_all.py") 559 560 // Comparator to sort the dependencies in alphabetical order. 561 def dependencyComparator = { dependency1, dependency2 -> 562 return dependency1.id.compareTo(dependency2.id) 563 } 564 565 depGraph.dependencies.values().sort(dependencyComparator).each { dependency -> 566 if (excludeDependency(dependency)) { 567 return 568 } 569 def depPath = "${DOWNLOAD_DIRECTORY_NAME}/${dependency.id}" 570 def cipdPath = "${cipdBucket}/" 571 if (stripFromCipdPath) { 572 assert repoPath.startsWith(stripFromCipdPath) 573 cipdPath += repoPath.substring(stripFromCipdPath.length() + 1) 574 } else { 575 cipdPath += repoPath 576 } 577 // CIPD does not allow uppercase in names. 578 cipdPath += "/${depPath}".toLowerCase() 579 sb.append("""\ 580 | 581 | 'src/${repoPath}/${depPath}': { 582 | 'packages': [ 583 | { 584 | 'package': '${cipdPath}', 585 | 'version': 'version:${dependency.version}-${dependency.cipdSuffix}', 586 | }, 587 | ], 588 | 'condition': 'checkout_android', 589 | 'dep_type': 'cipd', 590 | }, 591 |""".stripMargin()) 592 } 593 594 def matcher = DEPS_GEN_PATTERN.matcher(depsFile.getText()) 595 if (!matcher.find()) throw new IllegalStateException("DEPS insertion point not found.") 596 depsFile.write(matcher.replaceFirst("${DEPS_TOKEN_START}\n${sb}\n ${DEPS_TOKEN_END}")) 597 } 598 599 private static void updateReadmeReferenceFile(List<String> directories, String readmePath) { 600 File refFile = new File(readmePath) 601 refFile.write(JsonOutput.prettyPrint(JsonOutput.toJson(directories)) + "\n") 602 } 603 604 public boolean excludeDependency(ChromiumDepGraph.DependencyDescription dependency) { 605 def onlyAndroidx = (repositoryPath == "third_party/androidx") 606 return dependency.exclude || EXISTING_LIBS.get(dependency.id) != null || 607 (onlyPlayServices && !isPlayServicesTarget(dependency.id)) || 608 (onlyAndroidx && !dependency.id.startsWith("androidx_")) || 609 (useDedicatedAndroidxDir && dependency.id == "androidx_legacy_legacy_preference_v14") 610 } 611 612 private String normalisePath(String pathRelativeToChromiumRoot) { 613 return project.file("${chromiumSourceRoot}/${pathRelativeToChromiumRoot}").absolutePath 614 } 615 616 private static String makeGnArray(String[] values) { 617 def sb = new StringBuilder(); 618 sb.append("["); 619 for (String value : values) { 620 sb.append("\""); 621 sb.append(value); 622 sb.append("\","); 623 } 624 sb.replace(sb.length() - 1, sb.length(), "]"); 625 return sb.toString(); 626 } 627 628 static String makeOwners() { 629 // Make it easier to upgrade existing dependencies without full third_party review. 630 return "file://third_party/android_deps/OWNERS" 631 } 632 633 static String makeReadme(ChromiumDepGraph.DependencyDescription dependency) { 634 def licenseString 635 // Replace license names with ones that are whitelisted, see third_party/PRESUBMIT.py 636 switch (dependency.licenseName) { 637 case "The Apache Software License, Version 2.0": 638 licenseString = "Apache Version 2.0" 639 break 640 default: 641 licenseString = dependency.licenseName 642 } 643 644 def securityCritical = dependency.supportsAndroid && dependency.isShipped 645 def licenseFile = dependency.isShipped? "LICENSE" : "NOT_SHIPPED" 646 647 return """\ 648 Name: ${dependency.displayName} 649 Short Name: ${dependency.name} 650 URL: ${dependency.url} 651 Version: ${dependency.version} 652 License: ${licenseString} 653 License File: ${licenseFile} 654 Security Critical: ${securityCritical? "yes" : "no"} 655 ${dependency.licenseAndroidCompatible? "License Android Compatible: yes" : ""} 656 Description: 657 ${dependency.description} 658 659 Local Modifications: 660 No modifications. 661 """.stripIndent() 662 } 663 664 static String makeCipdYaml(ChromiumDepGraph.DependencyDescription dependency, String cipdBucket, 665 String stripFromCipdPath, String repoPath) { 666 if (!stripFromCipdPath) { 667 stripFromCipdPath = '' 668 } 669 def cipdVersion = "${dependency.version}-${dependency.cipdSuffix}" 670 def cipdPath = "${cipdBucket}/" 671 if (stripFromCipdPath) { 672 assert repoPath.startsWith(stripFromCipdPath) 673 cipdPath += repoPath.substring(stripFromCipdPath.length() + 1) 674 } else { 675 cipdPath += repoPath 676 } 677 // CIPD does not allow uppercase in names. 678 cipdPath += "/${DOWNLOAD_DIRECTORY_NAME}/" + dependency.id.toLowerCase() 679 680 // NOTE: the fetch_all.py script relies on the format of this file! 681 // See fetch_all.py:GetCipdPackageInfo(). 682 // NOTE: keep the copyright year 2018 until this generated code is 683 // updated, avoiding annual churn of all cipd.yaml files. 684 def str = """\ 685 # Copyright 2018 The Chromium Authors. All rights reserved. 686 # Use of this source code is governed by a BSD-style license that can be 687 # found in the LICENSE file. 688 689 # To create CIPD package run the following command. 690 # cipd create --pkg-def cipd.yaml -tag version:${cipdVersion} 691 package: ${cipdPath} 692 description: "${dependency.displayName}" 693 data: 694 - file: ${dependency.fileName} 695 """.stripIndent() 696 697 return str 698 } 699 700 static String jsonDump(obj) { 701 return JsonOutput.prettyPrint(JsonOutput.toJson(obj)) 702 } 703 704 static void printDump(obj) { 705 getLogger().warn(jsonDump(obj)) 706 } 707 708 static HttpURLConnection connectAndFollowRedirects(String id, String sourceUrl) { 709 URL urlObj = new URL(sourceUrl) 710 HttpURLConnection connection 711 for (int i = 0; i < 10; ++i) { 712 // Several deps use this URL for their license, but it just points to license 713 // *template*. Generally the actual license can be found in the source code. 714 if (sourceUrl.contains("://opensource.org/licenses")) { 715 throw new RuntimeException("Found templated license URL for dependency " 716 + id + ": " + sourceUrl 717 + ". You will need to edit PROPERTY_OVERRIDES for this dep.") 718 } 719 connection = urlObj.openConnection() 720 switch (connection.getResponseCode()) { 721 case HttpURLConnection.HTTP_MOVED_PERM: 722 case HttpURLConnection.HTTP_MOVED_TEMP: 723 String location = connection.getHeaderField("Location"); 724 urlObj = new URL(urlObj, location); 725 continue 726 case HttpURLConnection.HTTP_OK: 727 return connection 728 default: 729 throw new RuntimeException( 730 "Url had statusCode=" + connection.getResponseCode() + ": " + sourceUrl) 731 } 732 } 733 throw new RuntimeException("Url in redirect loop: " + sourceUrl) 734 } 735 736 static void downloadFile(String id, String sourceUrl, File destinationFile) { 737 destinationFile.withOutputStream { out -> 738 try { 739 out << connectAndFollowRedirects(id, sourceUrl).getInputStream() 740 } catch (Exception e) { 741 throw new RuntimeException("Failed to fetch license for " + id + " url: " + sourceUrl, e) 742 } 743 } 744 } 745 746} 747