building plugins migrated to use artifacts compiler
[fedora-idea.git] / java / compiler / impl / src / com / intellij / openapi / deployment / DeploymentUtilImpl.java
blob3ffc51c56ea4fd935796f7b032b368b380015a1f
1 /*
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;
45 import java.io.File;
46 import java.io.FileFilter;
47 import java.io.IOException;
48 import java.util.*;
49 import java.util.jar.Attributes;
51 /**
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,
61 Module targetModule,
62 final String outputRelativePath,
63 @Nullable String possibleBaseOuputPath,
64 @Nullable PackagingFileFilter fileFilter) {
65 final File outputPath = getModuleOutputPath(sourceModule);
67 String[] sourceRoots = getSourceRootUrlsInReadAction(sourceModule);
68 boolean ok = true;
69 if (outputPath != null && sourceRoots.length != 0) {
70 ok = outputPath.exists();
71 boolean added = addItemsRecursively(items, outputPath, targetModule, outputRelativePath, fileFilter, possibleBaseOuputPath);
72 if (!added) {
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)),
79 additionalMessage);
80 ok = false;
83 return ok;
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();
91 });
94 private static File getModuleOutputPath(final Module module) {
95 return ApplicationManager.getApplication().runReadAction(new Computable<File>() {
96 @Nullable
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() {
111 public void run() {
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());
118 else {
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);
142 else {
143 fileDestination = appendToPath(fileDestination, file.getName());
147 if (file.isDirectory()) {
148 boolean ok;
149 if (packagingMethodIsCopy) {
150 ok = addItemsRecursively(items, file, module, fileDestination, null, possibleBaseOutputPath);
152 else {
153 fixPackagingMethod(packagingMethod, libraryLink, context);
154 BuildInstruction instruction = new JarAndCopyBuildInstructionImpl(module, file, fileDestination);
155 items.addInstruction(instruction);
156 ok = true;
158 if (!ok) {
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);
165 else {
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)) {
189 return;
191 checkPathDoNotNavigatesUpFromFile(fromFile);
192 checkPathDoNotNavigatesUpFromFile(toFile);
193 if (fromFile.isDirectory()) {
194 final File[] fromFiles = fromFile.listFiles();
195 toFile.mkdirs();
196 for (File file : fromFiles) {
197 copyFile(file, new File(toFile, file.getName()), context, writtenPaths, fileFilter);
199 return;
201 if (toFile.isDirectory()) {
202 context.addMessage(CompilerMessageCategory.ERROR,
203 CompilerBundle.message("message.text.destination.is.directory", createCopyErrorMessage(fromFile, toFile)), null, -1, -1);
204 return;
206 if (fromFile.equals(toFile)
207 || writtenPaths != null && !writtenPaths.add(toFile.getPath())) {
208 return;
210 if (!FileUtil.isFilePathAcceptable(toFile, fileFilter)) return;
211 if (context.getProgressIndicator() != null) {
212 context.getProgressIndicator().setText("Copying files");
213 context.getProgressIndicator().setText2(fromFile.getPath());
215 try {
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("..");
236 if (i != -1) {
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,
251 @NotNull File root,
252 Module module,
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) {
263 return false;
267 items.addFileCopyInstruction(root, true, module, outputRelativePath, fileFilter);
268 return true;
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;
282 return 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);
295 else {
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);
312 else {
313 builder = new StringBuilder();
316 for (String path : classpathElements) {
317 if (!existingPaths.contains(path)) {
318 if (builder.length() > 0) {
319 builder.append(' ');
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));
339 return true;
341 }, false);
342 return classpath;
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)) {
363 continue;
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),
386 null, -1, -1);
388 else {
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,
397 Module module,
398 String relativePath,
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);
407 else {
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();
426 @Nullable
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())));
443 return null;
446 @Nullable
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);