blob: 521e79199aacc2e9f04741e663cd59de78f34e6c [file] [log] [blame]
/*
* Copyright (C) 2020 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.tools.idea.projectsystem.gradle.sync
import com.android.tools.idea.IdeInfo
import com.android.tools.idea.gradle.project.GradleProjectInfo
import com.android.tools.idea.gradle.project.ProjectStructure
import com.android.tools.idea.gradle.project.SupportedModuleChecker
import com.android.tools.idea.gradle.project.model.AndroidModuleModel
import com.android.tools.idea.gradle.project.sync.idea.computeSdkReloadingAsNeeded
import com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys.ANDROID_MODEL
import com.android.tools.idea.gradle.project.sync.idea.data.service.ModuleModelDataService
import com.android.tools.idea.gradle.project.sync.setup.Facets.removeAllFacets
import com.android.tools.idea.gradle.project.sync.setup.post.MemorySettingsPostSyncChecker
import com.android.tools.idea.gradle.project.sync.setup.post.ProjectSetup
import com.android.tools.idea.gradle.project.sync.setup.post.ProjectStructureUsageTracker
import com.android.tools.idea.gradle.project.sync.setup.post.TimeBasedReminder
import com.android.tools.idea.gradle.project.sync.setup.post.setUpModules
import com.android.tools.idea.gradle.project.upgrade.recommendPluginUpgrade
import com.android.tools.idea.gradle.project.upgrade.shouldRecommendPluginUpgrade
import com.android.tools.idea.gradle.project.sync.validation.android.AndroidModuleValidator
import com.android.tools.idea.gradle.run.MakeBeforeRunTaskProvider
import com.android.tools.idea.gradle.variant.conflict.ConflictSet.findConflicts
import com.android.tools.idea.model.AndroidModel
import com.android.tools.idea.run.RunConfigurationChecker
import com.android.tools.idea.sdk.AndroidSdks
import com.android.tools.idea.sdk.IdeSdks
import com.google.common.annotations.VisibleForTesting
import com.intellij.compiler.options.CompileStepBeforeRun
import com.intellij.execution.BeforeRunTask
import com.intellij.execution.BeforeRunTaskProvider
import com.intellij.execution.RunManagerEx
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.Key
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.service.project.IdeModelsProvider
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task.Backgroundable
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.LanguageLevelModuleExtension
import com.intellij.openapi.util.io.FileUtil.getRelativePath
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
import com.intellij.openapi.vfs.VfsUtilCore
import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.android.facet.AndroidFacetProperties.PATH_LIST_SEPARATOR_IN_FACET_CONFIGURATION
import org.jetbrains.jps.model.serialization.PathMacroUtil
import java.io.File
import java.util.LinkedList
import java.util.concurrent.TimeUnit
/**
* Service that sets an Android SDK and facets to the modules of a project that has been imported from an Android-Gradle project.
*/
class AndroidModuleDataService @VisibleForTesting
internal constructor(private val myModuleValidatorFactory: AndroidModuleValidator.Factory) : ModuleModelDataService<AndroidModuleModel>() {
// This constructor is called by the IDE. See this module's plugin.xml file, implementation of extension 'externalProjectDataService'.
constructor() : this(AndroidModuleValidator.Factory())
override fun getTargetDataKey(): Key<AndroidModuleModel> = ANDROID_MODEL
public override fun importData(toImport: Collection<DataNode<AndroidModuleModel>>,
project: Project,
modelsProvider: IdeModifiableModelsProvider,
modelsByModuleName: Map<String, AndroidModuleModel>) {
val moduleValidator = myModuleValidatorFactory.create(project)
for (module in modelsProvider.modules) {
val androidModel = modelsByModuleName[module.name]
if (androidModel != null) {
// The SDK needs to be set here for Android modules, unfortunately we can't use intellijs
// code to set this us as we need to reload the SDKs in case AGP has just downloaded it.
// Android model is null for the root project module.
val sdkToUse = AndroidSdks.getInstance().computeSdkReloadingAsNeeded(
androidModel.androidProject.name,
androidModel.androidProject.compileTarget,
androidModel.androidProject.bootClasspath,
IdeSdks.getInstance()
)
if (sdkToUse != null) {
modelsProvider.getModifiableRootModel(module).sdk = sdkToUse
}
// Create the Android facet and attache to the module.
val androidFacet = modelsProvider.getModifiableFacetModel(module).getFacetByType(AndroidFacet.ID)
?: createAndroidFacet(module, modelsProvider)
androidModel.setModule(module)
// Configure that Android facet from the information in the AndroidModuleModel.
configureFacet(androidFacet, androidModel)
// Set language level if available
val languageLevel = androidModel.javaLanguageLevel
if (languageLevel != null) {
modelsProvider.getModifiableRootModel(module).getModuleExtension(LanguageLevelModuleExtension::class.java).languageLevel = languageLevel
}
moduleValidator.validate(module, androidModel)
}
else {
// If we don't have a model for this module then we need to ensure that no Android facets are left on the module.
val facetModel = modelsProvider.getModifiableFacetModel(module)
removeAllFacets(facetModel, AndroidFacet.ID)
}
}
if (modelsByModuleName.isNotEmpty()) {
moduleValidator.fixAndReportFoundIssues()
}
}
/**
* This may be called from either the EDT or a background thread depending on if the project import is being run synchronously.
*/
override fun onSuccessImport(imported: Collection<DataNode<AndroidModuleModel>>,
projectData: ProjectData?,
project: Project,
modelsProvider: IdeModelsProvider) {
GradleProjectInfo.getInstance(project).isNewProject = false
GradleProjectInfo.getInstance(project).isImportedProject = false
ApplicationManager.getApplication().executeOnPooledThread {
if (shouldRecommendPluginUpgrade(project)) recommendPluginUpgrade(project)
}
if (IdeInfo.getInstance().isAndroidStudio) {
MemorySettingsPostSyncChecker
.checkSettings(project, TimeBasedReminder(project, "memory.settings.postsync", TimeUnit.DAYS.toMillis(1)))
}
ProjectStructureUsageTracker(project).trackProjectStructure()
SupportedModuleChecker.getInstance().checkForSupportedModules(project)
findConflicts(project).showSelectionConflicts()
ProjectSetup(project).setUpProject(false /* sync successful */)
RunConfigurationChecker.getInstance(project).ensureRunConfigsInvokeBuild()
ProjectStructure.getInstance(project).analyzeProjectStructure()
ProgressManager.getInstance().run(object : Backgroundable(project, "Setting up modules...") {
override fun run(indicator: ProgressIndicator) {
setUpModules(project)
}
})
}
}
/**
* Creates an [AndroidFacet] on the given [module] with the default facet configuration.
*/
private fun createAndroidFacet(module: Module, modelsProvider: IdeModifiableModelsProvider): AndroidFacet {
val model = modelsProvider.getModifiableFacetModel(module)
val facetType = AndroidFacet.getFacetType()
val facet = facetType.createFacet(module, AndroidFacet.NAME, facetType.createDefaultConfiguration(), null)
model.addFacet(facet)
return facet
}
/**
* Configures the given [androidFacet] with the information that is present in the given [androidModuleModel].
*
* Note: we use the currently selected variant of the [androidModuleModel] to perform the configuration.
*/
private fun configureFacet(androidFacet: AndroidFacet, androidModuleModel: AndroidModuleModel) {
@Suppress("DEPRECATION") // One of the legitimate assignments to the property.
androidFacet.properties.ALLOW_USER_CONFIGURATION = false
androidFacet.properties.PROJECT_TYPE = androidModuleModel.androidProject.projectType
val modulePath = androidModuleModel.rootDirPath
val sourceProvider = androidModuleModel.defaultSourceProvider
androidFacet.properties.MANIFEST_FILE_RELATIVE_PATH = relativePath(modulePath, sourceProvider.manifestFile)
androidFacet.properties.RES_FOLDER_RELATIVE_PATH = relativePath(modulePath, sourceProvider.resDirectories.firstOrNull())
androidFacet.properties.ASSETS_FOLDER_RELATIVE_PATH = relativePath(modulePath, sourceProvider.assetsDirectories.firstOrNull())
androidFacet.properties.RES_FOLDERS_RELATIVE_PATH = (androidModuleModel.activeSourceProviders.flatMap { provider ->
provider.resDirectories
} + androidModuleModel.mainArtifact.generatedResourceFolders).joinToString(PATH_LIST_SEPARATOR_IN_FACET_CONFIGURATION) { file ->
VfsUtilCore.pathToUrl(file.absolutePath)
}
val testGenResources = androidModuleModel.artifactForAndroidTest?.generatedResourceFolders ?: listOf()
// Why don't we include the standard unit tests source providers here?
val testSourceProviders = androidModuleModel.androidTestSourceProviders
androidFacet.properties.TEST_RES_FOLDERS_RELATIVE_PATH = (testSourceProviders.flatMap { provider ->
provider.resDirectories
} + testGenResources).joinToString(PATH_LIST_SEPARATOR_IN_FACET_CONFIGURATION) { file ->
VfsUtilCore.pathToUrl(file.absolutePath)
}
AndroidModel.set(androidFacet, androidModuleModel)
androidModuleModel.syncSelectedVariantAndTestArtifact(androidFacet)
}
// It is safe to use "/" instead of File.separator. JpsAndroidModule uses it.
private const val SEPARATOR = "/"
private fun relativePath(basePath: File, file: File?): String {
val relativePath = if (file != null) getRelativePath(basePath, file) else null
if (relativePath != null && !relativePath.startsWith(SEPARATOR)) {
return SEPARATOR + toSystemIndependentName(relativePath)
}
return ""
}
// TODO: Find a better place for this method.
private fun setMakeStepInJUnitConfiguration(
project: Project,
targetProvider: BeforeRunTaskProvider<*>,
runConfiguration: RunConfiguration
) {
// Only "make" steps of beforeRunTasks should be overridden (see http://b.android.com/194704 and http://b.android.com/227280)
val newBeforeRunTasks: MutableList<BeforeRunTask<*>> = LinkedList()
val runManager = RunManagerEx.getInstanceEx(project)
for (beforeRunTask in runManager.getBeforeRunTasks(runConfiguration)) {
if (beforeRunTask.providerId == CompileStepBeforeRun.ID) {
if (runManager.getBeforeRunTasks(runConfiguration, MakeBeforeRunTaskProvider.ID).isEmpty()) {
val task = targetProvider.createTask(runConfiguration)
if (task != null) {
task.isEnabled = true
newBeforeRunTasks.add(task)
}
}
}
else {
newBeforeRunTasks.add(beforeRunTask)
}
}
runManager.setBeforeRunTasks(runConfiguration, newBeforeRunTasks)
}