/* * Copyright 2000-2013 JetBrains s.r.o. * Copyright 2014-2014 AS3Boyan * Copyright 2014-2014 Elias Ku * * 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 com.intellij.plugins.haxe.haxelib; import com.google.common.base.Joiner; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.RootProvider; import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.plugins.haxe.config.HaxeTarget; import com.intellij.plugins.haxe.hxml.HXMLFileType; import com.intellij.plugins.haxe.hxml.psi.HXMLClasspath; import com.intellij.plugins.haxe.ide.module.HaxeModuleSettings; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlDocument; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import org.apache.log4j.Level; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.io.LocalFileFinder; import com.intellij.util.io.URLUtil; import java.io.File; import java.util.*; /** * Static interface to haxelib class path functionality. */ public class HaxelibClasspathUtils { static Logger LOG = Logger.getInstance("#com.intellij.plugins.haxe.haxelib.HaxelibClasspathUtils"); static { LOG.setLevel(Level.DEBUG); } /** Old environment variable name for the Haxe standard library location. */ final public static String HAXE_LIBRARY_PATH = "HAXE_LIBRARY_PATH"; /** Modern environment variable name for the Haxe standard library location. */ final public static String HAXE_STD_PATH = "HAXE_STD_PATH"; /** * Gets the libraries specified for the IDEA project; source paths and * class paths for project libraries excepting those named "haxelib|<lib_name>". * * @param project a project to get the class path settings for. * @return a list of class path URLs. */ @NotNull public static HaxeClasspath getUnmanagedProjectLibraryClasspath(@NotNull Project project) { LibraryTable libraryTable = ProjectLibraryTable.getInstance(project); if (null == libraryTable) return HaxeClasspath.EMPTY_CLASSPATH; HaxeClasspath classpath = new HaxeClasspath(); Library[] libraries = libraryTable.getLibraries(); for (Library library : libraries) { // // This is checking that the library name doesn't match "haxelib|lib_name". // That is, if it /is/ a haxelib entry, ignore it; grab the classpaths for // libs that aren't haxelibs. // if (!HaxelibParser.isManagedLibrary(library.getName())) { OrderRootType interestingRootTypes[] = {OrderRootType.SOURCES, OrderRootType.CLASSES}; for (OrderRootType rootType : interestingRootTypes) { for (String url : library.getUrls(rootType)) { if (!classpath.containsUrl(url)) { // The if just keeps us from churning. classpath.add(new HaxeIdeaItem(library.getName(), url)); } } } } } return classpath; } /** * Get the list of library names specified on the project. This *does not* * filter managed libraries of the form "haxelib|<lib_name>". * * @param project to get the libraries for. * @return a (possibly empty) list of libraries that are specified for the * project (in the library pane). */ @NotNull public static List<String> getProjectLibraryNames(@NotNull Project project) { return getProjectLibraryNames(project, false); } /** * Get the list of library names specified on the project. Managed haxelib * (of the form "haxelib|<lib_name>") libraries are included unless * filterManagedLibs is true. * * @param project to get the libraries for. * @param filterManagedLibs whether to remove managed haxelibs from the list. * @return a (possibly empty) list of libraries that are specified for the * project (in the library pane). */ @NotNull public static List<String> getProjectLibraryNames(@NotNull Project project, boolean filterManagedLibs) { List<String> nameList = new ArrayList<String>(); LibraryTable libraryTable = ProjectLibraryTable.getInstance(project); Library[] libraries = libraryTable.getLibraries(); for (Library library : libraries) { if (filterManagedLibs && HaxelibParser.isManagedLibrary(library.getName())) { continue; } nameList.add(library.getName()); } return nameList; } /** * Loads a classpath from the given library table. Workhorse for other APIs * in this class. * * @param libraryTable to load * @return the classpath */ @NotNull private static HaxeClasspath loadClasspathFrom(LibraryTable libraryTable) { HaxeClasspath classpath = new HaxeClasspath(); Library[] libraries = libraryTable.getLibraries(); OrderRootType interestingRootTypes[] = {OrderRootType.SOURCES, OrderRootType.CLASSES}; for (Library library : libraries) { for (OrderRootType rootType : interestingRootTypes) { for (String url : library.getUrls(rootType)) { if (!classpath.containsUrl(url)) { classpath.add(new HaxeIdeaItem(library.getName(), url)); } } } } return classpath; } /** * Get the classpath for all libraries in the project. This *does not* * filter managed libraries of the form "haxelib|<lib_name>". * * @param project to get the classpath for. * @return a (possibly empty) list of the classpaths for all of the libraries * that are specified on the project (in the library pane). */ @NotNull public static HaxeClasspath getProjectLibraryClasspath(@NotNull Project project) { LibraryTable libraryTable = ProjectLibraryTable.getInstance(project); if (null == libraryTable) return HaxeClasspath.EMPTY_CLASSPATH; return loadClasspathFrom(libraryTable); } /** * Get the classpath for the given SDK. This does not include * any paths from the project or modules. * * @param sdk to get path info from. * @return a (possibly empty) collection of class paths. These are NOT * necessarily properly ordered, but they are unique. */ @NotNull public static HaxeClasspath getSdkClasspath(@NotNull Sdk sdk) { HaxeClasspath classpath = new HaxeClasspath(); RootProvider rootProvider = sdk.getRootProvider(); OrderRootType interestingRootTypes[] = {OrderRootType.SOURCES, OrderRootType.CLASSES}; for (OrderRootType rootType : interestingRootTypes) { for (VirtualFile file : rootProvider.getFiles(rootType)) { if (!classpath.containsUrl(file.getUrl())) { classpath.add(new HaxelibItem(file.getName(), file.getUrl())); } } } return classpath; } /** * Get the classpath for the given module. This does not include any * paths from projects or SDKs. * * @param module to look up haxelib for. * @return a (possibly empty) collection of classpaths. These are NOT * necessarily properly ordered, but they are unique. */ @NotNull public static HaxeClasspath getModuleClasspath(@NotNull Module module) { ModuleRootManager rootManager = ModuleRootManager.getInstance(module); if (null == rootManager) return HaxeClasspath.EMPTY_CLASSPATH; ModifiableRootModel rootModel = rootManager.getModifiableModel(); LibraryTable libraryTable = rootModel.getModuleLibraryTable(); HaxeClasspath moduleClasspath = loadClasspathFrom(libraryTable); rootModel.dispose(); // MUST dispose of the model. return moduleClasspath; } /** * Get the classpath that the compiler will use without it being specified. * * @param module - the module to be compiled. * @return HaxeClasspath according to the module compilation options. */ @NotNull public static HaxeClasspath getImplicitClassPath(@NotNull Module module) { // Figure out which target is being compiled to, because that tells us // which backend implementation to use on the classpath. HaxeModuleSettings settings = HaxeModuleSettings.getInstance(module); HaxeTarget target = settings.getCompilationTarget(); String targetFlag = target.getFlag(); // Find the standard library. String libraryPath = System.getenv(HAXE_STD_PATH); if (null == libraryPath) { // Back off to the old name if the modern one isn't found. libraryPath = System.getenv(HAXE_LIBRARY_PATH); } if (null == libraryPath) { return HaxeClasspath.EMPTY_CLASSPATH; } HaxeClasspath cp = new HaxeClasspath(); if (null != targetFlag) { cp.add(new HaxeClasspathEntry("Std library target implementation", libraryPath + File.separator + targetFlag + File.separator + "_std")); } cp.add(new HaxeClasspathEntry("Standard Library", libraryPath)); return cp; } /** * Get the complete classpath for a module, including implicit paths and * all of the paths from the project and SDK. Must be run inside of a * read action. * * @param module to look up haxelib for. * @return a (possibly empty) collection of classpaths. These should * be properly ordered and are unique; duplicates occurring * later in the list are removed. */ @NotNull public static HaxeClasspath getFullClasspath(@NotNull Module module) { HaxeClasspath classpath = getImplicitClassPath(module); classpath.addAll(getModuleClasspath(module)); classpath.addAll(getProjectLibraryClasspath(module.getProject())); // This grabs either the module's SDK, or the inherited one, if any. classpath.addAll(getSdkClasspath(ModuleRootManager.getInstance(module).getSdk())); return classpath; } /** * Locate files and dependencies using 'haxelib path <name>' * * @param name name of the base file or library to search for. * @return a set of path name URLs, may be an empty list. */ @NotNull public static List<String> getHaxelibLibraryPathUrl(@NotNull Sdk sdk, @NotNull String name) { List<String> strings = HaxelibCommandUtils.issueHaxelibCommand(sdk, "path", name); List<String> classpathUrls = new ArrayList<String>(strings.size()); for (String string : strings) { if (!string.startsWith("-L") && !string.startsWith("-D")) { VirtualFile file = LocalFileFinder.findFile(string); if (file != null) { classpathUrls.add(file.getUrl()); } } } return classpathUrls; } /** * Locate files and dependencies using 'haxelib path <name>' * * @param name name of the base file or library to search for. * @return a set of HaxelibItems, may be an empty list. */ @NotNull public static HaxeClasspath getHaxelibLibraryPath(@NotNull Sdk sdk, @NotNull String name) { List<String> strings = HaxelibCommandUtils.issueHaxelibCommand(sdk, "path", name); HaxeClasspath classpath = new HaxeClasspath(strings.size()); for (String string : strings) { if (!string.startsWith("-L") && !string.startsWith("-D")) { VirtualFile file = LocalFileFinder.findFile(string); if (file != null) { // There are no duplicates in the return from haxelib, so no need to check contains(). classpath.add(new HaxelibItem(file.getPath(), file.getUrl())); } } } return classpath; } /** * Retrieve the list of libraries known to 'haxelib', using the version of * haxelib specified in the SDK. * * @param sdk the SDK to get installed libraries from. * @return a (possibly empty) list of libraries */ @NotNull public static List<String> getInstalledLibraries(@NotNull Sdk sdk) { // haxelib list output looks like: // lime-tools: 1.4.0 [1.5.6] // The library name comes first, followed by a colon, followed by a // list of the available versions. List<String> installedHaxelibs = new ArrayList<String>(); for (String s : HaxelibCommandUtils.issueHaxelibCommand(sdk, "list")) { installedHaxelibs.add(s.split(":")[0]); } return installedHaxelibs; } /** * Find haxe libraries matching a search word, using the version of haxelib * specified by the SDK. * * @param sdk the SDK to get installed libraries from. * @param word search word. Must follow haxelib conventions: partial words are OK, no globbing. * Empty matches everything. * @return */ @NotNull public static List<String> getAvailableLibrariesMatching(@NotNull Sdk sdk, @NotNull String word) { List<String> stringList = HaxelibCommandUtils.issueHaxelibCommand(sdk, "search", word); stringList.remove(stringList.size() - 1); return stringList; } /** * * @param project * @param dir * @param executable * @param sdk * @return */ @NotNull public static List<String> getProjectDisplayInformation(@NotNull Project project, @NotNull File dir, @NotNull String executable, @NotNull Sdk sdk) { List<String> strings1 = Collections.EMPTY_LIST; if (getInstalledLibraries(sdk).contains(executable)) { ArrayList<String> commandLineArguments = new ArrayList<String>(); commandLineArguments.add(HaxelibCommandUtils.getHaxelibPath(sdk)); commandLineArguments.add("run"); commandLineArguments.add(executable); commandLineArguments.add("display"); commandLineArguments.add("flash"); List<String> strings = HaxelibCommandUtils.getProcessStdout(commandLineArguments, dir); String s = Joiner.on("\n").join(strings); strings1 = getHXMLFileClasspaths(project, s); } return strings1; } /** * Turn some text into a file, parse it using the .hxml parser, and * return any HXML classpaths. * * @param project to include created files in. * @param text to parse. * @return A (possibly empty) list containing all of the classpaths found in the text. */ @NotNull public static List<String> getHXMLFileClasspaths(@NotNull Project project, @NotNull String text) { if (text.isEmpty()) { return Collections.EMPTY_LIST; } List<String> strings1; strings1 = new ArrayList<String>(); PsiFile psiFile = PsiFileFactory.getInstance(project).createFileFromText(HXMLFileType.INSTANCE, "data.hxml", text, 0, text.length() - 1); Collection<HXMLClasspath> hxmlClasspaths = PsiTreeUtil.findChildrenOfType(psiFile, HXMLClasspath.class); for (HXMLClasspath hxmlClasspath : hxmlClasspaths) { strings1.add(hxmlClasspath.getValue()); } return strings1; } /** * Retrieves the list of dependent haxe libraries from an XML-based * configuration file. * * @param psiFile name of the configuration file to read * @return a list of dependent libraries; may be empty, may have duplicates. * TODO: Collect names and pass them all to libraryManager.getClasspathForHaxelib(List) at once. */ @NotNull public static HaxeClasspath getHaxelibsFromXmlFile(@NotNull XmlFile psiFile, HaxelibLibraryCache libraryManager) { HaxeClasspath haxelibNewItems = new HaxeClasspath(); XmlFile xmlFile = (XmlFile)psiFile; XmlDocument document = xmlFile.getDocument(); if (document != null) { XmlTag rootTag = document.getRootTag(); if (rootTag != null) { XmlTag[] haxelibTags = rootTag.findSubTags("haxelib"); for (XmlTag haxelibTag : haxelibTags) { String name = haxelibTag.getAttributeValue("name"); if (name != null) { HaxeClasspath newPath = libraryManager.getClasspathForHaxelib(name); haxelibNewItems.addAll(newPath); } } } } return haxelibNewItems; } /** * Finds the first file on the classpath having the given name or relative path. * This is attempting to emulate what the compiler would do. * * @param module - Module from which to gather the classpath. * @param filePath - filePath name or relative path. * @return a VirtualFile if found, or null otherwise. */ @Nullable public static VirtualFile findFileOnClasspath(@NotNull final Module module, @NotNull final String filePath) { if (filePath.isEmpty()) { return null; } return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() { @Override public VirtualFile compute() { final String fixedPath = null == VirtualFileManager.extractProtocol(filePath) ? VirtualFileManager.constructUrl(URLUtil.FILE_PROTOCOL, filePath) : filePath; VirtualFile found = VirtualFileManager.getInstance().findFileByUrl(fixedPath); if (null == found) { found = findFileOnOneClasspath(getImplicitClassPath(module), filePath); } if (null == found) { found = findFileOnOneClasspath(getModuleClasspath(module), filePath); } if (null == found) { found = findFileOnOneClasspath(getProjectLibraryClasspath(module.getProject()), filePath); } if (null == found) { // This grabs either the module's SDK, or the inherited one, if any. found = findFileOnOneClasspath(getSdkClasspath(ModuleRootManager.getInstance(module).getSdk()), filePath); } return found; } }); } /** * Workhorse for findFileOnClasspath. * @param cp * @param filePath * @return */ @Nullable private static VirtualFile findFileOnOneClasspath(@NotNull HaxeClasspath cp, @NotNull final String filePath) { final VirtualFileManager vfmInstance = VirtualFileManager.getInstance(); class MyLambda implements HaxeClasspath.Lambda { public VirtualFile found = null; public boolean processEntry(HaxeClasspathEntry entry) { String dirUrl = entry.getUrl(); if (!URLUtil.containsScheme(dirUrl)) { dirUrl = vfmInstance.constructUrl(URLUtil.FILE_PROTOCOL, dirUrl); } VirtualFile dirName = vfmInstance.findFileByUrl(dirUrl); if (null != dirName && dirName.isDirectory()) { found = dirName.findFileByRelativePath(filePath); if (null != found) { return false; // Stop the search. } } return true; } } MyLambda lambda = new MyLambda(); cp.iterate(lambda); return lambda.found; } /** * Given a module and a list of files, find the first one that occurs on the * classpath. * * @param module - Module from which to obtain the classpath. * @param files - a list of files to check. * @return the first of the list of files that occurs on the classpath, or * null if none appear there. */ @Nullable public static VirtualFile findFirstFileOnClasspath(@NotNull final Module module, @NotNull final java.util.Collection<VirtualFile> files) { if (files.isEmpty()) { return null; } final VirtualFileManager vfmInstance = VirtualFileManager.getInstance(); final HaxeClasspath cp = ApplicationManager.getApplication().runReadAction(new Computable<HaxeClasspath>() { @Override public HaxeClasspath compute() { return getFullClasspath(module); } }); class MyLambda implements HaxeClasspath.Lambda { public VirtualFile found; public boolean processEntry(HaxeClasspathEntry entry) { String dirUrl = entry.getUrl(); if (!URLUtil.containsScheme(dirUrl)) { dirUrl = vfmInstance.constructUrl(URLUtil.FILE_PROTOCOL, dirUrl); } VirtualFile dirName = vfmInstance.findFileByUrl(dirUrl); if (null != dirName && dirName.isDirectory()) { for (VirtualFile f : files) { if (f.exists()) { // We have a complete path, compare the leading paths. String filePath = f.getCanonicalPath(); String dirPath = dirName.getCanonicalPath(); if (filePath.startsWith(dirPath)) { found = f; } } else { // We have a partial path, search the path for a matching file. found = dirName.findFileByRelativePath(f.toString()); } if (null != found) { return false; } } } return true; } } MyLambda lambda = new MyLambda(); cp.iterate(lambda); return lambda.found; } }