Skip to content

Commit 84f96c7

Browse files
authored
Reproducible builds: make parent pom content reproducible (#2826)
The parent pom contains the `<developer>` and `<contributor>` elements. The former is populated from ASF people information including role information (champion, mentor, chair, (P)PMC member, committer). The latter is retrieved from a GitHub API endpoint, ordered by number contributions. Especially the latter list is prone to vary between builds, which makes the parent pom not reproducible as the locally built one is likely different from the one that was built by the release managed (staged artifact). This change removes both lists, leaving a single static `<developer>` entry pointing to `https://polaris.apache.org/community/`. Related build-script code has been updated and no longer retrieves people information.
1 parent 84cf9e1 commit 84f96c7

File tree

2 files changed

+52
-124
lines changed

2 files changed

+52
-124
lines changed

build-logic/src/main/kotlin/publishing/configurePom.kt

Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import org.gradle.api.Project
2424
import org.gradle.api.Task
2525
import org.gradle.api.artifacts.component.ModuleComponentSelector
2626
import org.gradle.api.provider.Provider
27-
import org.gradle.api.publish.maven.MavenPom
2827
import org.gradle.api.publish.maven.MavenPublication
2928
import org.gradle.internal.extensions.stdlib.capitalized
3029

@@ -36,21 +35,22 @@ import org.gradle.internal.extensions.stdlib.capitalized
3635
* consumable by Maven.
3736
*
3837
* The root project generates the parent pom, containing all the necessary elements to pass Sonatype
39-
* validation and some more information like `<developers>` and `<contributors>`. Most of the
40-
* information is taken from publicly consumable Apache project information from
41-
* `https://projects.apache.org/json/projects/<project-name>>.json`. `<developers>` contains all
42-
* (P)PMC members and committers from that project info JSON, ordered by real name. `<contributors>`
43-
* is taken from GitHub's
44-
* `https://api.github.com/repos/apache/<project-name>/contributors?per_page=1000` endpoint to give
45-
* all contributors credit, ordered by number of contributions (as returned by that endpoint).
38+
* validation. Most of the information is taken from publicly consumable Apache project information
39+
* from `https://projects.apache.org/json/projects/<project-name>.json`. Changes to the Apache
40+
* project metadata, including podling information, will break the reproducibility of the build.
41+
*
42+
* Developer and contributor elements are intentionally *not* included in the POM. Such information
43+
* is not considered stable (enough) to satisfy reproducible build requirements. The generated POM
44+
* must be exactly the same when built by a release manager and by someone else to verify the built
45+
* artifact(s).
4646
*/
4747
internal fun configurePom(project: Project, mavenPublication: MavenPublication, task: Task) =
4848
mavenPublication.run {
4949
val e = project.extensions.getByType(PublishingHelperExtension::class.java)
5050

5151
pom {
5252
if (project != project.rootProject) {
53-
// Add the license to every pom to make it easier for downstream project to retrieve the
53+
// Add the license to every pom to make it easier for downstream projects to retrieve the
5454
// license.
5555
licenses {
5656
license {
@@ -75,7 +75,7 @@ internal fun configurePom(project: Project, mavenPublication: MavenPublication,
7575
task.doFirst {
7676
mavenPom.run {
7777
val asfName = e.asfProjectId.get()
78-
val projectPeople = fetchProjectPeople(asfName)
78+
val projectInfo = fetchProjectInformation(asfName)
7979

8080
organization {
8181
name.set("The Apache Software Foundation")
@@ -84,15 +84,15 @@ internal fun configurePom(project: Project, mavenPublication: MavenPublication,
8484
licenses {
8585
license {
8686
name.set("Apache-2.0") // SPDX identifier
87-
url.set(projectPeople.licenseUrl)
87+
url.set(projectInfo.licenseUrl)
8888
}
8989
}
9090
mailingLists {
9191
e.mailingLists.get().forEach { ml ->
9292
mailingList {
9393
name.set("${ml.capitalized()} Mailing List")
9494
subscribe.set("$ml-subscribe@$asfName.apache.org")
95-
unsubscribe.set("$ml-ubsubscribe@$asfName.apache.org")
95+
unsubscribe.set("$ml-unsubscribe@$asfName.apache.org")
9696
post.set("$ml@$asfName.apache.org")
9797
archive.set("https://lists.apache.org/list.html?$ml@$asfName.apache.org")
9898
}
@@ -104,7 +104,7 @@ internal fun configurePom(project: Project, mavenPublication: MavenPublication,
104104
e.overrideScm.orElse(
105105
githubRepoName
106106
.map { r -> "https://github.com/apache/$r" }
107-
.orElse(projectPeople.repository)
107+
.orElse(projectInfo.repository)
108108
)
109109

110110
scm {
@@ -115,59 +115,28 @@ internal fun configurePom(project: Project, mavenPublication: MavenPublication,
115115
val version = project.version.toString()
116116
if (!version.endsWith("-SNAPSHOT")) {
117117
val tagPrefix: String =
118-
e.overrideTagPrefix.orElse("apache-${projectPeople.apacheId}").get()
118+
e.overrideTagPrefix.orElse("apache-${projectInfo.apacheId}").get()
119119
tag.set("$tagPrefix-$version")
120120
}
121121
}
122122
issueManagement {
123123
val issuesUrl: Provider<String> =
124-
codeRepo.map { r -> "$r/issues" }.orElse(projectPeople.bugDatabase)
124+
codeRepo.map { r -> "$r/issues" }.orElse(projectInfo.bugDatabase)
125125
url.set(e.overrideIssueManagement.orElse(issuesUrl))
126126
}
127127

128-
name.set(e.overrideName.orElse("Apache ${projectPeople.name}"))
129-
description.set(e.overrideDescription.orElse(projectPeople.description))
130-
url.set(e.overrideProjectUrl.orElse(projectPeople.website))
131-
inceptionYear.set(projectPeople.inceptionYear.toString())
132-
133-
developers {
134-
projectPeople.people.forEach { person ->
135-
developer {
136-
this.id.set(person.apacheId)
137-
this.name.set(person.name)
138-
this.organization.set("Apache Software Foundation")
139-
this.email.set("${person.apacheId}@apache.org")
140-
this.roles.addAll(person.roles)
141-
}
142-
}
143-
}
128+
name.set(e.overrideName.orElse("Apache ${projectInfo.name}"))
129+
description.set(e.overrideDescription.orElse(projectInfo.description))
130+
url.set(e.overrideProjectUrl.orElse(projectInfo.website))
131+
inceptionYear.set(projectInfo.inceptionYear.toString())
144132

145-
addContributorsToPom(mavenPom, githubRepoName.get(), "Apache ${projectPeople.name}")
133+
developers { developer { url.set("https://$asfName.apache.org/community/") } }
146134
}
147135
}
148136
}
149137
}
150138
}
151139

152-
/** Adds contributors as returned by GitHub, in descending `contributions` order. */
153-
fun addContributorsToPom(mavenPom: MavenPom, asfName: String, asfProjectName: String) =
154-
mavenPom.run {
155-
contributors {
156-
val contributors: List<Map<String, Any>> =
157-
parseJson("https://api.github.com/repos/apache/$asfName/contributors?per_page=1000")
158-
contributors
159-
.filter { contributor -> contributor["type"] == "User" }
160-
.forEach { contributor ->
161-
contributor {
162-
name.set(contributor["login"] as String)
163-
url.set(contributor["url"] as String)
164-
organization.set("$asfProjectName, GitHub contributors")
165-
organizationUrl.set("https://github.com/apache/$asfName")
166-
}
167-
}
168-
}
169-
}
170-
171140
/**
172141
* Scans the generated `pom.xml` for `<dependencies>` in `<dependencyManagement>` that do not have a
173142
* `<version>` and adds one, if possible. Maven kinda requires `<version>` tags there, even if the

build-logic/src/main/kotlin/publishing/util.kt

Lines changed: 32 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -99,36 +99,26 @@ internal fun <T : Any> parseJson(urlStr: String): T {
9999

100100
/** Retrieves the project name, for example `Polaris` using the lower-case project ID. */
101101
internal fun fetchAsfProjectName(apacheId: String): String {
102-
val projectsAll: Map<String, Map<String, Any>> =
103-
parseJson("https://whimsy.apache.org/public/public_ldap_projects.json")
104-
val projects = unsafeCast<Map<String, Map<String, Any>>>(projectsAll["projects"])
105-
val project =
106-
projects[apacheId]
107-
?: throw IllegalArgumentException(
108-
"No project '$apacheId' found in https://whimsy.apache.org/public/public_ldap_projects.json"
109-
)
102+
val project = projectMap(apacheId)
110103
val isPodlingCurrent = project.containsKey("podling") && project["podling"] == "current"
111104
if (isPodlingCurrent) {
112-
val podlingsAll: Map<String, Map<String, Any>> =
113-
parseJson("https://whimsy.apache.org/public/public_podlings.json")
114-
val podlings = unsafeCast<Map<String, Map<String, Any>>>(podlingsAll["podling"])
115-
val podling =
116-
podlings[apacheId]
117-
?: throw IllegalArgumentException(
118-
"No podling '$apacheId' found in https://whimsy.apache.org/public/public_podlings.json"
119-
)
105+
val podling = podlingMap(apacheId)
120106
return podling["name"] as String
121107
} else {
122108
// top-level-project
123-
val committeesAll: Map<String, Map<String, Any>> =
124-
parseJson("https://whimsy.apache.org/public/committee-info.json")
125-
val committees = unsafeCast<Map<String, Map<String, Any>>>(committeesAll["committees"])
126-
val committee = unsafeCast<Map<String, Any>>(committees[apacheId])
109+
val committee = projectCommitteeMap(apacheId)
127110
return committee["display_name"] as String
128111
}
129112
}
130113

131-
internal fun fetchProjectPeople(apacheId: String): ProjectPeople {
114+
internal fun projectCommitteeMap(apacheId: String): Map<String, Any> {
115+
val committeesAll: Map<String, Map<String, Any>> =
116+
parseJson("https://whimsy.apache.org/public/committee-info.json")
117+
val committees = unsafeCast<Map<String, Map<String, Any>>>(committeesAll["committees"])
118+
return unsafeCast(committees[apacheId])
119+
}
120+
121+
internal fun projectMap(apacheId: String): Map<String, Any> {
132122
val projectsAll: Map<String, Map<String, Any>> =
133123
parseJson("https://whimsy.apache.org/public/public_ldap_projects.json")
134124
val projects = unsafeCast<Map<String, Map<String, Any>>>(projectsAll["projects"])
@@ -137,19 +127,26 @@ internal fun fetchProjectPeople(apacheId: String): ProjectPeople {
137127
?: throw IllegalArgumentException(
138128
"No project '$apacheId' found in https://whimsy.apache.org/public/public_ldap_projects.json"
139129
)
140-
val isPodlingCurrent = project.containsKey("podling") && project["podling"] == "current"
130+
return project
131+
}
141132

142-
val inceptionYear = (project["createTimestamp"] as String).subSequence(0, 4).toString().toInt()
133+
internal fun podlingMap(apacheId: String): Map<String, Any> {
134+
val podlingsAll: Map<String, Map<String, Any>> =
135+
parseJson("https://whimsy.apache.org/public/public_podlings.json")
136+
val podlings = unsafeCast<Map<String, Map<String, Any>>>(podlingsAll["podling"])
137+
val podling =
138+
podlings[apacheId]
139+
?: throw IllegalArgumentException(
140+
"No podling '$apacheId' found in https://whimsy.apache.org/public/public_podlings.json"
141+
)
142+
return podling
143+
}
143144

144-
// Committers
145-
val peopleProjectRoles: MutableMap<String, MutableList<String>> = mutableMapOf()
146-
val members = unsafeCast(project["members"]) as List<String>
147-
members.forEach { member -> peopleProjectRoles.put(member, mutableListOf("Committer")) }
145+
internal fun fetchProjectInformation(apacheId: String): ProjectInformation {
146+
val project = projectMap(apacheId)
147+
val isPodlingCurrent = project.containsKey("podling") && project["podling"] == "current"
148148

149-
// (P)PMC Members
150-
val pmcRoleName = if (isPodlingCurrent) "PPMC member" else "PMC member"
151-
val owners = unsafeCast(project["owners"]) as List<String>
152-
owners.forEach { member -> peopleProjectRoles[member]!!.add(pmcRoleName) }
149+
val inceptionYear = (project["createTimestamp"] as String).subSequence(0, 4).toString().toInt()
153150

154151
val projectName: String
155152
val description: String
@@ -158,14 +155,7 @@ internal fun fetchProjectPeople(apacheId: String): ProjectPeople {
158155
val licenseUrl: String
159156
val bugDatabase: String
160157
if (isPodlingCurrent) {
161-
val podlingsAll: Map<String, Map<String, Any>> =
162-
parseJson("https://whimsy.apache.org/public/public_podlings.json")
163-
val podlings = unsafeCast<Map<String, Map<String, Any>>>(podlingsAll["podling"])
164-
val podling =
165-
podlings[apacheId]
166-
?: throw IllegalArgumentException(
167-
"No podling '$apacheId' found in https://whimsy.apache.org/public/public_podlings.json"
168-
)
158+
val podling = podlingMap(apacheId)
169159
projectName = podling["name"] as String
170160
description = podling["description"] as String
171161
val podlingStatus = unsafeCast(podling["podlingStatus"]) as Map<String, Any>
@@ -174,12 +164,6 @@ internal fun fetchProjectPeople(apacheId: String): ProjectPeople {
174164
repository = "https://github.com/apache/$apacheId.git"
175165
bugDatabase = "https://github.com/apache/$apacheId/issues"
176166
licenseUrl = "https://www.apache.org/licenses/LICENSE-2.0.txt"
177-
178-
val champion = podling["champion"] as String
179-
peopleProjectRoles[champion]!!.add("Champion")
180-
181-
val mentors = unsafeCast(podling["mentors"]) as List<String>
182-
mentors.forEach { member -> peopleProjectRoles[member]!!.add("Mentor") }
183167
} else {
184168
// top-level-project
185169
val tlpPrj: Map<String, Any> =
@@ -189,33 +173,12 @@ internal fun fetchProjectPeople(apacheId: String): ProjectPeople {
189173
bugDatabase = tlpPrj["bug-database"] as String
190174
licenseUrl = tlpPrj["license"] as String
191175

192-
val committeesAll: Map<String, Map<String, Any>> =
193-
parseJson("https://whimsy.apache.org/public/committee-info.json")
194-
val committees = unsafeCast<Map<String, Map<String, Any>>>(committeesAll["committees"])
195-
val committee = unsafeCast<Map<String, Any>>(committees[apacheId])
196-
val pmcChair = unsafeCast<Map<String, Map<String, Any>>>(committee["chair"])
176+
val committee = projectCommitteeMap(apacheId)
197177
projectName = committee["display_name"] as String
198178
description = committee["description"] as String
199-
pmcChair.keys.forEach { chair -> peopleProjectRoles[chair]!!.add("PMC Chair") }
200179
}
201180

202-
val peopleNames: Map<String, Map<String, Any>> =
203-
parseJson("https://whimsy.apache.org/public/public_ldap_people.json")
204-
val people: Map<String, Map<String, Any>> =
205-
unsafeCast(peopleNames["people"]) as Map<String, Map<String, Any>>
206-
val peopleList =
207-
peopleProjectRoles.entries
208-
.map { entry ->
209-
val person =
210-
people[entry.key]
211-
?: throw IllegalStateException(
212-
"No person '${entry.key}' found in https://whimsy.apache.org/public/public_ldap_people.json"
213-
)
214-
ProjectMember(entry.key, person["name"]!! as String, entry.value)
215-
}
216-
.sortedBy { it.name }
217-
218-
return ProjectPeople(
181+
return ProjectInformation(
219182
apacheId,
220183
projectName,
221184
description,
@@ -224,11 +187,10 @@ internal fun fetchProjectPeople(apacheId: String): ProjectPeople {
224187
licenseUrl,
225188
bugDatabase,
226189
inceptionYear,
227-
peopleList,
228190
)
229191
}
230192

231-
internal class ProjectPeople(
193+
internal class ProjectInformation(
232194
val apacheId: String,
233195
val name: String,
234196
val description: String,
@@ -237,7 +199,4 @@ internal class ProjectPeople(
237199
val licenseUrl: String,
238200
val bugDatabase: String,
239201
val inceptionYear: Int,
240-
val people: List<ProjectMember>,
241202
)
242-
243-
internal class ProjectMember(val apacheId: String, val name: String, val roles: List<String>)

0 commit comments

Comments
 (0)