/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2014 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* 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.intellij.plugins.haxe.compilation;
import com.intellij.compiler.options.CompileStepBeforeRun;
import com.intellij.execution.ExecutorRegistry;
import com.intellij.execution.configurations.ModuleBasedConfiguration;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunConfigurationModule;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.plugins.haxe.HaxeBundle;
import com.intellij.plugins.haxe.config.HaxeTarget;
import com.intellij.plugins.haxe.config.sdk.HaxeSdkAdditionalDataBase;
import com.intellij.plugins.haxe.ide.module.HaxeModuleSettings;
import com.intellij.plugins.haxe.ide.module.HaxeModuleType;
import com.intellij.plugins.haxe.module.HaxeModuleSettingsBase;
import com.intellij.plugins.haxe.runner.debugger.HaxeDebugRunner;
import com.intellij.plugins.haxe.tests.runner.HaxeTestsConfiguration;
import com.intellij.plugins.haxe.util.HaxeCommonCompilerUtil;
import com.intellij.util.PathUtil;
import org.jetbrains.annotations.NotNull;
import java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class HaxeCompiler implements SourceProcessingCompiler {
private static final Logger LOG = Logger.getInstance("#com.intellij.plugins.haxe.compilation.HaxeCompiler");
/*
// flag to indicate whether a module needs to be built
private static HashMap<Module, Boolean> skipBuildMap = new HashMap<Module, Boolean>();
// holds the change set for each module to track whether anything changed since last build
private static HashMap<Module, Set<String>> changeSetMap = new HashMap<Module, Set<String>>();
*/
@NotNull
public String getDescription() {
return HaxeBundle.message("haxe.compiler.description");
}
@Override
public boolean validateConfiguration(CompileScope scope) {
return true;
}
@NotNull
@Override
public ProcessingItem[] getProcessingItems(CompileContext context) {
final List<ProcessingItem> itemList = new ArrayList<ProcessingItem>();
for (final Module module : getModulesToCompile(context.getCompileScope() /*, context.getProject() */)) {
itemList.add(new MyProcessingItem(module));
}
return itemList.toArray(new ProcessingItem[itemList.size()]);
}
private static List<Module> getModulesToCompile(final CompileScope scope /*, final Project project */) {
final List<Module> result = new ArrayList<Module>();
for (final Module module : scope.getAffectedModules()) {
if (ModuleType.get(module) != HaxeModuleType.getInstance()) continue;
result.add(module);
/*
boolean skipBuilding = false; // default: always build
//-- are any changes since last build
if (changeSetMap.get(module) != null) { // was built at least once, in past ...
final Set<String> latestChangeSet = new HashSet<String>();
Collection<VirtualFile> vFileCollection = FileTypeIndex
.getFiles(HaxeFileType.HAXE_FILE_TYPE, module.getModuleWithDependenciesScope());
final VirtualFile[] virtualFiles = vFileCollection.toArray(new VirtualFile[0]);
for (VirtualFile file : virtualFiles) {
final FileStatus fileStatus = FileStatusManager.getInstance(project).getStatus(file);
if (fileStatus.equals(FileStatus.NOT_CHANGED) || fileStatus.equals(FileStatus.UNKNOWN)) continue;
latestChangeSet.add(file.getPath());
}
skipBuilding = latestChangeSet.equals(changeSetMap.get(module));
changeSetMap.put(module, latestChangeSet);
}
else {
changeSetMap.put(module, new HashSet<String>());
}
skipBuildMap.put(module, new Boolean(skipBuilding));
if (! skipBuilding) {
result.add(module);
}
*/
}
return result;
}
@Override
public ProcessingItem[] process(CompileContext context, ProcessingItem[] items) {
final RunConfiguration runConfiguration = CompileStepBeforeRun.getRunConfiguration(context.getCompileScope());
if (runConfiguration instanceof ModuleBasedConfiguration) {
return run(context, items, (ModuleBasedConfiguration)runConfiguration);
}
return make(context, items);
}
private static ProcessingItem[] run(CompileContext context,
ProcessingItem[] items,
ModuleBasedConfiguration configuration) {
final Module module = configuration.getConfigurationModule().getModule();
if (module == null) {
context.addMessage(CompilerMessageCategory.ERROR,
HaxeBundle.message("no.module.for.run.configuration", configuration.getName()), null, -1, -1);
return ProcessingItem.EMPTY_ARRAY;
}
HaxeCommonCompilerUtil.CompilationContext compilationContext = createCompilationContext(context, module, configuration);
if (compileModule(context, module, compilationContext)) {
final int index = findProcessingItemIndexByModule(items, configuration.getConfigurationModule());
if (index != -1) {
return new ProcessingItem[]{items[index]};
}
}
return ProcessingItem.EMPTY_ARRAY;
}
private static ProcessingItem[] make(CompileContext context, ProcessingItem[] items) {
final List<ProcessingItem> result = new ArrayList<ProcessingItem>();
for (ProcessingItem processingItem : items) {
if (!(processingItem instanceof MyProcessingItem)) {
continue;
}
final MyProcessingItem myProcessingItem = (MyProcessingItem)processingItem;
if (compileModule(context, myProcessingItem.myModule, createCompilationContext(context, myProcessingItem.myModule, null))) {
result.add(processingItem);
}
}
return result.toArray(new ProcessingItem[result.size()]);
}
private static boolean compileModule(final CompileContext context,
Module module,
@NotNull final HaxeCommonCompilerUtil.CompilationContext compilationContext) {
/*
if ((skipBuildMap.get(module) != null) && (skipBuildMap.get(module).booleanValue())) {
return false;
}
*/
if (!ModuleUtil.getModuleType(module).equals(HaxeModuleType.getInstance())) {
return true;
}
boolean compiled = HaxeCommonCompilerUtil.compile(compilationContext);
if (!compiled) {
context.addMessage(CompilerMessageCategory.ERROR, "Compilation failed", null, 0, 0);
}
return compiled;
}
private static HaxeCommonCompilerUtil.CompilationContext createCompilationContext(final CompileContext context,
final Module module,
ModuleBasedConfiguration configuration) {
final HaxeModuleSettings settings = HaxeModuleSettings.getInstance(module);
final boolean isDebug = ExecutorRegistry.getInstance()
.isStarting(context.getProject(), DefaultDebugExecutor.EXECUTOR_ID, HaxeDebugRunner.HAXE_DEBUG_RUNNER_ID);
final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
final Sdk sdk = moduleRootManager.getSdk();
if (sdk == null) {
context.addMessage(CompilerMessageCategory.ERROR, HaxeBundle.message("no.sdk.for.module", module.getName()), null, -1, -1);
return null;
}
HaxeTestsConfiguration haxeTestsConfiguration = null;
if(configuration != null && configuration instanceof HaxeTestsConfiguration) {
haxeTestsConfiguration = (HaxeTestsConfiguration)configuration;
}
final HaxeTestsConfiguration finalHaxeTestsConfiguration = haxeTestsConfiguration;
return new HaxeCommonCompilerUtil.CompilationContext() {
private String myErrorRoot;
@NotNull
@Override
public HaxeModuleSettingsBase getModuleSettings() {
return settings;
}
@Override
public String getModuleName() {
return module.getName();
}
@Override
public String getCompilationClass() {
return getIsTestBuild() ? finalHaxeTestsConfiguration.getRunnerClass() : getModuleSettings().getMainClass();
}
@Override
public String getOutputFileName() {
return getFileNameWithCurrentExtension(getModuleSettings().getOutputFileName());
}
@Override
public Boolean getIsTestBuild() {
return finalHaxeTestsConfiguration != null;
}
@Override
public void errorHandler(String message) {
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
}
@Override
public void warningHandler(String message) {
context.addMessage(CompilerMessageCategory.WARNING, message, null, -1, -1);
}
@Override
public void infoHandler(String message) {
context.addMessage(CompilerMessageCategory.INFORMATION, message, null, -1, -1);
}
public void statisticsHandler(String message) {
context.addMessage(CompilerMessageCategory.INFORMATION, message, null, -1, -1);
}
@Override
public void log(String message) {
LOG.debug(message);
}
@Override
public String getSdkName() {
return sdk.getName();
}
@Override
public String getSdkHomePath() {
return sdk.getHomePath();
}
@Override
public String getHaxelibPath() {
SdkAdditionalData data = sdk.getSdkAdditionalData();
return data instanceof HaxeSdkAdditionalDataBase ? ((HaxeSdkAdditionalDataBase)data).getHaxelibPath() : null;
}
@Override
public boolean isDebug() {
return isDebug;
}
@Override
public List<String> getSourceRoots() {
final List<String> result = new ArrayList<String>();
for (VirtualFile sourceRoot : OrderEnumerator.orderEntries(module).recursively().withoutSdk().exportedOnly().sources().getRoots()) {
result.add(sourceRoot.getPath());
}
for (VirtualFile sourceRoot : OrderEnumerator.orderEntries(module).librariesOnly().getSourceRoots()) {
result.add(sourceRoot.getPath());
}
return result;
}
@Override
public String getCompileOutputPath() {
final CompilerModuleExtension moduleExtension = CompilerModuleExtension.getInstance(module);
final String outputUrl = moduleExtension != null ? moduleExtension.getCompilerOutputUrl() : null;
return VfsUtilCore.urlToPath(outputUrl);
}
@Override
public void setErrorRoot(String root) {
myErrorRoot = root;
}
@Override
public String getErrorRoot() {
return (myErrorRoot != null) ? myErrorRoot : PathUtil.getParentPath(module.getModuleFilePath());
}
@Override
public void handleOutput(String[] lines) {
HaxeCompilerUtil.fillContext(context, getErrorRoot(), lines);
}
@Override
public HaxeTarget getHaxeTarget() {
//actually only neko target is supported for tests
return getIsTestBuild() ? HaxeTarget.NEKO : getModuleSettings().getHaxeTarget();
}
private String getFileNameWithCurrentExtension(String fileName) {
if (getHaxeTarget() != null) {
return getHaxeTarget().getTargetFileNameWithExtension(FileUtil.getNameWithoutExtension(fileName));
}
return fileName;
}
@Override
public String getModuleDirPath() {
return PathUtil.getParentPath(module.getModuleFilePath());
}
};
}
private static int findProcessingItemIndexByModule(ProcessingItem[] items, RunConfigurationModule moduleConfiguration) {
final Module module = moduleConfiguration.getModule();
if (module == null || module.getModuleFile() == null) {
return -1;
}
for (int i = 0; i < items.length; ++i) {
if (module.getModuleFile().equals(items[i].getFile())) {
return i;
}
}
return -1;
}
@Override
public ValidityState createValidityState(DataInput in) throws IOException {
return new EmptyValidityState();
}
private static class MyProcessingItem implements ProcessingItem {
private final Module myModule;
private MyProcessingItem(Module module) {
myModule = module;
}
@NotNull
public VirtualFile getFile() {
return myModule.getModuleFile();
}
public ValidityState getValidityState() {
return new EmptyValidityState();
}
}
}