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