From bc8ca6f5a473ed958c82fd27b93328e0fbaec58a Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 3 Feb 2012 16:27:21 +0400 Subject: [PATCH] artifact builder for compile server --- .../jps/incremental/AllProjectScope.java | 6 +- .../jetbrains/jps/incremental/Builder.java | 4 + .../jps/incremental/BuilderRegistry.java | 27 +- .../jps/incremental/CompileContext.java | 4 + .../jps/incremental/CompileScope.java | 11 +- .../jps/incremental/IncProjectBuilder.java | 27 +- .../jps/incremental/ModuleRootsIndex.java | 10 + .../jps/incremental/ModulesAndFilesScope.java | 6 +- .../jps/incremental/ModulesScope.java | 5 +- .../jps/incremental/ProjectLevelBuilder.java | 12 +- .../ProjectLevelBuilderService.java | 11 + .../artifacts/ArtifactBuilderService.java | 16 ++ .../ArtifactCompilerPersistentData.java | 114 ++++++++ .../artifacts/ArtifactSourceFilesState.java | 189 +++++++++++++ .../ArtifactSourceTimestampStorage.java | 57 ++++ .../ArtifactSourceToOutputMapping.java | 44 +++ .../artifacts/ArtifactsBuildData.java | 61 +++++ .../artifacts/IncArtifactBuilder.java | 210 +++++++++++++++ .../incremental/artifacts/JarPathUtil.java | 20 ++ .../builders/LayoutElementBuilder.java | 12 + .../LayoutElementBuildersRegistry.java | 206 ++++++++++++++ .../artifacts/impl/ArtifactSorter.java | 202 ++++++++++++++ .../impl/DependentJarsEvaluator.java | 46 ++++ .../artifacts/impl/JarsBuilder.java | 251 ++++++++++++++++++ .../ArtifactCompilerInstructionCreator.java | 41 +++ ...rtifactCompilerInstructionCreatorBase.java | 91 +++++++ .../ArtifactInstructionsBuilder.java | 15 ++ .../ArtifactInstructionsBuilderContext.java | 16 ++ ...rtifactInstructionsBuilderContextImpl.java | 30 +++ .../ArtifactInstructionsBuilderImpl.java | 89 +++++++ .../instructions/ArtifactRootProcessor.java | 10 + .../instructions/ArtifactSourceRoot.java | 37 +++ .../CopyToDirectoryInstructionCreator.java | 56 ++++ .../instructions/DestinationInfo.java | 41 +++ .../instructions/ExplodedDestinationInfo.java | 30 +++ .../FileBasedArtifactSourceRoot.java | 43 +++ .../JarBasedArtifactSourceRoot.java | 46 ++++ .../instructions/JarDestinationInfo.java | 54 ++++ .../artifacts/instructions/JarInfo.java | 76 ++++++ .../PackIntoArchiveInstructionCreator.java | 71 +++++ .../SkipAllInstructionCreator.java | 44 +++ .../instructions/SourceFileFilter.java | 17 ++ .../storage/AbstractStateStorage.java | 11 +- .../incremental/storage/BuildDataManager.java | 108 ++++---- .../jps/server/ClasspathBootstrap.java | 6 +- .../jps/server/ServerMessageHandler.java | 8 +- .../org/jetbrains/jps/server/ServerState.java | 31 ++- .../jetbrains/ether/IncrementalTestCase.java | 6 +- jps/model/src/org/jetbrains/jps/PathUtil.java | 26 ++ 49 files changed, 2455 insertions(+), 99 deletions(-) create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilderService.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactBuilderService.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactCompilerPersistentData.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceFilesState.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceTimestampStorage.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceToOutputMapping.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactsBuildData.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/IncArtifactBuilder.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/JarPathUtil.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuilder.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuildersRegistry.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/DependentJarsEvaluator.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/JarsBuilder.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreator.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreatorBase.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilder.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContext.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContextImpl.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderImpl.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactRootProcessor.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactSourceRoot.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/CopyToDirectoryInstructionCreator.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/DestinationInfo.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ExplodedDestinationInfo.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/FileBasedArtifactSourceRoot.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarBasedArtifactSourceRoot.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarDestinationInfo.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarInfo.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/PackIntoArchiveInstructionCreator.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SkipAllInstructionCreator.java create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SourceFileFilter.java diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/AllProjectScope.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/AllProjectScope.java index 43d7a6b42903..4d08af6f11b1 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/AllProjectScope.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/AllProjectScope.java @@ -3,8 +3,10 @@ package org.jetbrains.jps.incremental; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.Module; import org.jetbrains.jps.Project; +import org.jetbrains.jps.artifacts.Artifact; import java.io.File; +import java.util.Set; /** * @author Eugene Zhuravlev @@ -14,8 +16,8 @@ public class AllProjectScope extends CompileScope { private final boolean myIsForcedCompilation; - public AllProjectScope(Project project, boolean forcedCompilation) { - super(project); + public AllProjectScope(Project project, Set artifacts, boolean forcedCompilation) { + super(project, artifacts); myIsForcedCompilation = forcedCompilation; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/Builder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/Builder.java index 4bfc4b9c294e..bf1265d0ca4a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/Builder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/Builder.java @@ -7,4 +7,8 @@ public abstract class Builder { public abstract String getName(); public abstract String getDescription(); + + public static enum ExitCode { + OK, ABORT, ADDITIONAL_PASS_REQUIRED + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/BuilderRegistry.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/BuilderRegistry.java index 51a128ddc6ff..7956d9210086 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/BuilderRegistry.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/BuilderRegistry.java @@ -1,5 +1,6 @@ package org.jetbrains.jps.incremental; +import org.jetbrains.jps.idea.OwnServiceLoader; import org.jetbrains.jps.incremental.groovy.GroovyBuilder; import org.jetbrains.jps.incremental.java.JavaBuilder; import org.jetbrains.jps.incremental.resources.ResourcesBuilder; @@ -16,7 +17,8 @@ public class BuilderRegistry { private static class Holder { static final BuilderRegistry ourInstance = new BuilderRegistry(); } - private final Map> myBuilders = new HashMap>(); + private final Map> myModuleLevelBuilders = new HashMap>(); + private final List myProjectLevelBuilders = new ArrayList(); private ExecutorService myTasksExecutor; public static BuilderRegistry getInstance() { @@ -25,7 +27,7 @@ public class BuilderRegistry { private BuilderRegistry() { for (BuilderCategory category : BuilderCategory.values()) { - myBuilders.put(category, new ArrayList()); + myModuleLevelBuilders.put(category, new ArrayList()); } final Runtime runtime = Runtime.getRuntime(); myTasksExecutor = Executors.newFixedThreadPool(runtime.availableProcessors()); @@ -35,16 +37,21 @@ public class BuilderRegistry { } }); + final OwnServiceLoader loader = OwnServiceLoader.load(ProjectLevelBuilderService.class); + + for (ProjectLevelBuilderService service : loader) { + myProjectLevelBuilders.add(service.createBuilder()); + } // todo: some builder registration mechanism for plugins needed - myBuilders.get(BuilderCategory.TRANSLATOR).add(new GroovyBuilder(true)); - myBuilders.get(BuilderCategory.TRANSLATOR).add(new JavaBuilder(myTasksExecutor)); - myBuilders.get(BuilderCategory.TRANSLATOR).add(new ResourcesBuilder()); - myBuilders.get(BuilderCategory.TRANSLATOR).add(new GroovyBuilder(false)); + myModuleLevelBuilders.get(BuilderCategory.TRANSLATOR).add(new GroovyBuilder(true)); + myModuleLevelBuilders.get(BuilderCategory.TRANSLATOR).add(new JavaBuilder(myTasksExecutor)); + myModuleLevelBuilders.get(BuilderCategory.TRANSLATOR).add(new ResourcesBuilder()); + myModuleLevelBuilders.get(BuilderCategory.TRANSLATOR).add(new GroovyBuilder(false)); } - public int getTotalBuilderCount() { + public int getModuleLevelBuilderCount() { int count = 0; for (BuilderCategory category : BuilderCategory.values()) { count += getBuilders(category).size(); @@ -61,7 +68,11 @@ public class BuilderRegistry { } public List getBuilders(BuilderCategory category){ - return Collections.unmodifiableList(myBuilders.get(category)); // todo + return Collections.unmodifiableList(myModuleLevelBuilders.get(category)); // todo + } + + public List getProjectLevelBuilders() { + return myProjectLevelBuilders; } public void shutdown() { diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContext.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContext.java index 562e30fdc462..baf4d2552cb1 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContext.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContext.java @@ -283,6 +283,10 @@ public class CompileContext extends UserDataHolderBase implements MessageHandler return myRootsIndex.getModuleRoots(module); } + public ModuleRootsIndex getRootsIndex() { + return myRootsIndex; + } + public void setDone(float done) { myDone = done; //processMessage(new ProgressMessage("", done)); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileScope.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileScope.java index d1f439c6eb43..c5dda79b3208 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileScope.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileScope.java @@ -4,20 +4,27 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.Module; import org.jetbrains.jps.ModuleChunk; import org.jetbrains.jps.Project; +import org.jetbrains.jps.artifacts.Artifact; import java.io.File; +import java.util.Set; /** * @author Eugene Zhuravlev * Date: 1/15/12 */ public abstract class CompileScope { - @NotNull private final Project myProject; + private final Set myArtifacts; - protected CompileScope(@NotNull Project project) { + protected CompileScope(@NotNull Project project, Set artifacts) { myProject = project; + myArtifacts = artifacts; + } + + public boolean isAffected(Artifact artifact) { + return myArtifacts.contains(artifact); } public abstract boolean isAffected(Module module, @NotNull File file); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java index 1c09deb3c4a4..1b4545b5e6ce 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java @@ -6,6 +6,7 @@ import com.intellij.util.io.PersistentEnumerator; import org.jetbrains.jps.*; import org.jetbrains.jps.api.CanceledStatus; import org.jetbrains.jps.api.RequestFuture; +import org.jetbrains.jps.artifacts.Artifact; import org.jetbrains.jps.incremental.java.ExternalJavacDescriptor; import org.jetbrains.jps.incremental.java.JavaBuilder; import org.jetbrains.jps.incremental.messages.BuildMessage; @@ -49,7 +50,7 @@ public class IncProjectBuilder { private float myModulesProcessed = 0.0f; private final float myTotalModulesWork; - private final int myTotalBuilderCount; + private final int myTotalModuleLevelBuilderCount; public IncProjectBuilder(ProjectDescriptor pd, BuilderRegistry builderRegistry, CanceledStatus cs) { myProjectDescriptor = pd; @@ -58,7 +59,7 @@ public class IncProjectBuilder { myProductionChunks = new ProjectChunks(pd.project, ClasspathKind.PRODUCTION_COMPILE); myTestChunks = new ProjectChunks(pd.project, ClasspathKind.TEST_COMPILE); myTotalModulesWork = (float)pd.rootsIndex.getTotalModuleCount() * 2; /* multiply by 2 to reflect production and test sources */ - myTotalBuilderCount = builderRegistry.getTotalBuilderCount(); + myTotalModuleLevelBuilderCount = builderRegistry.getModuleLevelBuilderCount(); } public void addMessageHandler(MessageHandler handler) { @@ -79,7 +80,7 @@ public class IncProjectBuilder { "Internal caches are corrupted or have outdated format, forcing project rebuild: " + e.getMessage())); flushContext(context); - context = createContext(new AllProjectScope(scope.getProject(), true), false, true); + context = createContext(new AllProjectScope(scope.getProject(), Collections.emptySet(), true), false, true); runBuild(context); } else { @@ -165,6 +166,9 @@ public class IncProjectBuilder { context.processMessage(new ProgressMessage("Building test sources")); buildChunks(context, myTestChunks); + context.processMessage(new ProgressMessage("Building project")); + runProjectLevelBuilders(context); + context.processMessage(new ProgressMessage("Running 'after' tasks")); runTasks(context, myBuilderRegistry.getAfterTasks()); @@ -333,7 +337,7 @@ public class IncProjectBuilder { context.onChunkBuildStart(chunk); for (BuilderCategory category : BuilderCategory.values()) { - runBuilders(context, chunk, category); + runModuleLevelBuilders(context, chunk, category); } } catch (ProjectBuildException e) { @@ -364,14 +368,14 @@ public class IncProjectBuilder { } } - private void runBuilders(final CompileContext context, ModuleChunk chunk, BuilderCategory category) throws ProjectBuildException { + private void runModuleLevelBuilders(final CompileContext context, ModuleChunk chunk, BuilderCategory category) throws ProjectBuildException { final List builders = myBuilderRegistry.getBuilders(category); if (builders.isEmpty()) { return; } boolean rebuildFromScratchRequested = false; - float stageCount = myTotalBuilderCount; + float stageCount = myTotalModuleLevelBuilderCount; int stagesPassed = 0; final int modulesInChunk = chunk.getModules().size(); @@ -397,7 +401,7 @@ public class IncProjectBuilder { if (!nextPassRequired) { // recalculate basis myModulesProcessed -= (stagesPassed * modulesInChunk) / stageCount; - stageCount += myTotalBuilderCount; + stageCount += myTotalModuleLevelBuilderCount; myModulesProcessed += (stagesPassed * modulesInChunk) / stageCount; } nextPassRequired = true; @@ -432,6 +436,15 @@ public class IncProjectBuilder { while (nextPassRequired); } + private void runProjectLevelBuilders(CompileContext context) throws ProjectBuildException { + for (ProjectLevelBuilder builder : myBuilderRegistry.getProjectLevelBuilders()) { + builder.build(context); + if (myCancelStatus.isCanceled()) { + throw new ProjectBuildException(CANCELED_MESSAGE); + } + } + } + private static void syncOutputFiles(final CompileContext context, ModuleChunk chunk) throws ProjectBuildException { final BuildDataManager dataManager = context.getDataManager(); final boolean compilingTests = context.isCompilingTests(); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleRootsIndex.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleRootsIndex.java index 05cd3ba21499..02c381241955 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleRootsIndex.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleRootsIndex.java @@ -4,6 +4,7 @@ import com.intellij.openapi.util.io.FileUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.Module; +import org.jetbrains.jps.PathUtil; import org.jetbrains.jps.Project; import java.io.File; @@ -17,6 +18,7 @@ public class ModuleRootsIndex { private final Map myRootToModuleMap = new HashMap(); private final Map> myModuleToRootsMap = new HashMap>(); private final int myTotalModuleCount; + private final Set myExcludedRoots = new HashSet(); public ModuleRootsIndex(Project project) { final Collection allModules = project.getModules().values(); @@ -39,6 +41,10 @@ public class ModuleRootsIndex { myRootToModuleMap.put(root, descriptor); moduleRoots.add(descriptor); } + for (String r : module.getExcludes()) { + final File root = new File(FileUtil.toCanonicalPath(r)); + myExcludedRoots.add(root); + } } } @@ -69,4 +75,8 @@ public class ModuleRootsIndex { } return null; } + + public boolean isExcluded(File file) { + return PathUtil.isUnder(myExcludedRoots, file); + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesAndFilesScope.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesAndFilesScope.java index 380c0daadf1f..0d2e2467605d 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesAndFilesScope.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesAndFilesScope.java @@ -3,6 +3,7 @@ package org.jetbrains.jps.incremental; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.Module; import org.jetbrains.jps.Project; +import org.jetbrains.jps.artifacts.Artifact; import java.io.File; import java.util.Collection; @@ -20,8 +21,9 @@ public class ModulesAndFilesScope extends CompileScope { private final Map> myFiles; private final boolean myForcedCompilation; - public ModulesAndFilesScope(Project project, Collection modules, Map> files, boolean isForcedCompilation) { - super(project); + public ModulesAndFilesScope(Project project, Collection modules, Map> files, Set artifacts, + boolean isForcedCompilation) { + super(project, artifacts); myFiles = files; myForcedCompilation = isForcedCompilation; myModules = new HashSet(modules); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesScope.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesScope.java index 56ffec4d7f46..be626e1719d8 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesScope.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModulesScope.java @@ -3,6 +3,7 @@ package org.jetbrains.jps.incremental; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.Module; import org.jetbrains.jps.Project; +import org.jetbrains.jps.artifacts.Artifact; import java.io.File; import java.util.Set; @@ -16,8 +17,8 @@ public class ModulesScope extends CompileScope { private final Set myModules; private final boolean myForcedCompilation; - public ModulesScope(Project project, Set modules, boolean isForcedCompilation) { - super(project); + public ModulesScope(Project project, Set modules, Set artifacts, boolean isForcedCompilation) { + super(project, artifacts); myModules = modules; myForcedCompilation = isForcedCompilation; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilder.java index c8296590c3aa..dadd38cc9928 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilder.java @@ -4,17 +4,9 @@ package org.jetbrains.jps.incremental; * @author nik */ public abstract class ProjectLevelBuilder extends Builder { - private final ProjectLevelBuilderCategory myCategory; - - protected ProjectLevelBuilder(ProjectLevelBuilderCategory category) { - myCategory = category; + protected ProjectLevelBuilder() { } - public abstract void build(CompileContext context); + public abstract void build(CompileContext context) throws ProjectBuildException; - public ProjectLevelBuilderCategory getCategory() { - return myCategory; - } - - public static enum ProjectLevelBuilderCategory { TRANSLATOR, PACKAGER } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilderService.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilderService.java new file mode 100644 index 000000000000..fb6dc5eeda14 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ProjectLevelBuilderService.java @@ -0,0 +1,11 @@ +package org.jetbrains.jps.incremental; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public abstract class ProjectLevelBuilderService { + @NotNull + public abstract ProjectLevelBuilder createBuilder(); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactBuilderService.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactBuilderService.java new file mode 100644 index 000000000000..6c4b8b19cd95 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactBuilderService.java @@ -0,0 +1,16 @@ +package org.jetbrains.jps.incremental.artifacts; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.incremental.ProjectLevelBuilder; +import org.jetbrains.jps.incremental.ProjectLevelBuilderService; + +/** + * @author nik + */ +public class ArtifactBuilderService extends ProjectLevelBuilderService { + @NotNull + @Override + public ProjectLevelBuilder createBuilder() { + return new IncArtifactBuilder(); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactCompilerPersistentData.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactCompilerPersistentData.java new file mode 100644 index 000000000000..65120bb9cf02 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactCompilerPersistentData.java @@ -0,0 +1,114 @@ +/* + * 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 org.jetbrains.jps.incremental.artifacts; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.io.IOUtil; +import gnu.trove.TIntHashSet; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author nik + */ +public class ArtifactCompilerPersistentData { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.generic.ArtifactCompilerPersistentData"); + private static final int VERSION = 0; + private File myFile; + private Map myArtifact2Id = new HashMap(); + private TIntHashSet myUsedIds = new TIntHashSet(); + private boolean myVersionChanged; + + public ArtifactCompilerPersistentData(File cacheStoreDirectory) throws IOException { + myFile = new File(cacheStoreDirectory, "info"); + if (!myFile.exists()) { + LOG.info("Artifacts compiler info file doesn't exist: " + myFile.getAbsolutePath()); + myVersionChanged = true; + return; + } + + DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(myFile))); + try { + final int version = input.readInt(); + if (version != VERSION) { + LOG.info("Artifacts compiler version changed (" + myFile.getAbsolutePath() + "): " + version + " -> " + VERSION); + myVersionChanged = true; + return; + } + + int size = input.readInt(); + while (size-- > 0) { + final String artifactName = IOUtil.readString(input); + final int id = input.readInt(); + myArtifact2Id.put(artifactName, id); + myUsedIds.add(id); + } + } + finally { + input.close(); + } + } + + public boolean isVersionChanged() { + return myVersionChanged; + } + + public void save() throws IOException { + final DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(myFile))); + try { + output.writeInt(VERSION); + output.writeInt(myArtifact2Id.size()); + + for (Map.Entry entry : myArtifact2Id.entrySet()) { + IOUtil.writeString(entry.getKey(), output); + output.writeInt(entry.getValue()); + } + } + finally { + output.close(); + } + } + + public int getId(@NotNull String artifactName) { + if (myArtifact2Id.containsKey(artifactName)) { + return myArtifact2Id.get(artifactName); + } + int id = 0; + while (myUsedIds.contains(id)) { + id++; + } + myArtifact2Id.put(artifactName, id); + myUsedIds.add(id); + return id; + } + + public Set getAllArtifacts() { + return myArtifact2Id.keySet(); + } + + public int removeArtifact(String target) { + return myArtifact2Id.remove(target); + } + + public void clean() { + FileUtil.delete(myFile); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceFilesState.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceFilesState.java new file mode 100644 index 000000000000..5adc718077ec --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceFilesState.java @@ -0,0 +1,189 @@ +package org.jetbrains.jps.incremental.artifacts; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ArrayUtil; +import org.jetbrains.jps.Project; +import org.jetbrains.jps.ProjectPaths; +import org.jetbrains.jps.artifacts.Artifact; +import org.jetbrains.jps.artifacts.LayoutElement; +import org.jetbrains.jps.incremental.ModuleRootsIndex; +import org.jetbrains.jps.incremental.artifacts.builders.LayoutElementBuildersRegistry; +import org.jetbrains.jps.incremental.artifacts.instructions.*; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author nik + */ +public class ArtifactSourceFilesState { + private final Project myProject; + private final Artifact myArtifact; + private final int myArtifactId; + private final ModuleRootsIndex myRootsIndex; + private final ArtifactSourceTimestampStorage myTimestampStorage; + private Set myChangedFiles = new HashSet(); + private Set myDeletedFiles = new HashSet(); + private ArtifactInstructionsBuilder myInstructionsBuilder; + private ArtifactSourceToOutputMapping myMapping; + private final AtomicBoolean myInitialized = new AtomicBoolean(); + private final File myMappingsFile; + + public ArtifactSourceFilesState(Artifact artifact, int artifactId, Project project, + ModuleRootsIndex rootsIndex, + ArtifactSourceTimestampStorage timestampStorage, + File artifactsDataDir) { + myProject = project; + myArtifact = artifact; + myRootsIndex = rootsIndex; + myTimestampStorage = timestampStorage; + myArtifactId = artifactId; + myMappingsFile = new File(artifactsDataDir, "mappings" + File.separator + artifactId); + } + + public ArtifactSourceToOutputMapping getOrCreateMapping() throws Exception { + if (myMapping == null) { + myMapping = new ArtifactSourceToOutputMapping(myMappingsFile); + } + return myMapping; + } + + public void clean() { + if (myMapping != null) { + myMapping.wipe(); + } + } + + public Set getChangedFiles() { + return myChangedFiles; + } + + public Set getDeletedFiles() { + return myDeletedFiles; + } + + public void initState() throws Exception { + /* + if (!myInitialized.compareAndSet(false, true)) { + return; + } + */ + + final Set currentPaths = new HashSet(); + myChangedFiles.clear(); + myDeletedFiles.clear(); + getOrCreateInstructions().processRoots(new ArtifactRootProcessor() { + @Override + public void process(ArtifactSourceRoot root, Collection destinations) throws Exception { + final File rootFile = root.getRootFile(); + if (rootFile.exists()) { + processRecursively(rootFile, root.getFilter(), currentPaths); + } + } + }); + final ArtifactSourceToOutputMapping mapping = getOrCreateMapping(); + final Iterator iterator = mapping.getKeysIterator(); + while (iterator.hasNext()) { + String path = iterator.next(); + if (!currentPaths.contains(path)) { + myDeletedFiles.add(path); + } + } + } + + public ArtifactSourceTimestampStorage getTimestampStorage() { + return myTimestampStorage; + } + + private void processRecursively(File file, SourceFileFilter filter, Set currentPaths) throws Exception { + final String filePath = FileUtil.toSystemIndependentName(FileUtil.toCanonicalPath(file.getPath())); + if (!filter.accept(filePath)) return; + + if (file.isDirectory()) { + final File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + processRecursively(child, filter, currentPaths); + } + } + } + else { + currentPaths.add(filePath); + final ArtifactSourceTimestampStorage.PerArtifactTimestamp[] state = myTimestampStorage.getState(filePath); + boolean upToDate = false; + if (state != null) { + for (ArtifactSourceTimestampStorage.PerArtifactTimestamp artifactTimestamp : state) { + if (artifactTimestamp.myArtifactId == myArtifactId && artifactTimestamp.myTimestamp == file.lastModified()) { + upToDate = true; + break; + } + } + } + if (!upToDate) { + myDeletedFiles.remove(filePath); + myChangedFiles.add(filePath); + } + } + } + + public ArtifactInstructionsBuilder getOrCreateInstructions() { + if (myInstructionsBuilder == null) { + myInstructionsBuilder = computeInstructions(); + } + return myInstructionsBuilder; + } + + private ArtifactInstructionsBuilder computeInstructions() { + final LayoutElement rootElement = myArtifact.getRootElement(); + ArtifactInstructionsBuilderContext context = new ArtifactInstructionsBuilderContextImpl(myProject, new ProjectPaths(myProject)); + final ArtifactInstructionsBuilderImpl instructionsBuilder = new ArtifactInstructionsBuilderImpl(myRootsIndex); + final CopyToDirectoryInstructionCreator instructionCreator = new CopyToDirectoryInstructionCreator(instructionsBuilder, myArtifact.getOutputPath()); + LayoutElementBuildersRegistry.getInstance().generateInstructions(rootElement, instructionCreator, context); + return instructionsBuilder; + } + + public void updateTimestamps(Set deletedFiles, Set changedFiles) throws Exception { + for (String filePath : deletedFiles) { + final ArtifactSourceTimestampStorage.PerArtifactTimestamp[] state = myTimestampStorage.getState(filePath); + if (state == null) continue; + for (int i = 0, length = state.length; i < length; i++) { + if (state[i].myArtifactId == myArtifactId) { + ArrayUtil.remove(state, i); + myTimestampStorage.update(filePath, state); + break; + } + } + } + for (String filePath : changedFiles) { + final ArtifactSourceTimestampStorage.PerArtifactTimestamp[] state = myTimestampStorage.getState(filePath); + if (state == null) continue; + for (int i = 0, length = state.length; i < length; i++) { + if (state[i].myArtifactId == myArtifactId) { + File file = new File(FileUtil.toSystemDependentName(filePath)); + state[i] = new ArtifactSourceTimestampStorage.PerArtifactTimestamp(myArtifactId, file.lastModified()); + myTimestampStorage.update(filePath, state); + break; + } + } + } + myDeletedFiles.clear(); + myChangedFiles.clear(); + } + + public void close() throws IOException { + if (myMapping != null) { + myMapping.close(); + } + } + + public void flush(boolean memoryCachesOnly) { + if (myMapping != null) { + myMapping.flush(memoryCachesOnly); + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceTimestampStorage.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceTimestampStorage.java new file mode 100644 index 000000000000..8f5bab766b6c --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceTimestampStorage.java @@ -0,0 +1,57 @@ +package org.jetbrains.jps.incremental.artifacts; + +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.jps.incremental.storage.AbstractStateStorage; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; + +/** + * @author nik + */ +public class ArtifactSourceTimestampStorage extends AbstractStateStorage { + private static final DataExternalizer TIMESTAMP_EXTERNALIZER = new DataExternalizer() { + @Override + public void save(DataOutput out, PerArtifactTimestamp[] value) throws IOException { + out.writeInt(value.length); + for (PerArtifactTimestamp timestamp : value) { + out.writeInt(timestamp.myArtifactId); + out.writeLong(timestamp.myTimestamp); + } + } + + @Override + public PerArtifactTimestamp[] read(DataInput in) throws IOException { + final int size = in.readInt(); + final PerArtifactTimestamp[] value = new PerArtifactTimestamp[size]; + for (int i = 0; i < size; i++) { + final int artifactId = in.readInt(); + final long timestamp = in.readLong(); + value[i] = new PerArtifactTimestamp(artifactId, timestamp); + } + return value; + } + }; + + public ArtifactSourceTimestampStorage(@NonNls File storePath) throws Exception { + super(storePath, new EnumeratorStringDescriptor(), TIMESTAMP_EXTERNALIZER); + } + + public void markDirty(String filePath) throws Exception { + update(filePath, null); + } + + public static class PerArtifactTimestamp { + public final int myArtifactId; + public final long myTimestamp; + + public PerArtifactTimestamp(int artifactId, long timestamp) { + myArtifactId = artifactId; + myTimestamp = timestamp; + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceToOutputMapping.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceToOutputMapping.java new file mode 100644 index 000000000000..9ef0750359ae --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactSourceToOutputMapping.java @@ -0,0 +1,44 @@ +package org.jetbrains.jps.incremental.artifacts; + +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.IOUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.jps.incremental.storage.AbstractStateStorage; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; + +/** + * @author nik + */ +public class ArtifactSourceToOutputMapping extends AbstractStateStorage { + private static DataExternalizer STRING_ARRAY_EXTERNALIZER = new DataExternalizer() { + private final byte[] myBuffer = IOUtil.allocReadWriteUTFBuffer(); + + @Override + public void save(DataOutput out, String[] value) throws IOException { + out.writeInt(value.length); + for (String path : value) { + IOUtil.writeUTFFast(myBuffer, out, path); + } + } + + @Override + public String[] read(DataInput in) throws IOException { + final int size = in.readInt(); + String[] result = new String[size]; + for (int i = 0; i < size; i++) { + final String path = IOUtil.readUTFFast(myBuffer, in); + result[i] = path; + } + return result; + } + }; + + public ArtifactSourceToOutputMapping(@NonNls File storePath) throws Exception { + super(storePath, new EnumeratorStringDescriptor(), STRING_ARRAY_EXTERNALIZER); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactsBuildData.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactsBuildData.java new file mode 100644 index 000000000000..7722afe014be --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/ArtifactsBuildData.java @@ -0,0 +1,61 @@ +package org.jetbrains.jps.incremental.artifacts; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.jps.Project; +import org.jetbrains.jps.artifacts.Artifact; +import org.jetbrains.jps.incremental.ModuleRootsIndex; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author nik + */ +public class ArtifactsBuildData { + private Map myArtifactState; + private final ArtifactSourceTimestampStorage myTimestampStorage; + private ArtifactCompilerPersistentData myPersistentData; + private final File myArtifactsDataDir; + + public ArtifactsBuildData(File artifactsDataDir) throws Exception { + myArtifactsDataDir = artifactsDataDir; + myTimestampStorage = new ArtifactSourceTimestampStorage(new File(artifactsDataDir, "timestamps")); + myArtifactState = new HashMap(); + myPersistentData = new ArtifactCompilerPersistentData(artifactsDataDir); + } + + public ArtifactSourceFilesState getOrCreateState(Artifact artifact, Project project, ModuleRootsIndex index) { + ArtifactSourceFilesState state = myArtifactState.get(artifact); + if (state == null) { + final int artifactId = myPersistentData.getId(artifact.getName()); + state = new ArtifactSourceFilesState(artifact, artifactId, project, index, myTimestampStorage, myArtifactsDataDir); + myArtifactState.put(artifact, state); + } + return state; + } + + public void clean() { + myTimestampStorage.wipe(); + myPersistentData.clean(); + for (ArtifactSourceFilesState state : myArtifactState.values()) { + state.clean(); + } + FileUtil.delete(myArtifactsDataDir); + } + + public void close() throws IOException { + myTimestampStorage.close(); + for (ArtifactSourceFilesState state : myArtifactState.values()) { + state.close(); + } + } + + public void flush(boolean memoryCachesOnly) { + myTimestampStorage.flush(memoryCachesOnly); + for (ArtifactSourceFilesState state : myArtifactState.values()) { + state.flush(memoryCachesOnly); + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/IncArtifactBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/IncArtifactBuilder.java new file mode 100644 index 000000000000..6ca0a195981d --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/IncArtifactBuilder.java @@ -0,0 +1,210 @@ +package org.jetbrains.jps.incremental.artifacts; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashSet; +import org.jetbrains.jps.PathUtil; +import org.jetbrains.jps.artifacts.Artifact; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.ProjectBuildException; +import org.jetbrains.jps.incremental.ProjectLevelBuilder; +import org.jetbrains.jps.incremental.artifacts.impl.ArtifactSorter; +import org.jetbrains.jps.incremental.artifacts.impl.JarsBuilder; +import org.jetbrains.jps.incremental.artifacts.instructions.*; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.incremental.messages.UptoDateFilesSavedEvent; +import org.jetbrains.jps.incremental.storage.BuildDataManager; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author nik + */ +public class IncArtifactBuilder extends ProjectLevelBuilder { + public static final String BUILDER_NAME = "artifacts"; + + public IncArtifactBuilder() { + super(); + } + + @Override + public void build(CompileContext context) throws ProjectBuildException { + Set affected = new HashSet(); + for (Artifact artifact : context.getProject().getArtifacts().values()) { + if (context.getScope().isAffected(artifact)) { + affected.add(artifact); + } + } + final Set toBuild = ArtifactSorter.addIncludedArtifacts(affected, context.getProject()); + Map artifactsMap = new HashMap(); + for (Artifact artifact : toBuild) { + artifactsMap.put(artifact.getName(), artifact); + } + + final ArtifactSorter sorter = new ArtifactSorter(context.getProject()); + final Map selfIncludingNameMap = sorter.getArtifactToSelfIncludingNameMap(); + for (String artifactName : sorter.getArtifactsSortedByInclusion()) { + final Artifact artifact = artifactsMap.get(artifactName); + if (artifact != null) { + final String selfIncluding = selfIncludingNameMap.get(artifactName); + if (selfIncluding != null) { + String name = selfIncluding.equals(artifact.getName()) ? "it" : "'" + selfIncluding + "' artifact"; + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Cannot build '" + artifactName + "' artifact: " + name + " includes itself in the output layout")); + break; + } + if (StringUtil.isEmpty(artifact.getOutputPath())) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Cannot build '" + artifactName + "' artifact: output path is not specified")); + break; + } + buildArtifact(artifact, context); + } + } + } + + private static void buildArtifact(Artifact artifact, CompileContext context) throws ProjectBuildException { + final BuildDataManager dataManager = context.getDataManager(); + try { + final ArtifactSourceFilesState state = dataManager.getArtifactsBuildData().getOrCreateState(artifact, + context.getProject(), context.getRootsIndex()); + state.initState(); + final Set deletedFiles = state.getDeletedFiles(); + final Set changedFiles = state.getChangedFiles(); + if (deletedFiles.isEmpty() && changedFiles.isEmpty()) { + return; + } + + context.processMessage(new ProgressMessage("Building artifact '" + artifact.getName() + "'...")); + final ArtifactSourceToOutputMapping mapping = state.getOrCreateMapping(); + final Set deletedJars = deleteOutdatedFiles(deletedFiles, context, mapping); + final ArtifactInstructionsBuilder instructions = state.getOrCreateInstructions(); + final Set changedJars = new THashSet(); + for (String deletedJar : deletedJars) { + ContainerUtil.addIfNotNull(instructions.getJarInfo(deletedJar), changedJars); + } + + Map updatedMappings = new HashMap(); + for (final String filePath : changedFiles) { + final List outputs = new SmartList(); + instructions.processContainingRoots(filePath, new ArtifactRootProcessor() { + @Override + public void process(ArtifactSourceRoot root, Collection destinations) throws Exception { + for (DestinationInfo destination : destinations) { + if (destination instanceof ExplodedDestinationInfo) { + copyFromRoot(root, filePath, destination.getOutputPath(), outputs); + } + else { + outputs.add(destination.getOutputFilePath()); + changedJars.add(((JarDestinationInfo)destination).getJarInfo()); + } + } + } + }); + updatedMappings.put(filePath, ArrayUtil.toStringArray(outputs)); + } + + JarsBuilder builder = new JarsBuilder(changedJars, null, context); + final boolean processed = builder.buildJars(new THashSet()); + if (!processed) { + return; + } + + state.updateTimestamps(deletedFiles, changedFiles); + for (Map.Entry entry : updatedMappings.entrySet()) { + mapping.appendData(entry.getKey(), entry.getValue()); + } + context.processMessage(UptoDateFilesSavedEvent.INSTANCE); + } + catch (Exception e) { + throw new ProjectBuildException(e); + } + } + + private static void copyFromRoot(ArtifactSourceRoot root, String path, String outputPath, List outputs) throws IOException { + if (root instanceof FileBasedArtifactSourceRoot) { + final File file = new File(FileUtil.toSystemDependentName(path)); + String targetPath; + if (!file.equals(root.getRootFile())) { + final String relativePath = FileUtil.getRelativePath(FileUtil.toSystemIndependentName(root.getRootFile().getPath()), path, '/'); + targetPath = PathUtil.appendToPath(outputPath, relativePath); + } + else { + targetPath = outputPath; + } + final File targetFile = new File(FileUtil.toSystemDependentName(targetPath)); + FileUtil.copyContent(file, targetFile); + outputs.add(outputPath); + } + else { + //todo[nik] + } + } + + private static Set deleteOutdatedFiles(Set deletedFiles, CompileContext context, + ArtifactSourceToOutputMapping mapping) throws Exception { + if (deletedFiles.isEmpty()) return Collections.emptySet(); + + context.processMessage(new ProgressMessage("Deleting outdated files...")); + Set pathsToDelete = new THashSet(); + for (String path : deletedFiles) { + final String[] outputPaths = mapping.getState(path); + Collections.addAll(pathsToDelete, outputPaths); + } + + int notDeletedFilesCount = 0; + final THashSet notDeletedJars = new THashSet(); + final THashSet deletedJars = new THashSet(); + + for (String fullPath : pathsToDelete) { + int end = fullPath.indexOf(JarPathUtil.JAR_SEPARATOR); + boolean isJar = end != -1; + String filePath = isJar ? fullPath.substring(0, end) : fullPath; + boolean deleted = false; + if (isJar) { + if (notDeletedJars.contains(filePath)) { + continue; + } + deleted = deletedJars.contains(filePath); + } + + File file = new File(FileUtil.toSystemDependentName(filePath)); + if (!deleted) { + deleted = FileUtil.delete(file); + } + + if (deleted) { + if (isJar) { + deletedJars.add(filePath); + } + } + else { + if (isJar) { + notDeletedJars.add(filePath); + } + if (notDeletedFilesCount++ > 50) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, "Deletion of outdated files stopped because too many files cannot be deleted")); + break; + } + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, "Cannot delete file '" + filePath + "'")); + } + } + + return deletedJars; + } + + @Override + public String getName() { + return BUILDER_NAME; + } + + @Override + public String getDescription() { + return "Artifacts builder"; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/JarPathUtil.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/JarPathUtil.java new file mode 100644 index 000000000000..4f97b040c89f --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/JarPathUtil.java @@ -0,0 +1,20 @@ +package org.jetbrains.jps.incremental.artifacts; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public class JarPathUtil { + public static final String JAR_SEPARATOR = "!/"; + + @NotNull + public static File getLocalFile(@NotNull String fullPath) { + final int i = fullPath.indexOf(JAR_SEPARATOR); + String filePath = i == -1 ? fullPath : fullPath.substring(0, i); + return new File(FileUtil.toSystemDependentName(filePath)); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuilder.java new file mode 100644 index 000000000000..054ebae52345 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuilder.java @@ -0,0 +1,12 @@ +package org.jetbrains.jps.incremental.artifacts.builders; + +import org.jetbrains.jps.artifacts.LayoutElement; +import org.jetbrains.jps.incremental.artifacts.instructions.ArtifactCompilerInstructionCreator; +import org.jetbrains.jps.incremental.artifacts.instructions.ArtifactInstructionsBuilderContext; + +/** + * @author nik + */ +public abstract class LayoutElementBuilder { + public abstract void generateInstructions(E element, ArtifactCompilerInstructionCreator instructionCreator, ArtifactInstructionsBuilderContext builderContext); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuildersRegistry.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuildersRegistry.java new file mode 100644 index 000000000000..3433fa4a7fa4 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/builders/LayoutElementBuildersRegistry.java @@ -0,0 +1,206 @@ +package org.jetbrains.jps.incremental.artifacts.builders; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.ClassMap; +import org.jetbrains.jps.Module; +import org.jetbrains.jps.artifacts.*; +import org.jetbrains.jps.incremental.artifacts.instructions.ArtifactCompilerInstructionCreator; +import org.jetbrains.jps.incremental.artifacts.instructions.ArtifactInstructionsBuilderContext; + +import java.io.File; +import java.util.List; + +/** + * @author nik + */ +public class LayoutElementBuildersRegistry { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.artifacts.builders.LayoutElementBuildersRegistry"); + + private static class InstanceHolder { + static final LayoutElementBuildersRegistry ourInstance = new LayoutElementBuildersRegistry(); + } + + public static LayoutElementBuildersRegistry getInstance() { + return InstanceHolder.ourInstance; + } + + private ClassMap myBuilders; + + private LayoutElementBuildersRegistry() { + myBuilders = new ClassMap(); + myBuilders.put(RootElement.class, new RootElementBuilder()); + myBuilders.put(DirectoryElement.class, new DirectoryElementBuilder()); + myBuilders.put(ArchiveElement.class, new ArchiveElementBuilder()); + myBuilders.put(DirectoryCopyElement.class, new DirectoryCopyElementBuilder()); + myBuilders.put(FileCopyElement.class, new FileCopyElementBuilder()); + myBuilders.put(ExtractedDirectoryElement.class, new ExtractedDirectoryElementBuilder()); + myBuilders.put(ModuleOutputElement.class, new ModuleOutputElementBuilder()); + myBuilders.put(ModuleTestOutputElement.class, new ModuleTestOutputElementBuilder()); + myBuilders.put(ComplexLayoutElement.class, new ComplexElementBuilder()); + } + + public void generateInstructions(LayoutElement layoutElement, ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + final LayoutElementBuilder builder = myBuilders.get(layoutElement.getClass()); + if (builder == null) { + LOG.error("Builder not found for artifact output layout element of class " + layoutElement.getClass()); + } + builder.generateInstructions(layoutElement, instructionCreator, builderContext); + } + + private void generateChildrenInstructions(CompositeLayoutElement element, ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + generateInstructions(element.getChildren(), instructionCreator, builderContext); + } + + private void generateSubstitutionInstructions(ComplexLayoutElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + final List substitution = element.getSubstitution(builderContext.getProject()); + if (substitution != null) { + generateInstructions(substitution, instructionCreator, builderContext); + } + } + + private void generateInstructions(final List elements, ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + for (LayoutElement child : elements) { + generateInstructions(child, instructionCreator, builderContext); + } + } + + private static void generateModuleOutputInstructions(String moduleName, + boolean tests, + ArtifactCompilerInstructionCreator creator, + ArtifactInstructionsBuilderContext context) { + final Module module = context.getProject().getModules().get(moduleName); + if (module != null) { + final File outputDir = context.getProjectPaths().getModuleOutputDir(module, tests); + if (outputDir != null) { + creator.addDirectoryCopyInstructions(outputDir); + } + } + } + + private class RootElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(RootElement element, ArtifactCompilerInstructionCreator instructionCreator, ArtifactInstructionsBuilderContext builderContext) { + generateChildrenInstructions(element, instructionCreator, builderContext); + } + } + + private class DirectoryElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(DirectoryElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + generateChildrenInstructions(element, instructionCreator.subFolder(element.getName()), builderContext); + } + } + + private class ArchiveElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(ArchiveElement element, ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + generateChildrenInstructions(element, instructionCreator.archive(element.getName()), builderContext); + } + } + + private static class DirectoryCopyElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(DirectoryCopyElement element, ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + final String dirPath = element.getDirPath(); + if (dirPath != null) { + final File directory = new File(FileUtil.toSystemDependentName(dirPath)); + if (directory.isDirectory()) { + instructionCreator.addDirectoryCopyInstructions(directory); + } + } + } + } + + private static class FileCopyElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(FileCopyElement element, ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + final String filePath = element.getFilePath(); + if (filePath != null) { + final File file = new File(FileUtil.toSystemDependentName(filePath)); + if (file.isFile()) { + final String fileName = element.getOutputFileName(); + instructionCreator.addFileCopyInstruction(file, fileName != null ? fileName : file.getName()); + } + } + } + } + + private static class ExtractedDirectoryElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(ExtractedDirectoryElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + final String jarPath = element.getJarPath(); + final String pathInJar = element.getPathInJar(); + File jarFile = new File(FileUtil.toSystemDependentName(jarPath)); + if (jarFile.isFile()) { + instructionCreator.addExtractDirectoryInstruction(jarFile, pathInJar); + } + } + } + + private static class ModuleOutputElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(ModuleOutputElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + generateModuleOutputInstructions(element.getModuleName(), false, instructionCreator, builderContext); + } + } + + private static class ModuleTestOutputElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(ModuleTestOutputElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + generateModuleOutputInstructions(element.getModuleName(), true, instructionCreator, builderContext); + } + } + + private class ComplexElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(ComplexLayoutElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + generateSubstitutionInstructions(element, instructionCreator, builderContext); + } + } + + private class ArtifactOutputElementBuilder extends LayoutElementBuilder { + @Override + public void generateInstructions(ArtifactLayoutElement element, + ArtifactCompilerInstructionCreator instructionCreator, + ArtifactInstructionsBuilderContext builderContext) { + final Artifact artifact = element.findArtifact(builderContext.getProject()); + if (artifact == null) return; + + final String outputPath = artifact.getOutputPath(); + if (StringUtil.isEmpty(outputPath)) { + generateSubstitutionInstructions(element, instructionCreator, builderContext); + return; + } + + final LayoutElement rootElement = artifact.getRootElement(); + final File outputDir = new File(FileUtil.toSystemDependentName(outputPath)); + if (rootElement instanceof ArchiveElement) { + final String fileName = ((ArchiveElement)rootElement).getName(); + instructionCreator.addFileCopyInstruction(new File(outputDir, fileName), fileName); + } + else { + instructionCreator.addDirectoryCopyInstructions(outputDir); + } + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java new file mode 100644 index 000000000000..5d1738e63769 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/ArtifactSorter.java @@ -0,0 +1,202 @@ +/* + * 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 org.jetbrains.jps.incremental.artifacts.impl; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Consumer; +import com.intellij.util.graph.CachingSemiGraph; +import com.intellij.util.graph.DFSTBuilder; +import com.intellij.util.graph.GraphGenerator; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntProcedure; +import groovy.lang.Closure; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.Project; +import org.jetbrains.jps.artifacts.Artifact; +import org.jetbrains.jps.artifacts.ArtifactLayoutElement; +import org.jetbrains.jps.artifacts.ComplexLayoutElement; + +import java.util.*; + +/** + * @author nik + */ +public class ArtifactSorter { + private final Project myProject; + private Map myArtifactToSelfIncludingName; + private List mySortedArtifacts; + + public ArtifactSorter(Project project) { + myProject = project; + } + + public Map getArtifactToSelfIncludingNameMap() { + if (myArtifactToSelfIncludingName == null) { + myArtifactToSelfIncludingName = computeArtifactToSelfIncludingNameMap(); + } + return myArtifactToSelfIncludingName; + } + + public List getArtifactsSortedByInclusion() { + if (mySortedArtifacts == null) { + mySortedArtifacts = doGetSortedArtifacts(); + } + return mySortedArtifacts; + } + + private List doGetSortedArtifacts() { + GraphGenerator graph = createArtifactsGraph(); + DFSTBuilder builder = new DFSTBuilder(graph); + builder.buildDFST(); + List names = new ArrayList(); + names.addAll(graph.getNodes()); + Collections.sort(names, builder.comparator()); + return names; + } + + private Map computeArtifactToSelfIncludingNameMap() { + final Map result = new HashMap(); + final GraphGenerator graph = createArtifactsGraph(); + for (String artifactName : graph.getNodes()) { + final Iterator in = graph.getIn(artifactName); + while (in.hasNext()) { + String next = in.next(); + if (next.equals(artifactName)) { + result.put(artifactName, artifactName); + break; + } + } + } + + final DFSTBuilder builder = new DFSTBuilder(graph); + builder.buildDFST(); + if (builder.isAcyclic() && result.isEmpty()) return Collections.emptyMap(); + + final TIntArrayList sccs = builder.getSCCs(); + sccs.forEach(new TIntProcedure() { + int myTNumber = 0; + public boolean execute(int size) { + if (size > 1) { + for (int j = 0; j < size; j++) { + final String artifactName = builder.getNodeByTNumber(myTNumber + j); + result.put(artifactName, artifactName); + } + } + myTNumber += size; + return true; + } + }); + + for (int i = 0; i < graph.getNodes().size(); i++) { + final String artifactName = builder.getNodeByTNumber(i); + if (!result.containsKey(artifactName)) { + final Iterator in = graph.getIn(artifactName); + while (in.hasNext()) { + final String name = result.get(in.next()); + if (name != null) { + result.put(artifactName, name); + } + } + } + } + + return result; + } + + @NotNull + public static Set addIncludedArtifacts(@NotNull Collection artifacts, @NotNull Project project) { + Set result = new HashSet(); + for (Artifact artifact : artifacts) { + collectIncludedArtifacts(artifact, project, new HashSet(), result, true); + } + return result; + } + + private static void collectIncludedArtifacts(Artifact artifact, final Project project, + final Set processed, final Set result, final boolean withOutputPathOnly) { + if (!processed.add(artifact)) { + return; + } + if (!withOutputPathOnly || !StringUtil.isEmpty(artifact.getOutputPath())) { + result.add(artifact); + } + + processIncludedArtifacts(artifact, project, new Consumer() { + @Override + public void consume(Artifact included) { + collectIncludedArtifacts(included, project, processed, result, withOutputPathOnly); + } + }); + } + + private GraphGenerator createArtifactsGraph() { + return GraphGenerator.create(CachingSemiGraph.create(new ArtifactsGraph(myProject))); + } + + private static void processIncludedArtifacts(Artifact artifact, + final Project project, + final Consumer consumer) { + artifact.getRootElement().process(project, new Closure(consumer) { + @Override + public Object call(Object arguments) { + if (arguments instanceof ArtifactLayoutElement) { + final Artifact includedArtifact = ((ArtifactLayoutElement)arguments).findArtifact(project); + if (includedArtifact != null) { + consumer.consume(includedArtifact); + } + } + if (arguments instanceof ComplexLayoutElement) { + return false; + } + return true; + } + }); + } + + private static class ArtifactsGraph implements GraphGenerator.SemiGraph { + private final Set myArtifactNames; + private final Project myProject; + + public ArtifactsGraph(Project project) { + myProject = project; + myArtifactNames = new LinkedHashSet(project.getArtifacts().keySet()); + } + + @Override + public Collection getNodes() { + return myArtifactNames; + } + + @Override + public Iterator getIn(String name) { + final Set included = new LinkedHashSet(); + final Artifact artifact = myProject.getArtifacts().get(name); + if (artifact != null) { + final Consumer consumer = new Consumer() { + @Override + public void consume(Artifact artifact) { + if (myArtifactNames.contains(artifact.getName())) { + included.add(artifact.getName()); + } + } + }; + processIncludedArtifacts(artifact, myProject, consumer); + } + return included.iterator(); + } + } + +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/DependentJarsEvaluator.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/DependentJarsEvaluator.java new file mode 100644 index 000000000000..df294e94d3ab --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/DependentJarsEvaluator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.impl; + +import com.intellij.openapi.util.Pair; +import org.jetbrains.jps.incremental.artifacts.instructions.JarDestinationInfo; +import org.jetbrains.jps.incremental.artifacts.instructions.JarInfo; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author nik + */ +public class DependentJarsEvaluator { + private final Set myJars = new LinkedHashSet(); + + public void addJarWithDependencies(final JarInfo jarInfo) { + if (myJars.add(jarInfo)) { + for (JarDestinationInfo destination : jarInfo.getJarDestinations()) { + addJarWithDependencies(destination.getJarInfo()); + } + for (Pair pair : jarInfo.getPackedJars()) { + addJarWithDependencies(pair.getSecond()); + } + } + } + + public Set getJars() { + return myJars; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/JarsBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/JarsBuilder.java new file mode 100644 index 000000000000..4b570e9717c6 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/impl/JarsBuilder.java @@ -0,0 +1,251 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.graph.CachingSemiGraph; +import com.intellij.util.graph.DFSTBuilder; +import com.intellij.util.graph.GraphGenerator; +import com.intellij.util.io.ZipUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.artifacts.IncArtifactBuilder; +import org.jetbrains.jps.incremental.artifacts.instructions.*; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.ProgressMessage; + +import java.io.*; +import java.util.*; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * @author nik + */ +public class JarsBuilder { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.packagingCompiler.JarsBuilder"); + private final Set myJarsToBuild; + private final FileFilter myFileFilter; + private final CompileContext myContext; + private Map myBuiltJars; + + public JarsBuilder(Set jarsToBuild, FileFilter fileFilter, CompileContext context) { + DependentJarsEvaluator evaluator = new DependentJarsEvaluator(); + for (JarInfo jarInfo : jarsToBuild) { + evaluator.addJarWithDependencies(jarInfo); + } + myJarsToBuild = evaluator.getJars(); + myFileFilter = fileFilter; + myContext = context; + } + + public boolean buildJars(Set writtenPaths) throws IOException { + myContext.processMessage(new ProgressMessage("Building archives...")); + + final JarInfo[] sortedJars = sortJars(); + if (sortedJars == null) { + return false; + } + + myBuiltJars = new HashMap(); + try { + for (JarInfo jar : sortedJars) { + buildJar(jar); + } + + myContext.processMessage(new ProgressMessage("Copying archives...")); + copyJars(writtenPaths); + } + finally { + deleteTemporaryJars(); + } + + + return true; + } + + private void deleteTemporaryJars() { + for (File file : myBuiltJars.values()) { + FileUtil.delete(file); + } + } + + private void copyJars(final Set writtenPaths) throws IOException { + for (Map.Entry entry : myBuiltJars.entrySet()) { + File fromFile = entry.getValue(); + boolean first = true; + for (DestinationInfo destination : entry.getKey().getAllDestinations()) { + if (destination instanceof ExplodedDestinationInfo) { + File toFile = new File(FileUtil.toSystemDependentName(destination.getOutputPath())); + + if (first) { + first = false; + renameFile(fromFile, toFile, writtenPaths); + fromFile = toFile; + } + else { + FileUtil.copyContent(fromFile, toFile); + } + + } + } + } + } + + private static void renameFile(final File fromFile, final File toFile, final Set writtenPaths) throws IOException { + FileUtil.rename(fromFile, toFile); + writtenPaths.add(toFile.getPath()); + } + + @Nullable + private JarInfo[] sortJars() { + final DFSTBuilder builder = new DFSTBuilder(GraphGenerator.create(CachingSemiGraph.create(new JarsGraph()))); + if (!builder.isAcyclic()) { + final Pair dependency = builder.getCircularDependency(); + String message = "Cannot build: circular dependency found between '" + dependency.getFirst().getPresentableDestination() + + "' and '" + dependency.getSecond().getPresentableDestination() + "'"; + myContext.processMessage(new CompilerMessage(IncArtifactBuilder.BUILDER_NAME, BuildMessage.Kind.ERROR, message)); + return null; + } + + JarInfo[] jars = myJarsToBuild.toArray(new JarInfo[myJarsToBuild.size()]); + Arrays.sort(jars, builder.comparator()); + jars = ArrayUtil.reverseArray(jars); + return jars; + } + + public Set getJarsToBuild() { + return myJarsToBuild; + } + + private void buildJar(final JarInfo jar) throws IOException { + if (jar.getPackedJars().isEmpty() && jar.getPackedRoots().isEmpty()) { + final String message = "Archive '" + jar.getPresentableDestination() + "' has no files so it won't be created"; + myContext.processMessage(new CompilerMessage(IncArtifactBuilder.BUILDER_NAME, BuildMessage.Kind.WARNING, message)); + return; + } + + myContext.processMessage(new ProgressMessage("Building " + jar.getPresentableDestination() + "...")); + File jarFile = FileUtil.createTempFile("artifactCompiler", "tmp"); + myBuiltJars.put(jar, jarFile); + + FileUtil.createParentDirs(jarFile); + final JarOutputStream jarOutputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile))); + + try { + final THashSet writtenPaths = new THashSet(); + for (Pair pair : jar.getPackedRoots()) { + final ArtifactSourceRoot root = pair.getSecond(); + if (root instanceof FileBasedArtifactSourceRoot) { + addFileToJar(jarOutputStream, jarFile, root.getRootFile(), pair.getFirst(), writtenPaths); + } + else { + extractFileAndAddToJar(jarOutputStream, (JarBasedArtifactSourceRoot)root, pair.getFirst(), writtenPaths); + } + } + + for (Pair nestedJar : jar.getPackedJars()) { + File nestedJarFile = myBuiltJars.get(nestedJar.getSecond()); + if (nestedJarFile != null) { + addFileToJar(jarOutputStream, jarFile, nestedJarFile, nestedJar.getFirst(), writtenPaths); + } + else { + LOG.debug("nested jar file " + nestedJar.getFirst() + " for " + jar.getPresentableDestination() + " not found"); + } + } + } + finally { + jarOutputStream.close(); + } + } + + private void extractFileAndAddToJar(JarOutputStream jarOutputStream, JarBasedArtifactSourceRoot root, String relativePath, THashSet writtenPaths) + throws IOException { + //todo[nik] + /* + relativePath = addParentDirectories(jarOutputStream, writtenPaths, relativePath); + if (!writtenPaths.add(relativePath)) return; + + final BufferedInputStream input = ArtifactCompilerUtil.getJarEntryInputStream(root, myContext); + if (input == null) return; + + ZipEntry entry = new ZipEntry(relativePath); + entry.setTime(root.getRootFile().lastModified()); + jarOutputStream.putNextEntry(entry); + FileUtil.copy(input, jarOutputStream); + jarOutputStream.closeEntry(); + */ + } + + private void addFileToJar(final @NotNull JarOutputStream jarOutputStream, final @NotNull File jarFile, @NotNull File file, + @NotNull String relativePath, final @NotNull THashSet writtenPaths) throws IOException { + if (!file.exists()) { + return; + } + + relativePath = addParentDirectories(jarOutputStream, writtenPaths, relativePath); + ZipUtil.addFileOrDirRecursively(jarOutputStream, jarFile, file, relativePath, myFileFilter, writtenPaths); + } + + private static String addParentDirectories(JarOutputStream jarOutputStream, THashSet writtenPaths, String relativePath) throws IOException { + while (StringUtil.startsWithChar(relativePath, '/')) { + relativePath = relativePath.substring(1); + } + int i = relativePath.indexOf('/'); + while (i != -1) { + String prefix = relativePath.substring(0, i+1); + if (!writtenPaths.contains(prefix) && prefix.length() > 1) { + addEntry(jarOutputStream, prefix); + writtenPaths.add(prefix); + } + i = relativePath.indexOf('/', i + 1); + } + return relativePath; + } + + private static void addEntry(final ZipOutputStream output, @NonNls final String relativePath) throws IOException { + ZipEntry e = new ZipEntry(relativePath); + e.setMethod(ZipEntry.STORED); + e.setSize(0); + e.setCrc(0); + output.putNextEntry(e); + output.closeEntry(); + } + + private class JarsGraph implements GraphGenerator.SemiGraph { + public Collection getNodes() { + return myJarsToBuild; + } + + public Iterator getIn(final JarInfo n) { + Set ins = new HashSet(); + for (JarDestinationInfo destination : n.getJarDestinations()) { + ins.add(destination.getJarInfo()); + } + return ins.iterator(); + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreator.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreator.java new file mode 100644 index 000000000000..534cf532508b --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + +/** + * @author nik + */ +public interface ArtifactCompilerInstructionCreator { + + void addFileCopyInstruction(@NotNull File file, @NotNull String outputFileName); + + void addDirectoryCopyInstructions(@NotNull File directory); + + void addExtractDirectoryInstruction(@NotNull File jarFile, @NotNull String pathInJar); + + void addDirectoryCopyInstructions(@NotNull File directory, @Nullable SourceFileFilter filter); + + ArtifactCompilerInstructionCreator subFolder(@NotNull String directoryName); + + ArtifactCompilerInstructionCreator archive(@NotNull String archiveFileName); + + ArtifactCompilerInstructionCreator subFolderByRelativePath(@NotNull String relativeDirectoryPath); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreatorBase.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreatorBase.java new file mode 100644 index 000000000000..19bb56c8f624 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactCompilerInstructionCreatorBase.java @@ -0,0 +1,91 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.incremental.ModuleRootsIndex; +import org.jetbrains.jps.incremental.artifacts.JarPathUtil; + +import java.io.File; +import java.util.List; + +/** + * @author nik + */ +public abstract class ArtifactCompilerInstructionCreatorBase implements ArtifactCompilerInstructionCreator { + protected final ArtifactInstructionsBuilderImpl myInstructionsBuilder; + + public ArtifactCompilerInstructionCreatorBase(ArtifactInstructionsBuilderImpl instructionsBuilder) { + myInstructionsBuilder = instructionsBuilder; + } + + public void addDirectoryCopyInstructions(@NotNull File directoryUrl) { + addDirectoryCopyInstructions(directoryUrl, null); + } + + public void addDirectoryCopyInstructions(@NotNull File directory, @Nullable SourceFileFilter filter) { + final boolean copyExcluded = myInstructionsBuilder.getRootsIndex().isExcluded(directory); + SourceFileFilter fileFilter = new SourceFileFilterImpl(filter, myInstructionsBuilder.getRootsIndex(), copyExcluded); + addDirectoryCopyInstructions(new FileBasedArtifactSourceRoot(directory, fileFilter)); + } + + @Override + public void addExtractDirectoryInstruction(@NotNull File jarFile, @NotNull String pathInJar) { + addDirectoryCopyInstructions(new JarBasedArtifactSourceRoot(jarFile, pathInJar, new SourceFileFilterImpl(null, myInstructionsBuilder.getRootsIndex(), false))); + } + + protected abstract void addDirectoryCopyInstructions(ArtifactSourceRoot root); + + @Override + public abstract ArtifactCompilerInstructionCreatorBase subFolder(@NotNull String directoryName); + + public ArtifactCompilerInstructionCreator subFolderByRelativePath(@NotNull String relativeDirectoryPath) { + final List folders = StringUtil.split(relativeDirectoryPath, "/"); + ArtifactCompilerInstructionCreator current = this; + for (String folder : folders) { + current = current.subFolder(folder); + } + return current; + } + + private static class SourceFileFilterImpl extends SourceFileFilter { + private final SourceFileFilter myBaseFilter; + private final ModuleRootsIndex myRootsIndex; + private final boolean myIncludeExcluded; + + private SourceFileFilterImpl(@Nullable SourceFileFilter baseFilter, @NotNull ModuleRootsIndex rootsIndex, boolean includeExcluded) { + myBaseFilter = baseFilter; + myRootsIndex = rootsIndex; + myIncludeExcluded = includeExcluded; + } + + @Override + public boolean accept(@NotNull String fullFilePath) { + if (myBaseFilter != null && !myBaseFilter.accept(fullFilePath)) return false; + + //todo[nik] check FileTypeManager.isFileIgnored() + if (!myIncludeExcluded) { + final File file = JarPathUtil.getLocalFile(fullFilePath); + if (myRootsIndex.isExcluded(file)) { + return false; + } + } + return true; + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilder.java new file mode 100644 index 000000000000..990c62f9d043 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilder.java @@ -0,0 +1,15 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.Nullable; + +/** + * @author nik + */ +public interface ArtifactInstructionsBuilder { + void processRoots(ArtifactRootProcessor processor) throws Exception; + + void processContainingRoots(String filePath, ArtifactRootProcessor processor) throws Exception; + + @Nullable + JarInfo getJarInfo(String outputPath); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContext.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContext.java new file mode 100644 index 000000000000..6bd2860800f0 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContext.java @@ -0,0 +1,16 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.Project; +import org.jetbrains.jps.ProjectPaths; + +/** + * @author nik + */ +public interface ArtifactInstructionsBuilderContext { + @NotNull + Project getProject(); + + @NotNull + ProjectPaths getProjectPaths(); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContextImpl.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContextImpl.java new file mode 100644 index 000000000000..f1cf30461f0e --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderContextImpl.java @@ -0,0 +1,30 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.Project; +import org.jetbrains.jps.ProjectPaths; + +/** + * @author nik + */ +public class ArtifactInstructionsBuilderContextImpl implements ArtifactInstructionsBuilderContext { + private final Project myProject; + private final ProjectPaths myProjectPaths; + + public ArtifactInstructionsBuilderContextImpl(Project project, ProjectPaths projectPaths) { + myProject = project; + myProjectPaths = projectPaths; + } + + @NotNull + @Override + public Project getProject() { + return myProject; + } + + @NotNull + @Override + public ProjectPaths getProjectPaths() { + return myProjectPaths; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderImpl.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderImpl.java new file mode 100644 index 000000000000..32aa1ffe4568 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactInstructionsBuilderImpl.java @@ -0,0 +1,89 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.incremental.ModuleRootsIndex; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author nik + */ +public class ArtifactInstructionsBuilderImpl implements ArtifactInstructionsBuilder { + private final Map mySourceByOutput; + private final Map myJarByPath; + private final MultiMap myInstructions; + private final ModuleRootsIndex myRootsIndex; + + public ArtifactInstructionsBuilderImpl(ModuleRootsIndex rootsIndex) { + myRootsIndex = rootsIndex; + mySourceByOutput = new HashMap(); + myJarByPath = new HashMap(); + myInstructions = new MultiMap(); + } + + public boolean addDestination(@NotNull ArtifactSourceRoot root, @NotNull DestinationInfo destinationInfo) { + if (destinationInfo instanceof ExplodedDestinationInfo && root instanceof FileBasedArtifactSourceRoot + && root.getRootFile().equals(new File(FileUtil.toSystemDependentName(destinationInfo.getOutputFilePath())))) { + return false; + } + + if (checkOutputPath(destinationInfo.getOutputPath(), root)) { + myInstructions.putValue(root, destinationInfo); + return true; + } + return false; + } + + public ModuleRootsIndex getRootsIndex() { + return myRootsIndex; + } + + public boolean checkOutputPath(final String outputPath, final ArtifactSourceRoot sourceFile) { + //todo[nik] combine intersecting roots + ArtifactSourceRoot old = mySourceByOutput.get(outputPath); + if (old == null) { + mySourceByOutput.put(outputPath, sourceFile); + return true; + } + //todo[nik] show warning? + return false; + } + + public boolean registerJarFile(@NotNull JarInfo jarInfo, @NotNull String outputPath) { + if (mySourceByOutput.containsKey(outputPath) || myJarByPath.containsKey(outputPath)) { + return false; + } + myJarByPath.put(outputPath, jarInfo); + return true; + } + + @Override + @Nullable + public JarInfo getJarInfo(String outputPath) { + return myJarByPath.get(outputPath); + } + + @Override + public void processRoots(ArtifactRootProcessor processor) throws Exception { + for (Map.Entry> entry : myInstructions.entrySet()) { + processor.process(entry.getKey(), entry.getValue()); + } + } + + @Override + public void processContainingRoots(String filePath, ArtifactRootProcessor processor) throws Exception { + //todo[nik] improve? + for (Map.Entry> entry : myInstructions.entrySet()) { + final ArtifactSourceRoot root = entry.getKey(); + if (root.containsFile(filePath)) { + processor.process(root, entry.getValue()); + } + } + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactRootProcessor.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactRootProcessor.java new file mode 100644 index 000000000000..c90152dff468 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactRootProcessor.java @@ -0,0 +1,10 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import java.util.Collection; + +/** + * @author nik + */ +public interface ArtifactRootProcessor { + void process(ArtifactSourceRoot root, Collection destinations) throws Exception; +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactSourceRoot.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactSourceRoot.java new file mode 100644 index 000000000000..b0bbf9e79d51 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ArtifactSourceRoot.java @@ -0,0 +1,37 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public abstract class ArtifactSourceRoot { + private final SourceFileFilter myFilter; + + protected ArtifactSourceRoot(@NotNull SourceFileFilter filter) { + myFilter = filter; + } + + @NotNull + public abstract File getRootFile(); + + public abstract boolean containsFile(String filePath); + + public SourceFileFilter getFilter() { + return myFilter; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return myFilter.equals(((ArtifactSourceRoot)o).myFilter); + } + + @Override + public int hashCode() { + return myFilter.hashCode(); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/CopyToDirectoryInstructionCreator.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/CopyToDirectoryInstructionCreator.java new file mode 100644 index 000000000000..5e27d032e174 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/CopyToDirectoryInstructionCreator.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public class CopyToDirectoryInstructionCreator extends ArtifactCompilerInstructionCreatorBase { + private final String myOutputPath; + + public CopyToDirectoryInstructionCreator(ArtifactInstructionsBuilderImpl builder, String outputPath) { + super(builder); + myOutputPath = outputPath; + } + + public void addFileCopyInstruction(@NotNull File file, @NotNull String outputFileName) { + myInstructionsBuilder.addDestination(new FileBasedArtifactSourceRoot(file, SourceFileFilter.ALL), new ExplodedDestinationInfo(myOutputPath + "/" + outputFileName)); + } + + @Override + protected void addDirectoryCopyInstructions(ArtifactSourceRoot root) { + myInstructionsBuilder.addDestination(root, new ExplodedDestinationInfo(myOutputPath)); + } + + public CopyToDirectoryInstructionCreator subFolder(@NotNull String directoryName) { + return new CopyToDirectoryInstructionCreator(myInstructionsBuilder, myOutputPath + "/" + directoryName); + } + + public ArtifactCompilerInstructionCreator archive(@NotNull String archiveFileName) { + String jarOutputPath = myOutputPath + "/" + archiveFileName; + final JarInfo jarInfo = new JarInfo(); + if (!myInstructionsBuilder.registerJarFile(jarInfo, jarOutputPath)) { + return new SkipAllInstructionCreator(myInstructionsBuilder); + } + final ExplodedDestinationInfo destination = new ExplodedDestinationInfo(jarOutputPath); + jarInfo.addDestination(destination); + return new PackIntoArchiveInstructionCreator(myInstructionsBuilder, jarInfo, "", destination); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/DestinationInfo.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/DestinationInfo.java new file mode 100644 index 000000000000..831c1fbf271b --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/DestinationInfo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public abstract class DestinationInfo { + private final String myOutputPath; + private final String myOutputFilePath; + + protected DestinationInfo(@NotNull final String outputPath, @NotNull String outputFilePath) { + myOutputFilePath = outputFilePath; + myOutputPath = outputPath; + } + + @NotNull + public String getOutputPath() { + return myOutputPath; + } + + @NotNull + public String getOutputFilePath() { + return myOutputFilePath; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ExplodedDestinationInfo.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ExplodedDestinationInfo.java new file mode 100644 index 000000000000..f9192e39abe9 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/ExplodedDestinationInfo.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +/** + * @author nik + */ +public class ExplodedDestinationInfo extends DestinationInfo { + public ExplodedDestinationInfo(final String outputPath) { + super(outputPath, outputPath); + } + + public String toString() { + return getOutputPath(); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/FileBasedArtifactSourceRoot.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/FileBasedArtifactSourceRoot.java new file mode 100644 index 000000000000..51df48f08dd8 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/FileBasedArtifactSourceRoot.java @@ -0,0 +1,43 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public class FileBasedArtifactSourceRoot extends ArtifactSourceRoot { + private final File myFile; + + public FileBasedArtifactSourceRoot(@NotNull File file, @NotNull SourceFileFilter filter) { + super(filter); + myFile = file; + } + + @NotNull + @Override + public File getRootFile() { + return myFile; + } + + @Override + public boolean containsFile(String filePath) { + return FileUtil.isAncestor(myFile, new File(FileUtil.toSystemDependentName(filePath)), false) && getFilter().accept(filePath); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + return myFile.equals(((FileBasedArtifactSourceRoot)o).myFile); + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + myFile.hashCode(); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarBasedArtifactSourceRoot.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarBasedArtifactSourceRoot.java new file mode 100644 index 000000000000..7e8784b1774f --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarBasedArtifactSourceRoot.java @@ -0,0 +1,46 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public class JarBasedArtifactSourceRoot extends ArtifactSourceRoot { + private final File myJarFile; + private final String myPathInJar; + + public JarBasedArtifactSourceRoot(@NotNull File jarFile, @NotNull String pathInJar, @NotNull SourceFileFilter filter) { + super(filter); + myJarFile = jarFile; + myPathInJar = pathInJar; + } + + @NotNull + @Override + public File getRootFile() { + return myJarFile; + } + + @Override + public boolean containsFile(String filePath) { + return new File(FileUtil.toSystemDependentName(filePath)).equals(myJarFile); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + JarBasedArtifactSourceRoot root = (JarBasedArtifactSourceRoot)o; + return myJarFile.equals(root.myJarFile) && myPathInJar.equals(root.myPathInJar); + } + + @Override + public int hashCode() { + return 31 * (31 * super.hashCode() + myJarFile.hashCode()) + myPathInJar.hashCode(); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarDestinationInfo.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarDestinationInfo.java new file mode 100644 index 000000000000..61553f7bd440 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarDestinationInfo.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.text.StringUtil; + +/** + * @author nik + */ +public class JarDestinationInfo extends DestinationInfo { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.artifacts.instructions.JarDestinationInfo"); + private final String myPathInJar; + private final JarInfo myJarInfo; + + public JarDestinationInfo(final String pathInJar, final JarInfo jarInfo, DestinationInfo jarDestination) { + super(appendPathInJar(jarDestination.getOutputPath(), pathInJar), jarDestination.getOutputFilePath()); + LOG.assertTrue(!pathInJar.startsWith(".."), pathInJar); + myPathInJar = StringUtil.startsWithChar(pathInJar, '/') ? pathInJar : "/" + pathInJar; + myJarInfo = jarInfo; + } + + private static String appendPathInJar(String outputPath, String pathInJar) { + LOG.assertTrue(outputPath.length() > 0 && outputPath.charAt(outputPath.length() - 1) != '/'); + LOG.assertTrue(pathInJar.length() > 0 && pathInJar.charAt(0) != '/'); + return outputPath + "!/" + pathInJar; + } + + public String getPathInJar() { + return myPathInJar; + } + + public JarInfo getJarInfo() { + return myJarInfo; + } + + public String toString() { + return myPathInJar + "(" + getOutputPath() + ")"; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarInfo.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarInfo.java new file mode 100644 index 000000000000..db496203ed8b --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/JarInfo.java @@ -0,0 +1,76 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import com.intellij.openapi.util.Pair; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * @author nik + */ +public class JarInfo { + private final List> myPackedRoots; + private final LinkedHashSet> myPackedJars; + private final List myDestinations; + + public JarInfo() { + myDestinations = new ArrayList(); + myPackedRoots = new ArrayList>(); + myPackedJars = new LinkedHashSet>(); + } + + public void addDestination(DestinationInfo info) { + myDestinations.add(info); + if (info instanceof JarDestinationInfo) { + JarDestinationInfo destinationInfo = (JarDestinationInfo)info; + destinationInfo.getJarInfo().myPackedJars.add(Pair.create(destinationInfo.getPathInJar(), this)); + } + } + + public void addContent(String pathInJar, ArtifactSourceRoot sourceFile) { + myPackedRoots.add(Pair.create(pathInJar, sourceFile)); + } + + public List> getPackedRoots() { + return myPackedRoots; + } + + public LinkedHashSet> getPackedJars() { + return myPackedJars; + } + + public List getJarDestinations() { + final ArrayList list = new ArrayList(); + for (DestinationInfo destination : myDestinations) { + if (destination instanceof JarDestinationInfo) { + list.add((JarDestinationInfo)destination); + } + } + return list; + } + + public List getAllDestinations() { + return myDestinations; + } + + public String getPresentableDestination() { + return !myDestinations.isEmpty() ? myDestinations.get(0).getOutputPath() : ""; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/PackIntoArchiveInstructionCreator.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/PackIntoArchiveInstructionCreator.java new file mode 100644 index 000000000000..feba02779bed --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/PackIntoArchiveInstructionCreator.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public class PackIntoArchiveInstructionCreator extends ArtifactCompilerInstructionCreatorBase { + private final DestinationInfo myJarDestination; + private final JarInfo myJarInfo; + private final String myPathInJar; + + public PackIntoArchiveInstructionCreator(ArtifactInstructionsBuilderImpl builder, JarInfo jarInfo, + String pathInJar, DestinationInfo jarDestination) { + super(builder); + myJarInfo = jarInfo; + myPathInJar = pathInJar; + myJarDestination = jarDestination; + } + + @Override + protected void addDirectoryCopyInstructions(ArtifactSourceRoot root) { + addCopyInstruction(myPathInJar, root); + } + + public void addFileCopyInstruction(@NotNull File file, @NotNull String outputFileName) { + addCopyInstruction(childPathInJar(outputFileName), new FileBasedArtifactSourceRoot(file, SourceFileFilter.ALL)); + } + + private void addCopyInstruction(String pathInJar, final ArtifactSourceRoot root) { + if (myInstructionsBuilder.addDestination(root, new JarDestinationInfo(pathInJar, myJarInfo, myJarDestination))) { + myJarInfo.addContent(pathInJar, root); + } + } + + private String childPathInJar(String fileName) { + return myPathInJar.length() == 0 ? fileName : myPathInJar + "/" + fileName; + } + + public PackIntoArchiveInstructionCreator subFolder(@NotNull String directoryName) { + return new PackIntoArchiveInstructionCreator(myInstructionsBuilder, myJarInfo, childPathInJar(directoryName), myJarDestination); + } + + public ArtifactCompilerInstructionCreator archive(@NotNull String archiveFileName) { + final JarInfo jarInfo = new JarInfo(); + final String outputPath = myJarDestination.getOutputPath() + "/" + archiveFileName; + if (!myInstructionsBuilder.registerJarFile(jarInfo, outputPath)) { + return new SkipAllInstructionCreator(myInstructionsBuilder); + } + final JarDestinationInfo destination = new JarDestinationInfo(childPathInJar(archiveFileName), myJarInfo, myJarDestination); + jarInfo.addDestination(destination); + return new PackIntoArchiveInstructionCreator(myInstructionsBuilder, jarInfo, "", destination); + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SkipAllInstructionCreator.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SkipAllInstructionCreator.java new file mode 100644 index 000000000000..41798ad19b28 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SkipAllInstructionCreator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author nik + */ +public class SkipAllInstructionCreator extends ArtifactCompilerInstructionCreatorBase { + public SkipAllInstructionCreator(ArtifactInstructionsBuilderImpl builder) { + super(builder); + } + + public void addFileCopyInstruction(@NotNull File file, @NotNull String outputFileName) { + } + + @Override + protected void addDirectoryCopyInstructions(ArtifactSourceRoot root) { + } + + public SkipAllInstructionCreator subFolder(@NotNull String directoryName) { + return this; + } + + public SkipAllInstructionCreator archive(@NotNull String archiveFileName) { + return this; + } +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SourceFileFilter.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SourceFileFilter.java new file mode 100644 index 000000000000..631865f2bc85 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/artifacts/instructions/SourceFileFilter.java @@ -0,0 +1,17 @@ +package org.jetbrains.jps.incremental.artifacts.instructions; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public abstract class SourceFileFilter { + public static final SourceFileFilter ALL = new SourceFileFilter() { + @Override + public boolean accept(@NotNull String fullFilePath) { + return true; + } + }; + + public abstract boolean accept(@NotNull String fullFilePath); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/AbstractStateStorage.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/AbstractStateStorage.java index 9b7ba49e1749..4ab0d58eb54a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/AbstractStateStorage.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/AbstractStateStorage.java @@ -5,6 +5,7 @@ import com.intellij.util.io.DataExternalizer; import com.intellij.util.io.KeyDescriptor; import com.intellij.util.io.PersistentHashMap; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; import java.io.DataOutput; import java.io.File; @@ -64,7 +65,7 @@ public abstract class AbstractStateStorage { } } - public void update(Key key, T state) throws Exception { + public void update(Key key, @Nullable T state) throws Exception { if (state != null) { synchronized (myDataLock) { myMap.put(key, state); @@ -115,4 +116,12 @@ public abstract class AbstractStateStorage { return new PersistentHashMap(file, myKeyDescriptor, myStateExternalizer); } + public void flush(boolean memoryCachesOnly) { + if (memoryCachesOnly) { + dropMemoryCache(); + } + else { + force(); + } + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java index 37de0978ae63..a6935dbcd70f 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/storage/BuildDataManager.java @@ -5,6 +5,7 @@ import com.intellij.openapi.util.io.FileUtil; import org.jetbrains.annotations.Nullable; import org.jetbrains.ether.dependencyView.Mappings; import org.jetbrains.jps.incremental.Paths; +import org.jetbrains.jps.incremental.artifacts.ArtifactsBuildData; import java.io.File; import java.io.IOException; @@ -27,12 +28,15 @@ public class BuildDataManager { private final Map myTestSourceToOutputs = new HashMap(); private final SourceToFormMapping mySrcToFormMap; + private final ArtifactsBuildData myArtifactsBuildData; private final Mappings myMappings; public BuildDataManager(String projectName, final boolean useMemoryTempCaches) throws Exception { myProjectName = projectName; mySrcToFormMap = new SourceToFormMapping(new File(getSourceToFormsRoot(), "data")); myMappings = new Mappings(getMappingsRoot(), useMemoryTempCaches); + final File artifactsDataDir = new File(Paths.getDataStorageRoot(projectName), "artifacts"); + myArtifactsBuildData = new ArtifactsBuildData(artifactsDataDir); } public SourceToOutputMapping getSourceToOutputMap(String moduleName, boolean testSources) throws Exception { @@ -48,6 +52,10 @@ public class BuildDataManager { return mapping; } + public ArtifactsBuildData getArtifactsBuildData() { + return myArtifactsBuildData; + } + public SourceToFormMapping getSourceToFormMap() { return mySrcToFormMap; } @@ -58,60 +66,51 @@ public class BuildDataManager { public void clean() throws IOException { try { - synchronized (mySourceToOutputLock) { - try { - closeOutputToSourceStorages(); - } - finally { - FileUtil.delete(getSourceToOutputsRoot()); - } - } + myArtifactsBuildData.clean(); } finally { try { - wipeStorage(getSourceToFormsRoot(), mySrcToFormMap); - } - finally { - final Mappings mappings = myMappings; - if (mappings != null) { - synchronized (mappings) { - mappings.clean(); + synchronized (mySourceToOutputLock) { + try { + closeOutputToSourceStorages(); + } + finally { + FileUtil.delete(getSourceToOutputsRoot()); } } - else { - FileUtil.delete(getMappingsRoot()); + } + finally { + try { + wipeStorage(getSourceToFormsRoot(), mySrcToFormMap); + } + finally { + final Mappings mappings = myMappings; + if (mappings != null) { + synchronized (mappings) { + mappings.clean(); + } + } + else { + FileUtil.delete(getMappingsRoot()); + } } } } } public void flush(boolean memoryCachesOnly) { + myArtifactsBuildData.flush(memoryCachesOnly); synchronized (mySourceToOutputLock) { for (Map.Entry entry : myProductionSourceToOutputs.entrySet()) { final SourceToOutputMapping mapping = entry.getValue(); - if (memoryCachesOnly) { - mapping.dropMemoryCache(); - } - else { - mapping.force(); - } + mapping.flush(memoryCachesOnly); } for (Map.Entry entry : myTestSourceToOutputs.entrySet()) { final SourceToOutputMapping mapping = entry.getValue(); - if (memoryCachesOnly) { - mapping.dropMemoryCache(); - } - else { - mapping.force(); - } + mapping.flush(memoryCachesOnly); } } - if (memoryCachesOnly) { - mySrcToFormMap.dropMemoryCache(); - } - else { - mySrcToFormMap.force(); - } + mySrcToFormMap.flush(memoryCachesOnly); final Mappings mappings = myMappings; if (mappings != null) { synchronized (mappings) { @@ -122,27 +121,32 @@ public class BuildDataManager { public void close() throws IOException { try { - synchronized (mySourceToOutputLock) { - closeOutputToSourceStorages(); - } + myArtifactsBuildData.close(); } finally { try { - closeStorage(mySrcToFormMap); + synchronized (mySourceToOutputLock) { + closeOutputToSourceStorages(); + } } finally { - final Mappings mappings = myMappings; - if (mappings != null) { - synchronized (mappings) { - try { - mappings.close(); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException) { - throw ((IOException)cause); + try { + closeStorage(mySrcToFormMap); + } + finally { + final Mappings mappings = myMappings; + if (mappings != null) { + synchronized (mappings) { + try { + mappings.close(); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw ((IOException)cause); + } + throw e; } - throw e; } } } @@ -158,7 +162,7 @@ public class BuildDataManager { closeStorage(entry.getValue()); } catch (IOException e) { - if (ex != null) { + if (e != null) { ex = e; } } @@ -168,7 +172,7 @@ public class BuildDataManager { closeStorage(entry.getValue()); } catch (IOException e) { - if (ex != null) { + if (e != null) { ex = e; } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ClasspathBootstrap.java b/jps/jps-builders/src/org/jetbrains/jps/server/ClasspathBootstrap.java index f520c8128d28..d95a41544503 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ClasspathBootstrap.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ClasspathBootstrap.java @@ -82,7 +82,11 @@ public class ClasspathBootstrap { cp.add(getResourcePath(FileMonitor.class)); // jna-utils.jar cp.add(getResourcePath(ClassWriter.class)); // asm cp.add(getResourcePath(org.objectweb.asm.commons.EmptyVisitor.class)); // asm-commons - cp.add(getResourcePath(MacroExpander.class)); // jps-model + final File jpsModel = getResourcePath(MacroExpander.class); + cp.add(jpsModel); // jps-model + cp.add(new File(jpsModel.getParentFile(), "jps-javaee")); + cp.add(new File(jpsModel.getParentFile(), "jps-gwt")); + cp.add(new File(jpsModel.getParentFile(), "jps-jpa")); cp.add(getResourcePath(AlienFormFileException.class)); // forms-compiler cp.add(getResourcePath(GroovyException.class)); // groovy cp.add(getResourcePath(org.jdom.input.SAXBuilder.class)); // jdom diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java index 5afd8908e8f8..cf00e51af4b0 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java @@ -188,8 +188,7 @@ class ServerMessageHandler extends SimpleChannelHandler { channelContext.setAttachment(sessionId); final BuildType buildType = convertCompileType(compileType); final CompilationTask task = new CompilationTask( - sessionId, channelContext, projectId, buildType, compileRequest.getModuleNameList(), compileRequest.getFilePathList() - ); + sessionId, channelContext, projectId, buildType, compileRequest.getModuleNameList(), Collections.emptySet(), compileRequest.getFilePathList()); final RunnableFuture future = getCompileTaskExecutor(projectId).submit(task); myBuildsInProgress.add(new Pair(future, task)); return null; @@ -230,6 +229,7 @@ class ServerMessageHandler extends SimpleChannelHandler { private final ChannelHandlerContext myChannelContext; private final String myProjectPath; private final BuildType myBuildType; + private final Collection myArtifacts; private final Collection myPaths; private final Set myModules; private volatile boolean myCanceled = false; @@ -239,11 +239,13 @@ class ServerMessageHandler extends SimpleChannelHandler { String projectId, BuildType buildType, Collection modules, + Collection artifacts, Collection paths) { mySessionId = sessionId; myChannelContext = channelContext; myProjectPath = projectId; myBuildType = buildType; + myArtifacts = artifacts; myPaths = paths; myModules = new HashSet(modules); } @@ -262,7 +264,7 @@ class ServerMessageHandler extends SimpleChannelHandler { final Ref hasErrors = new Ref(false); final Ref markedFilesUptodate = new Ref(false); try { - ServerState.getInstance().startBuild(myProjectPath, myBuildType, myModules, myPaths, new MessageHandler() { + ServerState.getInstance().startBuild(myProjectPath, myBuildType, myModules, myArtifacts, myPaths, new MessageHandler() { public void processMessage(BuildMessage buildMessage) { final JpsRemoteProto.Message.Response response; if (buildMessage instanceof FileGeneratedEvent) { diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java b/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java index 1c8da9b339f8..ba7bd3963f74 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java @@ -13,6 +13,7 @@ import org.jetbrains.jps.api.BuildType; import org.jetbrains.jps.api.CanceledStatus; import org.jetbrains.jps.api.GlobalLibrary; import org.jetbrains.jps.api.SdkLibrary; +import org.jetbrains.jps.artifacts.Artifact; import org.jetbrains.jps.idea.IdeaProjectLoader; import org.jetbrains.jps.incremental.*; import org.jetbrains.jps.incremental.messages.BuildMessage; @@ -119,7 +120,8 @@ class ServerState { } } - public void startBuild(String projectPath, BuildType buildType, Set modules, Collection paths, final MessageHandler msgHandler, CanceledStatus cs) throws Throwable{ + public void startBuild(String projectPath, BuildType buildType, Set modules, Collection artifacts, + Collection paths, final MessageHandler msgHandler, CanceledStatus cs) throws Throwable{ final String projectName = getProjectName(projectPath); @@ -161,7 +163,7 @@ class ServerState { final Project project = pd.project; try { - final CompileScope compileScope = createCompilationScope(buildType, pd, modules, paths); + final CompileScope compileScope = createCompilationScope(buildType, pd, modules, artifacts, paths); final IncProjectBuilder builder = new IncProjectBuilder(pd, BuilderRegistry.getInstance(), cs); if (msgHandler != null) { builder.addMessageHandler(msgHandler); @@ -191,17 +193,32 @@ class ServerState { } } - private static CompileScope createCompilationScope(BuildType buildType, ProjectDescriptor pd, Set modules, Collection paths) throws Exception { + private static CompileScope createCompilationScope(BuildType buildType, ProjectDescriptor pd, Set modules, + Collection artifactNames, Collection paths) throws Exception { + Set artifacts = new HashSet(); + if (artifactNames.isEmpty() && buildType == BuildType.PROJECT_REBUILD) { + artifacts.addAll(pd.project.getArtifacts().values()); + } + else { + final Map artifactMap = pd.project.getArtifacts(); + for (String name : artifactNames) { + final Artifact artifact = artifactMap.get(name); + if (!StringUtil.isEmpty(artifact.getOutputPath())) { + artifacts.add(artifact); + } + } + } + final CompileScope compileScope; if (buildType == BuildType.PROJECT_REBUILD || (modules.isEmpty() && paths.isEmpty())) { - compileScope = new AllProjectScope(pd.project, buildType != BuildType.MAKE); + compileScope = new AllProjectScope(pd.project, artifacts, buildType != BuildType.MAKE); } else { final Set forcedModules; if (!modules.isEmpty()) { forcedModules = new HashSet(); for (Module m : pd.project.getModules().values()) { - if (modules.contains(m.getName())){ + if (modules.contains(m.getName())) { forcedModules.add(m); } } @@ -236,10 +253,10 @@ class ServerState { } if (filesToCompile.isEmpty()) { - compileScope = new ModulesScope(pd.project, forcedModules, buildType != BuildType.MAKE); + compileScope = new ModulesScope(pd.project, forcedModules, artifacts, buildType != BuildType.MAKE); } else { - compileScope = new ModulesAndFilesScope(pd.project, forcedModules, filesToCompile, buildType != BuildType.MAKE); + compileScope = new ModulesAndFilesScope(pd.project, forcedModules, filesToCompile, artifacts, buildType != BuildType.MAKE); } } return compileScope; diff --git a/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java b/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java index bfffa1a8767d..9b9bf010e319 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java +++ b/jps/jps-builders/testSrc/org/jetbrains/ether/IncrementalTestCase.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.Project; import org.jetbrains.jps.Sdk; import org.jetbrains.jps.api.CanceledStatus; +import org.jetbrains.jps.artifacts.Artifact; import org.jetbrains.jps.idea.IdeaProjectLoader; import org.jetbrains.jps.incremental.*; import org.jetbrains.jps.incremental.storage.BuildDataManager; @@ -36,6 +37,7 @@ import org.jetbrains.jps.server.ClasspathBootstrap; import org.jetbrains.jps.server.ProjectDescriptor; import java.io.*; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Properties; @@ -320,7 +322,7 @@ public abstract class IncrementalTestCase extends TestCase { new IncProjectBuilder( projectDescriptor, BuilderRegistry.getInstance(), CanceledStatus.NULL ).build( - new AllProjectScope(project, true), false, true + new AllProjectScope(project, Collections.emptySet(), true), false, true ); modify(); @@ -332,7 +334,7 @@ public abstract class IncrementalTestCase extends TestCase { new IncProjectBuilder( projectDescriptor, BuilderRegistry.getInstance(), CanceledStatus.NULL ).build( - new AllProjectScope(project, false), true, false + new AllProjectScope(project, Collections.emptySet(), false), true, false ); FileAssert.assertEquals(new File(getBaseDir() + ".log"), new File(getWorkDir() + ".log")); diff --git a/jps/model/src/org/jetbrains/jps/PathUtil.java b/jps/model/src/org/jetbrains/jps/PathUtil.java index 95500ec66d5f..add4364add89 100644 --- a/jps/model/src/org/jetbrains/jps/PathUtil.java +++ b/jps/model/src/org/jetbrains/jps/PathUtil.java @@ -1,6 +1,8 @@ package org.jetbrains.jps; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.net.URI; @@ -10,6 +12,30 @@ import java.util.Set; * @author nik */ public class PathUtil { + //todo[nik] copied from DeploymentUtil + public static String trimForwardSlashes(@NotNull String path) { + while (path.length() != 0 && (path.charAt(0) == '/' || path.charAt(0) == File.separatorChar)) { + path = path.substring(1); + } + return path; + } + + //todo[nik] copied from DeploymentUtil + public static String appendToPath(@NotNull String basePath, @NotNull String relativePath) { + final boolean endsWithSlash = StringUtil.endsWithChar(basePath, '/') || StringUtil.endsWithChar(basePath, '\\'); + final boolean startsWithSlash = StringUtil.startsWithChar(relativePath, '/') || StringUtil.startsWithChar(relativePath, '\\'); + String tail; + if (endsWithSlash && startsWithSlash) { + tail = trimForwardSlashes(relativePath); + } + else if (!endsWithSlash && !startsWithSlash && basePath.length() > 0 && relativePath.length() > 0) { + tail = "/" + relativePath; + } + else { + tail = relativePath; + } + return basePath + tail; + } public static String toPath(URI uri) { if (uri.getScheme() == null) {