Skip to content

Commit f1d31f8

Browse files
author
David Motsonashvili
committed
First commit, removed root files and moved configuration to BomGeneratorTask
1 parent a493044 commit f1d31f8

File tree

8 files changed

+910
-0
lines changed

8 files changed

+910
-0
lines changed

buildSrc/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,21 @@ dependencies {
4242
implementation "com.google.firebase:perf-plugin:$perfPluginVersion"
4343

4444
implementation 'org.jsoup:jsoup:1.11.2'
45+
implementation "com.google.auto.value:auto-value-annotations:1.6.6"
46+
annotationProcessor "com.google.auto.value:auto-value:1.6.5"
4547
implementation 'digital.wup:android-maven-publish:3.6.2'
4648
implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72'
4749
implementation 'org.json:json:20180813'
4850
implementation 'io.opencensus:opencensus-api:0.18.0'
4951
implementation 'io.opencensus:opencensus-exporter-stats-stackdriver:0.18.0'
52+
implementation "org.eclipse.aether:aether-api:1.0.0.v20140518"
53+
implementation "org.eclipse.aether:aether-util:1.0.0.v20140518"
54+
implementation "org.eclipse.aether:aether-impl:1.0.0.v20140518"
55+
implementation "org.eclipse.aether:aether-connector-basic:1.0.0.v20140518"
56+
implementation "org.eclipse.aether:aether-transport-file:1.0.0.v20140518"
57+
implementation "org.eclipse.aether:aether-transport-http:1.0.0.v20140518"
58+
implementation "org.eclipse.aether:aether-transport-wagon:1.0.0.v20140518"
59+
implementation "org.apache.maven:maven-aether-provider:3.1.0"
5060
runtime 'io.opencensus:opencensus-impl:0.18.0'
5161
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
5262
implementation 'org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.17-g013-9b8280a'
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
16+
package com.google.firebase.gradle.bomgenerator;
17+
18+
import static java.util.stream.Collectors.toList;
19+
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.net.URL;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.util.HashMap;
27+
import java.util.HashSet;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Optional;
31+
import java.util.Set;
32+
import java.util.stream.Collectors;
33+
34+
import org.eclipse.aether.resolution.VersionRangeResolutionException;
35+
import org.gradle.api.DefaultTask;
36+
import org.gradle.api.tasks.TaskAction;
37+
import org.w3c.dom.Document;
38+
import org.w3c.dom.Element;
39+
import org.w3c.dom.NodeList;
40+
import org.xml.sax.SAXException;
41+
42+
import com.google.common.collect.ImmutableList;
43+
import com.google.common.collect.ImmutableMap;
44+
import com.google.common.collect.Sets;
45+
import com.google.firebase.gradle.bomgenerator.model.Dependency;
46+
import com.google.firebase.gradle.bomgenerator.model.VersionBump;
47+
48+
import javax.xml.parsers.DocumentBuilder;
49+
import javax.xml.parsers.DocumentBuilderFactory;
50+
import javax.xml.parsers.ParserConfigurationException;
51+
52+
public class BomGeneratorTask extends DefaultTask {
53+
private static final List<String> BOM_ARTIFACTS = ImmutableList.of(
54+
"com.crashlytics.sdk.android:crashlytics",
55+
"com.google.firebase:firebase-ads",
56+
"com.google.firebase:firebase-ads-lite",
57+
"com.google.firebase:firebase-analytics",
58+
"com.google.firebase:firebase-analytics-ktx",
59+
"com.google.firebase:firebase-appindexing",
60+
"com.google.firebase:firebase-auth",
61+
"com.google.firebase:firebase-auth-ktx",
62+
"com.google.firebase:firebase-common",
63+
"com.google.firebase:firebase-common-ktx",
64+
"com.google.firebase:firebase-config",
65+
"com.google.firebase:firebase-config-ktx",
66+
"com.google.firebase:firebase-core",
67+
"com.google.firebase:firebase-crash",
68+
"com.google.firebase:firebase-crashlytics",
69+
"com.google.firebase:firebase-crashlytics-ktx",
70+
"com.google.firebase:firebase-crashlytics-ndk",
71+
"com.google.firebase:firebase-database",
72+
"com.google.firebase:firebase-database-ktx",
73+
"com.google.firebase:firebase-dynamic-links",
74+
"com.google.firebase:firebase-dynamic-links-ktx",
75+
"com.google.firebase:firebase-encoders",
76+
"com.google.firebase:firebase-firestore",
77+
"com.google.firebase:firebase-firestore-ktx",
78+
"com.google.firebase:firebase-functions",
79+
"com.google.firebase:firebase-functions-ktx",
80+
"com.google.firebase:firebase-iid",
81+
"com.google.firebase:firebase-inappmessaging",
82+
"com.google.firebase:firebase-inappmessaging-display",
83+
"com.google.firebase:firebase-inappmessaging-display-ktx",
84+
"com.google.firebase:firebase-inappmessaging-ktx",
85+
"com.google.firebase:firebase-installations",
86+
"com.google.firebase:firebase-installations-ktx",
87+
"com.google.firebase:firebase-messaging",
88+
"com.google.firebase:firebase-messaging-directboot",
89+
"com.google.firebase:firebase-messaging-ktx",
90+
"com.google.firebase:firebase-ml-model-interpreter",
91+
"com.google.firebase:firebase-ml-vision",
92+
"com.google.firebase:firebase-perf",
93+
"com.google.firebase:firebase-perf-ktx",
94+
"com.google.firebase:firebase-storage",
95+
"com.google.firebase:firebase-storage-ktx");
96+
private static final List<String> IGNORED_ARTIFACTS = ImmutableList.of(
97+
"crash-plugin",
98+
"firebase-abt",
99+
"firebase-analytics-impl",
100+
"firebase-analytics-impl-license",
101+
"firebase-analytics-license",
102+
"firebase-annotations",
103+
"firebase-appdistribution-gradle",
104+
"firebase-appindexing-license",
105+
"firebase-auth-common",
106+
"firebase-auth-impl",
107+
"firebase-auth-interop",
108+
"firebase-auth-license",
109+
"firebase-encoders-json",
110+
"firebase-auth-module",
111+
"firebase-bom",
112+
"firebase-common-license",
113+
"firebase-components",
114+
"firebase-config-license",
115+
"firebase-crash-license",
116+
"firebase-crashlytics-buildtools",
117+
"firebase-crashlytics-gradle",
118+
"firebase-database-collection",
119+
"firebase-database-connection",
120+
"firebase-database-connection-license",
121+
"firebase-database-license",
122+
"firebase-datatransport",
123+
"firebase-dynamic-links-license",
124+
"firebase-functions-license",
125+
"firebase-iid-interop",
126+
"firebase-iid-license",
127+
"firebase-invites",
128+
"firebase-measurement-connector",
129+
"firebase-measurement-connector-impl",
130+
"firebase-messaging-license",
131+
"firebase-ml-common",
132+
"firebase-ml-vision-internal-vkp",
133+
"firebase-perf-license",
134+
"firebase-plugins",
135+
"firebase-storage-common",
136+
"firebase-storage-common-license",
137+
"firebase-storage-license",
138+
"perf-plugin",
139+
"protolite-well-known-types",
140+
"testlab-instr-lib",
141+
"firebase-installations-interop",
142+
"google-services",
143+
"gradle",
144+
"firebase-ml-vision-automl",
145+
"firebase-ml-vision-barcode-model",
146+
"firebase-ml-vision-face-model",
147+
"firebase-ml-vision-image-label-model",
148+
"firebase-ml-vision-object-detection-model",
149+
"firebase-ml-natural-language",
150+
"firebase-ml-natural-language-language-id-model",
151+
"firebase-ml-natural-language-smart-reply",
152+
"firebase-ml-natural-language-smart-reply-model",
153+
"firebase-ml-natural-language-translate",
154+
"firebase-ml-natural-language-translate-model");
155+
private static final List<String> IMPORTANT_NON_FIREBASE_LIBRARIES = ImmutableList.of(
156+
"com.google.gms:google-services",
157+
"com.android.tools.build:gradle",
158+
"com.google.firebase:perf-plugin",
159+
"com.google.firebase:firebase-crashlytics-gradle",
160+
"com.google.firebase:firebase-appdistribution-gradle");
161+
162+
private Set<String> ignoredFirebaseArtifacts;
163+
private Set<String> bomArtifacts;
164+
private Set<String> allFirebaseArtifacts;
165+
166+
public Map<String, String> versionOverrides = new HashMap<>();
167+
168+
/**
169+
* This task generates a current Bill of Materials (BoM) based on the latest versions of
170+
* everything in gMaven. This is meant to be a post-release task so that the BoM contains the most
171+
* recent versions of all artifacts.
172+
*
173+
* <p>Version overrides may be given to this task in a map like so: versionOverrides =
174+
* ["com.google.firebase:firebase-firestore": "17.0.1"]
175+
*/
176+
@TaskAction
177+
public void generateBom() throws Exception {
178+
// Repo Access Setup
179+
RepositoryClient depPopulator = new RepositoryClient();
180+
181+
// Prepare script by pulling the state of the world (checking configuration files and gMaven artifacts)
182+
bomArtifacts = new HashSet(BOM_ARTIFACTS);
183+
ignoredFirebaseArtifacts = new HashSet(IGNORED_ARTIFACTS);
184+
allFirebaseArtifacts = depPopulator.getAllFirebaseArtifacts();
185+
allFirebaseArtifacts.addAll(IMPORTANT_NON_FIREBASE_LIBRARIES);
186+
187+
// Find version for BoM artifact. First version released should be 15.0.0
188+
String currentVersion =
189+
depPopulator
190+
.getLastPublishedVersion(Dependency.create("com.google.firebase", "firebase-bom"))
191+
.orElse("15.0.0");
192+
193+
// We need to get the content of the current BoM to compute version bumps.
194+
Map<String, String> previousBomVersions = getBomMap(currentVersion);
195+
196+
// Generate list of firebase libraries, ping gmaven for current versions, and override as needed
197+
// from local settings
198+
List<Dependency> allFirebaseDependencies = buildVersionedDependencyList(depPopulator, previousBomVersions);
199+
200+
List<Dependency> bomDependencies = allFirebaseDependencies.stream().filter(dep -> bomArtifacts.contains(dep.fullArtifactId())).collect(toList());
201+
202+
// Sanity check that there are no unaccounted for artifacts that we might want in the BoM
203+
Set<String> bomArtifactIds = bomArtifacts.stream().map(x -> x.split(":")[1]).collect(Collectors.toSet());
204+
Set<String> allFirebaseArtifactIds = allFirebaseArtifacts.stream().map(x -> x.split(":")[1]).collect(Collectors.toSet());
205+
Set<String> invalidArtifacts = Sets.difference(Sets.difference(allFirebaseArtifactIds, bomArtifactIds), ignoredFirebaseArtifacts);
206+
207+
if (!invalidArtifacts.isEmpty()) {
208+
throw new RuntimeException("Some dependencies are unaccounted for, add to BomGeneratorTask#IGNORED_ARTIFACTS or " +
209+
"BomGeneratorTask#BOM_ARTIFACTS. Unaccounted for dependencies: " + invalidArtifacts.toString());
210+
}
211+
String version = findArtifactVersion(bomDependencies, currentVersion, previousBomVersions);
212+
213+
214+
// Surface generated pom for sanity checking and testing, and then write it.
215+
Path projectRootDir = this.getProject().getRootDir().toPath();
216+
PomXmlWriter xmlWriter = new PomXmlWriter(bomDependencies, version, projectRootDir);
217+
MarkdownDocumentationWriter documentationWriter = new MarkdownDocumentationWriter(bomDependencies, version, previousBomVersions, currentVersion);
218+
RecipeVersionWriter recipeWriter = new RecipeVersionWriter(allFirebaseDependencies);
219+
Document outputXmlDoc = xmlWriter.generatePomXml();
220+
String outputDocumentation = documentationWriter.generateDocumentation();
221+
String outputRecipe = recipeWriter.generateVersionUpdate();
222+
xmlWriter.writeXmlDocument(outputXmlDoc);
223+
documentationWriter.writeDocumentation(outputDocumentation);
224+
recipeWriter.writeVersionUpdate(outputRecipe);
225+
}
226+
227+
// Finds the version for the BoM artifact.
228+
private String findArtifactVersion(List<Dependency> firebaseDependencies, String currentVersion, Map<String, String> previousBomVersions)
229+
throws VersionRangeResolutionException {
230+
Optional<VersionBump> bump =
231+
firebaseDependencies.stream().map(Dependency::versionBump).distinct().sorted().findFirst();
232+
233+
if (firebaseDependencies.size() < previousBomVersions.size()) {
234+
bump = Optional.of(VersionBump.MAJOR);
235+
}
236+
237+
return bump.map(x -> VersionBump.bumpVersionBy(currentVersion, x))
238+
.orElseThrow(() -> new RuntimeException("Could not figure out how to bump version"));
239+
}
240+
241+
private Dependency overrideVersion(Dependency dep) {
242+
if (versionOverrides.containsKey(dep.fullArtifactId())) {
243+
return Dependency.create(
244+
dep.groupId(),
245+
dep.artifactId(),
246+
versionOverrides.get(dep.fullArtifactId()),
247+
VersionBump.PATCH);
248+
} else {
249+
return dep;
250+
}
251+
}
252+
253+
private List<Dependency> buildVersionedDependencyList(RepositoryClient depPopulator, Map<String, String> previousBomVersions) {
254+
return allFirebaseArtifacts.stream().map(dep -> {
255+
String[] splitDep = dep.split(":");
256+
return Dependency.create(splitDep[0], splitDep[1]);
257+
}).map(dep -> depPopulator.populateDependencyVersion(dep, previousBomVersions))
258+
.map(this::overrideVersion)
259+
.collect(toList());
260+
}
261+
262+
private Map<String, String> getBomMap(String bomVersion) {
263+
String bomUrl = "https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-bom/"
264+
+ bomVersion + "/firebase-bom-" + bomVersion + ".pom";
265+
try(InputStream index = new URL(bomUrl).openStream()) {
266+
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
267+
factory.setValidating(true);
268+
factory.setIgnoringElementContentWhitespace(true);
269+
DocumentBuilder builder = factory.newDocumentBuilder();
270+
Document doc = builder.parse(index);
271+
NodeList dependencyList = doc.getElementsByTagName("dependency");
272+
ImmutableMap.Builder<String, String> outputBuilder = ImmutableMap.builder();
273+
for (int i = 0; i < dependencyList.getLength(); i++) {
274+
Element artifact = (Element) dependencyList.item(i);
275+
String groupId = artifact.getElementsByTagName("groupId").item(0).getTextContent();
276+
String artifactId = artifact.getElementsByTagName("artifactId").item(0).getTextContent();
277+
String version = artifact.getElementsByTagName("version").item(0).getTextContent();
278+
outputBuilder.put(groupId + ":" + artifactId, version);
279+
}
280+
return outputBuilder.build();
281+
} catch (SAXException | IOException | ParserConfigurationException e) {
282+
throw new RuntimeException("Failed to get contents of BoM version " + bomVersion, e);
283+
}
284+
}
285+
}

0 commit comments

Comments
 (0)