blob: af28ddf1758c802e74903184abeb8a5dc50bd57c [file] [log] [blame]
/*
* 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.android.build.gradle.internal;
import static com.android.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY;
import static com.android.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED;
import static com.android.builder.model.AndroidProject.PROPERTY_INVOKED_FROM_IDE;
import static com.android.ide.common.blame.parser.JsonEncodedGradleMessageParser.STDOUT_ERROR_TAG;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.AndroidGradleOptions;
import com.android.build.gradle.api.BaseVariant;
import com.android.build.gradle.internal.dsl.CoreBuildType;
import com.android.build.gradle.internal.dsl.CoreProductFlavor;
import com.android.build.gradle.internal.model.ArtifactMetaDataImpl;
import com.android.build.gradle.internal.model.JavaArtifactImpl;
import com.android.build.gradle.internal.model.SyncIssueImpl;
import com.android.build.gradle.internal.model.SyncIssueKey;
import com.android.build.gradle.internal.variant.DefaultSourceProviderContainer;
import com.android.builder.core.ErrorReporter;
import com.android.builder.model.AndroidArtifact;
import com.android.builder.model.ArtifactMetaData;
import com.android.builder.model.JavaArtifact;
import com.android.builder.model.SourceProvider;
import com.android.builder.model.SourceProviderContainer;
import com.android.builder.model.SyncIssue;
import com.android.ide.common.blame.Message;
import com.android.ide.common.blame.MessageJsonSerializer;
import com.android.ide.common.blame.SourceFilePosition;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import java.io.File;
import java.util.Collection;
import java.util.Map;
/**
* For storing additional model information.
*/
public class ExtraModelInfo extends ErrorReporter {
@NonNull
private final Project project;
private final boolean isLibrary;
@NonNull
private final ErrorFormatMode errorFormatMode;
private final Map<SyncIssueKey, SyncIssue> syncIssues = Maps.newHashMap();
private final Map<String, ArtifactMetaData> extraArtifactMap = Maps.newHashMap();
private final ListMultimap<String, AndroidArtifact> extraAndroidArtifacts = ArrayListMultimap.create();
private final ListMultimap<String, JavaArtifact> extraJavaArtifacts = ArrayListMultimap.create();
private final ListMultimap<String, SourceProviderContainer> extraBuildTypeSourceProviders = ArrayListMultimap.create();
private final ListMultimap<String, SourceProviderContainer> extraProductFlavorSourceProviders = ArrayListMultimap.create();
private final ListMultimap<String, SourceProviderContainer> extraMultiFlavorSourceProviders = ArrayListMultimap.create();
@Nullable
private final Gson mGson;
public ExtraModelInfo(@NonNull Project project, boolean isLibrary) {
super(computeModelQueryMode(project));
this.project = project;
this.isLibrary = isLibrary;
errorFormatMode = computeErrorFormatMode(project);
if (errorFormatMode == ErrorFormatMode.MACHINE_PARSABLE) {
GsonBuilder gsonBuilder = new GsonBuilder();
MessageJsonSerializer.registerTypeAdapters(gsonBuilder);
mGson = gsonBuilder.create();
} else {
mGson = null;
}
}
public boolean isLibrary() {
return isLibrary;
}
public Map<SyncIssueKey, SyncIssue> getSyncIssues() {
return syncIssues;
}
@Override
@NonNull
public SyncIssue handleSyncError(@NonNull String data, int type, @NonNull String msg) {
SyncIssue issue;
switch (getMode()) {
case STANDARD:
if (!isDependencyIssue(type)) {
throw new GradleException(msg);
}
// if it's a dependency issue we don't throw right away. we'll
// throw during build instead.
// but we do log.
project.getLogger().warn("WARNING: " + msg);
issue = new SyncIssueImpl(type, SyncIssue.SEVERITY_ERROR, data, msg);
break;
case IDE_LEGACY:
// compat mode for the only issue supported before the addition of SyncIssue
// in the model.
if (type != SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) {
throw new GradleException(msg);
}
// intended fall-through
case IDE:
// new IDE, able to support SyncIssue.
issue = new SyncIssueImpl(type, SyncIssue.SEVERITY_ERROR, data, msg);
syncIssues.put(SyncIssueKey.from(issue), issue);
break;
default:
throw new RuntimeException("Unknown SyncIssue type");
}
return issue;
}
private static boolean isDependencyIssue(int type) {
switch (type) {
case SyncIssue.TYPE_UNRESOLVED_DEPENDENCY:
case SyncIssue.TYPE_DEPENDENCY_IS_APK:
case SyncIssue.TYPE_DEPENDENCY_IS_APKLIB:
case SyncIssue.TYPE_NON_JAR_LOCAL_DEP:
case SyncIssue.TYPE_NON_JAR_PACKAGE_DEP:
case SyncIssue.TYPE_NON_JAR_PROVIDED_DEP:
case SyncIssue.TYPE_JAR_DEPEND_ON_AAR:
case SyncIssue.TYPE_MISMATCH_DEP:
return true;
}
return false;
}
@Override
public void receiveMessage(@NonNull Message message) {
StringBuilder errorStringBuilder = new StringBuilder();
if (errorFormatMode == ErrorFormatMode.HUMAN_READABLE) {
for (SourceFilePosition pos : message.getSourceFilePositions()) {
errorStringBuilder.append(pos.toString());
errorStringBuilder.append(' ');
}
if (errorStringBuilder.length() > 0) {
errorStringBuilder.append(": ");
}
errorStringBuilder.append(message.getText()).append("\n");
} else {
//noinspection ConstantConditions mGson != null when errorFormatMode == MACHINE_PARSABLE
errorStringBuilder.append(STDOUT_ERROR_TAG)
.append(mGson.toJson(message)).append("\n");
}
String messageString = errorStringBuilder.toString();
switch (message.getKind()) {
case ERROR:
project.getLogger().error(messageString);
break;
case WARNING:
project.getLogger().warn(messageString);
break;
case INFO:
project.getLogger().info(messageString);
break;
case STATISTICS:
project.getLogger().trace(messageString);
break;
case UNKNOWN:
project.getLogger().debug(messageString);
break;
case SIMPLE:
project.getLogger().info(messageString);
break;
}
}
public Collection<ArtifactMetaData> getExtraArtifacts() {
return extraArtifactMap.values();
}
public Collection<AndroidArtifact> getExtraAndroidArtifacts(@NonNull String variantName) {
return extraAndroidArtifacts.get(variantName);
}
public Collection<JavaArtifact> getExtraJavaArtifacts(@NonNull String variantName) {
return extraJavaArtifacts.get(variantName);
}
public Collection<SourceProviderContainer> getExtraFlavorSourceProviders(
@NonNull String flavorName) {
return extraProductFlavorSourceProviders.get(flavorName);
}
public Collection<SourceProviderContainer> getExtraBuildTypeSourceProviders(
@NonNull String buildTypeName) {
return extraBuildTypeSourceProviders.get(buildTypeName);
}
public void registerArtifactType(@NonNull String name,
boolean isTest,
int artifactType) {
if (extraArtifactMap.get(name) != null) {
throw new IllegalArgumentException(
String.format("Artifact with name %1$s already registered.", name));
}
extraArtifactMap.put(name, new ArtifactMetaDataImpl(name, isTest, artifactType));
}
public void registerBuildTypeSourceProvider(@NonNull String name,
@NonNull CoreBuildType buildType,
@NonNull SourceProvider sourceProvider) {
if (extraArtifactMap.get(name) == null) {
throw new IllegalArgumentException(String.format(
"Artifact with name %1$s is not yet registered. Use registerArtifactType()",
name));
}
extraBuildTypeSourceProviders.put(buildType.getName(),
new DefaultSourceProviderContainer(name, sourceProvider));
}
public void registerProductFlavorSourceProvider(@NonNull String name,
@NonNull CoreProductFlavor productFlavor,
@NonNull SourceProvider sourceProvider) {
if (extraArtifactMap.get(name) == null) {
throw new IllegalArgumentException(String.format(
"Artifact with name %1$s is not yet registered. Use registerArtifactType()",
name));
}
extraProductFlavorSourceProviders.put(productFlavor.getName(),
new DefaultSourceProviderContainer(name, sourceProvider));
}
public void registerMultiFlavorSourceProvider(@NonNull String name,
@NonNull String flavorName,
@NonNull SourceProvider sourceProvider) {
if (extraArtifactMap.get(name) == null) {
throw new IllegalArgumentException(String.format(
"Artifact with name %1$s is not yet registered. Use registerArtifactType()",
name));
}
extraMultiFlavorSourceProviders.put(flavorName,
new DefaultSourceProviderContainer(name, sourceProvider));
}
public void registerJavaArtifact(
@NonNull String name,
@NonNull BaseVariant variant,
@NonNull String assembleTaskName,
@NonNull String javaCompileTaskName,
@NonNull Collection<File> generatedSourceFolders,
@NonNull Iterable<String> ideSetupTaskNames,
@NonNull Configuration configuration,
@NonNull File classesFolder,
@NonNull File javaResourcesFolder,
@Nullable SourceProvider sourceProvider) {
ArtifactMetaData artifactMetaData = extraArtifactMap.get(name);
if (artifactMetaData == null) {
throw new IllegalArgumentException(String.format(
"Artifact with name %1$s is not yet registered. Use registerArtifactType()",
name));
}
if (artifactMetaData.getType() != ArtifactMetaData.TYPE_JAVA) {
throw new IllegalArgumentException(
String.format("Artifact with name %1$s is not of type JAVA", name));
}
JavaArtifact artifact = new JavaArtifactImpl(
name, assembleTaskName, javaCompileTaskName, ideSetupTaskNames,
generatedSourceFolders, classesFolder, javaResourcesFolder, null,
new ConfigurationDependencies(configuration), sourceProvider, null);
extraJavaArtifacts.put(variant.getName(), artifact);
}
/**
* Returns whether we are just trying to build a model for the IDE instead of building. This
* means we will attempt to resolve dependencies even if some are broken/unsupported to avoid
* failing the import in the IDE.
*/
private static EvaluationMode computeModelQueryMode(@NonNull Project project) {
if (AndroidGradleOptions.buildModelOnlyAdvanced(project)) {
return EvaluationMode.IDE;
}
if (AndroidGradleOptions.buildModelOnly(project)) {
return EvaluationMode.IDE_LEGACY;
}
return EvaluationMode.STANDARD;
}
private static ErrorFormatMode computeErrorFormatMode(@NonNull Project project) {
if (AndroidGradleOptions.invokedFromIde(project)) {
return ErrorFormatMode.MACHINE_PARSABLE;
} else {
return ErrorFormatMode.HUMAN_READABLE;
}
}
public enum ErrorFormatMode {
MACHINE_PARSABLE, HUMAN_READABLE
}
}