Java tutorial
// Copyright 2014 Pants project contributors (see CONTRIBUTORS.md). // Licensed under the Apache License, Version 2.0 (see LICENSE). package com.twitter.intellij.pants.util; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.CapturingProcessHandler; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessOutput; import com.intellij.ide.SaveAndSyncHandler; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManager; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder; import com.intellij.openapi.externalSystem.model.DataNode; import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.Key; import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; import com.intellij.openapi.externalSystem.util.ExternalSystemUtil; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.JavaSdk; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.PsiFile; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.ObjectUtils; import com.intellij.util.PathUtil; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.twitter.intellij.pants.PantsBundle; import com.twitter.intellij.pants.PantsException; import com.twitter.intellij.pants.model.PantsOptions; import com.twitter.intellij.pants.model.PantsSourceType; import com.twitter.intellij.pants.model.PantsTargetAddress; import com.twitter.intellij.pants.model.SimpleExportResult; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.JpsProject; import org.jetbrains.jps.model.java.JpsJavaSdkType; import org.jetbrains.jps.model.library.JpsLibrary; import org.jetbrains.jps.model.library.impl.sdk.JpsSdkImpl; import org.jetbrains.jps.model.library.sdk.JpsSdkReference; import java.io.File; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; public class PantsUtil { public static final Gson gson = new Gson(); public static final Type TYPE_LIST_STRING = new TypeToken<List<String>>() { }.getType(); public static final Type TYPE_SET_STRING = new TypeToken<Set<String>>() { }.getType(); public static final ScheduledExecutorService scheduledThreadPool = Executors .newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "Pants-Plugin-Pool"); } }); private static final Logger LOG = Logger.getInstance(PantsUtil.class); private static final List<String> PYTHON_PLUGIN_IDS = ContainerUtil.immutableList("PythonCore", "Pythonid"); private static final String PANTS_VERSION_REGEXP = "pants_version: (.+)"; private static final String PEX_RELATIVE_PATH = ".pants.d/bin/pants.pex"; /** * This aims to prepares for any breakage we might introduce from pants side, in which case we can adjust the version * of Pants `idea-plugin` goal to be greater than 0.1.0. * @see <a href="https://github.com/pantsbuild/pants/blob/d31ec5b4b1fb4f91e5beb685539ea14278dc62cf/src/python/pants/backend/project_info/tasks/idea_plugin_gen.py#L28">Pants `idea-plugin` goal version</a> */ private static final String PANTS_IDEA_PLUGIN_VERESION_MIN = "0.0.1"; private static final String PANTS_IDEA_PLUGIN_VERESION_MAX = "0.1.0"; /** * @param vFile a virtual file pointing at either a file or a directory * @return <code>null</code> if `vFile` is not a BUILD file or if it is a directory that * does not contain one * @deprecated {@link #findBUILDFiles(VirtualFile)} should be used instead, as this is likely * a sign that you're missing BUILD files */ @Nullable public static VirtualFile findBUILDFile(@Nullable VirtualFile vFile) { if (vFile == null) { return null; } else { return findBUILDFiles(vFile).stream().findFirst().orElse(null); } } /** * @param vFile a virtual file pointing at either a file or a directory * @return a collection with one item if `vFile` is a valid BUILD file, an empty collection * if `vFile` is a file but not a valid BUILD file, or if `vFile` is a directory then all * the valid build files that are in it. */ @NotNull public static Collection<VirtualFile> findBUILDFiles(@NotNull VirtualFile vFile) { if (vFile.isDirectory()) { return Stream.of(vFile.getChildren()).filter(f -> isBUILDFileName(f.getName())) .collect(Collectors.toList()); } if (isBUILDFileName(vFile.getName())) { return Collections.singleton(vFile); } return Collections.emptyList(); } public static boolean isBUILDFilePath(@NotNull String path) { return isBUILDFileName(PathUtil.getFileName(path)); } private static boolean isBUILDFile(@NotNull VirtualFile virtualFile) { return !virtualFile.isDirectory() && isBUILDFileName(virtualFile.getName()); } public static boolean isBUILDFileName(@NotNull String name) { return StringUtil.equalsIgnoreCase(PantsConstants.BUILD, FileUtil.getNameWithoutExtension(name)); } /** * Checks if it's a BUILD file or folder under a Pants project * * @param file - a BUILD file or a directory */ public static boolean isPantsProjectFile(VirtualFile file) { if (file.isDirectory()) { return findPantsExecutable(file) != null; } return isBUILDFileName(file.getName()); } @Nullable public static String findPantsVersion(@Nullable VirtualFile workingDir) { final VirtualFile pantsIniFile = findPantsIniFile(workingDir); return pantsIniFile == null ? null : findVersionInFile(pantsIniFile); } @Nullable public static VirtualFile findPantsIniFile(@Nullable VirtualFile workingDir) { return workingDir != null ? workingDir.findChild(PantsConstants.PANTS_INI) : null; } @Nullable private static String findVersionInFile(@NotNull VirtualFile file) { try { final String fileContent = VfsUtilCore.loadText(file); final List<String> matches = StringUtil.findMatches(fileContent, Pattern.compile(PANTS_VERSION_REGEXP)); return matches.isEmpty() ? null : matches.iterator().next(); } catch (IOException e) { return null; } } @Nullable public static VirtualFile findFolderWithPex() { return findFolderWithPex(VfsUtil.getUserHomeDir()); } @Nullable public static VirtualFile findFolderWithPex(@Nullable VirtualFile userHomeDir) { return findFileRelativeToDirectory(PEX_RELATIVE_PATH, userHomeDir); } @Nullable public static VirtualFile findPexVersionFile(@NotNull VirtualFile folderWithPex, @NotNull String pantsVersion) { final String filePrefix = "pants-" + pantsVersion; return ContainerUtil.find(folderWithPex.getChildren(), new Condition<VirtualFile>() { @Override public boolean value(VirtualFile virtualFile) { return "pex".equalsIgnoreCase(virtualFile.getExtension()) && virtualFile.getName().startsWith(filePrefix); } }); } @Nullable public static File findBuildRoot(@NotNull File file) { final File pantsExecutable = findPantsExecutable(file); return pantsExecutable != null ? pantsExecutable.getParentFile() : null; } @Nullable public static VirtualFile findBuildRoot(@NotNull String filePath) { final VirtualFile pantsExecutable = findPantsExecutable(filePath); return pantsExecutable != null ? pantsExecutable.getParent() : null; } @Nullable public static VirtualFile findBuildRoot(@NotNull Project project) { return findBuildRoot(project.getProjectFile()); } @Nullable public static VirtualFile findBuildRoot(@NotNull PsiFile psiFile) { final VirtualFile virtualFile = psiFile.getOriginalFile().getVirtualFile(); return virtualFile != null ? findBuildRoot(virtualFile) : findBuildRoot(psiFile.getProject()); } @Nullable public static VirtualFile findBuildRoot(@NotNull Module module) { final VirtualFile moduleFile = module.getModuleFile(); if (moduleFile != null) { return findBuildRoot(moduleFile); } final ModuleRootManager rootManager = ModuleRootManager.getInstance(module); for (VirtualFile contentRoot : rootManager.getContentRoots()) { final VirtualFile buildRoot = findBuildRoot(contentRoot); if (buildRoot != null) { return buildRoot; } } return null; } @Nullable public static VirtualFile findBuildRoot(@Nullable VirtualFile file) { final VirtualFile pantsExecutable = findPantsExecutable(file); return pantsExecutable != null ? pantsExecutable.getParent() : null; } @Nullable public static VirtualFile findDistExportClasspathDirectory(@NotNull Module module) { final VirtualFile buildRoot = findBuildRoot(module); if (buildRoot == null) { return null; } return VirtualFileManager.getInstance() .refreshAndFindFileByUrl("file://" + buildRoot.getPath() + "/dist/export-classpath"); } @Nullable public static VirtualFile findProjectManifestJar(@NotNull Project myProject) { Module[] modules = ModuleManager.getInstance(myProject).getModules(); if (modules.length == 0) { return null; } Module moduleSample = modules[0]; VirtualFile classpathDir = findDistExportClasspathDirectory(moduleSample); if (classpathDir == null) { return null; } String manifestUrl = classpathDir.getUrl() + "/manifest.jar"; VirtualFile manifest = VirtualFileManager.getInstance().refreshAndFindFileByUrl(manifestUrl); if (manifest != null) { return manifest; } return null; } public static GeneralCommandLine defaultCommandLine(@NotNull Project project) throws PantsException { VirtualFile pantsExecutable = findPantsExecutable(project); return defaultCommandLine(pantsExecutable.getPath()); } public static GeneralCommandLine defaultCommandLine(@NotNull String projectPath) throws PantsException { final File pantsExecutable = findPantsExecutable(new File(projectPath)); if (pantsExecutable == null) { throw new PantsException("Couldn't find pants executable for: " + projectPath); } return defaultCommandLine(pantsExecutable); } @NotNull public static GeneralCommandLine defaultCommandLine(@NotNull File pantsExecutable) { final GeneralCommandLine commandLine = new GeneralCommandLine(); boolean runFromSources = Boolean.valueOf(System.getProperty("pants.dev.run")); if (runFromSources) { commandLine.getEnvironment().put("PANTS_DEV", "1"); } final String pantsExecutablePath = StringUtil.notNullize(System.getProperty("pants.executable.path"), pantsExecutable.getAbsolutePath()); commandLine.setExePath(pantsExecutablePath); final String workingDir = pantsExecutable.getParentFile().getAbsolutePath(); return commandLine.withWorkDirectory(workingDir); } public static Collection<String> listAllTargets(@NotNull String projectPath) throws PantsException { try { final String fileContent = removeWhitespace(FileUtil.loadFile(new File(projectPath))); final Set<String> result = new TreeSet<String>(); result.addAll(StringUtil.findMatches(fileContent, Pattern.compile("\\Wname=(['\"])([\\w-_]+)\\1"), 2)); return result; } catch (IOException e) { throw new PantsException(e.getMessage()); } } public static String removeWhitespace(@NotNull String text) { return text.replaceAll("\\s", ""); } public static boolean isGeneratableFile(@NotNull String path) { // todo(fkorotkov): make it configurable or get it from patns. // maybe mark target as a target that generates sources and // we need to refresh the project for any change in the corresponding module // https://github.com/pantsbuild/intellij-pants-plugin/issues/13 return FileUtilRt.extensionEquals(path, PantsConstants.THRIFT_EXT) || FileUtilRt.extensionEquals(path, PantsConstants.ANTLR_EXT) || FileUtilRt.extensionEquals(path, PantsConstants.ANTLR_4_EXT) || FileUtilRt.extensionEquals(path, PantsConstants.PROTOBUF_EXT); } @NotNull @Nls public static String getCanonicalModuleName(@NotNull @NonNls String targetName) { // Do not use ':' because it is used as a separator in a classpath // while running the app. As well as path separators return replaceDelimitersInTargetName(targetName, '_'); } @NotNull @Nls public static String getCanonicalTargetId(@NotNull @NonNls String targetName) { return replaceDelimitersInTargetName(targetName, '.'); } private static String replaceDelimitersInTargetName(@NotNull @NonNls String targetName, char delimeter) { return targetName.replace(':', delimeter).replace('/', delimeter).replace('\\', delimeter); } @NotNull public static List<PantsTargetAddress> getTargetAddressesFromModule(@Nullable Module module) { if (module == null || !isPantsModule(module)) { return Collections.emptyList(); } final String targets = module.getOptionValue(PantsConstants.PANTS_TARGET_ADDRESSES_KEY); if (targets == null) { return Collections.emptyList(); } return ContainerUtil.mapNotNull(hydrateTargetAddresses(targets), new Function<String, PantsTargetAddress>() { @Override public PantsTargetAddress fun(String targetAddress) { return PantsTargetAddress.fromString(targetAddress); } }); } public static boolean isPantsProject(@NotNull Project project) { return ContainerUtil.exists(ModuleManager.getInstance(project).getModules(), new Condition<Module>() { @Override public boolean value(Module module) { return isPantsModule(module); } }); } /** * Determine whether a project is trigger by Pants `idea-plugin` goal by * looking at the "pants_idea_plugin_version" property. */ public static boolean isSeedPantsProject(@NotNull Project project) { class SeedPantsProjectKeys { private static final String PANTS_IDEA_PLUGIN_VERSION = "pants_idea_plugin_version"; } if (isPantsProject(project)) { return false; } String version = PropertiesComponent.getInstance(project) .getValue(SeedPantsProjectKeys.PANTS_IDEA_PLUGIN_VERSION); if (version == null) { return false; } if (versionCompare(version, PANTS_IDEA_PLUGIN_VERESION_MIN) < 0 || versionCompare(version, PANTS_IDEA_PLUGIN_VERESION_MAX) > 0) { Messages.showInfoMessage(project, PantsBundle.message("pants.idea.plugin.goal.version.unsupported"), "Version Error"); return false; } return true; } public static boolean isPantsModule(@NotNull Module module) { final String systemId = module.getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY); return StringUtil.equals(systemId, PantsConstants.SYSTEM_ID.getId()); } @NotNull public static PantsSourceType getSourceTypeForTargetType(@Nullable String targetType) { try { return targetType == null ? PantsSourceType.SOURCE : PantsSourceType.valueOf(StringUtil.toUpperCase(targetType)); } catch (IllegalArgumentException e) { LOG.warn("Got invalid source type " + targetType, e); return PantsSourceType.SOURCE; } } public static boolean isResource(PantsSourceType sourceType) { return sourceType == PantsSourceType.RESOURCE || sourceType == PantsSourceType.TEST_RESOURCE; } @Nullable public static VirtualFile findBUILDFileForModule(@NotNull Module module) { final String linkedPantsBUILD = getPathFromAddress(module, ExternalSystemConstants.LINKED_PROJECT_PATH_KEY); final String linkedPantsBUILDUrl = linkedPantsBUILD != null ? VfsUtil.pathToUrl(linkedPantsBUILD) : null; final VirtualFile virtualFile = linkedPantsBUILDUrl != null ? VirtualFileManager.getInstance().findFileByUrl(linkedPantsBUILDUrl) : null; if (virtualFile == null) { return null; } return isBUILDFile(virtualFile) ? virtualFile : findBUILDFile(virtualFile); } public static <K, V1, V2> Map<K, V2> mapValues(Map<K, V1> map, Function<V1, V2> fun) { final Map<K, V2> result = new HashMap<K, V2>(map.size()); for (K key : map.keySet()) { final V1 originalValue = map.get(key); final V2 newValue = fun.fun(originalValue); if (newValue != null) { result.put(key, newValue); } } return result; } public static <K, V> Map<K, V> filterByValue(Map<K, V> map, Condition<V> condition) { final Map<K, V> result = new HashMap<K, V>(map.size()); for (Map.Entry<K, V> entry : map.entrySet()) { final K key = entry.getKey(); final V value = entry.getValue(); if (condition.value(value)) { result.put(key, value); } } return result; } @Nullable public static String getRelativeProjectPath(@NotNull File projectFile) { final File buildRoot = findBuildRoot(projectFile); return buildRoot == null ? null : getRelativeProjectPath(buildRoot, projectFile); } @Nullable public static String getRelativeProjectPath(@NotNull File workDirectory, @NotNull String projectPath) { final File projectFile = new File(projectPath); return getRelativeProjectPath(workDirectory, projectFile); } @Nullable public static String getRelativeProjectPath(@NotNull File workDirectory, @NotNull File projectFile) { return FileUtil.getRelativePath(workDirectory, projectFile.isDirectory() ? projectFile : projectFile.getParentFile()); } public static void refreshAllProjects(@NotNull Project project) { if (!isPantsProject(project) && !isSeedPantsProject(project)) { return; } ApplicationManager.getApplication() .runWriteAction(() -> FileDocumentManager.getInstance().saveAllDocuments()); final ImportSpecBuilder specBuilder = new ImportSpecBuilder(project, PantsConstants.SYSTEM_ID); ProgressExecutionMode executionMode = ApplicationManager.getApplication().isUnitTestMode() ? ProgressExecutionMode.MODAL_SYNC : ProgressExecutionMode.IN_BACKGROUND_ASYNC; specBuilder.use(executionMode); ExternalSystemUtil.refreshProjects(specBuilder); } @Nullable public static VirtualFile findFileByAbsoluteOrRelativePath(@NotNull String fileOrDirPath, @NotNull Project project) { final VirtualFile absoluteVirtualFile = VirtualFileManager.getInstance() .findFileByUrl(VfsUtil.pathToUrl(fileOrDirPath)); if (absoluteVirtualFile != null) { return absoluteVirtualFile; } return findFileRelativeToBuildRoot(project, fileOrDirPath); } @Nullable public static VirtualFile findFileRelativeToBuildRoot(@NotNull Project project, @NotNull String fileOrDirPath) { final VirtualFile buildRoot = findBuildRoot(project); return findFileRelativeToDirectory(fileOrDirPath, buildRoot); } @Nullable public static VirtualFile findFileRelativeToBuildRoot(@NotNull PsiFile psiFile, @NotNull String fileOrDirPath) { final VirtualFile buildRoot = findBuildRoot(psiFile); return findFileRelativeToDirectory(fileOrDirPath, buildRoot); } @Nullable private static VirtualFile findFileRelativeToDirectory(@NotNull @Nls String fileOrDirPath, @Nullable VirtualFile directory) { return directory != null ? directory.findFileByRelativePath(fileOrDirPath) : null; } /** * {@code processor} should return false if we don't want to step into the directory. */ public static void traverseDirectoriesRecursively(@NotNull File root, @NotNull Processor<File> processor) { final LinkedList<File> queue = new LinkedList<File>(); queue.add(root); while (!queue.isEmpty()) { final File file = queue.removeFirst(); if (file.isFile()) { continue; } if (!processor.process(file)) { continue; } final File[] children = file.listFiles(); if (children != null) { ContainerUtil.addAll(queue, children); } } } @Contract(value = "_, null -> null", pure = true) public static String getPathFromAddress(@NotNull Module module, @Nullable String key) { final String address = key != null ? module.getOptionValue(key) : null; return PantsTargetAddress.extractPath(address); } public static void copyDirContent(@NotNull File fromDir, @NotNull File toDir) throws IOException { final File[] children = ObjectUtils.notNull(fromDir.listFiles(), ArrayUtil.EMPTY_FILE_ARRAY); for (File child : children) { final File target = new File(toDir, child.getName()); if (child.isFile()) { FileUtil.copy(child, target); } else { FileUtil.copyDir(child, target, false); } } } public static ProcessOutput getCmdOutput(@NotNull GeneralCommandLine command, @Nullable ProcessAdapter processAdapter) throws ExecutionException { return getOutput(command.createProcess(), processAdapter); } public static ProcessOutput getOutput(@NotNull Process process, @Nullable ProcessAdapter processAdapter) { final CapturingProcessHandler processHandler = new CapturingProcessHandler(process, Charset.defaultCharset(), "PantsUtil command"); if (processAdapter != null) { processHandler.addProcessListener(processAdapter); } return processHandler.runProcess(); } public static boolean isPythonAvailable() { for (String pluginId : PYTHON_PLUGIN_IDS) { final IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId(pluginId)); if (plugin != null && plugin.isEnabled()) { return true; } } return false; } @Contract("null -> false") public static boolean isExecutable(@Nullable String filePath) { if (filePath == null) { return false; } final File file = new File(filePath); return file.exists() && file.isFile() && file.canExecute(); } @NotNull public static String resolveSymlinks(@NotNull String path) { try { return new File(path).getCanonicalPath(); } catch (IOException e) { throw new ExternalSystemException("Can't resolve symbolic links for " + path, e); } } @NotNull public static String fileNameWithoutExtension(@NotNull String name) { int index = name.lastIndexOf('.'); if (index < 0) return name; return name.substring(0, index); } @Contract(pure = true) public static <T> boolean forall(@NotNull Iterable<T> iterable, @NotNull Condition<T> condition) { for (T value : iterable) { if (!condition.value(value)) { return false; } } return true; } @NotNull public static <T> List<T> findChildren(@NotNull DataNode<?> dataNode, @NotNull Key<T> key) { return ContainerUtil.mapNotNull(ExternalSystemApiUtil.findAll(dataNode, key), new Function<DataNode<T>, T>() { @Override public T fun(DataNode<T> node) { return node.getData(); } }); } public static ProcessOutput getProcessOutput(@NotNull GeneralCommandLine command, @Nullable ProcessAdapter processAdapter) throws ExecutionException { return getOutput(command.createProcess(), processAdapter); } /** * @param project JpsProject * @return Path to IDEA Project JDK if exists, else null */ @Nullable public static String getJdkPathFromExternalBuilder(@NotNull JpsProject project) { JpsSdkReference sdkReference = project.getSdkReferencesTable().getSdkReference(JpsJavaSdkType.INSTANCE); if (sdkReference != null) { String sdkName = sdkReference.getSdkName(); JpsLibrary lib = project.getModel().getGlobal().getLibraryCollection().findLibrary(sdkName); if (lib != null && lib.getProperties() instanceof JpsSdkImpl) { return ((JpsSdkImpl) lib.getProperties()).getHomePath(); } } return null; } /** * @return Path to IDEA Project JDK if exists, else null */ @Nullable public static String getJdkPathFromIntelliJCore() { // Followed example in com.twitter.intellij.pants.testFramework.PantsIntegrationTestCase.setUpInWriteAction() final Sdk sdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); String javaHome = null; if (sdk.getHomeDirectory() != null) { javaHome = sdk.getHomeDirectory().getParent().getPath(); } return javaHome; } /** * @param jdkPath path to IDEA Project JDK * @return --jvm-distributions-paths with the parameter if jdkPath is not null, * otherwise the flag with empty parameter so user can tell there is issue finding the IDEA project JDK. */ @NotNull public static String getJvmDistributionPathParameter(@Nullable final String jdkPath) throws Exception { if (jdkPath != null) { HashMap<String, List<String>> distributionFlag = new HashMap<String, List<String>>(); distributionFlag.put(System.getProperty("os.name").toLowerCase(), Collections.singletonList(jdkPath)); return PantsConstants.PANTS_CLI_OPTION_JVM_DISTRIBUTIONS_PATHS + "=" + new Gson().toJson(distributionFlag); } else { throw new Exception("No IDEA Project JDK Found"); } } @NotNull public static Set<String> hydrateTargetAddresses(@NotNull String addresses) { return gson.fromJson(addresses, TYPE_SET_STRING); } @NotNull public static String dehydrateTargetAddresses(@NotNull Set<String> addresses) { return gson.toJson(addresses); } public static boolean isGenTarget(@NotNull String address) { return StringUtil.startsWithIgnoreCase(address, ".pants.d") || StringUtil.startsWithIgnoreCase(address, PantsConstants.PANTS_PROJECT_MODULE_ID_PREFIX) || // Checking "_synthetic_resources" is a temporary fix. It also needs to match the postfix added from pants in // src.python.pants.backend.python.targets.python_target.PythonTarget#_synthetic_resources_target // TODO: The long term solution is collect non-synthetic targets at pre-compile stage // https://github.com/pantsbuild/intellij-pants-plugin/issues/83 address.toLowerCase().endsWith("_synthetic_resources"); } public static Set<String> filterGenTargets(@NotNull Collection<String> addresses) { return new HashSet<String>(ContainerUtil.filter(addresses, new Condition<String>() { @Override public boolean value(String targetAddress) { return !isGenTarget(targetAddress); } })); } public static boolean supportExportDefaultJavaSdk(@NotNull final String pantsExecutable) { return versionCompare(SimpleExportResult.getExportResult(pantsExecutable).getVersion(), "1.0.7") >= 0; } @Nullable public static Sdk getDefaultJavaSdk(@NotNull final String pantsExecutable) { SimpleExportResult exportResult = SimpleExportResult.getExportResult(pantsExecutable); if (versionCompare(exportResult.getVersion(), "1.0.7") >= 0) { String defaultPlatform = exportResult.getJvmPlatforms().getDefaultPlatform(); boolean strict = Boolean.parseBoolean(PantsOptions.getPantsOptions(pantsExecutable) .get(PantsConstants.PANTS_OPTION_TEST_JUNIT_STRICT_JVM_VERSION)); String jdkName = String.format("JDK from pants %s", defaultPlatform); String jdkHome = exportResult.getPreferredJvmDistributions().get(defaultPlatform) .get(strict ? "strict" : "non_strict"); return JavaSdk.getInstance().createJdk(jdkName, jdkHome); } return null; } /** * Copied from: http://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java * Compares two version strings. * <p/> * Use this instead of String.compareTo() for a non-lexicographical * comparison that works for version strings. e.g. "1.10".compareTo("1.6"). * * @param str1 a string of ordinal numbers separated by decimal points. * @param str2 a string of ordinal numbers separated by decimal points. * @return The result is a negative integer if str1 is _numerically_ less than str2. * The result is a positive integer if str1 is _numerically_ greater than str2. * The result is zero if the strings are _numerically_ equal. * @note It does not work if "1.10" is supposed to be equal to "1.10.0". */ public static Integer versionCompare(String str1, String str2) { String[] vals1 = str1.split("\\."); String[] vals2 = str2.split("\\."); int i = 0; // set index to first non-equal ordinal or length of shortest version string while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { i++; } // compare first non-equal ordinal number if (i < vals1.length && i < vals2.length) { int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); return Integer.signum(diff); } // the strings are equal or one string is a substring of the other // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" else { return Integer.signum(vals1.length - vals2.length); } } /** * Reliable way to find pants executable by a project once it is imported. * Use project's module in project to find the `buildRoot`, * then use `buildRoot` to find pantsExecutable. */ public static VirtualFile findPantsExecutable(@NotNull Project project) { Module[] modules = ModuleManager.getInstance(project).getModules(); if (modules.length == 0) { throw new PantsException("No module found in project."); } Module moduleSample = modules[0]; VirtualFile buildRoot = findBuildRoot(moduleSample); return findPantsExecutable(buildRoot); } @Nullable public static VirtualFile findPantsExecutable(@NotNull String projectPath) { // guard against VirtualFileManager throwing an NPE in tests that don't stand up an IDEA instance if (ApplicationManager.getApplication() == null) { return null; } final VirtualFile buildFile = VirtualFileManager.getInstance() .findFileByUrl(VfsUtil.pathToUrl(projectPath)); return findPantsExecutable(buildFile); } @Nullable public static File findPantsExecutable(@Nullable File file) { if (file == null) return null; if (file.isDirectory()) { final File pantsFile = new File(file, PantsConstants.PANTS); if (pantsFile.exists() && !pantsFile.isDirectory()) { return pantsFile; } } return findPantsExecutable(file.getParentFile()); } @Nullable private static VirtualFile findPantsExecutable(@Nullable VirtualFile file) { if (file == null) return null; if (file.isDirectory()) { final VirtualFile pantsFile = file.findChild(PantsConstants.PANTS); if (pantsFile != null && !pantsFile.isDirectory()) { return pantsFile; } } return findPantsExecutable(file.getParent()); } public static List<String> convertToTargetSpecs(String importPath, List<String> targetNames) { File importPathFile = new File(importPath); final String projectDir = isBUILDFileName(importPathFile.getName()) ? importPathFile.getParent() : importPathFile.getPath(); final String relativeProjectDir = getRelativeProjectPath(new File(projectDir)); // If relativeProjectDir is null, that means the projectDir is already relative. String relativePath = ObjectUtils.notNull(relativeProjectDir, projectDir); if (targetNames.isEmpty()) { return Collections.singletonList(relativePath + "::"); } else { return targetNames.stream().map(targetName -> relativePath + ":" + targetName) .collect(Collectors.toList()); } } public static void synchronizeFiles() { /** * Run in SYNC in unit test mode, and {@link com.twitter.intellij.pants.testFramework.PantsIntegrationTestCase.doImport} * is required to be wrapped in WriteAction. Otherwise it will run in async mode. */ if (ApplicationManager.getApplication().isUnitTestMode() && ApplicationManager.getApplication().isWriteAccessAllowed()) { ApplicationManager.getApplication().runWriteAction(() -> { FileDocumentManager.getInstance().saveAllDocuments(); SaveAndSyncHandler.getInstance().refreshOpenFiles(); VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); /** synchronous */ }); } else { ApplicationManager.getApplication().invokeLater(() -> { FileDocumentManager.getInstance().saveAllDocuments(); SaveAndSyncHandler.getInstance().refreshOpenFiles(); VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); /** asynchronous */ }); } } public static void invalidatePluginCaches() { PantsOptions.clearCache(); SimpleExportResult.clearCache(); } }