package com.intellij.lang.javascript.flex.build; import com.intellij.CommonBundle; import com.intellij.flex.FlexCommonBundle; import com.intellij.flex.FlexCommonUtils; import com.intellij.lang.javascript.flex.FlexBundle; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration; import com.intellij.lang.javascript.flex.projectStructure.options.BCUtils; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompilerMessage; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.NullableComputable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class FlexCompilationManager { private final CompileContext myCompileContext; private final int myMaxParallelCompilations; private final int myTasksAmount; private final Collection<FlexCompilationTask> myNotStartedTasks; private final Collection<FlexCompilationTask> myInProgressTasks; private final Collection<FlexCompilationTask> myFinishedTasks; private boolean myCompilationFinished; private final FlexCompilerDependenciesCache myCompilerDependenciesCache; static final Pattern OUTPUT_FILE_CREATED_PATTERN = Pattern.compile("(\\[.*\\] )?(.+) \\(([0-9]+) bytes\\)"); private static final String BYTES_WRITTEN_TO = " bytes written to "; public FlexCompilationManager(final CompileContext context, final Collection<FlexCompilationTask> compilationTasks) { myCompileContext = context; myMaxParallelCompilations = FlexCompilerProjectConfiguration.getInstance(context.getProject()).MAX_PARALLEL_COMPILATIONS; myTasksAmount = compilationTasks.size(); myNotStartedTasks = new LinkedList<>(compilationTasks); myInProgressTasks = new LinkedList<>(); myFinishedTasks = new LinkedList<>(); myCompilationFinished = false; myCompilerDependenciesCache = FlexCompilerHandler.getInstance(context.getProject()).getCompilerDependenciesCache(); } public void compile() { try { while (!myNotStartedTasks.isEmpty() || !myInProgressTasks.isEmpty()) { if (myCompileContext.getProgressIndicator().isCanceled()) { for (FlexCompilationTask task : myInProgressTasks) { task.cancel(); } break; } checkFinishedTasks(); startNewTaskIfPossible(); updateProgressIndicator(); try { //noinspection BusyWait Thread.sleep(200); } catch (InterruptedException e) { assert false; } } } finally { //noinspection SynchronizeOnThis synchronized (this) { myCompilationFinished = true; } } } public synchronized void addMessage(final FlexCompilationTask task, CompilerMessageCategory category, final String message, final @Nullable String url, final int lineNum, final int columnNum) { if (!myCompilationFinished) { if (message.contains(FlexCommonUtils.COULD_NOT_CREATE_JVM)) { category = CompilerMessageCategory.ERROR; } if (category == CompilerMessageCategory.INFORMATION) { final int bytesWrittenIndex = message.indexOf(BYTES_WRITTEN_TO); if (bytesWrittenIndex > 0) { // ASC 2.0 sources: MXMLC.bytes_written_to_file_in_seconds_format=${byteCount} bytes written to ${path} in ${seconds} seconds final int inIndex = message.lastIndexOf(" in "); if (inIndex > bytesWrittenIndex) { final String outputFilePath = message.substring(bytesWrittenIndex + BYTES_WRITTEN_TO.length(), inIndex); // need to refresh FS in order to notify artifact compiler that Flex output has changed if (!ApplicationManager.getApplication().isUnitTestMode()) { refreshAndFindFileInWriteAction(outputFilePath); } } } else { final Matcher matcher = OUTPUT_FILE_CREATED_PATTERN.matcher(message); if (matcher.matches()) { final String outputFilePath = matcher.group(2); // need to refresh FS in order to notify artifact compiler that Flex output has changed if (!ApplicationManager.getApplication().isUnitTestMode()) { refreshAndFindFileInWriteAction(outputFilePath); } } } } final String prefix = getMessagePrefix(task); myCompileContext.addMessage(category, prefix + message, url, lineNum, columnNum); if (message.contains(FlexCommonUtils.OUT_OF_MEMORY) || message.contains(FlexCommonUtils.JAVA_HEAP_SPACE)) { myCompileContext .addMessage(CompilerMessageCategory.ERROR, prefix + FlexCommonBundle.message("increase.flex.compiler.heap", CommonBundle.settingsActionPath()), null, -1, -1); } } } public boolean isRebuild() { return !myCompileContext.isMake(); } private void checkFinishedTasks() { final Iterator<FlexCompilationTask> iterator = myInProgressTasks.iterator(); while (iterator.hasNext()) { FlexCompilationTask task = iterator.next(); if (task.isFinished()) { iterator.remove(); myFinishedTasks.add(task); if (task.isCompilationFailed()) { final Collection<FlexCompilationTask> cancelledTasks = cancelNotStartedDependentTasks(task); if (cancelledTasks.isEmpty()) { addMessage(task, CompilerMessageCategory.INFORMATION, FlexCommonBundle.message("compilation.failed"), null, -1, -1); } else { addMessage(task, CompilerMessageCategory.INFORMATION, FlexCommonBundle.message("compilation.failed.dependent.will.be.skipped"), null, -1, -1); for (final FlexCompilationTask cancelledTask : cancelledTasks) { addMessage(cancelledTask, CompilerMessageCategory.INFORMATION, FlexBundle.message("compilation.skipped"), null, -1, -1); } } } else { addMessage(task, CompilerMessageCategory.INFORMATION, FlexCommonBundle.message("compilation.successful"), null, -1, -1); final String prefix = getMessagePrefix(task); final List<String> taskMessages = new ArrayList<>(); for (CompilerMessage message : myCompileContext.getMessages(CompilerMessageCategory.INFORMATION)) { if (message.getMessage().startsWith(prefix)) { taskMessages.add(message.getMessage().substring(prefix.length())); } } try { FlexCompilationUtils.performPostCompileActions(task.getModule(), task.getBC(), taskMessages); } catch (FlexCompilerException e) { addMessage(task, CompilerMessageCategory.ERROR, e.getMessage(), e.getUrl(), e.getLine(), e.getColumn()); } } if (task.isCompilationFailed()) { myCompilerDependenciesCache.markBCDirty(task.getModule(), task.getBC()); } else { //noinspection SynchronizeOnThis synchronized (this) { myCompilerDependenciesCache.cacheBC(task.getModule(), task.getBC(), task.getConfigFiles()); } } } } } private String getMessagePrefix(final FlexCompilationTask task) { return "[" + task.getPresentableName() + "] "; } private Collection<FlexCompilationTask> cancelNotStartedDependentTasks(final FlexCompilationTask failedTask) { final Collection<FlexCompilationTask> tasksToCancel = new LinkedList<>(); appendAndCancelNotStartedDependentTasks(tasksToCancel, failedTask); if (BCUtils.canHaveRLMsAndRuntimeStylesheets(failedTask.getBC())) { appendAndCancelNotStartedRLMTasks(tasksToCancel, failedTask.getModule(), failedTask.getBC()); } return tasksToCancel; } private void appendAndCancelNotStartedDependentTasks(final Collection<FlexCompilationTask> cancelledTasks, final FlexCompilationTask task) { final Collection<FlexCompilationTask> tasksToCancel = new ArrayList<>(); for (FlexCompilationTask notStartedTask : myNotStartedTasks) { //noinspection ConstantConditions if (notStartedTask.getDependencies().contains(task.getBC())) { tasksToCancel.add(notStartedTask); } } for (FlexCompilationTask taskToCancel : tasksToCancel) { taskToCancel.cancel(); if (myNotStartedTasks.remove(taskToCancel)) { myFinishedTasks.add(taskToCancel); cancelledTasks.add(taskToCancel); appendAndCancelNotStartedDependentTasks(cancelledTasks, taskToCancel); } } } private void appendAndCancelNotStartedRLMTasks(final Collection<FlexCompilationTask> tasks, final Module module, final FlexBuildConfiguration bc) { final Iterator<FlexCompilationTask> iterator = myNotStartedTasks.iterator(); while (iterator.hasNext()) { final FlexCompilationTask task = iterator.next(); if (module == task.getModule() && bc.getName().equals(task.getBC().getName()) && BCUtils.isRLMTemporaryBC(task.getBC())) { iterator.remove(); myFinishedTasks.add(task); tasks.add(task); } } } private void startNewTaskIfPossible() { FlexCompilationTask taskToStart = null; if (!myNotStartedTasks.isEmpty() && myInProgressTasks.size() < myMaxParallelCompilations) { boolean allTasksHaveDependenciesOnlyInNotStarted = true; // to handle cyclic dependencies for (FlexCompilationTask task : myNotStartedTasks) { if (BCUtils.isRLMTemporaryBC(task.getBC()) && !isMainAppCompiledForRLM(task.getModule(), task.getBC())) { allTasksHaveDependenciesOnlyInNotStarted = false; continue; } if (hasDependenciesIn(task, myInProgressTasks)) { allTasksHaveDependenciesOnlyInNotStarted = false; continue; } if (hasDependenciesIn(task, myNotStartedTasks)) { continue; } taskToStart = task; break; } if (taskToStart == null && allTasksHaveDependenciesOnlyInNotStarted) { taskToStart = myNotStartedTasks.iterator().next(); // just take any from cycle dependencies } if (taskToStart != null) { myNotStartedTasks.remove(taskToStart); if (myCompilerDependenciesCache.isNothingChangedSincePreviousCompilation(taskToStart.getModule(), taskToStart.getBC())) { addMessage(taskToStart, CompilerMessageCategory.INFORMATION, FlexBundle.message("compilation.skipped.because.nothing.changed"), null, -1, -1); taskToStart.cancel(); myFinishedTasks.add(taskToStart); try { FlexCompilationUtils.performPostCompileActions(taskToStart.getModule(), taskToStart.getBC(), Collections.emptyList()); } catch (FlexCompilerException e) { addMessage(taskToStart, CompilerMessageCategory.ERROR, e.getMessage(), e.getUrl(), e.getLine(), e.getColumn()); } } else { taskToStart.start(this); myInProgressTasks.add(taskToStart); } startNewTaskIfPossible(); } } } private boolean isMainAppCompiledForRLM(final Module module, final FlexBuildConfiguration rlmBC) { for (FlexCompilationTask task : myFinishedTasks) { final FlexBuildConfiguration bc = task.getBC(); if (task.getModule() == module && bc.getName().equals(rlmBC.getName()) && !BCUtils.isRLMTemporaryBC(bc) && !BCUtils.isRuntimeStyleSheetBC(bc) && BCUtils.canHaveRLMsAndRuntimeStylesheets(bc) && bc.getRLMs().size() > 0) { return true; } } return false; } private static boolean hasDependenciesIn(final FlexCompilationTask task, final Collection<FlexCompilationTask> tasksToSearchDependencies) { for (final FlexCompilationTask otherTask : tasksToSearchDependencies) { //noinspection ConstantConditions if (task.getDependencies().contains(otherTask.getBC())) { return true; } } return false; } private void updateProgressIndicator() { final ProgressIndicator progressIndicator = myCompileContext.getProgressIndicator(); progressIndicator.setFraction(1. * myFinishedTasks.size() / myTasksAmount); final StringBuilder builder = new StringBuilder(); if (!myInProgressTasks.isEmpty()) { for (FlexCompilationTask inProgressTask : myInProgressTasks) { if (builder.length() > 0) builder.append(", "); builder.append(inProgressTask.getPresentableName()); } progressIndicator.setText(FlexCommonBundle.message("compiling", builder.toString())); } } static VirtualFile refreshAndFindFileInWriteAction(final String outputFilePath, final String... possibleBaseDirs) { final LocalFileSystem localFileSystem = LocalFileSystem.getInstance(); final Ref<VirtualFile> outputFileRef = new Ref<>(); final Application app = ApplicationManager.getApplication(); app.invokeAndWait(() -> outputFileRef.set(app.runWriteAction(new NullableComputable<VirtualFile>() { public VirtualFile compute() { VirtualFile outputFile = localFileSystem.refreshAndFindFileByPath(outputFilePath); //if (outputFile == null) { // outputFile = // localFileSystem.refreshAndFindFileByPath(FlexUtils.getFlexCompilerWorkDirPath(project, null) + "/" + outputFilePath); //} if (outputFile == null) { for (final String baseDir : possibleBaseDirs) { outputFile = localFileSystem.refreshAndFindFileByPath(baseDir + "/" + outputFilePath); if (outputFile != null) { break; } } } if (outputFile == null) return null; // it's important because this file has just been created outputFile.refresh(false, false); return outputFile; } }))); return outputFileRef.get(); } }