/* * Copyright 2013-2016 consulo.io * * 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 consulo.compiler.impl; import com.intellij.compiler.ModuleCompilerUtil; import com.intellij.compiler.impl.*; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.*; import com.intellij.openapi.compiler.Compiler; import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Chunk; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.graph.Graph; import com.intellij.util.graph.GraphGenerator; import com.intellij.util.graph.InboundSemiGraph; import com.intellij.util.messages.MessageBus; import consulo.annotations.RequiredReadAction; import consulo.compiler.CompilerConfiguration; import consulo.compiler.CompilerConfigurationImpl; import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.lang.reflect.Array; import java.util.*; import java.util.concurrent.Semaphore; @State(name = "CompilerManager", storages = @Storage(value = "compiler.xml")) public class CompilerManagerImpl extends CompilerManager implements PersistentStateComponent<Element> { private class ListenerNotificator implements CompileStatusNotification { @Nullable private final CompileStatusNotification myDelegate; private ListenerNotificator(@Nullable CompileStatusNotification delegate) { myDelegate = delegate; } @Override public void finished(boolean aborted, int errors, int warnings, final CompileContext compileContext) { myEventPublisher.compilationFinished(aborted, errors, warnings, compileContext); if (myDelegate != null) { myDelegate.finished(aborted, errors, warnings, compileContext); } } } private final Project myProject; private final ExcludedEntriesConfiguration myExcludedEntriesConfiguration = new ExcludedEntriesConfiguration(); private final List<TranslatingCompiler> myTranslatingCompilers = new ArrayList<>(); private final List<Compiler> myCompilers = new ArrayList<>(); private final Map<TranslatingCompiler, Collection<FileType>> myTranslatingCompilerInputFileTypes = new HashMap<>(); private final Map<TranslatingCompiler, Collection<FileType>> myTranslatingCompilerOutputFileTypes = new HashMap<>(); private CompilationStatusListener myEventPublisher; private Set<LocalFileSystem.WatchRequest> myWatchRoots; private final Semaphore myCompilationSemaphore = new Semaphore(1, true); private final Set<FileType> myCompilableFileTypes = new THashSet<>(); private Compiler[] myAllCompilers; public CompilerManagerImpl(final Project project, final MessageBus messageBus) { myProject = project; if (myProject.isDefault()) { return; } myEventPublisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS); List<TranslatingCompiler> translatingCompilers = new ArrayList<>(); for (Compiler compiler : Compiler.EP_NAME.getExtensions(project)) { compiler.registerCompilableFileTypes(myCompilableFileTypes::add); if (compiler instanceof TranslatingCompiler) { TranslatingCompiler translatingCompiler = (TranslatingCompiler)compiler; translatingCompilers.add(translatingCompiler); myTranslatingCompilerInputFileTypes.put(translatingCompiler, Arrays.asList(translatingCompiler.getInputFileTypes())); myTranslatingCompilerOutputFileTypes.put(translatingCompiler, Arrays.asList(translatingCompiler.getOutputFileTypes())); } else { myCompilers.add(compiler); } } final List<Chunk<TranslatingCompiler>> chunks = ModuleCompilerUtil.getSortedChunks(createCompilerGraph(translatingCompilers)); for (Chunk<TranslatingCompiler> chunk : chunks) { myTranslatingCompilers.addAll(chunk.getNodes()); } final File projectGeneratedSrcRoot = CompilerPaths.getGeneratedDataDirectory(project); projectGeneratedSrcRoot.mkdirs(); final LocalFileSystem lfs = LocalFileSystem.getInstance(); myWatchRoots = lfs.addRootsToWatch(Collections.singletonList(FileUtil.toCanonicalPath(projectGeneratedSrcRoot.getPath())), true); Disposer.register(project, () -> { lfs.removeWatchedRoots(myWatchRoots); if (ApplicationManager.getApplication().isUnitTestMode()) { // force cleanup for created compiler system directory with generated sources FileUtil.delete(CompilerPaths.getCompilerSystemDirectory(project)); } }); } @NotNull @Override public Collection<FileType> getRegisteredInputTypes(@NotNull TranslatingCompiler compiler) { final Collection<FileType> fileTypes = myTranslatingCompilerInputFileTypes.get(compiler); return fileTypes == null ? Collections.<FileType>emptyList() : fileTypes; } @NotNull @Override public Collection<FileType> getRegisteredOutputTypes(@NotNull TranslatingCompiler compiler) { final Collection<FileType> fileTypes = myTranslatingCompilerOutputFileTypes.get(compiler); return fileTypes == null ? Collections.<FileType>emptyList() : fileTypes; } @NotNull @Override public Compiler[] getAllCompilers() { if (myAllCompilers == null) { List<Compiler> list = new ArrayList<>(myCompilers.size() + myTranslatingCompilers.size()); list.addAll(myCompilers); list.addAll(myTranslatingCompilers); myAllCompilers = list.toArray(new Compiler[list.size()]); } return myAllCompilers; } @Override @NotNull public <T extends Compiler> T[] getCompilers(@NotNull Class<T> compilerClass) { return getCompilers(compilerClass, Conditions.<Compiler>alwaysTrue()); } @Override @NotNull @SuppressWarnings("unchecked") public <T extends Compiler> T[] getCompilers(@NotNull Class<T> compilerClass, Condition<Compiler> filter) { final List<T> compilers = new ArrayList<>(myCompilers.size()); for (final Compiler item : myCompilers) { if (compilerClass.isAssignableFrom(item.getClass()) && filter.value(item)) { compilers.add((T)item); } } for (final Compiler item : myTranslatingCompilers) { if (compilerClass.isAssignableFrom(item.getClass()) && filter.value(item)) { compilers.add((T)item); } } final T[] array = (T[])Array.newInstance(compilerClass, compilers.size()); return compilers.toArray(array); } private Graph<TranslatingCompiler> createCompilerGraph(final List<TranslatingCompiler> compilers) { return GraphGenerator.generate(new InboundSemiGraph<TranslatingCompiler>() { @Override public Collection<TranslatingCompiler> getNodes() { return compilers; } @Override public Iterator<TranslatingCompiler> getIn(TranslatingCompiler compiler) { final Collection<FileType> compilerInput = myTranslatingCompilerInputFileTypes.get(compiler); if (compilerInput == null || compilerInput.isEmpty()) { return Collections.<TranslatingCompiler>emptySet().iterator(); } final Set<TranslatingCompiler> inCompilers = new HashSet<>(); for (Map.Entry<TranslatingCompiler, Collection<FileType>> entry : myTranslatingCompilerOutputFileTypes.entrySet()) { final Collection<FileType> outputs = entry.getValue(); TranslatingCompiler comp = entry.getKey(); if (outputs != null && ContainerUtil.intersects(compilerInput, outputs)) { inCompilers.add(comp); } } return inCompilers.iterator(); } }); } @Override public boolean isCompilableFileType(@NotNull FileType type) { return myCompilableFileTypes.contains(type); } @Override @NotNull public CompileTask[] getBeforeTasks() { return CompileTask.BEFORE_EP_NAME.getExtensions(); } @Override @NotNull public CompileTask[] getAfterTasks() { return CompileTask.AFTER_EP_NAME.getExtensions(); } @Override public void compile(@NotNull VirtualFile[] files, CompileStatusNotification callback) { compile(createFilesCompileScope(files), callback); } @Override public void compile(@NotNull Module module, CompileStatusNotification callback) { new CompileDriver(myProject).compile(createModuleCompileScope(module, false), new ListenerNotificator(callback), true); } @Override public void compile(@NotNull CompileScope scope, CompileStatusNotification callback) { new CompileDriver(myProject).compile(scope, new ListenerNotificator(callback), false); } @Override public void make(CompileStatusNotification callback) { new CompileDriver(myProject).make(createProjectCompileScope(), new ListenerNotificator(callback)); } @Override public void make(@NotNull Module module, CompileStatusNotification callback) { new CompileDriver(myProject).make(createModuleCompileScope(module, true), new ListenerNotificator(callback)); } @Override public void make(@NotNull Project project, @NotNull Module[] modules, CompileStatusNotification callback) { new CompileDriver(myProject).make(createModuleGroupCompileScope(project, modules, true), new ListenerNotificator(callback)); } @Override public void make(@NotNull CompileScope scope, CompileStatusNotification callback) { new CompileDriver(myProject).make(scope, new ListenerNotificator(callback)); } @Override public void make(@NotNull CompileScope scope, Condition<Compiler> filter, @Nullable CompileStatusNotification callback) { final CompileDriver compileDriver = new CompileDriver(myProject); compileDriver.setCompilerFilter(filter); compileDriver.make(scope, new ListenerNotificator(callback)); } @Override public boolean isUpToDate(@NotNull final CompileScope scope) { return new CompileDriver(myProject).isUpToDate(scope); } @Override @RequiredReadAction public void rebuild(CompileStatusNotification callback) { new CompileDriver(myProject).rebuild(new ListenerNotificator(callback)); } @Override public void executeTask(@NotNull CompileTask task, @NotNull CompileScope scope, String contentName, Runnable onTaskFinished) { final CompileDriver compileDriver = new CompileDriver(myProject); compileDriver.executeCompileTask(task, scope, contentName, onTaskFinished); } @Override public void addCompilationStatusListener(@NotNull final CompilationStatusListener listener) { myProject.getMessageBus().connect().subscribe(CompilerTopics.COMPILATION_STATUS, listener); } @Override public void addCompilationStatusListener(@NotNull CompilationStatusListener listener, @NotNull Disposable parentDisposable) { myProject.getMessageBus().connect(parentDisposable).subscribe(CompilerTopics.COMPILATION_STATUS, listener); } @Override public void removeCompilationStatusListener(@NotNull final CompilationStatusListener listener) { } @Override public boolean isExcludedFromCompilation(@NotNull VirtualFile file) { return myExcludedEntriesConfiguration.isExcluded(file); } @Override public ExcludedEntriesConfiguration getExcludedEntriesConfiguration() { return myExcludedEntriesConfiguration; } @Override @NotNull public CompileScope createFilesCompileScope(@NotNull final VirtualFile[] files) { CompileScope[] scopes = new CompileScope[files.length]; for (int i = 0; i < files.length; i++) { scopes[i] = new OneProjectItemCompileScope(myProject, files[i]); } return new CompositeScope(scopes); } @NotNull @Override @RequiredReadAction public CompileScope createProjectCompileScope() { Module[] modules = ModuleManager.getInstance(myProject).getModules(); return createModulesCompileScope(modules, false); } @Override @NotNull public CompileScope createModuleCompileScope(@NotNull final Module module, final boolean includeDependentModules) { for (CompileModuleScopeFactory compileModuleScopeFactory : CompileModuleScopeFactory.EP_NAME.getExtensions()) { FileIndexCompileScope scope = compileModuleScopeFactory.createScope(module, includeDependentModules); if (scope != null) { return scope; } } return new ModuleCompileScope(module, includeDependentModules); } @Override @NotNull public CompileScope createModulesCompileScope(@NotNull final Module[] modules, final boolean includeDependentModules) { List<CompileScope> list = new ArrayList<>(modules.length); for (Module module : modules) { list.add(createModuleCompileScope(module, includeDependentModules)); } return new CompositeScope(list); } @Override @NotNull public CompileScope createModuleGroupCompileScope(@NotNull final Project project, @NotNull final Module[] modules, final boolean includeDependentModules) { List<CompileScope> list = new ArrayList<>(modules.length); for (Module module : modules) { list.add(createModuleCompileScope(module, includeDependentModules)); } return new CompositeScope(list); } @Override public boolean isValidationEnabled(Module moduleType) { return true; } @Nullable @Override public Element getState() { final Element state = new Element("state"); CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject); configuration.getState(state); if (!myExcludedEntriesConfiguration.isEmpty()) { Element element = new Element("exclude-from-compilation"); myExcludedEntriesConfiguration.writeExternal(element); state.addContent(element); } return state; } @Override public void loadState(Element state) { if (myProject.isInitialized()) { throw new IllegalArgumentException("Project is not initialized yet. Please do not call CompilerManager inside #initCompoment()"); } Element exclude = state.getChild("exclude-from-compilation"); if (exclude != null) { myExcludedEntriesConfiguration.readExternal(exclude); } CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject); configuration.loadState(state); } public Semaphore getCompilationSemaphore() { return myCompilationSemaphore; } @Override public boolean isCompilationActive() { return myCompilationSemaphore.availablePermits() == 0; } }