2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.openapi
.deployment
;
18 import com
.intellij
.compiler
.impl
.packagingCompiler
.BuildRecipeImpl
;
19 import com
.intellij
.compiler
.impl
.packagingCompiler
.JarAndCopyBuildInstructionImpl
;
20 import com
.intellij
.openapi
.application
.ApplicationManager
;
21 import com
.intellij
.openapi
.compiler
.CompileContext
;
22 import com
.intellij
.openapi
.compiler
.CompilerBundle
;
23 import com
.intellij
.openapi
.compiler
.CompilerMessageCategory
;
24 import com
.intellij
.openapi
.compiler
.make
.*;
25 import com
.intellij
.openapi
.diagnostic
.Logger
;
26 import com
.intellij
.openapi
.extensions
.Extensions
;
27 import com
.intellij
.openapi
.module
.Module
;
28 import com
.intellij
.openapi
.module
.ModuleUtil
;
29 import com
.intellij
.openapi
.roots
.*;
30 import com
.intellij
.openapi
.roots
.libraries
.Library
;
31 import com
.intellij
.openapi
.util
.Computable
;
32 import com
.intellij
.openapi
.util
.SystemInfo
;
33 import com
.intellij
.openapi
.util
.io
.FileUtil
;
34 import com
.intellij
.openapi
.util
.text
.StringUtil
;
35 import com
.intellij
.openapi
.vfs
.VfsUtil
;
36 import com
.intellij
.psi
.PsiFile
;
37 import com
.intellij
.psi
.xml
.XmlDocument
;
38 import com
.intellij
.psi
.xml
.XmlFile
;
39 import com
.intellij
.util
.PathUtil
;
40 import com
.intellij
.util
.descriptors
.ConfigFile
;
41 import org
.jetbrains
.annotations
.NonNls
;
42 import org
.jetbrains
.annotations
.NotNull
;
43 import org
.jetbrains
.annotations
.Nullable
;
46 import java
.io
.FileFilter
;
47 import java
.io
.IOException
;
49 import java
.util
.jar
.Attributes
;
52 * @author Alexey Kudravtsev
54 public class DeploymentUtilImpl
extends DeploymentUtil
{
55 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.deployment.MakeUtilImpl");
56 @NonNls private static final String JAR_SUFFIX
= ".jar";
58 public boolean addModuleOutputContents(@NotNull CompileContext context
,
59 @NotNull BuildRecipe items
,
60 @NotNull final Module sourceModule
,
62 final String outputRelativePath
,
63 @Nullable String possibleBaseOuputPath
,
64 @Nullable PackagingFileFilter fileFilter
) {
65 final File outputPath
= getModuleOutputPath(sourceModule
);
67 String
[] sourceRoots
= getSourceRootUrlsInReadAction(sourceModule
);
69 if (outputPath
!= null && sourceRoots
.length
!= 0) {
70 ok
= outputPath
.exists();
71 boolean added
= addItemsRecursively(items
, outputPath
, targetModule
, outputRelativePath
, fileFilter
, possibleBaseOuputPath
);
73 LOG
.assertTrue(possibleBaseOuputPath
!= null);
74 String additionalMessage
= CompilerBundle
.message("message.text.change.module.output.directory.or.module.exploded.directory",
75 ModuleUtil
.getModuleNameInReadAction(sourceModule
),
76 ModuleUtil
.getModuleNameInReadAction(targetModule
));
77 reportRecursiveCopying(context
, outputPath
.getPath(), appendToPath(possibleBaseOuputPath
, outputRelativePath
),
78 CompilerBundle
.message("module.output.directory", ModuleUtil
.getModuleNameInReadAction(sourceModule
)),
86 private static String
[] getSourceRootUrlsInReadAction(final Module module
) {
87 return ApplicationManager
.getApplication().runReadAction(new Computable
<String
[]>() {
88 public String
[] compute() {
89 return ModuleRootManager
.getInstance(module
).getSourceRootUrls();
94 private static File
getModuleOutputPath(final Module module
) {
95 return ApplicationManager
.getApplication().runReadAction(new Computable
<File
>() {
97 public File
compute() {
98 final String url
= CompilerModuleExtension
.getInstance(module
).getCompilerOutputUrl();
99 if (url
== null) return null;
100 return new File(PathUtil
.toPresentableUrl(url
));
105 public void addLibraryLink(@NotNull final CompileContext context
,
106 @NotNull final BuildRecipe items
,
107 @NotNull final LibraryLink libraryLink
,
108 @NotNull final Module module
,
109 @Nullable final String possibleBaseOutputPath
) {
110 ApplicationManager
.getApplication().runReadAction(new Runnable() {
112 String outputRelativePath
;
113 final PackagingMethod packagingMethod
= libraryLink
.getPackagingMethod();
114 if (packagingMethod
.equals(PackagingMethod
.COPY_FILES_AND_LINK_VIA_MANIFEST
)
115 || packagingMethod
.equals(PackagingMethod
.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST
)) {
116 outputRelativePath
= getRelativePathForManifestLinking(libraryLink
.getURI());
119 outputRelativePath
= libraryLink
.getURI();
122 final List
<String
> urls
= libraryLink
.getClassesRootUrls();
124 boolean isDestinationDirectory
= true;
125 if (LibraryLink
.MODULE_LEVEL
.equals(libraryLink
.getLevel()) && urls
.size() == 1 && outputRelativePath
.endsWith(JAR_SUFFIX
)) {
126 isDestinationDirectory
= false;
129 for (String url
: urls
) {
130 final String path
= PathUtil
.toPresentableUrl(url
);
131 final File file
= new File(path
);
132 boolean packagingMethodIsCopy
= packagingMethod
.equals(PackagingMethod
.COPY_FILES_AND_LINK_VIA_MANIFEST
) ||
133 packagingMethod
.equals(PackagingMethod
.COPY_FILES
);
135 String fileDestination
= outputRelativePath
;
136 if (isDestinationDirectory
) {
137 if (file
.isDirectory()) {
138 if (!packagingMethodIsCopy
) {
139 fileDestination
= appendToPath(fileDestination
, file
.getName() + JAR_SUFFIX
);
143 fileDestination
= appendToPath(fileDestination
, file
.getName());
147 if (file
.isDirectory()) {
149 if (packagingMethodIsCopy
) {
150 ok
= addItemsRecursively(items
, file
, module
, fileDestination
, null, possibleBaseOutputPath
);
153 fixPackagingMethod(packagingMethod
, libraryLink
, context
);
154 BuildInstruction instruction
= new JarAndCopyBuildInstructionImpl(module
, file
, fileDestination
);
155 items
.addInstruction(instruction
);
159 final String name
= libraryLink
.getPresentableName();
160 String additionalMessage
= CompilerBundle
.message("message.text.adjust.library.path");
161 reportRecursiveCopying(context
, file
.getPath(), fileDestination
,
162 CompilerBundle
.message("directory.description.library.directory", name
), additionalMessage
);
166 items
.addFileCopyInstruction(file
, false, module
, fileDestination
, null);
173 private static void fixPackagingMethod(final PackagingMethod packagingMethod
, final LibraryLink libraryLink
, final CompileContext context
) {
174 if (!packagingMethod
.equals(PackagingMethod
.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST
) &&
175 !packagingMethod
.equals(PackagingMethod
.JAR_AND_COPY_FILE
)) {
176 libraryLink
.setPackagingMethod(PackagingMethod
.JAR_AND_COPY_FILE
);
177 String message
= CompilerBundle
.message("message.text.packaging.method.for.library.reset", libraryLink
.getPresentableName(),
178 PackagingMethod
.JAR_AND_COPY_FILE
);
179 context
.addMessage(CompilerMessageCategory
.WARNING
, message
, null, -1, -1);
183 public void copyFile(@NotNull final File fromFile
,
184 @NotNull final File toFile
,
185 @NotNull CompileContext context
,
186 @Nullable Set
<String
> writtenPaths
,
187 @Nullable FileFilter fileFilter
) throws IOException
{
188 if (fileFilter
!= null && !fileFilter
.accept(fromFile
)) {
191 checkPathDoNotNavigatesUpFromFile(fromFile
);
192 checkPathDoNotNavigatesUpFromFile(toFile
);
193 if (fromFile
.isDirectory()) {
194 final File
[] fromFiles
= fromFile
.listFiles();
196 for (File file
: fromFiles
) {
197 copyFile(file
, new File(toFile
, file
.getName()), context
, writtenPaths
, fileFilter
);
201 if (toFile
.isDirectory()) {
202 context
.addMessage(CompilerMessageCategory
.ERROR
,
203 CompilerBundle
.message("message.text.destination.is.directory", createCopyErrorMessage(fromFile
, toFile
)), null, -1, -1);
206 if (fromFile
.equals(toFile
)
207 || writtenPaths
!= null && !writtenPaths
.add(toFile
.getPath())) {
210 if (!FileUtil
.isFilePathAcceptable(toFile
, fileFilter
)) return;
211 if (context
.getProgressIndicator() != null) {
212 context
.getProgressIndicator().setText("Copying files");
213 context
.getProgressIndicator().setText2(fromFile
.getPath());
216 if (LOG
.isDebugEnabled()) {
217 LOG
.debug("Copy file '" + fromFile
+ "' to '"+toFile
+"'");
219 if (toFile
.exists() && !SystemInfo
.isFileSystemCaseSensitive
) {
220 File canonicalFile
= toFile
.getCanonicalFile();
221 if (!canonicalFile
.getAbsolutePath().equals(toFile
.getAbsolutePath())) {
222 FileUtil
.delete(toFile
);
225 FileUtil
.copy(fromFile
, toFile
);
227 catch (IOException e
) {
228 context
.addMessage(CompilerMessageCategory
.ERROR
, createCopyErrorMessage(fromFile
, toFile
) + ": "+e
.getLocalizedMessage(), null, -1, -1);
232 // OS X is sensitive for that
233 private static void checkPathDoNotNavigatesUpFromFile(File file
) {
234 String path
= file
.getPath();
235 int i
= path
.indexOf("..");
237 String filepath
= path
.substring(0,i
-1);
238 File filepart
= new File(filepath
);
239 if (filepart
.exists() && !filepart
.isDirectory()) {
240 LOG
.error("Incorrect file path: '" + path
+ '\'');
245 private static String
createCopyErrorMessage(final File fromFile
, final File toFile
) {
246 return CompilerBundle
.message("message.text.error.copying.file.to.file", FileUtil
.toSystemDependentName(fromFile
.getPath()),
247 FileUtil
.toSystemDependentName(toFile
.getPath()));
250 public final boolean addItemsRecursively(@NotNull BuildRecipe items
,
253 String outputRelativePath
,
254 @Nullable PackagingFileFilter fileFilter
,
255 @Nullable String possibleBaseOutputPath
) {
256 if (outputRelativePath
== null) outputRelativePath
= "";
257 outputRelativePath
= trimForwardSlashes(outputRelativePath
);
259 if (possibleBaseOutputPath
!= null) {
260 File file
= new File(possibleBaseOutputPath
, outputRelativePath
);
261 String relativePath
= getRelativePath(root
, file
);
262 if (relativePath
!= null && !relativePath
.startsWith("..") && relativePath
.length() != 0) {
267 items
.addFileCopyInstruction(root
, true, module
, outputRelativePath
, fileFilter
);
271 public void reportDeploymentDescriptorDoesNotExists(ConfigFile descriptor
, CompileContext context
, Module module
) {
272 final String description
= module
.getModuleType().getName() + " '" + module
.getName() + '\'';
273 String descriptorPath
= VfsUtil
.urlToPath(descriptor
.getUrl());
274 final String message
=
275 CompilerBundle
.message("message.text.compiling.item.deployment.descriptor.could.not.be.found", description
, descriptorPath
);
276 context
.addMessage(CompilerMessageCategory
.ERROR
, message
, null, -1, -1);
279 public static String
getRelativePathForManifestLinking(String relativePath
) {
280 if (!StringUtil
.startsWithChar(relativePath
, '/')) relativePath
= '/' + relativePath
;
281 relativePath
= ".." + relativePath
;
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 static void setManifestAttributes(final Attributes mainAttributes
, final @Nullable List
<String
> classpathElements
) {
301 if (classpathElements
!= null && classpathElements
.size() > 0) {
302 StringBuilder builder
;
303 Set
<String
> existingPaths
= new HashSet
<String
>();
304 String oldClassPath
= mainAttributes
.getValue(Attributes
.Name
.CLASS_PATH
);
305 if (oldClassPath
!= null) {
306 StringTokenizer tokenizer
= new StringTokenizer(oldClassPath
);
307 while (tokenizer
.hasMoreTokens()) {
308 existingPaths
.add(tokenizer
.nextToken());
310 builder
= new StringBuilder(oldClassPath
);
313 builder
= new StringBuilder();
316 for (String path
: classpathElements
) {
317 if (!existingPaths
.contains(path
)) {
318 if (builder
.length() > 0) {
321 builder
.append(path
);
324 mainAttributes
.put(Attributes
.Name
.CLASS_PATH
, builder
.toString());
326 ManifestBuilder
.setGlobalAttributes(mainAttributes
);
329 public static List
<String
> getExternalDependenciesClasspath(final BuildRecipe buildRecipe
) {
330 final List
<String
> classpath
= new ArrayList
<String
>();
332 buildRecipe
.visitInstructions(new BuildInstructionVisitor() {
333 public boolean visitInstruction(BuildInstruction instruction
) throws RuntimeException
{
334 final String outputRelativePath
= instruction
.getOutputRelativePath();
335 if (instruction
.isExternalDependencyInstruction()) {
336 final String jarReference
= PathUtil
.getCanonicalPath("/tmp/" + outputRelativePath
).substring(1);
337 classpath
.add(trimForwardSlashes(jarReference
));
345 public void addJavaModuleOutputs(@NotNull final Module module
,
346 @NotNull ModuleLink
[] containingModules
,
347 @NotNull BuildRecipe instructions
,
348 @NotNull CompileContext context
,
349 String explodedPath
, final String linkContainerDescription
) {
350 addJavaModuleOutputs(module
, containingModules
, instructions
, context
, explodedPath
, linkContainerDescription
, null);
353 public void addJavaModuleOutputs(@NotNull final Module module
,
354 @NotNull ModuleLink
[] containingModules
,
355 @NotNull BuildRecipe instructions
,
356 @NotNull CompileContext context
,
357 @Nullable String possibleExplodedPath
, final String linkContainerDescription
, @Nullable Map
<Module
, PackagingFileFilter
> fileFilters
) {
358 for (ModuleLink moduleLink
: containingModules
) {
359 Module childModule
= moduleLink
.getModule();
360 if (childModule
!= null) {
361 final PackagingMethod packagingMethod
= moduleLink
.getPackagingMethod();
362 if (PackagingMethod
.DO_NOT_PACKAGE
.equals(packagingMethod
)) {
366 PackagingFileFilter fileFilter
= fileFilters
!= null ? fileFilters
.get(childModule
) : null;
368 if (PackagingMethod
.JAR_AND_COPY_FILE
.equals(packagingMethod
)) {
369 addJarJavaModuleOutput(instructions
, childModule
, moduleLink
.getURI(), context
, linkContainerDescription
, fileFilter
);
371 else if (PackagingMethod
.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST
.equals(packagingMethod
)) {
372 String relativePath
= getRelativePathForManifestLinking(moduleLink
.getURI());
373 addJarJavaModuleOutput(instructions
, childModule
, relativePath
, context
, linkContainerDescription
, fileFilter
);
375 else if (PackagingMethod
.COPY_FILES
.equals(packagingMethod
)) {
376 addModuleOutputContents(context
, instructions
, childModule
, module
, moduleLink
.getURI(), possibleExplodedPath
, fileFilter
);
378 else if (PackagingMethod
.COPY_FILES_AND_LINK_VIA_MANIFEST
.equals(packagingMethod
)) {
379 moduleLink
.setPackagingMethod(PackagingMethod
.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST
);
380 String relativePath
= getRelativePathForManifestLinking(moduleLink
.getURI());
381 addJarJavaModuleOutput(instructions
, childModule
, relativePath
, context
, linkContainerDescription
, fileFilter
);
382 context
.addMessage(CompilerMessageCategory
.WARNING
,
383 CompilerBundle
.message("message.text.packaging.method.for.module.reset.to.method",
384 ModuleUtil
.getModuleNameInReadAction(childModule
),
385 PackagingMethod
.JAR_AND_COPY_FILE_AND_LINK_VIA_MANIFEST
),
389 LOG
.info("invalid packaging method " + packagingMethod
+ " for module '" + ModuleUtil
.getModuleNameInReadAction(childModule
)
390 + "' in module '" + ModuleUtil
.getModuleNameInReadAction(module
) + "'");
396 private static void addJarJavaModuleOutput(BuildRecipe instructions
,
399 CompileContext context
, final String linkContainerDescription
, @Nullable PackagingFileFilter fileFilter
) {
400 final String
[] sourceUrls
= getSourceRootUrlsInReadAction(module
);
401 if (sourceUrls
.length
> 0) {
402 final File outputPath
= getModuleOutputPath(module
);
403 if (outputPath
!= null) {
404 if ("/".equals(relativePath
) || "".equals(relativePath
) || getRelativePathForManifestLinking("/").equals(relativePath
)) {
405 context
.addMessage(CompilerMessageCategory
.ERROR
, CompilerBundle
.message("message.text.invalid.output.path.for.module.jar", relativePath
, module
.getName(), linkContainerDescription
), null, -1, -1);
408 instructions
.addInstruction(new JarAndCopyBuildInstructionImpl(module
, outputPath
, relativePath
, fileFilter
));
414 public ModuleLink
createModuleLink(@NotNull Module dep
, @NotNull Module module
) {
415 return new ModuleLinkImpl(dep
, module
);
418 public LibraryLink
createLibraryLink(Library library
, @NotNull Module parentModule
) {
419 return new LibraryLinkImpl(library
, parentModule
);
422 public BuildRecipe
createBuildRecipe() {
423 return new BuildRecipeImpl();
427 public String
getConfigFileErrorMessage(final ConfigFile configFile
) {
428 if (configFile
.getVirtualFile() == null) {
429 String path
= FileUtil
.toSystemDependentName(VfsUtil
.urlToPath(configFile
.getUrl()));
430 return CompilerBundle
.message("mesage.text.deployment.descriptor.file.not.exist", path
);
432 PsiFile psiFile
= configFile
.getPsiFile();
433 if (psiFile
== null || !psiFile
.isValid()) {
434 return CompilerBundle
.message("message.text.deployment.description.invalid.file");
437 if (psiFile
instanceof XmlFile
) {
438 XmlDocument document
= ((XmlFile
)psiFile
).getDocument();
439 if (document
== null || document
.getRootTag() == null) {
440 return CompilerBundle
.message("message.text.xml.file.invalid", FileUtil
.toSystemDependentName(VfsUtil
.urlToPath(configFile
.getUrl())));
447 public static String
getOrCreateExplodedDir(final BuildParticipant buildParticipant
) {
448 BuildConfiguration buildConfiguration
= buildParticipant
.getBuildConfiguration();
449 if (buildConfiguration
.isExplodedEnabled()) {
450 return buildConfiguration
.getExplodedPath();
452 return buildParticipant
.getOrCreateTemporaryDirForExploded();
455 public static BuildParticipantProvider
[] getBuildParticipantProviders() {
456 return Extensions
.getExtensions(BuildParticipantProvider
.EXTENSION_POINT_NAME
);