| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import groovy.json.JsonOutput |
| import groovy.text.SimpleTemplateEngine |
| import groovy.text.Template |
| import groovy.transform.SourceURI |
| import org.gradle.api.DefaultTask |
| import org.gradle.api.Project |
| import org.gradle.api.tasks.Input |
| import org.gradle.api.tasks.TaskAction |
| |
| import java.nio.file.Path |
| import java.nio.file.Paths |
| import java.util.concurrent.ConcurrentHashMap |
| import java.util.concurrent.ConcurrentMap |
| import java.util.concurrent.Executors |
| import java.util.concurrent.ExecutorService |
| import java.util.concurrent.Future |
| import java.util.regex.Pattern |
| import java.util.regex.Matcher |
| |
| /** |
| * Task to download dependencies specified in {@link ChromiumPlugin} and configure the Chromium build to integrate them. |
| * Used by declaring a new task in a {@code build.gradle} file: |
| * <pre> |
| * task myTaskName(type: BuildConfigGenerator) { |
| * repositoryPath 'build_files_and_repository_location/' |
| * } |
| * </pre> |
| */ |
| class BuildConfigGenerator extends DefaultTask { |
| |
| private static final String BUILD_GN_TOKEN_START = '# === Generated Code Start ===' |
| private static final String BUILD_GN_TOKEN_END = '# === Generated Code End ===' |
| private static final Pattern BUILD_GN_GEN_PATTERN = Pattern.compile( |
| "${BUILD_GN_TOKEN_START}(.*)${BUILD_GN_TOKEN_END}", Pattern.DOTALL) |
| private static final String GEN_REMINDER = |
| '# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.\n' |
| private static final String DEPS_TOKEN_START = '# === ANDROID_DEPS Generated Code Start ===' |
| private static final String DEPS_TOKEN_END = '# === ANDROID_DEPS Generated Code End ===' |
| private static final Pattern DEPS_GEN_PATTERN = Pattern.compile( |
| "${DEPS_TOKEN_START}(.*)${DEPS_TOKEN_END}", Pattern.DOTALL) |
| private static final String DOWNLOAD_DIRECTORY_NAME = 'libs' |
| // The 3pp bot now adds an epoch to the version tag, this needs to be kept in sync with 3pp epoch at: |
| /* groovylint-disable-next-line LineLength */ |
| // https://source.chromium.org/chromium/infra/infra/+/master:recipes/recipe_modules/support_3pp/resolved_spec.py?q=symbol:PACKAGE_EPOCH&ss=chromium |
| private static final String THREEPP_EPOCH = '2' |
| |
| // Use this to exclude a dep from being depended upon but keep the target. |
| private static final List<String> DISALLOW_DEPS = [ |
| // Only useful for SDK < Q where monochrome cannot use profiles because webview. |
| 'androidx_profileinstaller_profileinstaller', |
| ] |
| |
| // These targets will not be downloaded from maven. Deps onto them will be made |
| // to point to the existing targets instead. |
| static final Map<String, String> EXISTING_LIBS = [ |
| com_ibm_icu_icu4j: '//third_party/icu4j:icu4j_java', |
| com_almworks_sqlite4java_sqlite4java: '//third_party/sqlite4java:sqlite4java_java', |
| com_google_guava_listenablefuture: '//third_party/android_deps:guava_android_java', |
| com_jakewharton_android_repackaged_dalvik_dx: '//third_party/aosp_dalvik:aosp_dalvik_dx_java', |
| junit_junit: '//third_party/junit:junit', |
| net_bytebuddy_byte_buddy_android: '//third_party/byte_buddy:byte_buddy_android_java', |
| org_hamcrest_hamcrest_core: '//third_party/hamcrest:hamcrest_core_java', |
| org_hamcrest_hamcrest_integration: '//third_party/hamcrest:hamcrest_integration_java', |
| org_hamcrest_hamcrest_library: '//third_party/hamcrest:hamcrest_library_java', |
| org_jetbrains_kotlin_kotlin_stdlib: '//third_party/kotlin_stdlib:kotlin_stdlib_java', |
| org_jetbrains_annotations: '//third_party/kotlin_stdlib:kotlin_stdlib_java', |
| org_jetbrains_kotlin_kotlin_stdlib_common: '//third_party/kotlin_stdlib:kotlin_stdlib_java', |
| ] |
| |
| // Some libraries have such long names they'll create a path that exceeds the 200 char path limit, which is |
| // enforced by presubmit checks for Windows. This mapping shortens the name for .info files. |
| // Needs to match mapping in fetch_all.py. |
| private static final Map<String, String> REDUCED_ID_LENGTH_MAP = [ |
| 'com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework': |
| 'com_google_android_accessibility_test_framework', |
| ] |
| |
| // These targets will still be downloaded from maven. Any deps onto them will be made |
| // to point to the aliased target instead. |
| static final Map<String, String> ALIASED_LIBS = [ |
| // Use fully-qualified labels here since androidx might refer to them. |
| com_google_android_material_material: '//third_party/android_deps:material_design_java', |
| com_google_android_play_feature_delivery: '//third_party/android_deps:playcore_java', |
| com_google_dagger_dagger_compiler: '//third_party/android_deps:dagger_processor', |
| com_google_dagger_dagger: '//third_party/android_deps:dagger_java', |
| com_google_guava_failureaccess: '//third_party/android_deps:guava_android_java', |
| com_google_guava_guava_android: '//third_party/android_deps:guava_android_java', |
| com_google_protobuf_protobuf_javalite: '//third_party/android_deps:protobuf_lite_runtime_java', |
| // Logic for google_play_services_package added below. |
| ] |
| |
| // Targets that are disabled when enable_chrome_android_internal=true. |
| static final Map<String, String> CONDITIONAL_LIBS = [ |
| com_google_android_material_material: '!defined(material_design_target)', |
| com_google_android_play_feature_delivery: '!defined(playcore_target)', |
| com_google_dagger_dagger_compiler: '!defined(dagger_annotation_processor_target)', |
| com_google_dagger_dagger_producers: '!defined(dagger_annotation_processor_target)', |
| com_google_dagger_dagger_spi: '!defined(dagger_annotation_processor_target)', |
| com_google_dagger_dagger: '!defined(dagger_java_target)', |
| com_google_guava_guava_android: '!defined(guava_android_target)', |
| com_google_protobuf_protobuf_javalite: '!defined(android_proto_runtime)', |
| // Logic for google_play_services_package added below. |
| ] |
| |
| /** |
| * Prefixes of androidx dependencies which are allowed to use non-SNAPSHOT |
| * versions. These are the legacy androidx targets that are no longer being |
| * released regularly (thus are not part of the snapshots) but are still |
| * required by chromium. |
| * |
| * If an assert fails pointing at a dep which *does* exist in the androidx |
| * snapshot then adding it here will only silence the sanity check rather |
| * than fix the underlying issue (which is we should always use the snapshot |
| * versions of androidx deps when possible). A better solution could be to |
| * add it in //third_party/androidx/build.gradle.template |
| */ |
| static final Set<String> ALLOWED_ANDROIDX_NON_SNAPSHOT_DEPS_PREFIXES = [ |
| 'androidx_constraintlayout', |
| 'androidx_documentfile', |
| 'androidx_legacy', |
| 'androidx_localbroadcastmanager_localbroadcastmanager', |
| 'androidx_media3_media3', |
| 'androidx_multidex_multidex', |
| 'androidx_print', |
| 'androidx_test', |
| ] |
| |
| // Prefixes of autorolled libraries in //third_party/android_deps_autorolled. |
| static final List<String> AUTOROLLED_LIB_PREFIXES = [] |
| |
| static final String AUTOROLLED_REPO_PATH = 'third_party/android_deps_autorolled' |
| |
| static final String COPYRIGHT_HEADER = '''\ |
| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| '''.stripIndent(/* forceGroovyBehavior */ true) |
| |
| // This cache allows us to download license files from the same URL at most once. |
| static final ConcurrentMap<String, String> URL_TO_STRING_CACHE = new ConcurrentHashMap<>() |
| |
| /** |
| * Directory where the artifacts will be downloaded and where files will be generated. |
| * Note: this path is specified as relative to the chromium source root, and must be normalised |
| * to an absolute path before being used, as Groovy would base relative path where the script |
| * is being executed. |
| */ |
| @Input |
| String repositoryPath |
| |
| /** Relative path to the Chromium source root from the build.gradle file. */ |
| @Input |
| String chromiumSourceRoot |
| |
| /** Name of the cipd root package. */ |
| @Input |
| String cipdBucket |
| |
| /** Skips license file import. */ |
| @Input |
| boolean skipLicenses |
| |
| /** Array with visibility for targets which are not listed in build.gradle */ |
| @Input |
| String[] internalTargetVisibility |
| |
| /** Whether to ignore DEPS file. */ |
| @Input |
| boolean ignoreDEPS |
| |
| /** The URI of the file BuildConfigGenerator.groovy */ |
| @Input |
| @SourceURI |
| URI sourceUri |
| |
| static String translateTargetName(String targetName) { |
| if (isPlayServicesTarget(targetName)) { |
| return targetName.replaceFirst('com_', '').replaceFirst('android_gms_', '') |
| } |
| return targetName |
| } |
| |
| static boolean isPlayServicesTarget(String dependencyId) { |
| // Firebase has historically been treated as a part of play services, so it counts here for backwards |
| // compatibility. Datatransport is new as of 2019 and is used by many play services libraries. |
| return Pattern.matches('.*google.*(play_services|firebase|datatransport).*', dependencyId) |
| } |
| |
| static String makeOwners() { |
| // Make it easier to upgrade existing dependencies without full third_party review. |
| return 'file://third_party/android_deps/OWNERS\n' |
| } |
| |
| static String makeReadme(ChromiumDepGraph.DependencyDescription dependency) { |
| List<String> licenseStrings = [] |
| for (ChromiumDepGraph.LicenseSpec license : dependency.licenses) { |
| // Replace license names with ones that are whitelisted, see third_party/PRESUBMIT.py |
| switch (license.name) { |
| case 'The Apache License, Version 2.0': |
| case 'The Apache Software License, Version 2.0': |
| licenseStrings.add('Apache Version 2.0') |
| break |
| case 'GNU General Public License, version 2, with the Classpath Exception': |
| licenseStrings.add('GPL v2 with the classpath exception') |
| break |
| default: |
| licenseStrings.add(license.name) |
| } |
| } |
| String licenseString = String.join(', ', licenseStrings) |
| |
| boolean securityCritical = dependency.supportsAndroid && dependency.isShipped |
| String cpePrefix = dependency.cpePrefix ? dependency.cpePrefix : 'unknown' |
| |
| return """\ |
| Name: ${dependency.displayName} |
| Short Name: ${dependency.name} |
| URL: ${dependency.url} |
| Version: ${dependency.version} |
| License: ${licenseString} |
| License File: LICENSE |
| CPEPrefix: ${cpePrefix} |
| Security Critical: ${securityCritical ? 'yes' : 'no'} |
| Shipped: ${dependency.isShipped ? 'yes' : 'no'} |
| ${dependency.licenseAndroidCompatible ? 'License Android Compatible: yes' : ''} |
| Description: |
| ${dependency.description} |
| |
| Local Modifications: |
| No modifications. |
| """.stripIndent(/* forceGroovyBehavior */ true) |
| } |
| |
| static String makeCipdYaml(ChromiumDepGraph.DependencyDescription dependency, String cipdBucket, String repoPath) { |
| String cipdVersion = "${THREEPP_EPOCH}@${dependency.version}.${dependency.cipdSuffix}" |
| String cipdPath = "${cipdBucket}/${repoPath}" |
| // CIPD does not allow uppercase in names. |
| cipdPath += "/${DOWNLOAD_DIRECTORY_NAME}/$dependency.directoryName" |
| |
| // NOTE: The fetch_all.py script relies on the format of this file! See fetch_all.py:GetCipdPackageInfo(). |
| // NOTE: Keep the copyright year 2018 until this generated code is updated, avoiding annual churn of all |
| // cipd.yaml files. |
| return """\ |
| # Copyright 2018 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # To create CIPD package run the following command. |
| # cipd create --pkg-def cipd.yaml -tag version:${cipdVersion} |
| package: ${cipdPath} |
| description: "${dependency.displayName}" |
| data: |
| - file: ${dependency.fileName} |
| """.stripIndent(/* forceGroovyBehavior */ true) |
| } |
| |
| static void validateLicenses(ChromiumDepGraph.DependencyDescription dependency) { |
| if (dependency.licenses.empty) { |
| throw new RuntimeException("Missing license for ${dependency.id}.") |
| } |
| |
| for (ChromiumDepGraph.LicenseSpec license : dependency.licenses) { |
| if (!license.path?.trim() && !license.url?.trim()) { |
| throw new RuntimeException("Missing license for ${dependency.id}. License Name was: ${license.name}") |
| } |
| } |
| } |
| |
| static void downloadLicenses(ChromiumDepGraph.DependencyDescription dependency, |
| String normalisedRepoPath, |
| ExecutorService downloadExecutor, |
| List<Future> downloadTasks) { |
| String depDir = "$normalisedRepoPath/${computeDepDir(dependency)}" |
| for (int i = 0; i < dependency.licenses.size(); ++i) { |
| ChromiumDepGraph.LicenseSpec license = dependency.licenses[i] |
| if (!license.path?.trim() && license.url?.trim()) { |
| String destFileSuffix = (dependency.licenses.size() > 1) ? "${i + 1}.tmp" : '' |
| File destFile = new File("${depDir}/LICENSE${destFileSuffix}") |
| downloadTasks.add(downloadExecutor.submit { |
| downloadFile(dependency.id, license.url, destFile) |
| if (destFile.text.contains('<html')) { |
| throw new RuntimeException("Found HTML in LICENSE file at ${license.url}. " |
| + "Please add an override to ChromiumDepGraph.groovy for ${dependency.id}.") |
| } |
| }) |
| } |
| } |
| } |
| |
| static void mergeLicenses(ChromiumDepGraph.DependencyDescription dependency, String normalisedRepoPath) { |
| String depDir = computeDepDir(dependency) |
| File outFile = new File("${normalisedRepoPath}/${depDir}/LICENSE") |
| |
| if (dependency.licenses.size() == 1) { |
| String licensePath0 = dependency.licenses.get(0).path?.trim() |
| if (licensePath0) { |
| outFile.write(new File("${normalisedRepoPath}/${licensePath0}").text) |
| } |
| return |
| } |
| |
| outFile.write('Third-Party Software Licenses\n') |
| for (int i = 0; i < dependency.licenses.size(); ++i) { |
| ChromiumDepGraph.LicenseSpec licenseSpec = dependency.licenses[i] |
| outFile.append("\n${i + 1}. ${licenseSpec.name}\n\n") |
| String licensePath = licenseSpec.path ? licenseSpec.path.trim() : "${depDir}/LICENSE${i + 1}.tmp" |
| outFile.append(new File("${normalisedRepoPath}/${licensePath}").text) |
| } |
| } |
| |
| static String make3ppPb(String cipdBucket, String repoPath) { |
| String pkgPrefix = "${cipdBucket}/${repoPath}/${DOWNLOAD_DIRECTORY_NAME}" |
| |
| return COPYRIGHT_HEADER + '\n' + GEN_REMINDER + """ |
| create { |
| source { |
| script { name: "fetch.py" } |
| } |
| } |
| |
| upload { |
| pkg_prefix: "${pkgPrefix}" |
| universal: true |
| } |
| """.stripIndent(/* forceGroovyBehavior */ true) |
| } |
| |
| static String make3ppFetch(Template fetchTemplate, ChromiumDepGraph.DependencyDescription dependency) { |
| Map bindMap = [ |
| copyrightHeader: COPYRIGHT_HEADER, |
| dependency: dependency, |
| ] |
| return fetchTemplate.make(bindMap).toString() |
| } |
| |
| static String jsonDump(Object obj) { |
| return JsonOutput.prettyPrint(JsonOutput.toJson(obj)) |
| } |
| |
| static void printDump(Object obj) { |
| // Cannot reference logger directly due to this being a static method. |
| /* groovylint-disable-next-line UnnecessaryGetter */ |
| getLogger().warn(jsonDump(obj)) |
| } |
| |
| static HttpURLConnection connectAndFollowRedirects(String id, String sourceUrl) { |
| // Several deps use this URL for their license, but it just points to license |
| // *template*. Generally the actual license can be found in the source code. |
| if (sourceUrl.contains('://opensource.org/licenses')) { |
| throw new RuntimeException('Found templated license URL for dependency ' |
| + id + ': ' + sourceUrl |
| + '. You will need to edit PROPERTY_OVERRIDES for this dep.') |
| } |
| URL urlObj = new URL(sourceUrl) |
| HttpURLConnection connection |
| for (int i = 0; i < 10; ++i) { |
| connection = urlObj.openConnection() |
| connection.setRequestProperty("Accept", "text/plain"); |
| switch (connection.responseCode) { |
| case HttpURLConnection.HTTP_MOVED_PERM: |
| case HttpURLConnection.HTTP_MOVED_TEMP: |
| String location = connection.getHeaderField('Location') |
| urlObj = new URL(urlObj, location) |
| continue |
| case HttpURLConnection.HTTP_OK: |
| return connection |
| default: |
| throw new RuntimeException("Url had statusCode=$connection.responseCode: $sourceUrl") |
| } |
| } |
| throw new RuntimeException("Url in redirect loop: $sourceUrl") |
| } |
| |
| static void downloadFile(String id, String sourceUrl, File destinationFile) { |
| destinationFile.withOutputStream { out -> |
| try { |
| out << URL_TO_STRING_CACHE.computeIfAbsent(sourceUrl) { k -> |
| connectAndFollowRedirects(id, k).inputStream.text |
| } |
| } catch (Exception e) { |
| throw new RuntimeException("Failed to fetch license for $id url: $sourceUrl", e) |
| } |
| } |
| } |
| |
| @TaskAction |
| void main() { |
| // Do not run task on subprojects. |
| if (project != project.rootProject) { |
| return |
| } |
| |
| skipLicenses = skipLicenses ?: project.hasProperty('skipLicenses') |
| |
| Path fetchTemplatePath = Paths.get(sourceUri).resolveSibling('3ppFetch.template') |
| Template fetchTemplate = new SimpleTemplateEngine().createTemplate(fetchTemplatePath.toFile()) |
| |
| Set<Project> allProjects = [] as Set |
| allProjects.add(project) |
| allProjects.addAll(project.subprojects) |
| ChromiumDepGraph graph = new ChromiumDepGraph( |
| projects: allProjects, logger: project.logger, skipLicenses: skipLicenses) |
| String normalisedRepoPath = normalisePath(repositoryPath) |
| |
| // 1. Parse the dependency data |
| graph.collectDependencies() |
| |
| // 2. Import artifacts into the local repository |
| List<String> dependencyDirectories = [] |
| ExecutorService downloadExecutor = Executors.newCachedThreadPool() |
| List<Future> downloadTasks = [] |
| List<ChromiumDepGraph.DependencyDescription> mergeLicensesDeps = [] |
| graph.dependencies.values().each { dependency -> |
| if (excludeDependency(dependency) || computeJavaGroupForwardingTargets(dependency)) { |
| return |
| } |
| |
| ChromiumDepGraph.DependencyDescription dependencyForLogging = dependency.clone() |
| // jsonDump() throws StackOverflowError for ResolvedArtifact. |
| dependencyForLogging.artifact = null |
| |
| logger.debug "Processing ${dependency.name}: \n${jsonDump(dependencyForLogging)}" |
| String depDir = BuildConfigGenerator.computeDepDir(dependency) |
| String absoluteDepDir = "${normalisedRepoPath}/${depDir}" |
| |
| dependencyDirectories.add(depDir) |
| |
| if (new File("${absoluteDepDir}/${dependency.fileName}").exists()) { |
| logger.quiet("${dependency.id} exists, skipping.") |
| return |
| } |
| |
| project.copy { |
| from dependency.artifact.file |
| into absoluteDepDir |
| } |
| |
| new File("${absoluteDepDir}/README.chromium").write(makeReadme(dependency)) |
| new File("${absoluteDepDir}/cipd.yaml").write(makeCipdYaml(dependency, cipdBucket, repositoryPath)) |
| new File("${absoluteDepDir}/OWNERS").write(makeOwners()) |
| |
| // Enable 3pp flow for //third_party/android_deps only. |
| // TODO(crbug.com/1132368): Enable 3pp flow for subprojects as well. |
| if (repositoryPath == 'third_party/android_deps') { |
| if (dependency.fileUrl) { |
| String absoluteDep3ppDir = "${absoluteDepDir}/3pp" |
| new File(absoluteDep3ppDir).mkdirs() |
| new File("${absoluteDep3ppDir}/3pp.pb").write(make3ppPb(cipdBucket, repositoryPath)) |
| File fetchFile = new File("${absoluteDep3ppDir}/fetch.py") |
| fetchFile.write(make3ppFetch(fetchTemplate, dependency)) |
| fetchFile.setExecutable(true, false) |
| } else { |
| throw new RuntimeException("Failed to generate 3pp files for ${dependency.id} with empty fileUrl.") |
| } |
| } |
| |
| if (!skipLicenses) { |
| validateLicenses(dependency) |
| downloadLicenses(dependency, normalisedRepoPath, downloadExecutor, downloadTasks) |
| mergeLicensesDeps.add(dependency) |
| } |
| } |
| downloadExecutor.shutdown() |
| // Check for exceptions. |
| for (Future task : downloadTasks) { |
| task.get() |
| } |
| |
| mergeLicensesDeps.each { dependency -> |
| mergeLicenses(dependency, normalisedRepoPath) |
| } |
| |
| // Skip when --no-subprojects is passed. |
| if (project.subprojects) { |
| validateAndroidX(graph.dependencies.values()) |
| } |
| |
| // 3. Generate the root level build files |
| updateBuildTargetDeclaration(graph, normalisedRepoPath) |
| if (!ignoreDEPS) { |
| updateDepsDeclaration(graph, cipdBucket, repositoryPath, |
| "${normalisedRepoPath}/../../DEPS") |
| } |
| dependencyDirectories.sort { path1, path2 -> return path1 <=> path2 } |
| updateReadmeReferenceFile(dependencyDirectories, |
| "${normalisedRepoPath}/additional_readme_paths.json") |
| } |
| |
| void appendBuildTarget(ChromiumDepGraph.DependencyDescription dependency, |
| Map<String, ChromiumDepGraph.DependencyDescription> allDependencies, |
| StringBuilder sb) { |
| if (excludeDependency(dependency) || !dependency.generateTarget) { |
| return |
| } |
| |
| String targetName = translateTargetName(dependency.id) + '_java' |
| List<String> javaDeps = computeJavaGroupForwardingTargets(dependency) ?: dependency.children |
| Set<String> addedDeps = new HashSet<String>(); |
| |
| String depsStr = '' |
| javaDeps?.each { childDep -> |
| ChromiumDepGraph.DependencyDescription dep = allDependencies[childDep] |
| if (dep.exclude || dep.id in DISALLOW_DEPS) { |
| return |
| } |
| // Special case: If a child dependency is an existing lib, rather than skipping |
| // it, replace the child dependency with the existing lib. |
| String aliasedLib = ALIASED_LIBS.get(dep.id) |
| aliasedLib = aliasedLib != null ? aliasedLib : EXISTING_LIBS.get(dep.id) |
| |
| // The failureaccess alias needs to map to -jre or -android guava. |
| if (aliasedLib == '//third_party/android_deps:guava_android_java' && !dependency.supportsAndroid) { |
| aliasedLib = ':com_google_guava_guava_java' |
| } |
| |
| String depTargetName = translateTargetName(dep.id) + '_java' |
| String gnTarget; |
| if (targetName.startsWith('org_robolectric') && dep.id.contains('guava')) { |
| // Change from guava-jre to guava-android so that we don't get two copies of Guava |
| // in robolectric tests (since code-under-test might depend on guava-android). |
| gnTarget = '//third_party/android_deps:guava_android_java' |
| } else if (aliasedLib) { |
| gnTarget = aliasedLib |
| } else if (isInDifferentRepo(dep)) { |
| String thirdPartyDir = (dep.id.startsWith('androidx')) ? 'androidx' : 'android_deps' |
| gnTarget = "//third_party/${thirdPartyDir}:${depTargetName}" |
| } else { |
| gnTarget = ":${depTargetName}" |
| } |
| |
| if (targetName.contains('guava') && ( |
| gnTarget == '//third_party/android_deps:guava_android_java' || |
| gnTarget == ':com_google_guava_guava_java')) { |
| // Prevent circular dep caused by having listenablefuture aliased to guava_android. |
| return |
| } |
| if (gnTarget.startsWith(':google_play_services_') || gnTarget.startsWith(':google_firebase_')) { |
| gnTarget = '$google_play_services_package' + gnTarget |
| } |
| // Target aliases can cause dupes. |
| if (addedDeps.add(gnTarget)) { |
| depsStr += "\"${gnTarget}\"," |
| } |
| } |
| |
| String condition = CONDITIONAL_LIBS.get(dependency.id) |
| if (isPlayServicesTarget(dependency.id)) { |
| assert condition == null : dependency.id |
| condition = 'google_play_services_package == "//third_party/android_deps"' |
| } |
| |
| String libPath = "${DOWNLOAD_DIRECTORY_NAME}/${dependency.directoryName}" |
| sb.append(GEN_REMINDER) |
| if (condition != null) { |
| sb.append("if ($condition) {\n") |
| } |
| if (dependency.extension == 'jar') { |
| String targetType = targetName.startsWith('androidx') ? 'androidx_java_prebuilt' : 'java_prebuilt' |
| sb.append("""\ |
| ${targetType}("${targetName}") { |
| jar_path = "${libPath}/${dependency.fileName}" |
| output_name = "${dependency.id}" |
| """.stripIndent(/* forceGroovyBehavior */ true)) |
| if (dependency.supportsAndroid) { |
| sb.append(' supports_android = true\n') |
| } |
| } else if (dependency.extension == 'aar') { |
| String targetType = (targetName.startsWith('androidx') ? |
| 'androidx_android_aar_prebuilt' : 'android_aar_prebuilt') |
| sb.append("""\ |
| ${targetType}("${targetName}") { |
| aar_path = "${libPath}/${dependency.fileName}" |
| info_path = "${libPath}/${BuildConfigGenerator.reducedDepencencyId(dependency.id)}.info" |
| """.stripIndent(/* forceGroovyBehavior */ true)) |
| } else if (dependency.extension == 'group') { |
| String targetType = (targetName.startsWith('androidx') ? |
| 'androidx_java_group' : 'java_group') |
| sb.append("""\ |
| ${targetType}("${targetName}") { |
| """.stripIndent(/* forceGroovyBehavior */ true)) |
| } else { |
| throw new IllegalStateException('Dependency type should be JAR or AAR or group') |
| } |
| |
| // Skip jdeps analysis of direct deps for third-party prebuilt targets. Many of these targets from maven are |
| // missing direct dependencies for its code, but this code is typically not called in practice and is removed |
| // by R8. Rely on R8's TraceReferences check to catch any actually missing dependencies. Although it would be |
| // helpful to have the full list of correct direct deps for these prebuilt targets, in practice it is too |
| // onerous to maintain for each third party maven prebuilt target. |
| if (dependency.extension == 'jar' || dependency.extension == 'aar') { |
| sb.append(' enable_bytecode_checks = false\n') |
| } |
| |
| sb.append(generateBuildTargetVisibilityDeclaration(dependency)) |
| |
| if (dependency.testOnly) { |
| sb.append(' testonly = true\n') |
| } |
| if (!depsStr.empty) { |
| sb.append(" deps = [${depsStr}]\n") |
| } |
| addSpecialTreatment(sb, dependency.id, dependency.extension) |
| |
| sb.append('}\n') |
| if (condition != null) { |
| sb.append("}\n") |
| } |
| } |
| |
| String generateBuildTargetVisibilityDeclaration(ChromiumDepGraph.DependencyDescription dependency) { |
| StringBuilder sb = new StringBuilder() |
| String aliasedLib = ALIASED_LIBS.get(dependency.id) |
| if (aliasedLib) { |
| // Cannot add only the specific target because doing so breaks nested template target. |
| String visibilityLabel = aliasedLib.replaceAll(':.*', ':*') |
| sb.append(' # Target is swapped out when internal code is enabled.\n') |
| sb.append(" # Please depend on $aliasedLib instead.\n") |
| sb.append(" visibility = [ \"$visibilityLabel\" ]\n") |
| } else if (!dependency.visible) { |
| sb.append(' # To remove visibility constraint, add this dependency to\n') |
| sb.append(" # //${repositoryPath}/build.gradle.\n") |
| sb.append("visibility = ${makeGnArray(internalTargetVisibility)}\n") |
| } |
| return sb.toString() |
| } |
| |
| boolean excludeDependency(ChromiumDepGraph.DependencyDescription dependency) { |
| if (dependency.exclude || EXISTING_LIBS.get(dependency.id)) { |
| return true |
| } |
| return isInDifferentRepo(dependency) |
| } |
| |
| boolean isInDifferentRepo(ChromiumDepGraph.DependencyDescription dependency) { |
| boolean isAndroidxRepository = (repositoryPath == 'third_party/androidx') |
| boolean isAndroidxDependency = (dependency.id.startsWith('androidx')) |
| if (isAndroidxRepository != isAndroidxDependency) { |
| return true |
| } |
| if (repositoryPath == AUTOROLLED_REPO_PATH) { |
| String targetName = translateTargetName(dependency.id) + '_java' |
| return !isTargetAutorolled(targetName) |
| } |
| return false |
| } |
| |
| /** If |dependency| should be a java_group(), returns target to forward to. Returns null otherwise. */ |
| List<String> computeJavaGroupForwardingTargets(ChromiumDepGraph.DependencyDescription dependency) { |
| String targetName = translateTargetName(dependency.id) + '_java' |
| if (repositoryPath != AUTOROLLED_REPO_PATH && isTargetAutorolled(targetName)) { |
| return ["//${AUTOROLLED_REPO_PATH}:${targetName}"] |
| } else if (dependency.extension == 'group') { |
| return dependency.children |
| } |
| return [] |
| } |
| |
| private static String reducedDepencencyId(String dependencyId) { |
| return REDUCED_ID_LENGTH_MAP.get(dependencyId) ?: dependencyId |
| } |
| |
| private static String makeGnArray(String[] values) { |
| StringBuilder sb = new StringBuilder() |
| sb.append('[') |
| for (String value : values) { |
| sb.append('"') |
| sb.append(value) |
| sb.append('",') |
| } |
| sb.replace(sb.length() - 1, sb.length(), ']') |
| return sb.toString() |
| } |
| |
| private static String computeDepDir(ChromiumDepGraph.DependencyDescription dependency) { |
| return "${DOWNLOAD_DIRECTORY_NAME}/${dependency.directoryName}" |
| } |
| |
| private static void addSpecialTreatment(StringBuilder sb, String dependencyId, String dependencyExtension) { |
| addPreconditionsOverrideTreatment(sb, dependencyId) |
| |
| if (dependencyId.startsWith('org_robolectric')) { |
| sb.append(' is_robolectric = true\n') |
| } |
| if (dependencyExtension == 'aar' && dependencyId.startsWith('com_android_support')) { |
| // The androidx and com_android_support libraries have duplicate resources such as |
| // 'primary_text_default_material_dark'. |
| sb.append(' resource_overlay = true\n') |
| } |
| if (dependencyExtension == 'jar' && ( |
| dependencyId.startsWith('io_grpc_') || |
| dependencyId == 'com_google_firebase_firebase_encoders' || |
| dependencyId == 'com_google_guava_guava_android')) { |
| sb.append(' # https://crbug.com/1412551\n') |
| sb.append(' requires_android = true\n') |
| } |
| |
| switch (dependencyId) { |
| case 'com_android_support_coordinatorlayout': |
| sb.append('\n') |
| sb.append(' # Reduce binary size. https:crbug.com/954584\n') |
| sb.append(' ignore_proguard_configs = true\n') |
| break |
| case 'com_android_support_support_compat': |
| sb.append('\n') |
| sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') |
| sb.append(' ignore_aidl = true\n') |
| sb.append(' ignore_manifest = true\n') |
| // Necessary to not have duplicate classes after jetification. |
| // They can be removed when we no longer jetify targets |
| // that depend on com_android_support_support_compat. |
| sb.append('''\ |
| | jar_excluded_patterns = [ |
| | "android/support/v4/graphics/drawable/IconCompatParcelizer.class", |
| | "android/support/v4/os/ResultReceiver*", |
| | "androidx/core/graphics/drawable/IconCompatParcelizer.class", |
| | "androidx/core/internal/package-info.class", |
| | "android/support/v4/app/INotificationSideChannel*", |
| | "android/support/v4/os/IResultReceiver*", |
| | ] |
| | |
| |'''.stripMargin()) |
| break |
| case 'com_android_support_support_media_compat': |
| sb.append('\n') |
| sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') |
| sb.append(' ignore_aidl = true\n') |
| break |
| case 'com_android_support_transition': |
| // Not specified in the POM, compileOnly dependency not supposed to be used unless |
| // the library is present: b/70887421 |
| sb.append(' deps += [":com_android_support_support_fragment_java"]\n') |
| break |
| case 'com_android_support_versionedparcelable': |
| sb.append('\n') |
| sb.append(' # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n') |
| sb.append(' ignore_aidl = true\n') |
| // Necessary to not have identical classes after jetification. |
| // They can be removed when we no longer jetify targets |
| // that depend on com_android_support_versionedparcelable. |
| sb.append('''\ |
| | jar_excluded_patterns = [ |
| | "android/support/v4/graphics/drawable/IconCompat.class", |
| | "androidx/*", |
| | ] |
| | |
| |'''.stripMargin()) |
| break |
| case 'com_android_tools_sdk_common': |
| case 'com_android_tools_common': |
| case 'com_android_tools_layoutlib_layoutlib_api': |
| sb.append('\n') |
| sb.append(' # This target does not come with most of its dependencies and is\n') |
| sb.append(' # only meant to be used by the resources shrinker. If you wish to use\n') |
| sb.append(' # this for other purposes, change buildCompileNoDeps in build.gradle.\n') |
| sb.append(' visibility = [ "//build/android/unused_resources:*" ]\n') |
| break |
| case 'com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework': |
| sb.append(' # These are included locally in third_party/accessibility_test_framework.\n') |
| sb.append(' jar_excluded_patterns = [\n') |
| sb.append(' "com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResult\\$AccessibilityCheckResultType.class",\n') |
| sb.append(' "com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResult.class",\n') |
| sb.append(' "com/google/android/apps/common/testing/accessibility/framework/integrations/espresso/AccessibilityValidator*",\n') |
| sb.append(' ]\n') |
| break |
| case 'com_google_android_gms_play_services_basement': |
| sb.append(' # https://crbug.com/989505\n') |
| sb.append(' jar_excluded_patterns += ["META-INF/proguard/*"]\n') |
| // Deprecated deps jar but still needed by play services basement. |
| sb.append(' input_jars_paths=["$android_sdk/optional/org.apache.http.legacy.jar"]\n') |
| break |
| case 'com_google_android_gms_play_services_maps': |
| sb.append(' # Ignore the dependency to org.apache.http.legacy. See crbug.com/1084879.\n') |
| sb.append(' ignore_manifest = true\n') |
| break |
| case 'com_google_android_material_material': |
| sb.with { |
| append('\n') |
| append(' # Reduce binary size. https:crbug.com/954584\n') |
| append(' ignore_proguard_configs = true\n') |
| append(' proguard_configs = ["material_design.flags"]\n') |
| append('\n') |
| append(' # Ensure ConstraintsLayout is not included by unused layouts:\n') |
| append(' # https://crbug.com/1292510\n') |
| // Keep in sync with the copy in fetch_all.py. |
| append(' resource_exclusion_globs = [\n') |
| append(' "res/layout*/*calendar*",\n') |
| append(' "res/layout*/*chip_input*",\n') |
| append(' "res/layout*/*clock*",\n') |
| append(' "res/layout*/*picker*",\n') |
| append(' "res/layout*/*time*",\n') |
| append(' ]\n') |
| } |
| break |
| case 'com_google_ar_core': |
| // Target .aar file contains .so libraries that need to be extracted, |
| // and android_aar_prebuilt template will fail if it's not set explictly. |
| sb.append(' extract_native_libraries = true\n') |
| break |
| case 'com_google_guava_guava': |
| case 'com_google_guava_guava_android': |
| sb.append('\n') |
| sb.append(' # Dep needed to fix:\n') |
| sb.append(' # warning: unknown enum constant ReflectionSupport$Level.FULL\n') |
| sb.append(' deps += [":com_google_j2objc_j2objc_annotations_java"]\n') |
| sb.append('\n') |
| sb.append(' # Always bundle this part of guava in with the main target.\n') |
| sb.append(' public_deps = [":com_google_guava_failureaccess_java"]\n') |
| break |
| case 'com_google_protobuf_protobuf_javalite': |
| sb.with { |
| append(' # Prebuilt protos in the runtime library.\n') |
| append(' # If you want to use these protos, you should create a proto_java_library\n') |
| append(' # target for them. See crbug.com/1103399 for discussion.\n') |
| append(' jar_excluded_patterns = [\n') |
| append(' "com/google/protobuf/Any*",\n') |
| append(' "com/google/protobuf/Duration*",\n') |
| append(' "com/google/protobuf/FieldMask*",\n') |
| append(' "com/google/protobuf/Timestamp*",\n') |
| append(' ]') |
| } |
| break |
| case 'net_sf_kxml_kxml2': |
| sb.append(' # Target needs to exclude *xmlpull* files as already included in Android SDK.\n') |
| sb.append(' jar_excluded_patterns = [ "*xmlpull*" ]\n') |
| break |
| case 'org_jetbrains_kotlinx_kotlinx_coroutines_android': |
| case 'org_jetbrains_kotlinx_kotlinx_coroutines_guava': |
| sb.append('requires_android = true') |
| break |
| case 'org_mockito_mockito_android': |
| sb.append(' # Depends on third_party/byte_buddy:byte_buddy_android_java\n') |
| sb.append(' requires_android = true\n') |
| break |
| case 'org_mockito_mockito_core': |
| sb.append(' # Uses java.time which does not exist until API 26.\n') |
| sb.append(' # Modifications are added in third_party/mockito.\n') |
| sb.append(' jar_excluded_patterns = [\n') |
| sb.append(' "org/mockito/internal/junit/ExceptionFactory*",\n') |
| sb.append(' "org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValues*",\n') |
| sb.append(' ]') |
| break |
| } |
| } |
| |
| private static void addPreconditionsOverrideTreatment(StringBuilder sb, String dependencyId) { |
| String targetName = translateTargetName(dependencyId) |
| switch (targetName) { |
| case 'com_google_guava_guava_android': |
| case 'google_play_services_basement': |
| String libraryDep = '//third_party/android_deps/local_modifications/preconditions:' + |
| computePreconditionsStubLibraryForDep(dependencyId) |
| sb.append(""" |
| | |
| | jar_excluded_patterns = [] |
| | if (!enable_java_asserts) { |
| | # Omit the file since we use our own copy. |
| | jar_excluded_patterns += [ |
| | "${computePreconditionsClassForDep(dependencyId)}", |
| | ] |
| | deps += [ |
| | "${libraryDep}", |
| | ] |
| | } |
| |""".stripMargin()) |
| } |
| } |
| |
| private static String computePreconditionsStubLibraryForDep(String dependencyId) { |
| String targetName = translateTargetName(dependencyId) |
| switch (targetName) { |
| case 'com_google_guava_guava_android': |
| return 'guava_stub_preconditions_java' |
| case 'google_play_services_basement': |
| return 'gms_stub_preconditions_java' |
| } |
| return null |
| } |
| |
| private static String computePreconditionsClassForDep(String dependencyId) { |
| String targetName = translateTargetName(dependencyId) |
| switch (targetName) { |
| case 'com_google_guava_guava_android': |
| return 'com/google/common/base/Preconditions.class' |
| case 'google_play_services_basement': |
| return 'com/google/android/gms/common/internal/Preconditions.class' |
| } |
| return null |
| } |
| |
| private static void updateReadmeReferenceFile(List<String> directories, String readmePath) { |
| File refFile = new File(readmePath) |
| refFile.write(JsonOutput.prettyPrint(JsonOutput.toJson(directories)) + '\n') |
| } |
| |
| private void updateBuildTargetDeclaration(ChromiumDepGraph depGraph, String normalisedRepoPath) { |
| File buildFile = new File("${normalisedRepoPath}/BUILD.gn") |
| StringBuilder sb = new StringBuilder() |
| |
| // Comparator to sort the dependency in alphabetical order, with the visible ones coming |
| // before all the internal ones. |
| Closure dependencyComparator = { dependency1, dependency2 -> |
| int visibilityResult = Boolean.compare(dependency1.visible, dependency2.visible) |
| if (visibilityResult != 0) { |
| return -visibilityResult |
| } |
| return dependency1.id <=> dependency2.id |
| } |
| |
| List<ChromiumDepGraph.DependencyDescription> buildCompileDependencies |
| buildCompileDependencies = depGraph.dependencies.values().findAll { |
| dependency -> dependency.usedInBuild |
| } |
| |
| buildCompileDependencies.sort(dependencyComparator).each { dependency -> |
| appendBuildTarget(dependency, depGraph.dependencies, sb) |
| } |
| |
| sb.append('if (!limit_android_deps) {\n') |
| List<ChromiumDepGraph.DependencyDescription> otherDependencies |
| otherDependencies = depGraph.dependencies.values().findAll { |
| dependency -> !dependency.usedInBuild |
| } |
| otherDependencies.sort(dependencyComparator).each { dependency -> |
| appendBuildTarget(dependency, depGraph.dependencies, sb) |
| } |
| sb.append('}\n') |
| |
| Matcher matcher = BUILD_GN_GEN_PATTERN.matcher(buildFile.text) |
| if (!matcher.find()) { |
| throw new IllegalStateException('BUILD.gn insertion point not found.') |
| } |
| String out = "${BUILD_GN_TOKEN_START}\n$sb\n${BUILD_GN_TOKEN_END}" |
| buildFile.write(matcher.replaceFirst(Matcher.quoteReplacement(out))) |
| } |
| |
| private void validateAndroidX( |
| Collection<ChromiumDepGraph.DependencyDescription> dependencies) { |
| dependencies.each { dependency -> |
| if (dependency.id.contains('androidx') && |
| dependency.fileName && !dependency.fileName.contains('SNAPSHOT')) { |
| boolean hasAllowedDep = ALLOWED_ANDROIDX_NON_SNAPSHOT_DEPS_PREFIXES.any { |
| allowedPrefix -> dependency.id.startsWith(allowedPrefix) |
| } |
| if (!hasAllowedDep) { |
| String errorMsg = ("${dependency.fileName} uses non-SNAPSHOT version. " |
| + "If this is expected, add ${dependency.id} to " |
| + '|ALLOWED_ANDROIDX_NON_SNAPSHOT_DEPS_PREFIXES| list.') |
| throw new IllegalStateException(errorMsg) |
| } |
| } |
| } |
| } |
| |
| private void updateDepsDeclaration(ChromiumDepGraph depGraph, String cipdBucket, |
| String repoPath, String depsFilePath) { |
| File depsFile = new File(depsFilePath) |
| StringBuilder sb = new StringBuilder() |
| // Note: The string we're inserting is nested 1 level, hence the 2 leading spaces. Same |
| // applies to the multiline package declaration string below. |
| sb.append(' # Generated by //third_party/android_deps/fetch_all.py') |
| |
| // Comparator to sort the dependencies in alphabetical order. |
| Closure dependencyComparator = { dependency1, dependency2 -> |
| dependency1.id <=> dependency2.id |
| } |
| |
| depGraph.dependencies.values().sort(dependencyComparator).each { dependency -> |
| if (excludeDependency(dependency) || computeJavaGroupForwardingTargets(dependency)) { |
| return |
| } |
| String depPath = "${DOWNLOAD_DIRECTORY_NAME}/${dependency.directoryName}" |
| String cipdPath = "${cipdBucket}/${repoPath}/${depPath}" |
| sb.append("""\ |
| | |
| | 'src/${repoPath}/${depPath}': { |
| | 'packages': [ |
| | { |
| | 'package': '${cipdPath}', |
| | 'version': 'version:${THREEPP_EPOCH}@${dependency.version}.${dependency.cipdSuffix}', |
| | }, |
| | ], |
| | 'condition': 'checkout_android', |
| | 'dep_type': 'cipd', |
| | }, |
| |""".stripMargin()) |
| } |
| |
| Matcher matcher = DEPS_GEN_PATTERN.matcher(depsFile.text) |
| if (!matcher.find()) { |
| throw new IllegalStateException('DEPS insertion point not found.') |
| } |
| depsFile.write(matcher.replaceFirst("${DEPS_TOKEN_START}\n${sb}\n ${DEPS_TOKEN_END}")) |
| } |
| |
| private boolean isTargetAutorolled(String targetName) { |
| for (String autorolledLibPrefix in AUTOROLLED_LIB_PREFIXES) { |
| if (targetName.startsWith(autorolledLibPrefix)) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| private String normalisePath(String pathRelativeToChromiumRoot) { |
| return project.file("${chromiumSourceRoot}/${pathRelativeToChromiumRoot}").absolutePath |
| } |
| |
| } |