blob: b363eb8b3395258b7fb17d5634e1c0b8cf1643dd [file] [log] [blame]
package org.jetbrains.plugins.gradle.service;
import com.intellij.openapi.externalSystem.service.project.PlatformFacade;
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.gradle.StartParameter;
import org.gradle.util.DistributionLocator;
import org.gradle.util.GradleVersion;
import org.gradle.wrapper.PathAssembler;
import org.gradle.wrapper.WrapperConfiguration;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.settings.DistributionType;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import org.jetbrains.plugins.gradle.util.GradleEnvironment;
import org.jetbrains.plugins.gradle.util.GradleLog;
import org.jetbrains.plugins.gradle.util.GradleUtil;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
/**
* Encapsulates algorithm of gradle libraries discovery.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since 8/4/11 11:06 AM
*/
@SuppressWarnings("MethodMayBeStatic")
public class GradleInstallationManager {
public static final Pattern GRADLE_JAR_FILE_PATTERN;
public static final Pattern ANY_GRADLE_JAR_FILE_PATTERN;
public static final Pattern ANT_JAR_PATTERN = Pattern.compile("ant(-(.*))?\\.jar");
public static final Pattern IVY_JAR_PATTERN = Pattern.compile("ivy(-(.*))?\\.jar");
private static final String[] GRADLE_START_FILE_NAMES;
@NonNls private static final String GRADLE_ENV_PROPERTY_NAME;
static {
// Init static data with ability to redefine it locally.
GRADLE_JAR_FILE_PATTERN = Pattern.compile(System.getProperty("gradle.pattern.core.jar", "gradle-(core-)?(\\d.*)\\.jar"));
ANY_GRADLE_JAR_FILE_PATTERN = Pattern.compile(System.getProperty("gradle.pattern.core.jar", "gradle-(.*)\\.jar"));
GRADLE_START_FILE_NAMES = System.getProperty("gradle.start.file.names", "gradle:gradle.cmd:gradle.sh").split(":");
GRADLE_ENV_PROPERTY_NAME = System.getProperty("gradle.home.env.key", "GRADLE_HOME");
}
@NotNull private final PlatformFacade myPlatformFacade;
@Nullable private Ref<File> myCachedGradleHomeFromPath;
public GradleInstallationManager(@NotNull PlatformFacade facade) {
myPlatformFacade = facade;
}
/**
* Allows to get file handles for the gradle binaries to use.
*
* @param gradleHome gradle sdk home
* @return file handles for the gradle binaries; <code>null</code> if gradle is not discovered
*/
@Nullable
public Collection<File> getAllLibraries(@Nullable File gradleHome) {
if (gradleHome == null || !gradleHome.isDirectory()) {
return null;
}
List<File> result = ContainerUtilRt.newArrayList();
File libs = new File(gradleHome, "lib");
File[] files = libs.listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().endsWith(".jar")) {
result.add(file);
}
}
}
File plugins = new File(libs, "plugins");
files = plugins.listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().endsWith(".jar")) {
result.add(file);
}
}
}
return result.isEmpty() ? null : result;
}
@Nullable
public File getGradleHome(@Nullable Project project, @NotNull String linkedProjectPath) {
return doGetGradleHome(project, linkedProjectPath);
}
/**
* Tries to return file handle that points to the gradle installation home.
*
* @param project target project (if any)
* @param linkedProjectPath path to the target linked project config
* @return file handle that points to the gradle installation home (if any)
*/
@Nullable
private File doGetGradleHome(@Nullable Project project, @NotNull String linkedProjectPath) {
if (project == null) {
return null;
}
GradleProjectSettings settings = GradleSettings.getInstance(project).getLinkedProjectSettings(linkedProjectPath);
if (settings == null || settings.getDistributionType() == null) {
return null;
}
return getGradleHome(settings.getDistributionType(), linkedProjectPath, settings.getGradleHome());
}
@Nullable
public File getGradleHome(@NotNull DistributionType distributionType, @NotNull String linkedProjectPath, @Nullable String gradleHome) {
File candidate = null;
switch (distributionType) {
case LOCAL:
if (gradleHome != null) {
candidate = new File(gradleHome);
}
break;
case DEFAULT_WRAPPED:
WrapperConfiguration wrapperConfiguration = GradleUtil.getWrapperConfiguration(linkedProjectPath);
candidate = getWrappedGradleHome(linkedProjectPath, wrapperConfiguration);
break;
case WRAPPED:
// not supported yet
break;
case BUNDLED:
WrapperConfiguration bundledWrapperSettings = new WrapperConfiguration();
DistributionLocator distributionLocator = new DistributionLocator();
bundledWrapperSettings.setDistribution(distributionLocator.getDistributionFor(GradleVersion.current()));
candidate = getWrappedGradleHome(linkedProjectPath, bundledWrapperSettings);
break;
}
File result = null;
if (candidate != null) {
result = isGradleSdkHome(candidate) ? candidate : null;
}
if (result != null) {
return result;
}
return getAutodetectedGradleHome();
}
/**
* Tries to deduce gradle location from current environment.
*
* @return gradle home deduced from the current environment (if any); <code>null</code> otherwise
*/
@Nullable
public File getAutodetectedGradleHome() {
File result = getGradleHomeFromPath();
return result == null ? getGradleHomeFromEnvProperty() : result;
}
/**
* Tries to return gradle home that is defined as a dependency to the given module.
*
* @param module target module
* @return file handle that points to the gradle installation home defined as a dependency of the given module (if any)
*/
@Nullable
public VirtualFile getGradleHome(@Nullable Module module) {
if (module == null) {
return null;
}
final VirtualFile[] roots = OrderEnumerator.orderEntries(module).getAllLibrariesAndSdkClassesRoots();
if (roots == null) {
return null;
}
for (VirtualFile root : roots) {
if (root != null && isGradleSdkHome(root)) {
return root;
}
}
return null;
}
/**
* Tries to return gradle home defined as a dependency of the given module; falls back to the project-wide settings otherwise.
*
* @param module target module that can have gradle home as a dependency
* @param project target project which gradle home setting should be used if module-specific gradle location is not defined
* @return gradle home derived from the settings of the given entities (if any); <code>null</code> otherwise
*/
@Nullable
public VirtualFile getGradleHome(@Nullable Module module, @Nullable Project project, @NotNull String linkedProjectPath) {
final VirtualFile result = getGradleHome(module);
if (result != null) {
return result;
}
final File home = getGradleHome(project, linkedProjectPath);
return home == null ? null : LocalFileSystem.getInstance().refreshAndFindFileByIoFile(home);
}
/**
* Tries to discover gradle installation path from the configured system path
*
* @return file handle for the gradle directory if it's possible to deduce from the system path; <code>null</code> otherwise
*/
@Nullable
public File getGradleHomeFromPath() {
Ref<File> ref = myCachedGradleHomeFromPath;
if (ref != null) {
return ref.get();
}
String path = System.getenv("PATH");
if (path == null) {
return null;
}
for (String pathEntry : path.split(File.pathSeparator)) {
File dir = new File(pathEntry);
if (!dir.isDirectory()) {
continue;
}
for (String fileName : GRADLE_START_FILE_NAMES) {
File startFile = new File(dir, fileName);
if (startFile.isFile()) {
File candidate = dir.getParentFile();
if (isGradleSdkHome(candidate)) {
myCachedGradleHomeFromPath = new Ref<File>(candidate);
return candidate;
}
}
}
}
return null;
}
/**
* Tries to discover gradle installation via environment property.
*
* @return file handle for the gradle directory deduced from the system property (if any)
*/
@Nullable
public File getGradleHomeFromEnvProperty() {
String path = System.getenv(GRADLE_ENV_PROPERTY_NAME);
if (path == null) {
return null;
}
File candidate = new File(path);
return isGradleSdkHome(candidate) ? candidate : null;
}
/**
* Does the same job as {@link #isGradleSdkHome(File)} for the given virtual file.
*
* @param file gradle installation home candidate
* @return <code>true</code> if given file points to the gradle installation; <code>false</code> otherwise
*/
public boolean isGradleSdkHome(@Nullable VirtualFile file) {
if (file == null) {
return false;
}
return isGradleSdkHome(new File(file.getPath()));
}
/**
* Allows to answer if given virtual file points to the gradle installation root.
*
* @param file gradle installation root candidate
* @return <code>true</code> if we consider that given file actually points to the gradle installation root;
* <code>false</code> otherwise
*/
public boolean isGradleSdkHome(@Nullable File file) {
if (file == null) {
return false;
}
final File libs = new File(file, "lib");
if (!libs.isDirectory()) {
if (GradleEnvironment.DEBUG_GRADLE_HOME_PROCESSING) {
GradleLog.LOG.info(String.format(
"Gradle sdk check failed for the path '%s'. Reason: it doesn't have a child directory named 'lib'", file.getAbsolutePath()
));
}
return false;
}
final boolean found = isGradleSdk(libs.listFiles());
if (GradleEnvironment.DEBUG_GRADLE_HOME_PROCESSING) {
GradleLog.LOG.info(String.format("Gradle home check %s for the path '%s'", found ? "passed" : "failed", file.getAbsolutePath()));
}
return found;
}
/**
* Allows to answer if given virtual file points to the gradle installation root.
*
* @param file gradle installation root candidate
* @return <code>true</code> if we consider that given file actually points to the gradle installation root;
* <code>false</code> otherwise
*/
public boolean isGradleSdkHome(String gradleHomePath) {
return isGradleSdkHome(new File(gradleHomePath));
}
/**
* Allows to answer if given files contain the one from gradle installation.
*
* @param files files to process
* @return <code>true</code> if one of the given files is from the gradle installation; <code>false</code> otherwise
*/
public boolean isGradleSdk(@Nullable VirtualFile... files) {
if (files == null) {
return false;
}
File[] arg = new File[files.length];
for (int i = 0; i < files.length; i++) {
arg[i] = new File(files[i].getPath());
}
return isGradleSdk(arg);
}
private boolean isGradleSdk(@Nullable File... files) {
return findGradleJar(files) != null;
}
@Nullable
private File findGradleJar(@Nullable File... files) {
if (files == null) {
return null;
}
for (File file : files) {
if (GRADLE_JAR_FILE_PATTERN.matcher(file.getName()).matches()) {
return file;
}
}
if (GradleEnvironment.DEBUG_GRADLE_HOME_PROCESSING) {
StringBuilder filesInfo = new StringBuilder();
for (File file : files) {
filesInfo.append(file.getAbsolutePath()).append(';');
}
if (filesInfo.length() > 0) {
filesInfo.setLength(filesInfo.length() - 1);
}
GradleLog.LOG.info(String.format(
"Gradle sdk check fails. Reason: no one of the given files matches gradle JAR pattern (%s). Files: %s",
GRADLE_JAR_FILE_PATTERN.toString(), filesInfo
));
}
return null;
}
/**
* Allows to ask for the classpath roots of the classes that are additionally provided by the gradle integration (e.g. gradle class
* files, bundled groovy-all jar etc).
*
* @param project target project to use for gradle home retrieval
* @return classpath roots of the classes that are additionally provided by the gradle integration (if any);
* <code>null</code> otherwise
*/
@Nullable
public List<VirtualFile> getClassRoots(@Nullable Project project) {
List<File> files = getClassRoots(project, null);
if(files == null) return null;
final LocalFileSystem localFileSystem = LocalFileSystem.getInstance();
final JarFileSystem jarFileSystem = JarFileSystem.getInstance();
return ContainerUtil.mapNotNull(files, new Function<File, VirtualFile>() {
@Override
public VirtualFile fun(File file) {
final VirtualFile virtualFile = localFileSystem.refreshAndFindFileByIoFile(file);
return virtualFile != null ? jarFileSystem.getJarRootForLocalFile(virtualFile) : null;
}
});
}
@Nullable
public List<File> getClassRoots(@Nullable Project project, @Nullable String rootProjectPath) {
if (project == null) return null;
if(rootProjectPath == null) {
for (Module module : myPlatformFacade.getModules(project)) {
rootProjectPath = module.getOptionValue(ExternalSystemConstants.ROOT_PROJECT_PATH_KEY);
List<File> result = findGradleSdkClasspath(project, rootProjectPath);
if(!result.isEmpty()) return result;
}
} else {
return findGradleSdkClasspath(project, rootProjectPath);
}
return null;
}
private List<File> findGradleSdkClasspath(Project project, String rootProjectPath) {
List<File> result = new ArrayList<File>();
if (StringUtil.isEmpty(rootProjectPath)) return result;
File gradleHome = getGradleHome(project, rootProjectPath);
if (gradleHome == null || !gradleHome.isDirectory()) {
return result;
}
File src = new File(gradleHome, "src");
if (src.isDirectory()) {
if(new File(src, "org").isDirectory()) {
addRoots(result, src);
} else {
addRoots(result, src.listFiles());
}
}
final Collection<File> libraries = getAllLibraries(gradleHome);
if (libraries == null) {
return result;
}
for (File file : libraries) {
if (isGradleBuildClasspathLibrary(file)) {
ContainerUtil.addIfNotNull(result, file);
}
}
return result;
}
private boolean isGradleBuildClasspathLibrary(File file) {
String fileName = file.getName();
return ANY_GRADLE_JAR_FILE_PATTERN.matcher(fileName).matches()
|| ANT_JAR_PATTERN.matcher(fileName).matches()
|| IVY_JAR_PATTERN.matcher(fileName).matches()
|| GroovyConfigUtils.matchesGroovyAll(fileName);
}
private void addRoots(@NotNull List<File> result, @Nullable File... files) {
if (files == null) return;
for (File file : files) {
if (file == null || !file.isDirectory()) continue;
result.add(file);
}
}
private File getWrappedGradleHome(String linkedProjectPath, @Nullable final WrapperConfiguration wrapperConfiguration) {
if (wrapperConfiguration == null) {
return null;
}
File gradleSystemDir;
if ("PROJECT".equals(wrapperConfiguration.getDistributionBase())) {
gradleSystemDir = new File(linkedProjectPath, ".gradle");
}
else {
gradleSystemDir = StartParameter.DEFAULT_GRADLE_USER_HOME;
}
if (!gradleSystemDir.isDirectory()) {
return null;
}
PathAssembler.LocalDistribution localDistribution = new PathAssembler(gradleSystemDir).getDistribution(wrapperConfiguration);
if (localDistribution.getDistributionDir() == null) {
return null;
}
File[] distFiles = localDistribution.getDistributionDir().listFiles(new FileFilter() {
@Override
public boolean accept(File f) {
return f.isDirectory() && StringUtil.startsWith(f.getName(), "gradle-");
}
});
return distFiles == null || distFiles.length == 0 ? null : distFiles[0];
}
}