in case of cleanup failure do not try to clean again
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / server / ServerState.java
1 package org.jetbrains.jps.server;
2
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.util.io.FileUtil;
5 import com.intellij.openapi.util.text.StringUtil;
6 import org.codehaus.groovy.runtime.MethodClosure;
7 import org.jetbrains.annotations.Nullable;
8 import org.jetbrains.jps.Library;
9 import org.jetbrains.jps.Module;
10 import org.jetbrains.jps.Project;
11 import org.jetbrains.jps.Sdk;
12 import org.jetbrains.jps.api.BuildType;
13 import org.jetbrains.jps.api.CanceledStatus;
14 import org.jetbrains.jps.api.GlobalLibrary;
15 import org.jetbrains.jps.api.SdkLibrary;
16 import org.jetbrains.jps.idea.IdeaProjectLoader;
17 import org.jetbrains.jps.incremental.*;
18 import org.jetbrains.jps.incremental.messages.BuildMessage;
19 import org.jetbrains.jps.incremental.messages.CompilerMessage;
20 import org.jetbrains.jps.incremental.storage.BuildDataManager;
21 import org.jetbrains.jps.incremental.storage.ProjectTimestamps;
22 import org.jetbrains.jps.incremental.storage.TimestampStorage;
23
24 import java.io.File;
25 import java.lang.reflect.Method;
26 import java.util.*;
27
28 /**
29  * @author Eugene Zhuravlev
30  *         Date: 9/10/11
31  * @noinspection UnusedDeclaration
32  */
33 class ServerState {
34   private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.server.ServerState");
35   public static final String IDEA_PROJECT_DIRNAME = ".idea";
36
37   private final Map<String, ProjectDescriptor> myProjects = new HashMap<String, ProjectDescriptor>();
38
39   private final Object myConfigurationLock = new Object();
40   private final Map<String, String> myPathVariables = new HashMap<String, String>();
41   private final List<GlobalLibrary> myGlobalLibraries = new ArrayList<GlobalLibrary>();
42   private volatile String myGlobalEncoding = null;
43   private volatile boolean myKeepTempCachesInMemory = false;
44
45   public void setGlobals(List<GlobalLibrary> libs, Map<String, String> pathVars, String globalEncoding) {
46     synchronized (myConfigurationLock) {
47       clearCahedState();
48       myGlobalLibraries.addAll(libs);
49       myPathVariables.putAll(pathVars);
50       myGlobalEncoding = StringUtil.isEmpty(globalEncoding)? null : globalEncoding;
51     }
52   }
53
54   public final void clearCahedState() {
55     synchronized (myConfigurationLock) {
56       for (Map.Entry<String, ProjectDescriptor> entry : myProjects.entrySet()) {
57         final String projectPath = entry.getKey();
58         final ProjectDescriptor descriptor = entry.getValue();
59         descriptor.release();
60       }
61       myProjects.clear(); // projects should be reloaded against the latest data
62       myGlobalLibraries.clear();
63       myPathVariables.clear();
64     }
65   }
66
67   public boolean isKeepTempCachesInMemory() {
68     return myKeepTempCachesInMemory;
69   }
70
71   public void setKeepTempCachesInMemory(boolean keepTempCachesInMemory) {
72     myKeepTempCachesInMemory = keepTempCachesInMemory;
73   }
74
75   public void notifyFileChanged(ProjectDescriptor pd, File file) {
76     try {
77       final RootDescriptor rd = pd.rootsIndex.getModuleAndRoot(file);
78       if (rd != null) {
79         pd.fsState.markDirty(file, rd, pd.timestamps.getStorage());
80       }
81     }
82     catch (Exception e) {
83       LOG.error(e); // todo
84     }
85   }
86
87   public void notifyFileDeleted(final ProjectDescriptor pd, File file) {
88     try {
89       final RootDescriptor moduleAndRoot = pd.rootsIndex.getModuleAndRoot(file);
90       if (moduleAndRoot != null) {
91         pd.fsState.registerDeleted(moduleAndRoot.module, file, moduleAndRoot.isTestRoot, pd.timestamps.getStorage());
92       }
93     }
94     catch (Exception e) {
95       LOG.error(e); // todo
96     }
97   }
98
99   @Nullable
100   public ProjectDescriptor getProjectDescriptor(String projectPath) {
101     final ProjectDescriptor pd;
102     synchronized (myConfigurationLock) {
103       pd = myProjects.get(projectPath);
104       if (pd != null) {
105         pd.incUsageCounter();
106       }
107     }
108     return pd;
109   }
110
111   public void clearProjectCache(Collection<String> projectPaths) {
112     synchronized (myConfigurationLock) {
113       for (String projectPath : projectPaths) {
114         final ProjectDescriptor descriptor = myProjects.remove(projectPath);
115         if (descriptor != null) {
116           descriptor.release();
117         }
118       }
119     }
120   }
121
122   public void startBuild(String projectPath, BuildType buildType, Set<String> modules, Collection<String> paths, final MessageHandler msgHandler, CanceledStatus cs) throws Throwable{
123
124     final String projectName = getProjectName(projectPath);
125
126     ProjectDescriptor pd;
127     synchronized (myConfigurationLock) {
128       pd = myProjects.get(projectPath);
129       if (pd == null) {
130         final Project project = loadProject(projectPath);
131         final FSState fsState = new FSState(false);
132         ProjectTimestamps timestamps = null;
133         BuildDataManager dataManager = null;
134         try {
135           timestamps = new ProjectTimestamps(projectName);
136           dataManager = new BuildDataManager(projectName, myKeepTempCachesInMemory);
137         }
138         catch (Exception e) {
139           // second try
140           e.printStackTrace(System.err);
141           if (timestamps != null) {
142             timestamps.close();
143           }
144           if (dataManager != null) {
145             dataManager.close();
146           }
147           buildType = BuildType.PROJECT_REBUILD; // force project rebuild
148           FileUtil.delete(Paths.getDataStorageRoot(projectName));
149           timestamps = new ProjectTimestamps(projectName);
150           dataManager = new BuildDataManager(projectName, myKeepTempCachesInMemory);
151           // second attempt succeded
152           msgHandler.processMessage(new CompilerMessage("compile-server", BuildMessage.Kind.INFO, "Project rebuild forced: " + e.getMessage()));
153         }
154
155         pd = new ProjectDescriptor(projectName, project, fsState, timestamps, dataManager);
156         myProjects.put(projectPath, pd);
157       }
158       pd.incUsageCounter();
159     }
160
161     final Project project = pd.project;
162
163     try {
164       final CompileScope compileScope = createCompilationScope(buildType, pd, modules, paths);
165       final IncProjectBuilder builder = new IncProjectBuilder(pd, BuilderRegistry.getInstance(), cs);
166       if (msgHandler != null) {
167         builder.addMessageHandler(msgHandler);
168       }
169       switch (buildType) {
170         case PROJECT_REBUILD:
171           builder.build(compileScope, false, true);
172           break;
173
174         case FORCED_COMPILATION:
175           builder.build(compileScope, false, false);
176           break;
177
178         case MAKE:
179           builder.build(compileScope, true, false);
180           break;
181
182         case CLEAN:
183           //todo[nik]
184   //        new ProjectBuilder(new GantBinding(), project).clean();
185           break;
186       }
187     }
188     finally {
189       pd.release();
190       clearZipIndexCache();
191     }
192   }
193
194   private static CompileScope createCompilationScope(BuildType buildType, ProjectDescriptor pd, Set<String> modules, Collection<String> paths) throws Exception {
195     final CompileScope compileScope;
196     if (buildType == BuildType.PROJECT_REBUILD || (modules.isEmpty() && paths.isEmpty())) {
197       compileScope = new AllProjectScope(pd.project, buildType != BuildType.MAKE);
198     }
199     else {
200       final Set<Module> forcedModules;
201       if (!modules.isEmpty()) {
202         forcedModules = new HashSet<Module>();
203         for (Module m : pd.project.getModules().values()) {
204           if (modules.contains(m.getName())){
205             forcedModules.add(m);
206           }
207         }
208       }
209       else {
210         forcedModules = Collections.emptySet();
211       }
212
213       final TimestampStorage tsStorage = pd.timestamps.getStorage();
214
215       final Map<Module, Set<File>> filesToCompile;
216       if (!paths.isEmpty()) {
217         filesToCompile = new HashMap<Module, Set<File>>();
218         for (String path : paths) {
219           final File file = new File(path);
220           final RootDescriptor rd = pd.rootsIndex.getModuleAndRoot(file);
221           if (rd != null) {
222             Set<File> files = filesToCompile.get(rd.module);
223             if (files == null) {
224               files = new HashSet<File>();
225               filesToCompile.put(rd.module, files);
226             }
227             files.add(file);
228             if (buildType == BuildType.FORCED_COMPILATION) {
229               pd.fsState.markDirty(file, rd, tsStorage);
230             }
231           }
232         }
233       }
234       else {
235         filesToCompile = Collections.emptyMap();
236       }
237
238       if (filesToCompile.isEmpty()) {
239         compileScope = new ModulesScope(pd.project, forcedModules, buildType != BuildType.MAKE);
240       }
241       else {
242         compileScope = new ModulesAndFilesScope(pd.project, forcedModules, filesToCompile, buildType != BuildType.MAKE);
243       }
244     }
245     return compileScope;
246   }
247
248
249   private static boolean ourCleanupFailed = false;
250
251   private static void clearZipIndexCache() {
252     if (!ourCleanupFailed) {
253       try {
254         final Class<?> indexClass = Class.forName("com.sun.tools.javac.zip.ZipFileIndex");
255         final Method clearMethod = indexClass.getMethod("clearCache");
256         clearMethod.invoke(null);
257       }
258       catch (Throwable ex) {
259         ourCleanupFailed = true;
260         LOG.info(ex);
261       }
262     }
263   }
264
265   private static String getProjectName(String projectPath) {
266     final File path = new File(projectPath);
267     final String name = path.getName().toLowerCase(Locale.US);
268     if (!isDirectoryBased(path) && name.endsWith(".ipr")) {
269       return name.substring(0, name.length() - ".ipr".length());
270     }
271     return name;
272   }
273
274   private Project loadProject(String projectPath) {
275     final Project project = new Project();
276     // setup JDKs and global libraries
277     final MethodClosure fakeClosure = new MethodClosure(new Object(), "hashCode");
278     for (GlobalLibrary library : myGlobalLibraries) {
279       if (library instanceof SdkLibrary) {
280         final SdkLibrary sdk = (SdkLibrary)library;
281         final Sdk jdk = project.createSdk("JavaSDK", sdk.getName(), sdk.getHomePath(), null);
282         jdk.setClasspath(sdk.getPaths());
283       }
284       else {
285         final Library lib = project.createGlobalLibrary(library.getName(), fakeClosure);
286         lib.setClasspath(library.getPaths());
287       }
288     }
289
290     final File projectFile = new File(projectPath);
291
292     //String root = dirBased ? projectPath : projectFile.getParent();
293
294     final String loadPath = isDirectoryBased(projectFile) ? new File(projectFile, IDEA_PROJECT_DIRNAME).getPath() : projectPath;
295     IdeaProjectLoader.loadFromPath(project, loadPath, myPathVariables, getStartupScript());
296     final String globalEncoding = myGlobalEncoding;
297     if (globalEncoding != null && project.getProjectCharset() == null) {
298       project.setProjectCharset(globalEncoding);
299     }
300     return project;
301   }
302
303   private static boolean isDirectoryBased(File projectFile) {
304     return !(projectFile.isFile() && projectFile.getName().endsWith(".ipr"));
305   }
306
307   private String getStartupScript() {
308     //return "import org.jetbrains.jps.*\n";
309     return null;
310   }
311
312   private static class InstanceHolder {
313     static final ServerState ourInstance = new ServerState();
314   }
315
316   public static ServerState getInstance() {
317     return InstanceHolder.ourInstance;
318   }
319
320 }