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