| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jetbrains.idea.devkit.build; |
| |
| import com.intellij.compiler.server.CompileServerPlugin; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.LangDataKeys; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.compiler.CompileContext; |
| import com.intellij.openapi.compiler.CompileStatusNotification; |
| import com.intellij.openapi.compiler.CompilerManager; |
| import com.intellij.openapi.compiler.make.ManifestBuilder; |
| import com.intellij.openapi.fileTypes.FileTypeManager; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleManager; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.CompilerModuleExtension; |
| import com.intellij.openapi.roots.OrderRootType; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.*; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.PathUtil; |
| import com.intellij.util.io.ZipUtil; |
| import com.intellij.util.xml.DomFileElement; |
| import com.intellij.util.xml.DomManager; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.idea.devkit.DevKitBundle; |
| import org.jetbrains.idea.devkit.dom.Extensions; |
| import org.jetbrains.idea.devkit.dom.IdeaPlugin; |
| import org.jetbrains.idea.devkit.module.PluginModuleType; |
| |
| import java.io.*; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.*; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarOutputStream; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| |
| /** |
| * User: anna |
| * Date: May 5, 2005 |
| */ |
| public class PrepareToDeployAction extends AnAction { |
| @NonNls private static final String ZIP_EXTENSION = ".zip"; |
| @NonNls private static final String JAR_EXTENSION = ".jar"; |
| @NonNls private static final String TEMP_PREFIX = "temp"; |
| @NonNls private static final String MIDDLE_LIB_DIR = "lib"; |
| |
| public void actionPerformed(final AnActionEvent e) { |
| final Module module = LangDataKeys.MODULE.getData(e.getDataContext()); |
| if (module != null && PluginModuleType.isOfType(module)) { |
| doPrepare(Arrays.asList(module), CommonDataKeys.PROJECT.getData(e.getDataContext())); |
| } |
| } |
| |
| public void doPrepare(final List<Module> pluginModules, final Project project) { |
| final List<String> errorMessages = new ArrayList<String>(); |
| final List<String> successMessages = new ArrayList<String>(); |
| |
| final CompilerManager compilerManager = CompilerManager.getInstance(project); |
| compilerManager.make(compilerManager.createModulesCompileScope(pluginModules.toArray(new Module[pluginModules.size()]), true), |
| new CompileStatusNotification() { |
| public void finished(final boolean aborted, |
| final int errors, |
| final int warnings, |
| final CompileContext compileContext) { |
| if (aborted || errors != 0) return; |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| public void run() { |
| for (Module aModule : pluginModules) { |
| if (!doPrepare(aModule, errorMessages, successMessages)) { |
| return; |
| } |
| } |
| |
| if (!errorMessages.isEmpty()) { |
| Messages.showErrorDialog(errorMessages.iterator().next(), DevKitBundle.message("error.occurred")); |
| } |
| else if (!successMessages.isEmpty()) { |
| StringBuilder messageBuf = new StringBuilder(); |
| for (String message : successMessages) { |
| if (messageBuf.length() != 0) { |
| messageBuf.append('\n'); |
| } |
| messageBuf.append(message); |
| } |
| Messages.showInfoMessage(messageBuf.toString(), |
| pluginModules.size() == 1 |
| ? DevKitBundle.message("success.deployment.message", pluginModules.get(0).getName()) |
| : DevKitBundle.message("success.deployment.message.all")); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| public static boolean doPrepare(final Module module, final List<String> errorMessages, final List<String> successMessages) { |
| final String pluginName = module.getName(); |
| final String defaultPath = new File(module.getModuleFilePath()).getParent() + File.separator + pluginName; |
| final HashSet<Module> modules = new HashSet<Module>(); |
| PluginBuildUtil.getDependencies(module, modules); |
| modules.add(module); |
| final Set<Library> libs = new HashSet<Library>(); |
| for (Module dep : modules) { |
| PluginBuildUtil.getLibraries(dep, libs); |
| } |
| |
| final Map<Module, String> jpsModules = collectJpsPluginModules(module); |
| modules.removeAll(jpsModules.keySet()); |
| |
| final boolean isZip = !libs.isEmpty() || !jpsModules.isEmpty(); |
| final String oldPath = defaultPath + (isZip ? JAR_EXTENSION : ZIP_EXTENSION); |
| final File oldFile = new File(oldPath); |
| if (oldFile.exists()) { |
| if (Messages |
| .showYesNoDialog(module.getProject(), DevKitBundle.message("suggest.to.delete", oldPath), DevKitBundle.message("info.message"), |
| Messages.getInformationIcon()) == Messages.YES) { |
| FileUtil.delete(oldFile); |
| } |
| } |
| |
| final String dstPath = defaultPath + (isZip ? ZIP_EXTENSION : JAR_EXTENSION); |
| final File dstFile = new File(dstPath); |
| return clearReadOnly(module.getProject(), dstFile) && ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { |
| public void run() { |
| |
| final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); |
| if (progressIndicator != null) { |
| progressIndicator.setText(DevKitBundle.message("prepare.for.deployment.common")); |
| progressIndicator.setIndeterminate(true); |
| } |
| try { |
| File jarFile = preparePluginsJar(module, modules); |
| if (isZip) { |
| processLibrariesAndJpsPlugins(jarFile, dstFile, pluginName, libs, jpsModules, progressIndicator); |
| } |
| else { |
| FileUtil.copy(jarFile, dstFile); |
| } |
| LocalFileSystem.getInstance().refreshIoFiles(Collections.singleton(dstFile), true, false, null); |
| successMessages.add(DevKitBundle.message("saved.message", isZip ? 1 : 2, pluginName, dstPath)); |
| } |
| catch (final IOException e) { |
| errorMessages.add(e.getMessage() + "\n(" + dstPath + ")"); |
| } |
| } |
| }, DevKitBundle.message("prepare.for.deployment", pluginName), true, module.getProject()); |
| |
| } |
| |
| @NotNull |
| private static Map<Module, String> collectJpsPluginModules(@NotNull Module module) { |
| XmlFile pluginXml = PluginModuleType.getPluginXml(module); |
| if (pluginXml == null) return Collections.emptyMap(); |
| |
| DomFileElement<IdeaPlugin> fileElement = DomManager.getDomManager(module.getProject()).getFileElement(pluginXml, IdeaPlugin.class); |
| if (fileElement == null) return Collections.emptyMap(); |
| |
| Map<Module, String> jpsPluginToOutputPath = new HashMap<Module, String>(); |
| IdeaPlugin plugin = fileElement.getRootElement(); |
| List<Extensions> extensions = plugin.getExtensions(); |
| for (Extensions extensionGroup : extensions) { |
| XmlTag extensionsTag = extensionGroup.getXmlTag(); |
| String defaultExtensionNs = extensionsTag.getAttributeValue("defaultExtensionNs"); |
| for (XmlTag tag : extensionsTag.getSubTags()) { |
| String name = tag.getLocalName(); |
| String qualifiedName = defaultExtensionNs != null ? defaultExtensionNs + "." + name : name; |
| if (CompileServerPlugin.EP_NAME.getName().equals(qualifiedName)) { |
| String classpath = tag.getAttributeValue("classpath"); |
| if (classpath != null) { |
| for (String path : StringUtil.split(classpath, ";")) { |
| String moduleName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(path)); |
| Module jpsModule = ModuleManager.getInstance(module.getProject()).findModuleByName(moduleName); |
| if (jpsModule != null) { |
| jpsPluginToOutputPath.put(jpsModule, path); |
| } |
| } |
| } |
| } |
| } |
| } |
| return jpsPluginToOutputPath; |
| } |
| |
| private static boolean clearReadOnly(final Project project, final File dstFile) { |
| //noinspection EmptyCatchBlock |
| final URL url; |
| try { |
| url = dstFile.toURL(); |
| } |
| catch (MalformedURLException e) { |
| return true; |
| } |
| final VirtualFile vfile = VfsUtil.findFileByURL(url); |
| return vfile == null || !ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(vfile).hasReadonlyFiles(); |
| } |
| |
| private static FileFilter createFilter(final ProgressIndicator progressIndicator, @Nullable final FileTypeManager fileTypeManager) { |
| return new FileFilter() { |
| public boolean accept(File pathName) { |
| if (progressIndicator != null) { |
| progressIndicator.setText2(""); |
| } |
| return fileTypeManager == null || !fileTypeManager.isFileIgnored(FileUtil.toSystemIndependentName(pathName.getName())); |
| } |
| }; |
| } |
| |
| private static void processLibrariesAndJpsPlugins(final File jarFile, final File zipFile, final String pluginName, |
| final Set<Library> libs, |
| Map<Module, String> jpsModules, final ProgressIndicator progressIndicator) throws IOException { |
| if (FileUtil.ensureCanCreateFile(zipFile)) { |
| ZipOutputStream zos = null; |
| try { |
| zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile))); |
| addStructure(pluginName, zos); |
| addStructure(pluginName + "/" + MIDDLE_LIB_DIR, zos); |
| final String entryName = pluginName + JAR_EXTENSION; |
| ZipUtil.addFileToZip(zos, jarFile, getZipPath(pluginName, entryName), new HashSet<String>(), |
| createFilter(progressIndicator, FileTypeManager.getInstance())); |
| for (Map.Entry<Module, String> entry : jpsModules.entrySet()) { |
| File jpsPluginJar = jarModulesOutput(Collections.singleton(entry.getKey()), null, null); |
| ZipUtil.addFileToZip(zos, jpsPluginJar, getZipPath(pluginName, entry.getValue()), null, null); |
| } |
| Set<String> usedJarNames = new HashSet<String>(); |
| usedJarNames.add(entryName); |
| Set<VirtualFile> jarredVirtualFiles = new HashSet<VirtualFile>(); |
| for (Library library : libs) { |
| final VirtualFile[] files = library.getFiles(OrderRootType.CLASSES); |
| for (VirtualFile virtualFile : files) { |
| if (jarredVirtualFiles.add(virtualFile)) { |
| if (virtualFile.getFileSystem() instanceof JarFileSystem) { |
| addLibraryJar(virtualFile, zipFile, pluginName, zos, usedJarNames, progressIndicator); |
| } |
| else { |
| makeAndAddLibraryJar(virtualFile, zipFile, pluginName, zos, usedJarNames, progressIndicator, library.getName()); |
| } |
| } |
| } |
| } |
| } |
| finally { |
| if (zos != null) zos.close(); |
| } |
| } |
| } |
| |
| private static String getZipPath(final String pluginName, final String entryName) { |
| return "/" + pluginName + "/" + MIDDLE_LIB_DIR + "/" + entryName; |
| } |
| |
| private static void makeAndAddLibraryJar(final VirtualFile virtualFile, |
| final File zipFile, |
| final String pluginName, |
| final ZipOutputStream zos, |
| final Set<String> usedJarNames, |
| final ProgressIndicator progressIndicator, |
| final String preferredName) throws IOException { |
| File libraryJar = FileUtil.createTempFile(TEMP_PREFIX, JAR_EXTENSION); |
| libraryJar.deleteOnExit(); |
| ZipOutputStream jar = null; |
| try { |
| jar = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(libraryJar))); |
| ZipUtil.addFileOrDirRecursively(jar, libraryJar, VfsUtilCore.virtualToIoFile(virtualFile), "", |
| createFilter(progressIndicator, FileTypeManager.getInstance()), null); |
| } |
| finally { |
| if (jar != null) jar.close(); |
| } |
| final String jarName = |
| getLibraryJarName(virtualFile.getName() + JAR_EXTENSION, usedJarNames, preferredName == null ? null : preferredName + JAR_EXTENSION); |
| ZipUtil.addFileOrDirRecursively(zos, zipFile, libraryJar, getZipPath(pluginName, jarName), createFilter(progressIndicator, null), null); |
| } |
| |
| private static String getLibraryJarName(final String fileName, Set<String> usedJarNames, @Nullable final String preferredName) { |
| String uniqueName; |
| if (preferredName != null && !usedJarNames.contains(preferredName)) { |
| uniqueName = preferredName; |
| } |
| else { |
| uniqueName = fileName; |
| if (usedJarNames.contains(uniqueName)) { |
| final int dotPos = uniqueName.lastIndexOf("."); |
| final String name = dotPos < 0 ? uniqueName : uniqueName.substring(0, dotPos); |
| final String ext = dotPos < 0 ? "" : uniqueName.substring(dotPos); |
| |
| int i = 0; |
| do { |
| i++; |
| uniqueName = name + i + ext; |
| } |
| while (usedJarNames.contains(uniqueName)); |
| } |
| } |
| usedJarNames.add(uniqueName); |
| return uniqueName; |
| } |
| |
| private static void addLibraryJar(final VirtualFile virtualFile, |
| final File zipFile, |
| final String pluginName, |
| final ZipOutputStream zos, |
| final Set<String> usedJarNames, |
| final ProgressIndicator progressIndicator) throws IOException { |
| File ioFile = VfsUtil.virtualToIoFile(virtualFile); |
| final String jarName = getLibraryJarName(ioFile.getName(), usedJarNames, null); |
| ZipUtil.addFileOrDirRecursively(zos, zipFile, ioFile, getZipPath(pluginName, jarName), createFilter(progressIndicator, null), null); |
| } |
| |
| private static void addStructure(@NonNls final String relativePath, final ZipOutputStream zos) throws IOException { |
| ZipEntry e = new ZipEntry(relativePath + "/"); |
| e.setMethod(ZipEntry.STORED); |
| e.setSize(0); |
| e.setCrc(0); |
| zos.putNextEntry(e); |
| zos.closeEntry(); |
| } |
| |
| private static File preparePluginsJar(Module module, final HashSet<Module> modules) throws IOException { |
| final PluginBuildConfiguration pluginModuleBuildProperties = PluginBuildConfiguration.getInstance(module); |
| final Manifest manifest = createOrFindManifest(pluginModuleBuildProperties); |
| |
| return jarModulesOutput(modules, manifest, pluginModuleBuildProperties.getPluginXmlPath()); |
| } |
| |
| private static File jarModulesOutput(@NotNull Set<Module> modules, @Nullable Manifest manifest, final @Nullable String pluginXmlPath) throws IOException { |
| File jarFile = FileUtil.createTempFile(TEMP_PREFIX, JAR_EXTENSION); |
| jarFile.deleteOnExit(); |
| ZipOutputStream jarPlugin = null; |
| try { |
| BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(jarFile)); |
| jarPlugin = manifest != null ? new JarOutputStream(out, manifest) : new JarOutputStream(out); |
| final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); |
| final Set<String> writtenItemRelativePaths = new HashSet<String>(); |
| for (Module module : modules) { |
| final VirtualFile compilerOutputPath = CompilerModuleExtension.getInstance(module).getCompilerOutputPath(); |
| if (compilerOutputPath == null) continue; //pre-condition: output dirs for all modules are up-to-date |
| ZipUtil.addDirToZipRecursively(jarPlugin, jarFile, new File(compilerOutputPath.getPath()), "", |
| createFilter(progressIndicator, FileTypeManager.getInstance()), writtenItemRelativePaths); |
| } |
| if (pluginXmlPath != null) { |
| ZipUtil.addFileToZip(jarPlugin, new File(pluginXmlPath), "/META-INF/plugin.xml", writtenItemRelativePaths, |
| createFilter(progressIndicator, null)); |
| } |
| } |
| finally { |
| if (jarPlugin != null) jarPlugin.close(); |
| } |
| return jarFile; |
| } |
| |
| public static Manifest createOrFindManifest(final PluginBuildConfiguration pluginModuleBuildProperties) throws IOException { |
| final Manifest manifest = new Manifest(); |
| final VirtualFile vManifest = pluginModuleBuildProperties.getManifest(); |
| if (pluginModuleBuildProperties.isUseUserManifest() && vManifest != null) { |
| InputStream in = null; |
| try { |
| in = new BufferedInputStream(vManifest.getInputStream()); |
| manifest.read(in); |
| } |
| finally { |
| if (in != null) in.close(); |
| } |
| } |
| else { |
| Attributes mainAttributes = manifest.getMainAttributes(); |
| ManifestBuilder.setGlobalAttributes(mainAttributes); |
| } |
| return manifest; |
| } |
| |
| public void update(AnActionEvent e) { |
| final Module module = LangDataKeys.MODULE.getData(e.getDataContext()); |
| boolean enabled = module != null && PluginModuleType.isOfType(module); |
| e.getPresentation().setVisible(enabled); |
| e.getPresentation().setEnabled(enabled); |
| if (enabled) { |
| e.getPresentation().setText(DevKitBundle.message("prepare.for.deployment", module.getName())); |
| } |
| } |
| } |