blob: 36021fd098b8b77875efeefdcb2b3e44b0b6268f [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.compiler.impl;
import com.intellij.compiler.impl.generic.GenericCompilerCache;
import com.intellij.compiler.impl.generic.GenericCompilerPersistentData;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.RunResult;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompilerBundle;
import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.compiler.CompilerPaths;
import com.intellij.openapi.compiler.generic.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.io.KeyDescriptor;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* @author nik
*/
public class GenericCompilerRunner {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.GenericCompilerRunner");
private static final Logger FULL_LOG = Logger.getInstance("#com.intellij.full-generic-compiler-log");
private CompileContext myContext;
private final boolean myForceCompile;
private final boolean myOnlyCheckStatus;
private final GenericCompiler<?,?,?>[] myCompilers;
private final Project myProject;
public GenericCompilerRunner(CompileContext context,
boolean forceCompile,
boolean onlyCheckStatus, final GenericCompiler[] compilers) {
myContext = context;
myForceCompile = forceCompile;
myOnlyCheckStatus = onlyCheckStatus;
myCompilers = compilers;
myProject = myContext.getProject();
}
public boolean invokeCompilers(GenericCompiler.CompileOrderPlace place) throws ExitException {
boolean didSomething = false;
try {
for (GenericCompiler<?,?,?> compiler : myCompilers) {
if (compiler.getOrderPlace().equals(place)) {
didSomething |= invokeCompiler(compiler);
}
}
}
catch (IOException e) {
LOG.info(e);
myContext.requestRebuildNextTime(e.getMessage());
throw new ExitException(ExitStatus.ERRORS);
}
catch (ExitException e) {
throw e;
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
LOG.info(e);
myContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
}
return didSomething;
}
private <Key, SourceState, OutputState> boolean invokeCompiler(GenericCompiler<Key, SourceState, OutputState> compiler) throws IOException,
ExitException {
return invokeCompiler(compiler, compiler.createInstance(myContext));
}
private <T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState>
boolean invokeCompiler(GenericCompiler<Key, SourceState, OutputState> compiler, final GenericCompilerInstance<T, Item, Key, SourceState, OutputState> instance) throws IOException,
ExitException {
final GenericCompilerCache<Key, SourceState, OutputState> cache = CompilerCacheManager.getInstance(myProject).getGenericCompilerCache(compiler);
GenericCompilerPersistentData
data = new GenericCompilerPersistentData(getGenericCompilerCacheDir(myProject, compiler), compiler.getVersion());
if (data.isVersionChanged()) {
LOG.info("Clearing cache for " + compiler.getDescription());
cache.wipe();
data.save();
}
final Set<String> targetsToRemove = new HashSet<String>(data.getAllTargets());
new ReadAction() {
protected void run(final Result result) {
for (T target : instance.getAllTargets()) {
targetsToRemove.remove(target.getId());
}
}
}.execute();
if (!myOnlyCheckStatus) {
for (final String target : targetsToRemove) {
final int id = data.removeId(target);
if (LOG.isDebugEnabled()) {
LOG.debug("Removing obsolete target '" + target + "' (id=" + id + ")");
}
final List<Key> keys = new ArrayList<Key>();
CompilerUtil.runInContext(myContext, "Processing obsolete targets...", new ThrowableRunnable<IOException>() {
@Override
public void run() throws IOException {
cache.processSources(id, new CommonProcessors.CollectProcessor<Key>(keys));
List<GenericCompilerCacheState<Key, SourceState, OutputState>> obsoleteSources = new ArrayList<GenericCompilerCacheState<Key,SourceState,OutputState>>();
for (Key key : keys) {
final GenericCompilerCache.PersistentStateData<SourceState, OutputState> state = cache.getState(id, key);
obsoleteSources.add(new GenericCompilerCacheState<Key,SourceState,OutputState>(key, state.mySourceState, state.myOutputState));
}
instance.processObsoleteTarget(target, obsoleteSources);
}
});
checkForErrorsOrCanceled();
for (Key key : keys) {
cache.remove(id, key);
}
}
}
final List<T> selectedTargets = new ReadAction<List<T>>() {
protected void run(final Result<List<T>> result) {
result.setResult(instance.getSelectedTargets());
}
}.execute().getResultObject();
boolean didSomething = false;
for (T target : selectedTargets) {
int id = data.getId(target.getId());
didSomething |= processTarget(target, id, compiler, instance, cache);
}
data.save();
return didSomething;
}
private void checkForErrorsOrCanceled() throws ExitException {
if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
throw new ExitException(ExitStatus.ERRORS);
}
if (myContext.getProgressIndicator().isCanceled()) {
throw new ExitException(ExitStatus.CANCELLED);
}
}
public static File getGenericCompilerCacheDir(Project project, GenericCompiler<?,?,?> compiler) {
return new File(CompilerPaths.getCacheStoreDirectory(project), compiler.getId());
}
private <T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState>
boolean processTarget(T target, final int targetId, final GenericCompiler<Key, SourceState, OutputState> compiler, final GenericCompilerInstance<T, Item, Key, SourceState, OutputState> instance,
final GenericCompilerCache<Key, SourceState, OutputState> cache) throws IOException, ExitException {
if (LOG.isDebugEnabled()) {
LOG.debug("Processing target '" + target + "' (id=" + targetId + ") by " + compiler);
}
final List<Item> items = instance.getItems(target);
checkForErrorsOrCanceled();
final List<GenericCompilerProcessingItem<Item, SourceState, OutputState>> toProcess = new ArrayList<GenericCompilerProcessingItem<Item,SourceState,OutputState>>();
final THashSet<Key> keySet = new THashSet<Key>(new SourceItemHashingStrategy<Key>(compiler));
final Ref<IOException> exception = Ref.create(null);
final Map<Item, SourceState> sourceStates = new HashMap<Item,SourceState>();
DumbService.getInstance(myProject).runReadActionInSmartMode(new Runnable() {
@Override
public void run() {
try {
for (Item item : items) {
final Key key = item.getKey();
keySet.add(key);
if (item.isExcluded()) continue;
final GenericCompilerCache.PersistentStateData<SourceState, OutputState> data = cache.getState(targetId, key);
SourceState sourceState = data != null ? data.mySourceState : null;
final OutputState outputState = data != null ? data.myOutputState : null;
if (myForceCompile || sourceState == null || !item.isSourceUpToDate(sourceState)
|| outputState == null || !item.isOutputUpToDate(outputState)) {
sourceStates.put(item, item.computeSourceState());
toProcess.add(new GenericCompilerProcessingItem<Item,SourceState,OutputState>(item, sourceState, outputState));
}
}
}
catch (IOException e) {
exception.set(e);
}
}
});
if (!exception.isNull()) {
throw exception.get();
}
final List<Key> toRemove = new ArrayList<Key>();
cache.processSources(targetId, new Processor<Key>() {
@Override
public boolean process(Key key) {
if (!keySet.contains(key)) {
toRemove.add(key);
}
return true;
}
});
if (LOG.isDebugEnabled()) {
LOG.debug(toProcess.size() + " items will be processed, " + toRemove.size() + " items will be removed");
for (int i = 0; i < getItemsCountToShowInLog(toProcess.size()); i++) {
LOG.debug("to process:" + toProcess.get(i).getItem().getKey());
}
for (int i = 0; i < getItemsCountToShowInLog(toRemove.size()); i++) {
LOG.debug("to delete:" + toRemove.get(i));
}
}
if (toProcess.isEmpty() && toRemove.isEmpty()) {
return false;
}
if (myOnlyCheckStatus) {
throw new ExitException(ExitStatus.CANCELLED);
}
List<GenericCompilerCacheState<Key, SourceState, OutputState>> obsoleteItems = new ArrayList<GenericCompilerCacheState<Key,SourceState,OutputState>>();
for (Key key : toRemove) {
final GenericCompilerCache.PersistentStateData<SourceState, OutputState> data = cache.getState(targetId, key);
obsoleteItems.add(new GenericCompilerCacheState<Key,SourceState,OutputState>(key, data.mySourceState, data.myOutputState));
}
final List<Item> processedItems = new ArrayList<Item>();
final List<File> filesToRefresh = new ArrayList<File>();
final List<File> dirsToRefresh = new ArrayList<File>();
instance.processItems(target, toProcess, obsoleteItems, new GenericCompilerInstance.OutputConsumer<Item>() {
@Override
public void addFileToRefresh(@NotNull File file) {
filesToRefresh.add(file);
}
@Override
public void addDirectoryToRefresh(@NotNull File dir) {
dirsToRefresh.add(dir);
}
@Override
public void addProcessedItem(@NotNull Item sourceItem) {
processedItems.add(sourceItem);
}
});
checkForErrorsOrCanceled();
CompilerUtil.runInContext(myContext, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
@Override
public void run() throws IOException {
for (Key key : toRemove) {
cache.remove(targetId, key);
}
CompilerUtil.refreshIOFiles(filesToRefresh);
CompilerUtil.refreshIODirectories(dirsToRefresh);
if (LOG.isDebugEnabled()) {
LOG.debug("refreshed " + filesToRefresh.size() + " files and " + dirsToRefresh.size() + " dirs");
for (int i = 0; i < getItemsCountToShowInLog(filesToRefresh.size()); i++) {
LOG.debug("file: " + filesToRefresh.get(i));
}
for (int i = 0; i < getItemsCountToShowInLog(dirsToRefresh.size()); i++) {
LOG.debug("dir: " + dirsToRefresh.get(i));
}
}
final RunResult runResult = new ReadAction() {
protected void run(final Result result) throws Throwable {
for (Item item : processedItems) {
SourceState sourceState = sourceStates.get(item);
if (sourceState == null) {
sourceState = item.computeSourceState();
}
cache.putState(targetId, item.getKey(), sourceState, item.computeOutputState());
}
}
}.executeSilently();
Throwable throwable = runResult.getThrowable();
if (throwable instanceof IOException) throw (IOException) throwable;
else if (throwable != null) throw new RuntimeException(throwable);
}
});
return true;
}
private static int getItemsCountToShowInLog(final int size) {
if (size > 100 && !FULL_LOG.isDebugEnabled()) {
return 100;
}
return size;
}
private class SourceItemHashingStrategy<S> implements TObjectHashingStrategy<S> {
private KeyDescriptor<S> myKeyDescriptor;
public SourceItemHashingStrategy(GenericCompiler<S, ?, ?> compiler) {
myKeyDescriptor = compiler.getItemKeyDescriptor();
}
@Override
public int computeHashCode(S object) {
return myKeyDescriptor.getHashCode(object);
}
@Override
public boolean equals(S o1, S o2) {
return myKeyDescriptor.isEqual(o1, o2);
}
}
}