migrated to artifacts
[fedora-idea.git] / java / compiler / impl / src / com / intellij / openapi / deployment / DeploymentUtilImpl.java
blob53f06711a56eaec4d513df584601be89346d388b
1 package com.intellij.openapi.deployment;
3 import com.intellij.compiler.impl.packagingCompiler.BuildRecipeImpl;
4 import com.intellij.compiler.impl.packagingCompiler.JarAndCopyBuildInstructionImpl;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.compiler.CompileContext;
7 import com.intellij.openapi.compiler.CompilerBundle;
8 import com.intellij.openapi.compiler.CompilerMessageCategory;
9 import com.intellij.openapi.compiler.make.*;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.extensions.Extensions;
12 import com.intellij.openapi.module.Module;
13 import com.intellij.openapi.module.ModuleUtil;
14 import com.intellij.openapi.roots.*;
15 import com.intellij.openapi.roots.libraries.Library;
16 import com.intellij.openapi.util.Computable;
17 import com.intellij.openapi.util.Ref;
18 import com.intellij.openapi.util.SystemInfo;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.vfs.VfsUtil;
22 import com.intellij.psi.PsiFile;
23 import com.intellij.psi.xml.XmlDocument;
24 import com.intellij.psi.xml.XmlFile;
25 import com.intellij.util.PathUtil;
26 import com.intellij.util.descriptors.ConfigFile;
27 import org.jetbrains.annotations.NonNls;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
31 import java.io.File;
32 import java.io.FileFilter;
33 import java.io.IOException;
34 import java.util.*;
35 import java.util.jar.Attributes;
36 import java.util.jar.JarFile;
37 import java.util.jar.Manifest;
39 /**
40 * @author Alexey Kudravtsev
42 public class DeploymentUtilImpl extends DeploymentUtil {
43 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.deployment.MakeUtilImpl");
44 @NonNls private static final String JAR_SUFFIX = ".jar";
46 public boolean addModuleOutputContents(@NotNull CompileContext context,
47 @NotNull BuildRecipe items,
48 @NotNull final Module sourceModule,
49 Module targetModule,
50 final String outputRelativePath,
51 @Nullable String possibleBaseOuputPath,
52 @Nullable PackagingFileFilter fileFilter) {
53 final File outputPath = getModuleOutputPath(sourceModule);
55 String[] sourceRoots = getSourceRootUrlsInReadAction(sourceModule);
56 boolean ok = true;
57 if (outputPath != null && sourceRoots.length != 0) {
58 ok = outputPath.exists();
59 boolean added = addItemsRecursively(items, outputPath, targetModule, outputRelativePath, fileFilter, possibleBaseOuputPath);
60 if (!added) {
61 LOG.assertTrue(possibleBaseOuputPath != null);
62 String additionalMessage = CompilerBundle.message("message.text.change.module.output.directory.or.module.exploded.directory",
63 ModuleUtil.getModuleNameInReadAction(sourceModule),
64 ModuleUtil.getModuleNameInReadAction(targetModule));
65 reportRecursiveCopying(context, outputPath.getPath(), appendToPath(possibleBaseOuputPath, outputRelativePath),
66 CompilerBundle.message("module.output.directory", ModuleUtil.getModuleNameInReadAction(sourceModule)),
67 additionalMessage);
68 ok = false;
71 return ok;
74 private static String[] getSourceRootUrlsInReadAction(final Module module) {
75 return ApplicationManager.getApplication().runReadAction(new Computable<String[]>() {
76 public String[] compute() {
77 return ModuleRootManager.getInstance(module).getSourceRootUrls();
79 });
82 private static File getModuleOutputPath(final Module module) {
83 return ApplicationManager.getApplication().runReadAction(new Computable<File>() {
84 @Nullable
85 public File compute() {
86 final String url = CompilerModuleExtension.getInstance(module).getCompilerOutputUrl();
87 if (url == null) return null;
88 return new File(PathUtil.toPresentableUrl(url));
90 });
93 public void addLibraryLink(@NotNull final CompileContext context,
94 @NotNull final BuildRecipe items,
95 @NotNull final LibraryLink libraryLink,
96 @NotNull final Module module,
97 @Nullable final String possibleBaseOutputPath) {
98 ApplicationManager.getApplication().runReadAction(new Runnable() {
99 public void run() {
100 String outputRelativePath;
101 final PackagingMethod packagingMethod = libraryLink.getPackagingMethod();
102 if (packagingMethod.equals(PackagingMethod.COPY_FILES_AND_LINK_VIA_MANIFEST)
103 || packagingMethod.equals(PackagingMethod.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST)) {
104 outputRelativePath = getRelativePathForManifestLinking(libraryLink.getURI());
106 else {
107 outputRelativePath = libraryLink.getURI();
110 final List<String> urls = libraryLink.getClassesRootUrls();
112 boolean isDestinationDirectory = true;
113 if (LibraryLink.MODULE_LEVEL.equals(libraryLink.getLevel()) && urls.size() == 1 && outputRelativePath.endsWith(JAR_SUFFIX)) {
114 isDestinationDirectory = false;
117 for (String url : urls) {
118 final String path = PathUtil.toPresentableUrl(url);
119 final File file = new File(path);
120 boolean packagingMethodIsCopy = packagingMethod.equals(PackagingMethod.COPY_FILES_AND_LINK_VIA_MANIFEST) ||
121 packagingMethod.equals(PackagingMethod.COPY_FILES);
123 String fileDestination = outputRelativePath;
124 if (isDestinationDirectory) {
125 if (file.isDirectory()) {
126 if (!packagingMethodIsCopy) {
127 fileDestination = appendToPath(fileDestination, file.getName() + JAR_SUFFIX);
130 else {
131 fileDestination = appendToPath(fileDestination, file.getName());
135 if (file.isDirectory()) {
136 boolean ok;
137 if (packagingMethodIsCopy) {
138 ok = addItemsRecursively(items, file, module, fileDestination, null, possibleBaseOutputPath);
140 else {
141 fixPackagingMethod(packagingMethod, libraryLink, context);
142 BuildInstruction instruction = new JarAndCopyBuildInstructionImpl(module, file, fileDestination);
143 items.addInstruction(instruction);
144 ok = true;
146 if (!ok) {
147 final String name = libraryLink.getPresentableName();
148 String additionalMessage = CompilerBundle.message("message.text.adjust.library.path");
149 reportRecursiveCopying(context, file.getPath(), fileDestination,
150 CompilerBundle.message("directory.description.library.directory", name), additionalMessage);
153 else {
154 items.addFileCopyInstruction(file, false, module, fileDestination, null);
161 private static void fixPackagingMethod(final PackagingMethod packagingMethod, final LibraryLink libraryLink, final CompileContext context) {
162 if (!packagingMethod.equals(PackagingMethod.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST) &&
163 !packagingMethod.equals(PackagingMethod.JAR_AND_COPY_FILE)) {
164 libraryLink.setPackagingMethod(PackagingMethod.JAR_AND_COPY_FILE);
165 String message = CompilerBundle.message("message.text.packaging.method.for.library.reset", libraryLink.getPresentableName(),
166 PackagingMethod.JAR_AND_COPY_FILE);
167 context.addMessage(CompilerMessageCategory.WARNING, message, null, -1, -1);
171 public void copyFile(@NotNull final File fromFile,
172 @NotNull final File toFile,
173 @NotNull CompileContext context,
174 @Nullable Set<String> writtenPaths,
175 @Nullable FileFilter fileFilter) throws IOException {
176 if (fileFilter != null && !fileFilter.accept(fromFile)) {
177 return;
179 checkPathDoNotNavigatesUpFromFile(fromFile);
180 checkPathDoNotNavigatesUpFromFile(toFile);
181 if (fromFile.isDirectory()) {
182 final File[] fromFiles = fromFile.listFiles();
183 toFile.mkdirs();
184 for (File file : fromFiles) {
185 copyFile(file, new File(toFile, file.getName()), context, writtenPaths, fileFilter);
187 return;
189 if (toFile.isDirectory()) {
190 context.addMessage(CompilerMessageCategory.ERROR,
191 CompilerBundle.message("message.text.destination.is.directory", createCopyErrorMessage(fromFile, toFile)), null, -1, -1);
192 return;
194 if (fromFile.equals(toFile)
195 || writtenPaths != null && !writtenPaths.add(toFile.getPath())) {
196 return;
198 if (!FileUtil.isFilePathAcceptable(toFile, fileFilter)) return;
199 if (context.getProgressIndicator() != null) {
200 context.getProgressIndicator().setText("Copying files");
201 context.getProgressIndicator().setText2(fromFile.getPath());
203 try {
204 if (LOG.isDebugEnabled()) {
205 LOG.debug("Copy file '" + fromFile + "' to '"+toFile+"'");
207 if (toFile.exists() && !SystemInfo.isFileSystemCaseSensitive) {
208 File canonicalFile = toFile.getCanonicalFile();
209 if (!canonicalFile.getAbsolutePath().equals(toFile.getAbsolutePath())) {
210 FileUtil.delete(toFile);
213 FileUtil.copy(fromFile, toFile);
215 catch (IOException e) {
216 context.addMessage(CompilerMessageCategory.ERROR, createCopyErrorMessage(fromFile, toFile) + ": "+e.getLocalizedMessage(), null, -1, -1);
220 // OS X is sensitive for that
221 private static void checkPathDoNotNavigatesUpFromFile(File file) {
222 String path = file.getPath();
223 int i = path.indexOf("..");
224 if (i != -1) {
225 String filepath = path.substring(0,i-1);
226 File filepart = new File(filepath);
227 if (filepart.exists() && !filepart.isDirectory()) {
228 LOG.error("Incorrect file path: '" + path + '\'');
233 private static String createCopyErrorMessage(final File fromFile, final File toFile) {
234 return CompilerBundle.message("message.text.error.copying.file.to.file", FileUtil.toSystemDependentName(fromFile.getPath()),
235 FileUtil.toSystemDependentName(toFile.getPath()));
238 public final boolean addItemsRecursively(@NotNull BuildRecipe items,
239 @NotNull File root,
240 Module module,
241 String outputRelativePath,
242 @Nullable PackagingFileFilter fileFilter,
243 @Nullable String possibleBaseOutputPath) {
244 if (outputRelativePath == null) outputRelativePath = "";
245 outputRelativePath = trimForwardSlashes(outputRelativePath);
247 if (possibleBaseOutputPath != null) {
248 File file = new File(possibleBaseOutputPath, outputRelativePath);
249 String relativePath = getRelativePath(root, file);
250 if (relativePath != null && !relativePath.startsWith("..") && relativePath.length() != 0) {
251 return false;
255 items.addFileCopyInstruction(root, true, module, outputRelativePath, fileFilter);
256 return true;
259 public void reportDeploymentDescriptorDoesNotExists(ConfigFile descriptor, CompileContext context, Module module) {
260 final String description = module.getModuleType().getName() + " '" + module.getName() + '\'';
261 String descriptorPath = VfsUtil.urlToPath(descriptor.getUrl());
262 final String message =
263 CompilerBundle.message("message.text.compiling.item.deployment.descriptor.could.not.be.found", description, descriptorPath);
264 context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
267 public static String getRelativePathForManifestLinking(String relativePath) {
268 if (!StringUtil.startsWithChar(relativePath, '/')) relativePath = '/' + relativePath;
269 relativePath = ".." + relativePath;
270 return relativePath;
273 public @Nullable File findUserSuppliedManifestFile(@NotNull BuildRecipe buildRecipe) {
274 final Ref<File> ref = Ref.create(null);
275 buildRecipe.visitInstructions(new BuildInstructionVisitor() {
276 public boolean visitInstruction(BuildInstruction instruction) throws Exception {
277 final File file = instruction.findFileByRelativePath(JarFile.MANIFEST_NAME);
278 ref.set(file);
279 return file == null;
281 }, false);
282 return ref.get();
285 public void checkConfigFile(final ConfigFile descriptor, final CompileContext compileContext, final Module module) {
286 if (new File(VfsUtil.urlToPath(descriptor.getUrl())).exists()) {
287 String message = getConfigFileErrorMessage(descriptor);
288 if (message != null) {
289 final String moduleDescription = module.getModuleType().getName() + " '" + module.getName() + '\'';
290 compileContext.addMessage(CompilerMessageCategory.ERROR,
291 CompilerBundle.message("message.text.compiling.module.message", moduleDescription, message),
292 descriptor.getUrl(), -1, -1);
295 else {
296 DeploymentUtil.getInstance().reportDeploymentDescriptorDoesNotExists(descriptor, compileContext, module);
300 public @Nullable Manifest createManifest(@NotNull BuildRecipe buildRecipe) {
301 if (findUserSuppliedManifestFile(buildRecipe) != null) {
302 return null;
305 final List<String> classpathElements = getExternalDependenciesClasspath(buildRecipe);
306 final Manifest manifest = new Manifest();
307 setManifestAttributes(manifest.getMainAttributes(), classpathElements);
308 return manifest;
311 public static void setManifestAttributes(final Attributes mainAttributes, final @Nullable List<String> classpathElements) {
312 if (classpathElements != null && classpathElements.size() > 0) {
313 StringBuilder builder;
314 Set<String> existingPaths = new HashSet<String>();
315 String oldClassPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH);
316 if (oldClassPath != null) {
317 StringTokenizer tokenizer = new StringTokenizer(oldClassPath);
318 while (tokenizer.hasMoreTokens()) {
319 existingPaths.add(tokenizer.nextToken());
321 builder = new StringBuilder(oldClassPath);
323 else {
324 builder = new StringBuilder();
327 for (String path : classpathElements) {
328 if (!existingPaths.contains(path)) {
329 if (builder.length() > 0) {
330 builder.append(' ');
332 builder.append(path);
335 mainAttributes.put(Attributes.Name.CLASS_PATH, builder.toString());
337 ManifestBuilder.setGlobalAttributes(mainAttributes);
340 public static List<String> getExternalDependenciesClasspath(final BuildRecipe buildRecipe) {
341 final List<String> classpath = new ArrayList<String>();
343 buildRecipe.visitInstructions(new BuildInstructionVisitor() {
344 public boolean visitInstruction(BuildInstruction instruction) throws RuntimeException {
345 final String outputRelativePath = instruction.getOutputRelativePath();
346 if (instruction.isExternalDependencyInstruction()) {
347 final String jarReference = PathUtil.getCanonicalPath("/tmp/" + outputRelativePath).substring(1);
348 classpath.add(trimForwardSlashes(jarReference));
350 return true;
352 }, false);
353 return classpath;
356 public void addJavaModuleOutputs(@NotNull final Module module,
357 @NotNull ModuleLink[] containingModules,
358 @NotNull BuildRecipe instructions,
359 @NotNull CompileContext context,
360 String explodedPath) {
361 addJavaModuleOutputs(module, containingModules, instructions, context, explodedPath, "");
364 public void addJavaModuleOutputs(@NotNull final Module module,
365 @NotNull ModuleLink[] containingModules,
366 @NotNull BuildRecipe instructions,
367 @NotNull CompileContext context,
368 String explodedPath, final String linkContainerDescription) {
369 addJavaModuleOutputs(module, containingModules, instructions, context, explodedPath, linkContainerDescription, null);
372 public void addJavaModuleOutputs(@NotNull final Module module,
373 @NotNull ModuleLink[] containingModules,
374 @NotNull BuildRecipe instructions,
375 @NotNull CompileContext context,
376 @Nullable String possibleExplodedPath, final String linkContainerDescription, @Nullable Map<Module, PackagingFileFilter> fileFilters) {
377 for (ModuleLink moduleLink : containingModules) {
378 Module childModule = moduleLink.getModule();
379 if (childModule != null) {
380 final PackagingMethod packagingMethod = moduleLink.getPackagingMethod();
381 if (PackagingMethod.DO_NOT_PACKAGE.equals(packagingMethod)) {
382 continue;
385 PackagingFileFilter fileFilter = fileFilters != null ? fileFilters.get(childModule) : null;
387 if (PackagingMethod.JAR_AND_COPY_FILE.equals(packagingMethod)) {
388 addJarJavaModuleOutput(instructions, childModule, moduleLink.getURI(), context, linkContainerDescription, fileFilter);
390 else if (PackagingMethod.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST.equals(packagingMethod)) {
391 String relativePath = getRelativePathForManifestLinking(moduleLink.getURI());
392 addJarJavaModuleOutput(instructions, childModule, relativePath, context, linkContainerDescription, fileFilter);
394 else if (PackagingMethod.COPY_FILES.equals(packagingMethod)) {
395 addModuleOutputContents(context, instructions, childModule, module, moduleLink.getURI(), possibleExplodedPath, fileFilter);
397 else if (PackagingMethod.COPY_FILES_AND_LINK_VIA_MANIFEST.equals(packagingMethod)) {
398 moduleLink.setPackagingMethod(PackagingMethod.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST);
399 String relativePath = getRelativePathForManifestLinking(moduleLink.getURI());
400 addJarJavaModuleOutput(instructions, childModule, relativePath, context, linkContainerDescription, fileFilter);
401 context.addMessage(CompilerMessageCategory.WARNING,
402 CompilerBundle.message("message.text.packaging.method.for.module.reset.to.method",
403 ModuleUtil.getModuleNameInReadAction(childModule),
404 PackagingMethod.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST),
405 null, -1, -1);
407 else {
408 LOG.info("invalid packaging method " + packagingMethod + " for module '" + ModuleUtil.getModuleNameInReadAction(childModule)
409 + "' in module '" + ModuleUtil.getModuleNameInReadAction(module) + "'");
415 private static void addJarJavaModuleOutput(BuildRecipe instructions,
416 Module module,
417 String relativePath,
418 CompileContext context, final String linkContainerDescription, @Nullable PackagingFileFilter fileFilter) {
419 final String[] sourceUrls = getSourceRootUrlsInReadAction(module);
420 if (sourceUrls.length > 0) {
421 final File outputPath = getModuleOutputPath(module);
422 if (outputPath != null) {
423 if ("/".equals(relativePath) || "".equals(relativePath) || getRelativePathForManifestLinking("/").equals(relativePath)) {
424 context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("message.text.invalid.output.path.for.module.jar", relativePath, module.getName(), linkContainerDescription), null, -1, -1);
426 else {
427 instructions.addInstruction(new JarAndCopyBuildInstructionImpl(module, outputPath, relativePath, fileFilter));
433 public ModuleLink createModuleLink(@NotNull Module dep, @NotNull Module module) {
434 return new ModuleLinkImpl(dep, module);
437 public LibraryLink createLibraryLink(Library library, @NotNull Module parentModule) {
438 return new LibraryLinkImpl(library, parentModule);
441 public BuildRecipe createBuildRecipe() {
442 return new BuildRecipeImpl();
445 @Nullable
446 public String getConfigFileErrorMessage(final ConfigFile configFile) {
447 if (configFile.getVirtualFile() == null) {
448 String path = FileUtil.toSystemDependentName(VfsUtil.urlToPath(configFile.getUrl()));
449 return CompilerBundle.message("mesage.text.deployment.descriptor.file.not.exist", path);
451 PsiFile psiFile = configFile.getPsiFile();
452 if (psiFile == null || !psiFile.isValid()) {
453 return CompilerBundle.message("message.text.deployment.description.invalid.file");
456 if (psiFile instanceof XmlFile) {
457 XmlDocument document = ((XmlFile)psiFile).getDocument();
458 if (document == null || document.getRootTag() == null) {
459 return CompilerBundle.message("message.text.xml.file.invalid", FileUtil.toSystemDependentName(VfsUtil.urlToPath(configFile.getUrl())));
462 return null;
465 @Nullable
466 public static String getOrCreateExplodedDir(final BuildParticipant buildParticipant) {
467 BuildConfiguration buildConfiguration = buildParticipant.getBuildConfiguration();
468 if (buildConfiguration.isExplodedEnabled()) {
469 return buildConfiguration.getExplodedPath();
471 return buildParticipant.getOrCreateTemporaryDirForExploded();
474 public static BuildParticipantProvider<?>[] getBuildParticipantProviders() {
475 return Extensions.getExtensions(BuildParticipantProvider.EXTENSION_POINT_NAME);