/* * 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 } }