blob: 7a9fde24bf43368e899a559ec647b47da5b66bf0 [file] [log] [blame]
/*
* Copyright 2016 The Bazel Authors. All rights reserved.
*
* 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.idea.blaze.base.sync.projectstructure;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.idea.blaze.base.io.FileAttributeProvider;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.output.PrintOutput;
import com.google.idea.blaze.base.settings.BlazeImportSettings;
import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/** Module editor implementation. */
public class ModuleEditorImpl implements BlazeSyncPlugin.ModuleEditor {
private static final Logger logger = Logger.getInstance(ModuleEditorImpl.class.getName());
private static final String EXTERNAL_SYSTEM_ID_KEY = "external.system.id";
private static final String EXTERNAL_SYSTEM_ID_VALUE = "Blaze";
private final Project project;
private final ModifiableModuleModel moduleModel;
private final File imlDirectory;
@VisibleForTesting public Map<String, ModifiableRootModel> modules = Maps.newHashMap();
public ModuleEditorImpl(Project project, BlazeImportSettings importSettings) {
this.project = project;
this.moduleModel = ModuleManager.getInstance(project).getModifiableModel();
this.imlDirectory = getImlDirectory(importSettings);
if (!FileAttributeProvider.getInstance().exists(imlDirectory)) {
if (!imlDirectory.mkdirs()) {
logger.error("Could not make directory: " + imlDirectory.getPath());
}
}
}
@Override
public Module createModule(String moduleName, ModuleType moduleType) {
Module module = moduleModel.findModuleByName(moduleName);
if (module == null) {
File imlFile = new File(imlDirectory, moduleName + ModuleFileType.DOT_DEFAULT_EXTENSION);
removeImlFile(imlFile);
module = moduleModel.newModule(imlFile.getPath(), moduleType.getId());
module.setOption(EXTERNAL_SYSTEM_ID_KEY, EXTERNAL_SYSTEM_ID_VALUE);
}
module.setOption(Module.ELEMENT_TYPE, moduleType.getId());
ModifiableRootModel modifiableModel =
ModuleRootManager.getInstance(module).getModifiableModel();
modules.put(module.getName(), modifiableModel);
modifiableModel.clear();
modifiableModel.inheritSdk();
CompilerModuleExtension compilerSettings =
modifiableModel.getModuleExtension(CompilerModuleExtension.class);
if (compilerSettings != null) {
compilerSettings.inheritCompilerOutputPath(false);
}
return module;
}
@Override
public ModifiableRootModel editModule(Module module) {
return modules.get(module.getName());
}
@Override
@Nullable
public Module findModule(String moduleName) {
return moduleModel.findModuleByName(moduleName);
}
public void commitWithGc(BlazeContext context) {
List<Module> orphanModules = Lists.newArrayList();
for (Module module : ModuleManager.getInstance(project).getModules()) {
if (!modules.containsKey(module.getName())) {
orphanModules.add(module);
}
}
if (orphanModules.size() > 0) {
context.output(
PrintOutput.log(String.format("Removing %d dead modules", orphanModules.size())));
for (Module module : orphanModules) {
if (module.isDisposed()) {
continue;
}
moduleModel.disposeModule(module);
File imlFile = new File(module.getModuleFilePath());
removeImlFile(imlFile);
}
}
context.output(PrintOutput.log(String.format("Workspace has %s modules", modules.size())));
commit();
}
@Override
public void commit() {
ModifiableModelCommitter.multiCommit(modules.values(), moduleModel);
}
private File getImlDirectory(BlazeImportSettings importSettings) {
return new File(BlazeDataStorage.getProjectDataDir(importSettings), "modules");
}
// Delete using the virtual file to ensure that IntelliJ properly updates its index.
// Otherwise, it is possible for IntelliJ to read the
// old IML file from its index and behave unpredictably
// (like failing to save the new IML files to disk).
private static void removeImlFile(final File imlFile) {
final VirtualFile imlVirtualFile = VfsUtil.findFileByIoFile(imlFile, true);
if (imlVirtualFile != null && imlVirtualFile.exists()) {
ApplicationManager.getApplication()
.runWriteAction(
new Runnable() {
@Override
public void run() {
try {
imlVirtualFile.delete(this);
} catch (IOException e) {
logger.warn(
String.format(
"Could not delete file: %s, will try to continue anyway.",
imlVirtualFile.getPath()),
e);
}
}
});
}
}
}