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
;
32 import java
.io
.FileFilter
;
33 import java
.io
.IOException
;
35 import java
.util
.jar
.Attributes
;
36 import java
.util
.jar
.JarFile
;
37 import java
.util
.jar
.Manifest
;
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
,
50 final String outputRelativePath
,
51 @Nullable String possibleBaseOuputPath
,
52 @Nullable PackagingFileFilter fileFilter
) {
53 final File outputPath
= getModuleOutputPath(sourceModule
);
55 String
[] sourceRoots
= getSourceRootUrlsInReadAction(sourceModule
);
57 if (outputPath
!= null && sourceRoots
.length
!= 0) {
58 ok
= outputPath
.exists();
59 boolean added
= addItemsRecursively(items
, outputPath
, targetModule
, outputRelativePath
, fileFilter
, possibleBaseOuputPath
);
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
)),
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();
82 private static File
getModuleOutputPath(final Module module
) {
83 return ApplicationManager
.getApplication().runReadAction(new Computable
<File
>() {
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
));
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() {
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());
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
);
131 fileDestination
= appendToPath(fileDestination
, file
.getName());
135 if (file
.isDirectory()) {
137 if (packagingMethodIsCopy
) {
138 ok
= addItemsRecursively(items
, file
, module
, fileDestination
, null, possibleBaseOutputPath
);
141 fixPackagingMethod(packagingMethod
, libraryLink
, context
);
142 BuildInstruction instruction
= new JarAndCopyBuildInstructionImpl(module
, file
, fileDestination
);
143 items
.addInstruction(instruction
);
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
);
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
)) {
179 checkPathDoNotNavigatesUpFromFile(fromFile
);
180 checkPathDoNotNavigatesUpFromFile(toFile
);
181 if (fromFile
.isDirectory()) {
182 final File
[] fromFiles
= fromFile
.listFiles();
184 for (File file
: fromFiles
) {
185 copyFile(file
, new File(toFile
, file
.getName()), context
, writtenPaths
, fileFilter
);
189 if (toFile
.isDirectory()) {
190 context
.addMessage(CompilerMessageCategory
.ERROR
,
191 CompilerBundle
.message("message.text.destination.is.directory", createCopyErrorMessage(fromFile
, toFile
)), null, -1, -1);
194 if (fromFile
.equals(toFile
)
195 || writtenPaths
!= null && !writtenPaths
.add(toFile
.getPath())) {
198 if (!FileUtil
.isFilePathAcceptable(toFile
, fileFilter
)) return;
199 if (context
.getProgressIndicator() != null) {
200 context
.getProgressIndicator().setText("Copying files");
201 context
.getProgressIndicator().setText2(fromFile
.getPath());
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("..");
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
,
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) {
255 items
.addFileCopyInstruction(root
, true, module
, outputRelativePath
, fileFilter
);
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
;
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
);
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);
296 DeploymentUtil
.getInstance().reportDeploymentDescriptorDoesNotExists(descriptor
, compileContext
, module
);
300 public @Nullable Manifest
createManifest(@NotNull BuildRecipe buildRecipe
) {
301 if (findUserSuppliedManifestFile(buildRecipe
) != null) {
305 final List
<String
> classpathElements
= getExternalDependenciesClasspath(buildRecipe
);
306 final Manifest manifest
= new Manifest();
307 setManifestAttributes(manifest
.getMainAttributes(), classpathElements
);
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
);
324 builder
= new StringBuilder();
327 for (String path
: classpathElements
) {
328 if (!existingPaths
.contains(path
)) {
329 if (builder
.length() > 0) {
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
));
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
)) {
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
),
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
,
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);
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();
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())));
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
);