project level builder introduced

This commit is contained in:
nik
2012-01-27 09:43:54 +04:00
parent b3d2d65eee
commit 54c77b6b83
8 changed files with 211 additions and 186 deletions
@@ -1,182 +1,10 @@
package org.jetbrains.jps.incremental;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.ether.dependencyView.Mappings;
import org.jetbrains.jps.Module;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.incremental.storage.SourceToOutputMapping;
import sun.util.LocaleServiceProviderPool;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: 9/17/11
* @author nik
*/
public abstract class Builder {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.Builder");
private static final Key<Set<File>> ALL_AFFECTED_FILES_KEY = Key.create("_all_affected_files_");
private static final Key<Set<File>> ALL_COMPILED_FILES_KEY = Key.create("_all_compiled_files_");
public static enum ExitCode {
OK, ABORT, ADDITIONAL_PASS_REQUIRED
}
public abstract String getName();
public abstract ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException;
public abstract String getDescription();
public void cleanupResources(CompileContext context, ModuleChunk chunk) {
ALL_AFFECTED_FILES_KEY.set(context, null);
ALL_COMPILED_FILES_KEY.set(context, null);
}
/**
* @param context
* @param delta
* @param chunk
* @param filesToCompile files compiled in this round
* @param successfullyCompiled
* @return true if additional compilation pass is required, false otherwise
* @throws Exception
*/
public final boolean updateMappings(CompileContext context, final Mappings delta, ModuleChunk chunk, Collection<File> filesToCompile, Collection<File> successfullyCompiled) throws Exception {
try {
boolean additionalPassRequired = false;
final Set<String> removedPaths = getRemovedPaths(context);
final Mappings globalMappings = context.getDataManager().getMappings();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (globalMappings) {
if (!context.isProjectRebuild() && context.shouldDifferentiate(chunk, context.isCompilingTests())) {
final Set<File> allCompiledFiles = getAllCompiledFilesContainer(context);
final Set<File> allAffectedFiles = getAllAffectedFilesContainer(context);
// mark as affected all files that were dirty before compilation
allAffectedFiles.addAll(filesToCompile);
// accumulate all successfully compiled in this round
allCompiledFiles.addAll(successfullyCompiled);
// unmark as affected all successfully compiled
allAffectedFiles.removeAll(successfullyCompiled);
final HashSet<File> affectedBeforeDif = new HashSet<File>(allAffectedFiles);
final boolean incremental = globalMappings.differentiate(
delta, removedPaths, successfullyCompiled, allCompiledFiles, allAffectedFiles
);
if (LOG.isDebugEnabled()) {
LOG.debug("Differentiate Results:");
LOG.debug(" Compiled Files:");
for (final File c : allCompiledFiles) {
LOG.debug(" " + c.getAbsolutePath());
}
LOG.debug(" Affected Files:");
for (final File c : allAffectedFiles) {
LOG.debug(" " + c.getAbsolutePath());
}
LOG.debug("End Of Differentiate Results.");
}
if (incremental) {
final Set<File> newlyAffectedFiles = new HashSet<File>(allAffectedFiles);
newlyAffectedFiles.removeAll(affectedBeforeDif);
newlyAffectedFiles.removeAll(allCompiledFiles); // the diff operation may have affected the class already compiled in thic compilation round
if (!newlyAffectedFiles.isEmpty()) {
for (File file : newlyAffectedFiles) {
context.markDirty(file);
}
additionalPassRequired = context.isMake() && chunkContainsAffectedFiles(context, chunk, newlyAffectedFiles);
}
}
else {
additionalPassRequired = context.isMake();
context.markDirtyRecursively(chunk);
}
}
globalMappings.integrate(delta, successfullyCompiled, removedPaths);
}
return additionalPassRequired;
}
catch(RuntimeException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw ((IOException)cause);
}
throw e;
}
}
// delete all class files that according to mappings correspond to given sources
public static void deleteCorrespondingOutputFiles(CompileContext context, Map<File, Module> sources) throws Exception {
if (!context.isProjectRebuild() && !sources.isEmpty()) {
for (Map.Entry<File, Module> pair : sources.entrySet()) {
final File file = pair.getKey();
final String srcPath = FileUtil.toSystemIndependentName(file.getPath());
final String moduleName = pair.getValue().getName().toLowerCase(Locale.US);
final SourceToOutputMapping srcToOut = context.getDataManager().getSourceToOutputMap(moduleName, context.isCompilingTests());
final Collection<String> outputs = srcToOut.getState(srcPath);
if (outputs != null) {
for (String output : outputs) {
FileUtil.delete(new File(output));
}
srcToOut.remove(srcPath);
}
}
}
}
private static boolean chunkContainsAffectedFiles(CompileContext context, ModuleChunk chunk, final Set<File> affected) throws Exception {
final Set<Module> chunkModules = new HashSet<Module>(chunk.getModules());
if (!chunkModules.isEmpty()) {
for (File file : affected) {
final RootDescriptor moduleAndRoot = context.getModuleAndRoot(file);
if (moduleAndRoot != null && chunkModules.contains(moduleAndRoot.module)) {
return true;
}
}
}
return false;
}
private static Set<File> getAllAffectedFilesContainer(CompileContext context) {
Set<File> allAffectedFiles = ALL_AFFECTED_FILES_KEY.get(context);
if (allAffectedFiles == null) {
allAffectedFiles = new HashSet<File>();
ALL_AFFECTED_FILES_KEY.set(context, allAffectedFiles);
}
return allAffectedFiles;
}
private static Set<File> getAllCompiledFilesContainer(CompileContext context) {
Set<File> allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context);
if (allCompiledFiles == null) {
allCompiledFiles = new HashSet<File>();
ALL_COMPILED_FILES_KEY.set(context, allCompiledFiles);
}
return allCompiledFiles;
}
private static Set<String> getRemovedPaths(CompileContext context) {
final Set<String> removed = Paths.CHUNK_REMOVED_SOURCES_KEY.get(context);
return removed != null? removed : Collections.<String>emptySet();
}
}
@@ -16,7 +16,7 @@ public class BuilderRegistry {
private static class Holder {
static final BuilderRegistry ourInstance = new BuilderRegistry();
}
private final Map<BuilderCategory, List<Builder>> myBuilders = new HashMap<BuilderCategory, List<Builder>>();
private final Map<BuilderCategory, List<ModuleLevelBuilder>> myBuilders = new HashMap<BuilderCategory, List<ModuleLevelBuilder>>();
private ExecutorService myTasksExecutor;
public static BuilderRegistry getInstance() {
@@ -25,7 +25,7 @@ public class BuilderRegistry {
private BuilderRegistry() {
for (BuilderCategory category : BuilderCategory.values()) {
myBuilders.put(category, new ArrayList<Builder>());
myBuilders.put(category, new ArrayList<ModuleLevelBuilder>());
}
final Runtime runtime = Runtime.getRuntime();
myTasksExecutor = Executors.newFixedThreadPool(runtime.availableProcessors());
@@ -60,7 +60,7 @@ public class BuilderRegistry {
return Collections.emptyList(); // todo
}
public List<Builder> getBuilders(BuilderCategory category){
public List<ModuleLevelBuilder> getBuilders(BuilderCategory category){
return Collections.unmodifiableList(myBuilders.get(category)); // todo
}
@@ -314,7 +314,7 @@ public class IncProjectBuilder {
finally {
try {
for (BuilderCategory category : BuilderCategory.values()) {
for (Builder builder : myBuilderRegistry.getBuilders(category)) {
for (ModuleLevelBuilder builder : myBuilderRegistry.getBuilders(category)) {
builder.cleanupResources(context, chunk);
}
}
@@ -334,7 +334,7 @@ public class IncProjectBuilder {
}
private void runBuilders(CompileContext context, ModuleChunk chunk, BuilderCategory category) throws ProjectBuildException {
final List<Builder> builders = myBuilderRegistry.getBuilders(category);
final List<ModuleLevelBuilder> builders = myBuilderRegistry.getBuilders(category);
if (builders.isEmpty()) {
return;
}
@@ -347,16 +347,16 @@ public class IncProjectBuilder {
do {
nextPassRequired = false;
context.beforeNextCompileRound(chunk);
for (Builder builder : builders) {
final Builder.ExitCode buildResult = builder.build(context, chunk);
for (ModuleLevelBuilder builder : builders) {
final ModuleLevelBuilder.ExitCode buildResult = builder.build(context, chunk);
if (buildResult == Builder.ExitCode.ABORT) {
if (buildResult == ModuleLevelBuilder.ExitCode.ABORT) {
throw new ProjectBuildException("Builder " + builder.getDescription() + " requested build stop");
}
if (myCancelStatus.isCanceled()) {
throw new ProjectBuildException(CANCELED_MESSAGE);
}
if (buildResult == Builder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
if (buildResult == ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
if (!nextPassRequired) {
// recalculate basis
myModulesProcessed -= (stagesPassed * modulesInChunk) / stageCount;
@@ -0,0 +1,177 @@
package org.jetbrains.jps.incremental;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.ether.dependencyView.Mappings;
import org.jetbrains.jps.Module;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.incremental.storage.SourceToOutputMapping;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: 9/17/11
*/
public abstract class ModuleLevelBuilder extends Builder {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.Builder");
private static final Key<Set<File>> ALL_AFFECTED_FILES_KEY = Key.create("_all_affected_files_");
private static final Key<Set<File>> ALL_COMPILED_FILES_KEY = Key.create("_all_compiled_files_");
public static enum ExitCode {
OK, ABORT, ADDITIONAL_PASS_REQUIRED
}
public abstract ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException;
public void cleanupResources(CompileContext context, ModuleChunk chunk) {
ALL_AFFECTED_FILES_KEY.set(context, null);
ALL_COMPILED_FILES_KEY.set(context, null);
}
/**
* @param context
* @param delta
* @param chunk
* @param filesToCompile files compiled in this round
* @param successfullyCompiled
* @return true if additional compilation pass is required, false otherwise
* @throws Exception
*/
public final boolean updateMappings(CompileContext context, final Mappings delta, ModuleChunk chunk, Collection<File> filesToCompile, Collection<File> successfullyCompiled) throws Exception {
try {
boolean additionalPassRequired = false;
final Set<String> removedPaths = getRemovedPaths(context);
final Mappings globalMappings = context.getDataManager().getMappings();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (globalMappings) {
if (!context.isProjectRebuild() && context.shouldDifferentiate(chunk, context.isCompilingTests())) {
final Set<File> allCompiledFiles = getAllCompiledFilesContainer(context);
final Set<File> allAffectedFiles = getAllAffectedFilesContainer(context);
// mark as affected all files that were dirty before compilation
allAffectedFiles.addAll(filesToCompile);
// accumulate all successfully compiled in this round
allCompiledFiles.addAll(successfullyCompiled);
// unmark as affected all successfully compiled
allAffectedFiles.removeAll(successfullyCompiled);
final HashSet<File> affectedBeforeDif = new HashSet<File>(allAffectedFiles);
final boolean incremental = globalMappings.differentiate(
delta, removedPaths, successfullyCompiled, allCompiledFiles, allAffectedFiles
);
if (LOG.isDebugEnabled()) {
LOG.debug("Differentiate Results:");
LOG.debug(" Compiled Files:");
for (final File c : allCompiledFiles) {
LOG.debug(" " + c.getAbsolutePath());
}
LOG.debug(" Affected Files:");
for (final File c : allAffectedFiles) {
LOG.debug(" " + c.getAbsolutePath());
}
LOG.debug("End Of Differentiate Results.");
}
if (incremental) {
final Set<File> newlyAffectedFiles = new HashSet<File>(allAffectedFiles);
newlyAffectedFiles.removeAll(affectedBeforeDif);
newlyAffectedFiles.removeAll(allCompiledFiles); // the diff operation may have affected the class already compiled in thic compilation round
if (!newlyAffectedFiles.isEmpty()) {
for (File file : newlyAffectedFiles) {
context.markDirty(file);
}
additionalPassRequired = context.isMake() && chunkContainsAffectedFiles(context, chunk, newlyAffectedFiles);
}
}
else {
additionalPassRequired = context.isMake();
context.markDirtyRecursively(chunk);
}
}
globalMappings.integrate(delta, successfullyCompiled, removedPaths);
}
return additionalPassRequired;
}
catch(RuntimeException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw ((IOException)cause);
}
throw e;
}
}
// delete all class files that according to mappings correspond to given sources
public static void deleteCorrespondingOutputFiles(CompileContext context, Map<File, Module> sources) throws Exception {
if (!context.isProjectRebuild() && !sources.isEmpty()) {
for (Map.Entry<File, Module> pair : sources.entrySet()) {
final File file = pair.getKey();
final String srcPath = FileUtil.toSystemIndependentName(file.getPath());
final String moduleName = pair.getValue().getName().toLowerCase(Locale.US);
final SourceToOutputMapping srcToOut = context.getDataManager().getSourceToOutputMap(moduleName, context.isCompilingTests());
final Collection<String> outputs = srcToOut.getState(srcPath);
if (outputs != null) {
for (String output : outputs) {
FileUtil.delete(new File(output));
}
srcToOut.remove(srcPath);
}
}
}
}
private static boolean chunkContainsAffectedFiles(CompileContext context, ModuleChunk chunk, final Set<File> affected) throws Exception {
final Set<Module> chunkModules = new HashSet<Module>(chunk.getModules());
if (!chunkModules.isEmpty()) {
for (File file : affected) {
final RootDescriptor moduleAndRoot = context.getModuleAndRoot(file);
if (moduleAndRoot != null && chunkModules.contains(moduleAndRoot.module)) {
return true;
}
}
}
return false;
}
private static Set<File> getAllAffectedFilesContainer(CompileContext context) {
Set<File> allAffectedFiles = ALL_AFFECTED_FILES_KEY.get(context);
if (allAffectedFiles == null) {
allAffectedFiles = new HashSet<File>();
ALL_AFFECTED_FILES_KEY.set(context, allAffectedFiles);
}
return allAffectedFiles;
}
private static Set<File> getAllCompiledFilesContainer(CompileContext context) {
Set<File> allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context);
if (allCompiledFiles == null) {
allCompiledFiles = new HashSet<File>();
ALL_COMPILED_FILES_KEY.set(context, allCompiledFiles);
}
return allCompiledFiles;
}
private static Set<String> getRemovedPaths(CompileContext context) {
final Set<String> removed = Paths.CHUNK_REMOVED_SOURCES_KEY.get(context);
return removed != null? removed : Collections.<String>emptySet();
}
}
@@ -0,0 +1,20 @@
package org.jetbrains.jps.incremental;
/**
* @author nik
*/
public abstract class ProjectLevelBuilder extends Builder {
private final ProjectLevelBuilderCategory myCategory;
protected ProjectLevelBuilder(ProjectLevelBuilderCategory category) {
myCategory = category;
}
public abstract void build(CompileContext context);
public ProjectLevelBuilderCategory getCategory() {
return myCategory;
}
public static enum ProjectLevelBuilderCategory { TRANSLATOR, PACKAGER }
}
@@ -29,7 +29,7 @@ import java.util.*;
* @author Eugene Zhuravlev
* Date: 10/25/11
*/
public class GroovyBuilder extends Builder {
public class GroovyBuilder extends ModuleLevelBuilder {
public static final String BUILDER_NAME = "groovy";
private final boolean myForStubs;
private final String myBuilderName;
@@ -43,7 +43,7 @@ public class GroovyBuilder extends Builder {
return myBuilderName;
}
public Builder.ExitCode build(final CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
public ModuleLevelBuilder.ExitCode build(final CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
ExitCode exitCode = ExitCode.OK;
final Map<File, Module> toCompile = new HashMap<File, Module>();
try {
@@ -49,7 +49,7 @@ import java.util.concurrent.ExecutorService;
* @author Eugene Zhuravlev
* Date: 9/21/11
*/
public class JavaBuilder extends Builder{
public class JavaBuilder extends ModuleLevelBuilder {
public static final String BUILDER_NAME = "java";
private static final String JAVA_EXTENSION = ".java";
private static final String FORM_EXTENSION = ".form";
@@ -19,7 +19,7 @@ import java.util.Locale;
* @author Eugene Zhuravlev
* Date: 10/6/11
*/
public class ResourcesBuilder extends Builder{
public class ResourcesBuilder extends ModuleLevelBuilder {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.resourses.ResourcesBuilder");
public static final String BUILDER_NAME = "resources";