1 /*
<lambda>null2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.build
18
19 import java.io.File
20 import org.gradle.api.GradleException
21 import org.gradle.api.Project
22
23 /** Validates the project's Maven group against Jetpack guidelines. */
24 fun Project.validateProjectMavenGroup(groupId: String) {
25 if (groupId.contains('-')) {
26 throw GradleException(
27 "Invalid Maven group! Found invalid character '-' in Maven group \"$groupId\" for " +
28 "$displayName.\n\nWas this supposed to be a sub-artifact of an existing group, " +
29 "ex. \"x.y:y-z\" rather than \"x.y-z:z\"?"
30 )
31 }
32 }
33
34 // Translate common phrases and marketing names into Maven name component equivalents.
35 private val mavenNameMap =
36 mapOf(
37 "android for cars" to "car",
38 "android wear" to "wear",
39 "internationalization" to "i18n",
40 "kotlin extensions" to "ktx",
41 "lint checks" to "lint",
42 "material components" to "material",
43 "material3 components" to "material3",
44 "workmanager" to "work",
45 "windowmanager" to "window",
46 )
47
48 // Allow a small set of common Maven name components that don't need to appear in the project name.
49 private val mavenNameAllowlist =
50 setOf(
51 "extension",
52 "extensions",
53 "for",
54 "integration",
55 "with",
56 )
57
58 /** Validates the project's Maven name against Jetpack guidelines. */
Projectnull59 fun Project.validateProjectMavenName(mavenName: String, groupId: String) {
60 // Tokenize the Maven name into components. This is *very* permissive regarding separators, and
61 // we may want to revisit that policy in the future.
62 val nameComponents =
63 mavenName
64 .lowercase()
65 .let { name ->
66 mavenNameMap.entries.fold(name) { newName, entry ->
67 newName.replace(entry.key, entry.value)
68 }
69 }
70 .split(" ", ",", ":", "-")
71 .toMutableList() - mavenNameAllowlist
72
73 // Remaining components *must* appear in the Maven coordinate. Shortening long (>10 char) words
74 // to five letters or more is allowed, as is changing the pluralization of words.
75 nameComponents
76 .find { nameComponent ->
77 !name.contains(nameComponent) &&
78 !groupId.contains(nameComponent) &&
79 !(nameComponent.length > 10 && name.contains(nameComponent.substring(0, 5))) &&
80 !(nameComponent.endsWith("s") && name.contains(nameComponent.dropLast(1)))
81 }
82 ?.let { missingComponent ->
83 throw GradleException(
84 "Invalid Maven name! Found \"$missingComponent\" in Maven name for $displayName, " +
85 "but not project name.\n\nConsider removing \"$missingComponent\" from" +
86 "\"$mavenName\"."
87 )
88 }
89 }
90
91 private const val GROUP_PREFIX = "androidx."
92
93 /** Validates the project structure against Jetpack guidelines. */
Projectnull94 fun Project.validateProjectStructure(groupId: String) {
95 if (!project.isValidateProjectStructureEnabled()) {
96 return
97 }
98
99 val shortGroupId =
100 if (groupId.startsWith(GROUP_PREFIX)) {
101 groupId.substring(GROUP_PREFIX.length)
102 } else {
103 groupId
104 }
105
106 // Fully-qualified Gradle project name should match the Maven coordinate.
107 val expectName = ":${shortGroupId.replace(".",":")}:${project.name}"
108 val actualName = project.path
109 if (expectName != actualName) {
110 throw GradleException(
111 "Invalid project structure! Expected $expectName as project name, found $actualName"
112 )
113 }
114
115 // Project directory should match the Maven coordinate.
116 val expectDir = shortGroupId.replace(".", File.separator) + "${File.separator}${project.name}"
117 // Canonical projectDir is needed because sometimes, at least in tests, on OSX, supportRoot
118 // starts with /var, and projectDir starts with /private/var (which are the same thing)
119 val canonicalProjectDir = project.projectDir.canonicalFile
120 val actualDir =
121 canonicalProjectDir.toRelativeString(project.getSupportRootFolder().canonicalFile)
122 if (expectDir != actualDir) {
123 throw GradleException(
124 "Invalid project structure! Expected $expectDir as project directory, found " +
125 actualDir
126 )
127 }
128 }
129