com.google.gms.googleservices.GoogleServicesTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of google-services Show documentation
Show all versions of google-services Show documentation
Gradle plug-in to build Firebase applications.
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gms.googleservices;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*/
public class GoogleServicesTask extends DefaultTask {
private static final String STATUS_DISABLED = "1";
private static final String STATUS_ENABLED = "2";
private static final String OAUTH_CLIENT_TYPE_WEB = "3";
private static final Pattern GOOGLE_APP_ID_REGEX =
Pattern.compile("(\\d+):(\\d+):(\\p{Alnum}+):(\\p{XDigit}+)");
private static final String GOOGLE_APP_ID_VERSION = "1";
/**
* The input is not technically optional but we want to control the error message.
* Without @Optional, Gradle will complain itself the file is missing.
*/
@InputFile @Optional
public File quickstartFile;
@OutputDirectory
public File intermediateDir;
@Input
public String packageName;
@Input
public String moduleGroup;
@Input
public String moduleGroupFirebase;
@Input
public String moduleVersion;
@Input
public String searchedLocation;
@TaskAction
public void action() throws IOException {
checkVersionConflict();
if (!quickstartFile.isFile()) {
throw new GradleException(String.format("File %s is missing. " +
"The Google Services Plugin cannot function without it. %n Searched Location: %s",
quickstartFile.getName(), searchedLocation));
}
getProject().getLogger().warn("Parsing json file: " + quickstartFile.getPath());
// delete content of outputdir.
deleteFolder(intermediateDir);
if (!intermediateDir.mkdirs()) {
throw new GradleException("Failed to create folder: " + intermediateDir);
}
JsonElement root = new JsonParser().parse(Files.newReader(quickstartFile, Charsets.UTF_8));
if (!root.isJsonObject()) {
throw new GradleException("Malformed root json");
}
JsonObject rootObject = root.getAsJsonObject();
Map resValues = new TreeMap();
Map> resAttributes = new TreeMap>();
handleProjectNumberAndProjectId(rootObject, resValues);
handleFirebaseUrl(rootObject, resValues);
JsonObject clientObject = getClientForPackageName(rootObject);
if (clientObject != null) {
handleAnalytics(clientObject, resValues);
handleMapsService(clientObject, resValues);
handleGoogleApiKey(clientObject, resValues);
handleGoogleAppId(clientObject, resValues);
handleWebClientId(clientObject, resValues);
} else {
throw new GradleException("No matching client found for package name '" + packageName + "'");
}
// write the values file.
File values = new File(intermediateDir, "values");
if (!values.exists() && !values.mkdirs()) {
throw new GradleException("Failed to create folder: " + values);
}
Files.write(getValuesContent(resValues, resAttributes), new File(values, "values.xml"), Charsets.UTF_8);
}
/**
* Check if there is any conflict between Play-Services Version
*/
private void checkVersionConflict() {
Project project = getProject();
ConfigurationContainer configurations = project.getConfigurations();
if (configurations == null) {
return;
}
boolean hasConflict = false;
for (Configuration configuration : configurations) {
if (configuration == null) {
continue;
}
DependencySet dependencies = configuration.getDependencies();
if (dependencies == null) {
continue;
}
for (Dependency dependency : dependencies) {
if (dependency == null || dependency.getGroup() == null || dependency.getVersion() == null) {
continue;
}
String dependencyGroup = dependency.getGroup();
String dependencyVersion = dependency.getVersion();
if ((dependencyGroup.equals(moduleGroup) || dependencyGroup.equals(moduleGroupFirebase))
&& !dependencyVersion.equals(moduleVersion)) {
hasConflict = true;
project.getLogger().warn("Found " + dependencyGroup + ":" +
dependency.getName() + ":" + dependencyVersion + ", but version " +
moduleVersion + " is needed for the google-services plugin.");
}
}
}
if (hasConflict) {
throw new GradleException("Please fix the version conflict either by updating the version " +
"of the google-services plugin (information about the latest version is available at " +
"https://bintray.com/android/android-tools/com.google.gms.google-services/) or updating " +
"the version of " + moduleGroup + " to " + moduleVersion + ".");
}
}
private void handleFirebaseUrl(JsonObject rootObject, Map resValues)
throws IOException {
JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
if (projectInfo == null) {
throw new GradleException("Missing project_info object");
}
JsonPrimitive firebaseUrl = projectInfo.getAsJsonPrimitive("firebase_url");
if (firebaseUrl != null) {
resValues.put("firebase_database_url", firebaseUrl.getAsString());
}
}
/**
* Handle project_info/project_number for @string/gcm_defaultSenderId, and fill the res map with the read value.
* @param rootObject the root Json object.
* @throws IOException
*/
private void handleProjectNumberAndProjectId(JsonObject rootObject, Map resValues)
throws IOException {
JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
if (projectInfo == null) {
throw new GradleException("Missing project_info object");
}
JsonPrimitive projectNumber = projectInfo.getAsJsonPrimitive("project_number");
if (projectNumber == null) {
throw new GradleException("Missing project_info/project_number object");
}
resValues.put("gcm_defaultSenderId", projectNumber.getAsString());
JsonPrimitive projectId = projectInfo.getAsJsonPrimitive("project_id");
if (projectId == null) {
throw new GradleException("Missing project_info/project_id object");
}
resValues.put("project_id", projectId.getAsString());
JsonPrimitive bucketName = projectInfo.getAsJsonPrimitive("storage_bucket");
if (bucketName != null) {
resValues.put("google_storage_bucket", bucketName.getAsString());
}
}
private void handleWebClientId(JsonObject clientObject, Map resValues) {
JsonArray array = clientObject.getAsJsonArray("oauth_client");
if (array != null) {
final int count = array.size();
for (int i = 0 ; i < count ; i++) {
JsonElement oauthClientElement = array.get(i);
if (oauthClientElement == null || !oauthClientElement.isJsonObject()) {
continue;
}
JsonObject oauthClientObject = oauthClientElement.getAsJsonObject();
JsonPrimitive clientType = oauthClientObject.getAsJsonPrimitive("client_type");
if (clientType == null) {
continue;
}
String clientTypeStr = clientType.getAsString();
if (!OAUTH_CLIENT_TYPE_WEB.equals(clientTypeStr)) {
continue;
}
JsonPrimitive clientId = oauthClientObject.getAsJsonPrimitive("client_id");
if (clientId == null) {
continue;
}
resValues.put("default_web_client_id", clientId.getAsString());
return;
}
}
}
/**
* Handle a client object for analytics (@xml/global_tracker)
* @param clientObject the client Json object.
* @throws IOException
*/
private void handleAnalytics(JsonObject clientObject, Map resValues)
throws IOException {
JsonObject analyticsService = getServiceByName(clientObject, "analytics_service");
if (analyticsService == null) return;
JsonObject analyticsProp = analyticsService.getAsJsonObject("analytics_property");
if (analyticsProp == null) return;
JsonPrimitive trackingId = analyticsProp.getAsJsonPrimitive("tracking_id");
if (trackingId == null) return;
resValues.put("ga_trackingId", trackingId.getAsString());
File xml = new File(intermediateDir, "xml");
if (!xml.exists() && !xml.mkdirs()) {
throw new GradleException("Failed to create folder: " + xml);
}
Files.write(getGlobalTrackerContent(
trackingId.getAsString()),
new File(xml, "global_tracker.xml"),
Charsets.UTF_8);
}
/**
* Handle a client object for maps (@string/google_maps_key).
* @param clientObject the client Json object.
* @throws IOException
*/
private void handleMapsService(JsonObject clientObject, Map resValues)
throws IOException {
JsonObject mapsService = getServiceByName(clientObject, "maps_service");
if (mapsService == null) return;
String apiKey = getAndroidApiKey(clientObject);
if (apiKey != null) {
resValues.put("google_maps_key", apiKey);
return;
}
throw new GradleException("Missing api_key/current_key object");
}
private void handleGoogleApiKey(JsonObject clientObject, Map resValues) {
String apiKey = getAndroidApiKey(clientObject);
if (apiKey != null) {
resValues.put("google_api_key", apiKey);
// TODO: remove this once SDK starts to use google_api_key.
resValues.put("google_crash_reporting_api_key", apiKey);
return;
}
// if google_crash_reporting_api_key is missing.
// throw new GradleException("Missing api_key/current_key object");
throw new GradleException("Missing api_key/current_key object");
}
private String getAndroidApiKey(JsonObject clientObject) {
JsonArray array = clientObject.getAsJsonArray("api_key");
if (array != null) {
final int count = array.size();
for (int i = 0 ; i < count ; i++) {
JsonElement apiKeyElement = array.get(i);
if (apiKeyElement == null || !apiKeyElement.isJsonObject()) {
continue;
}
JsonObject apiKeyObject = apiKeyElement.getAsJsonObject();
JsonPrimitive currentKey = apiKeyObject.getAsJsonPrimitive("current_key");
if (currentKey == null) {
continue;
}
return currentKey.getAsString();
}
}
return null;
}
private static void findStringByName(JsonObject jsonObject, String stringName,
Map resValues) {
JsonPrimitive id = jsonObject.getAsJsonPrimitive(stringName);
if (id != null) {
resValues.put(stringName, id.getAsString());
}
}
/**
* find an item in the "client" array that match the package name of the app
* @param jsonObject the root json object.
* @return a JsonObject representing the client entry or null if no match is found.
*/
private JsonObject getClientForPackageName(JsonObject jsonObject) {
JsonArray array = jsonObject.getAsJsonArray("client");
if (array != null) {
final int count = array.size();
for (int i = 0 ; i < count ; i++) {
JsonElement clientElement = array.get(i);
if (clientElement == null || !clientElement.isJsonObject()) {
continue;
}
JsonObject clientObject = clientElement.getAsJsonObject();
JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
if (clientInfo == null) continue;
JsonObject androidClientInfo = clientInfo.getAsJsonObject("android_client_info");
if (androidClientInfo == null) continue;
JsonPrimitive clientPackageName = androidClientInfo.getAsJsonPrimitive("package_name");
if (clientPackageName == null) continue;
if (packageName.equals(clientPackageName.getAsString())) {
return clientObject;
}
}
}
return null;
}
/**
* Handle a client object for Google App Id.
*/
private void handleGoogleAppId(JsonObject clientObject, Map resValues)
throws IOException {
JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
if (clientInfo == null) {
// Should not happen
throw new GradleException("Client does not have client info");
}
JsonPrimitive googleAppId = clientInfo.getAsJsonPrimitive("mobilesdk_app_id");
String googleAppIdStr = googleAppId == null ? null : googleAppId.getAsString();
if (Strings.isNullOrEmpty(googleAppIdStr)) {
throw new GradleException("Missing Google App Id. " +
"Please follow instructions on https://firebase.google.com/ to get a valid " +
"config file that contains a Google App Id");
}
Matcher matcher = GOOGLE_APP_ID_REGEX.matcher(googleAppIdStr);
if (!matcher.matches()) {
throw new GradleException("Unexpected format of Google App ID. " +
"Please follow instructions on https://firebase.google.com/ to get a config file " +
"that contains a valid Google App Id or update the plugin version if you believe " +
"your Google App Id [" + googleAppIdStr + "] is correct.");
}
String version = matcher.group(1);
if (!GOOGLE_APP_ID_VERSION.equals(version)) {
throw new GradleException("Google App Id Version is incompatible with this plugin. " +
"Please update the plugin version.");
}
String platform = matcher.group(3);
if (!platform.equals("android")) {
throw new GradleException("Expect Google App Id for Android App, but get " + platform);
}
resValues.put("google_app_id", googleAppIdStr);
}
/**
* Finds a service by name in the client object. Returns null if the service is not found
* or if the service is disabled.
*
* @param clientObject the json object that represents the client.
* @param serviceName the service name
* @return the service if found.
*/
private JsonObject getServiceByName(JsonObject clientObject, String serviceName) {
JsonObject services = clientObject.getAsJsonObject("services");
if (services == null) return null;
JsonObject service = services.getAsJsonObject(serviceName);
if (service == null) return null;
JsonPrimitive status = service.getAsJsonPrimitive("status");
if (status == null) return null;
String statusStr = status.getAsString();
if (STATUS_DISABLED.equals(statusStr)) return null;
if (!STATUS_ENABLED.equals(statusStr)) {
getLogger().warn(String.format("Status with value '%1$s' for service '%2$s' is unknown",
statusStr,
serviceName));
return null;
}
return service;
}
private static String getGlobalTrackerContent(String ga_trackingId) {
return "\n" +
"\n" +
" " + ga_trackingId + " \n" +
" \n";
}
private static String getValuesContent(Map values,
Map> attributes) {
StringBuilder sb = new StringBuilder(256);
sb.append("\n" +
"\n");
for (Map.Entry entry : values.entrySet()) {
String name = entry.getKey();
sb.append(" attr : attributes.get(name).entrySet()) {
sb.append(" ").append(attr.getKey()).append("=\"")
.append(attr.getValue()).append("\"");
}
}
sb.append(">").append(entry.getValue()).append(" \n");
}
sb.append(" \n");
return sb.toString();
}
private static void deleteFolder(final File folder) {
if (!folder.exists()) {
return;
}
File[] files = folder.listFiles();
if (files != null) {
for (final File file : files) {
if (file.isDirectory()) {
deleteFolder(file);
} else {
if (!file.delete()) {
throw new GradleException("Failed to delete: " + file);
}
}
}
}
if (!folder.delete()) {
throw new GradleException("Failed to delete: " + folder);
}
}
}