replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / gradle / src / org / jetbrains / plugins / gradle / service / GradleInstallationManager.java
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package org.jetbrains.plugins.gradle.service;
17
18 import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager;
19 import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkException;
20 import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil;
21 import com.intellij.openapi.externalSystem.service.notification.callback.OpenExternalSystemSettingsCallback;
22 import com.intellij.openapi.module.Module;
23 import com.intellij.openapi.module.ModuleManager;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.projectRoots.JdkUtil;
26 import com.intellij.openapi.projectRoots.Sdk;
27 import com.intellij.openapi.roots.OrderEnumerator;
28 import com.intellij.openapi.util.Ref;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.openapi.vfs.JarFileSystem;
31 import com.intellij.openapi.vfs.LocalFileSystem;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.util.containers.ContainerUtil;
34 import com.intellij.util.containers.ContainerUtilRt;
35 import org.gradle.StartParameter;
36 import org.gradle.util.DistributionLocator;
37 import org.gradle.util.GradleVersion;
38 import org.gradle.wrapper.PathAssembler;
39 import org.gradle.wrapper.WrapperConfiguration;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.plugins.gradle.settings.DistributionType;
44 import org.jetbrains.plugins.gradle.settings.GradleLocalSettings;
45 import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
46 import org.jetbrains.plugins.gradle.settings.GradleSettings;
47 import org.jetbrains.plugins.gradle.util.GradleEnvironment;
48 import org.jetbrains.plugins.gradle.util.GradleLog;
49 import org.jetbrains.plugins.gradle.util.GradleUtil;
50 import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
51
52 import java.io.File;
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.List;
56 import java.util.regex.Matcher;
57 import java.util.regex.Pattern;
58
59 /**
60  * Encapsulates algorithm of gradle libraries discovery.
61  * <p/>
62  * Thread-safe.
63  *
64  * @author Denis Zhdanov
65  * @since 8/4/11 11:06 AM
66  */
67 @SuppressWarnings("MethodMayBeStatic")
68 public class GradleInstallationManager {
69
70   public static final Pattern GRADLE_JAR_FILE_PATTERN;
71   public static final Pattern ANY_GRADLE_JAR_FILE_PATTERN;
72   public static final Pattern ANT_JAR_PATTERN = Pattern.compile("ant(-(.*))?\\.jar");
73   public static final Pattern IVY_JAR_PATTERN = Pattern.compile("ivy(-(.*))?\\.jar");
74
75   private static final String[] GRADLE_START_FILE_NAMES;
76   @NonNls private static final String GRADLE_ENV_PROPERTY_NAME;
77
78   static {
79     // Init static data with ability to redefine it locally.
80     GRADLE_JAR_FILE_PATTERN = Pattern.compile(System.getProperty("gradle.pattern.core.jar", "gradle-(core-)?(\\d.*)\\.jar"));
81     ANY_GRADLE_JAR_FILE_PATTERN = Pattern.compile(System.getProperty("gradle.pattern.core.jar", "gradle-(.*)\\.jar"));
82     GRADLE_START_FILE_NAMES = System.getProperty("gradle.start.file.names", "gradle:gradle.cmd:gradle.sh").split(":");
83     GRADLE_ENV_PROPERTY_NAME = System.getProperty("gradle.home.env.key", "GRADLE_HOME");
84   }
85
86   @Nullable private Ref<File> myCachedGradleHomeFromPath;
87
88   /**
89    * Allows to get file handles for the gradle binaries to use.
90    *
91    * @param gradleHome gradle sdk home
92    * @return file handles for the gradle binaries; {@code null} if gradle is not discovered
93    */
94   @Nullable
95   public Collection<File> getAllLibraries(@Nullable File gradleHome) {
96
97     if (gradleHome == null || !gradleHome.isDirectory()) {
98       return null;
99     }
100
101     List<File> result = ContainerUtilRt.newArrayList();
102
103     File libs = new File(gradleHome, "lib");
104     File[] files = libs.listFiles();
105     if (files != null) {
106       for (File file : files) {
107         if (file.getName().endsWith(".jar")) {
108           result.add(file);
109         }
110       }
111     }
112
113     File plugins = new File(libs, "plugins");
114     files = plugins.listFiles();
115     if (files != null) {
116       for (File file : files) {
117         if (file.getName().endsWith(".jar")) {
118           result.add(file);
119         }
120       }
121     }
122     return result.isEmpty() ? null : result;
123   }
124
125   @Nullable
126   public File getGradleHome(@Nullable Project project, @NotNull String linkedProjectPath) {
127     return doGetGradleHome(project, linkedProjectPath);
128   }
129
130   @Nullable
131   public Sdk getGradleJdk(@Nullable Project project, @NotNull String linkedProjectPath) {
132     return doGetGradleJdk(project, linkedProjectPath);
133   }
134
135   @Nullable
136   private Sdk doGetGradleJdk(@Nullable Project project, String linkedProjectPath) {
137     if (project == null) {
138       return null;
139     }
140
141     final GradleProjectSettings settings = GradleSettings.getInstance(project).getLinkedProjectSettings(linkedProjectPath);
142     if (settings == null) {
143       return null;
144     }
145
146     final String gradleJvm = settings.getGradleJvm();
147     final Sdk sdk;
148     try {
149       sdk = ExternalSystemJdkUtil.getJdk(project, gradleJvm);
150     }
151     catch (ExternalSystemJdkException e) {
152       throw new ExternalSystemJdkException(
153         String.format("Invalid Gradle JDK configuration found. <a href='%s'>Open Gradle Settings</a> \n",
154                       OpenExternalSystemSettingsCallback.ID),
155         linkedProjectPath, e, OpenExternalSystemSettingsCallback.ID);
156     }
157
158     if (sdk == null && gradleJvm != null) {
159       throw new ExternalSystemJdkException(
160         String.format("Invalid Gradle JDK configuration found. <a href='%s'>Open Gradle Settings</a> \n",
161                       OpenExternalSystemSettingsCallback.ID),
162         linkedProjectPath, null, OpenExternalSystemSettingsCallback.ID);
163     }
164
165     String sdkHomePath = sdk != null ? sdk.getHomePath() : null;
166     if (sdkHomePath != null && JdkUtil.checkForJre(sdkHomePath) && !JdkUtil.checkForJdk(sdkHomePath)) {
167       throw new ExternalSystemJdkException(
168         String.format("Please, use JDK instead of JRE for Gradle importer. <a href='%s'>Open Gradle Settings</a> \n",
169                       OpenExternalSystemSettingsCallback.ID),
170         linkedProjectPath, null, OpenExternalSystemSettingsCallback.ID);
171     }
172
173     return sdk;
174   }
175
176   /**
177    * Tries to return file handle that points to the gradle installation home.
178    *
179    * @param project           target project (if any)
180    * @param linkedProjectPath path to the target linked project config
181    * @return file handle that points to the gradle installation home (if any)
182    */
183   @Nullable
184   private File doGetGradleHome(@Nullable Project project, @NotNull String linkedProjectPath) {
185     if (project == null) {
186       return null;
187     }
188     GradleProjectSettings settings = GradleSettings.getInstance(project).getLinkedProjectSettings(linkedProjectPath);
189     if (settings == null || settings.getDistributionType() == null) {
190       return null;
191     }
192     String gradleHome = settings.getDistributionType() == DistributionType.WRAPPED
193                         ? GradleLocalSettings.getInstance(project).getGradleHome(linkedProjectPath)
194                         : settings.getGradleHome();
195     return getGradleHome(settings.getDistributionType(), linkedProjectPath, gradleHome);
196   }
197
198   @Nullable
199   private File getGradleHome(@NotNull DistributionType distributionType, @NotNull String linkedProjectPath, @Nullable String gradleHome) {
200     File candidate = null;
201     switch (distributionType) {
202       case LOCAL:
203       case WRAPPED:
204         if (gradleHome != null) {
205           candidate = new File(gradleHome);
206         }
207         break;
208       case DEFAULT_WRAPPED:
209         WrapperConfiguration wrapperConfiguration = GradleUtil.getWrapperConfiguration(linkedProjectPath);
210         candidate = getWrappedGradleHome(linkedProjectPath, wrapperConfiguration);
211         break;
212       case BUNDLED:
213         WrapperConfiguration bundledWrapperSettings = new WrapperConfiguration();
214         DistributionLocator distributionLocator = new DistributionLocator();
215         bundledWrapperSettings.setDistribution(distributionLocator.getDistributionFor(GradleVersion.current()));
216         candidate = getWrappedGradleHome(linkedProjectPath, bundledWrapperSettings);
217         break;
218     }
219
220     File result = null;
221     if (candidate != null) {
222       result = isGradleSdkHome(candidate) ? candidate : null;
223     }
224
225     if (result != null) {
226       return result;
227     }
228     return getAutodetectedGradleHome();
229   }
230
231   /**
232    * Tries to deduce gradle location from current environment.
233    *
234    * @return gradle home deduced from the current environment (if any); {@code null} otherwise
235    */
236   @Nullable
237   public File getAutodetectedGradleHome() {
238     File result = getGradleHomeFromPath();
239     return result == null ? getGradleHomeFromEnvProperty() : result;
240   }
241
242   /**
243    * Tries to return gradle home that is defined as a dependency to the given module.
244    *
245    * @param module target module
246    * @return file handle that points to the gradle installation home defined as a dependency of the given module (if any)
247    */
248   @Nullable
249   public VirtualFile getGradleHome(@Nullable Module module) {
250     if (module == null) {
251       return null;
252     }
253     final VirtualFile[] roots = OrderEnumerator.orderEntries(module).getAllLibrariesAndSdkClassesRoots();
254     if (roots == null) {
255       return null;
256     }
257     for (VirtualFile root : roots) {
258       if (root != null && isGradleSdkHome(root)) {
259         return root;
260       }
261     }
262     return null;
263   }
264
265   /**
266    * Tries to return gradle home defined as a dependency of the given module; falls back to the project-wide settings otherwise.
267    *
268    * @param module  target module that can have gradle home as a dependency
269    * @param project target project which gradle home setting should be used if module-specific gradle location is not defined
270    * @return gradle home derived from the settings of the given entities (if any); {@code null} otherwise
271    */
272   @Nullable
273   public VirtualFile getGradleHome(@Nullable Module module, @Nullable Project project, @NotNull String linkedProjectPath) {
274     final VirtualFile result = getGradleHome(module);
275     if (result != null) {
276       return result;
277     }
278
279     final File home = getGradleHome(project, linkedProjectPath);
280     return home == null ? null : LocalFileSystem.getInstance().refreshAndFindFileByIoFile(home);
281   }
282
283   /**
284    * Tries to discover gradle installation path from the configured system path
285    *
286    * @return file handle for the gradle directory if it's possible to deduce from the system path; {@code null} otherwise
287    */
288   @Nullable
289   public File getGradleHomeFromPath() {
290     Ref<File> ref = myCachedGradleHomeFromPath;
291     if (ref != null) {
292       return ref.get();
293     }
294     String path = System.getenv("PATH");
295     if (path == null) {
296       return null;
297     }
298     for (String pathEntry : path.split(File.pathSeparator)) {
299       File dir = new File(pathEntry);
300       if (!dir.isDirectory()) {
301         continue;
302       }
303       for (String fileName : GRADLE_START_FILE_NAMES) {
304         File startFile = new File(dir, fileName);
305         if (startFile.isFile()) {
306           File candidate = dir.getParentFile();
307           if (isGradleSdkHome(candidate)) {
308             myCachedGradleHomeFromPath = new Ref<>(candidate);
309             return candidate;
310           }
311         }
312       }
313     }
314     return null;
315   }
316
317   /**
318    * Tries to discover gradle installation via environment property.
319    *
320    * @return file handle for the gradle directory deduced from the system property (if any)
321    */
322   @Nullable
323   public File getGradleHomeFromEnvProperty() {
324     String path = System.getenv(GRADLE_ENV_PROPERTY_NAME);
325     if (path == null) {
326       return null;
327     }
328     File candidate = new File(path);
329     return isGradleSdkHome(candidate) ? candidate : null;
330   }
331
332   /**
333    * Does the same job as {@link #isGradleSdkHome(File)} for the given virtual file.
334    *
335    * @param file gradle installation home candidate
336    * @return {@code true} if given file points to the gradle installation; {@code false} otherwise
337    */
338   public boolean isGradleSdkHome(@Nullable VirtualFile file) {
339     if (file == null) {
340       return false;
341     }
342     return isGradleSdkHome(new File(file.getPath()));
343   }
344
345   /**
346    * Allows to answer if given virtual file points to the gradle installation root.
347    *
348    * @param file gradle installation root candidate
349    * @return {@code true} if we consider that given file actually points to the gradle installation root;
350    * {@code false} otherwise
351    */
352   public boolean isGradleSdkHome(@Nullable File file) {
353     if (file == null) {
354       return false;
355     }
356     final File libs = new File(file, "lib");
357     if (!libs.isDirectory()) {
358       if (GradleEnvironment.DEBUG_GRADLE_HOME_PROCESSING) {
359         GradleLog.LOG.info(String.format(
360           "Gradle sdk check failed for the path '%s'. Reason: it doesn't have a child directory named 'lib'", file.getAbsolutePath()
361         ));
362       }
363       return false;
364     }
365
366     final boolean found = isGradleSdk(libs.listFiles());
367     if (GradleEnvironment.DEBUG_GRADLE_HOME_PROCESSING) {
368       GradleLog.LOG.info(String.format("Gradle home check %s for the path '%s'", found ? "passed" : "failed", file.getAbsolutePath()));
369     }
370     return found;
371   }
372
373   /**
374    * Allows to answer if given virtual file points to the gradle installation root.
375    *
376    * @param file gradle installation root candidate
377    * @return {@code true} if we consider that given file actually points to the gradle installation root;
378    * {@code false} otherwise
379    */
380   public boolean isGradleSdkHome(String gradleHomePath) {
381     return isGradleSdkHome(new File(gradleHomePath));
382   }
383
384   /**
385    * Allows to answer if given files contain the one from gradle installation.
386    *
387    * @param files files to process
388    * @return {@code true} if one of the given files is from the gradle installation; {@code false} otherwise
389    */
390   public boolean isGradleSdk(@Nullable VirtualFile... files) {
391     if (files == null) {
392       return false;
393     }
394     File[] arg = new File[files.length];
395     for (int i = 0; i < files.length; i++) {
396       arg[i] = new File(files[i].getPath());
397     }
398     return isGradleSdk(arg);
399   }
400
401   private boolean isGradleSdk(@Nullable File... files) {
402     return findGradleJar(files) != null;
403   }
404
405   @Nullable
406   private File findGradleJar(@Nullable File... files) {
407     if (files == null) {
408       return null;
409     }
410     for (File file : files) {
411       if (GRADLE_JAR_FILE_PATTERN.matcher(file.getName()).matches()) {
412         return file;
413       }
414     }
415
416     if (GradleEnvironment.DEBUG_GRADLE_HOME_PROCESSING) {
417       StringBuilder filesInfo = new StringBuilder();
418       for (File file : files) {
419         filesInfo.append(file.getAbsolutePath()).append(';');
420       }
421       if (filesInfo.length() > 0) {
422         filesInfo.setLength(filesInfo.length() - 1);
423       }
424       GradleLog.LOG.info(String.format(
425         "Gradle sdk check fails. Reason: no one of the given files matches gradle JAR pattern (%s). Files: %s",
426         GRADLE_JAR_FILE_PATTERN.toString(), filesInfo
427       ));
428     }
429
430     return null;
431   }
432
433   /**
434    * Allows to ask for the classpath roots of the classes that are additionally provided by the gradle integration (e.g. gradle class
435    * files, bundled groovy-all jar etc).
436    *
437    * @param project target project to use for gradle home retrieval
438    * @return classpath roots of the classes that are additionally provided by the gradle integration (if any);
439    * {@code null} otherwise
440    */
441   @Nullable
442   public List<VirtualFile> getClassRoots(@Nullable Project project) {
443     List<File> files = getClassRoots(project, null);
444     if(files == null) return null;
445     final LocalFileSystem localFileSystem = LocalFileSystem.getInstance();
446     final JarFileSystem jarFileSystem = JarFileSystem.getInstance();
447     return ContainerUtil.mapNotNull(files, file -> {
448       final VirtualFile virtualFile = localFileSystem.refreshAndFindFileByIoFile(file);
449       return virtualFile != null ? jarFileSystem.getJarRootForLocalFile(virtualFile) : null;
450     });
451   }
452
453   @Nullable
454   public List<File> getClassRoots(@Nullable Project project, @Nullable String rootProjectPath) {
455     if (project == null) return null;
456
457     if(rootProjectPath == null) {
458       for (Module module : ModuleManager.getInstance(project).getModules()) {
459         rootProjectPath = ExternalSystemModulePropertyManager.getInstance(module).getRootProjectPath();
460         List<File> result = findGradleSdkClasspath(project, rootProjectPath);
461         if(!result.isEmpty()) return result;
462       }
463     } else {
464       return findGradleSdkClasspath(project, rootProjectPath);
465     }
466
467     return null;
468   }
469
470   @Nullable
471   public static String getGradleVersion(@Nullable String gradleHome) {
472     if (gradleHome == null) return null;
473     File libs = new File(gradleHome, "lib");
474     if(!libs.isDirectory()) return null;
475
476     File[] files = libs.listFiles();
477     if (files != null) {
478       for (File file : files) {
479         final Matcher matcher = GRADLE_JAR_FILE_PATTERN.matcher(file.getName());
480         if (matcher.matches()) {
481           return matcher.group(2);
482         }
483       }
484     }
485     return null;
486   }
487
488
489   private List<File> findGradleSdkClasspath(Project project, String rootProjectPath) {
490     List<File> result = new ArrayList<>();
491
492     if (StringUtil.isEmpty(rootProjectPath)) return result;
493
494     File gradleHome = getGradleHome(project, rootProjectPath);
495
496     if (gradleHome == null || !gradleHome.isDirectory()) {
497       return result;
498     }
499
500     File src = new File(gradleHome, "src");
501     if (src.isDirectory()) {
502       if(new File(src, "org").isDirectory()) {
503         addRoots(result, src);
504       } else {
505         addRoots(result, src.listFiles());
506       }
507     }
508
509     final Collection<File> libraries = getAllLibraries(gradleHome);
510     if (libraries == null) {
511       return result;
512     }
513
514     for (File file : libraries) {
515       if (isGradleBuildClasspathLibrary(file)) {
516         ContainerUtil.addIfNotNull(result, file);
517       }
518     }
519
520     return result;
521   }
522
523   private boolean isGradleBuildClasspathLibrary(File file) {
524     String fileName = file.getName();
525     return ANY_GRADLE_JAR_FILE_PATTERN.matcher(fileName).matches()
526            || ANT_JAR_PATTERN.matcher(fileName).matches()
527            || IVY_JAR_PATTERN.matcher(fileName).matches()
528            || GroovyConfigUtils.matchesGroovyAll(fileName);
529   }
530
531   private void addRoots(@NotNull List<File> result, @Nullable File... files) {
532     if (files == null) return;
533     for (File file : files) {
534       if (file == null || !file.isDirectory()) continue;
535       result.add(file);
536     }
537   }
538
539   private File getWrappedGradleHome(String linkedProjectPath, @Nullable final WrapperConfiguration wrapperConfiguration) {
540     if (wrapperConfiguration == null) {
541       return null;
542     }
543     File gradleSystemDir;
544
545     if ("PROJECT".equals(wrapperConfiguration.getDistributionBase())) {
546       gradleSystemDir = new File(linkedProjectPath, ".gradle");
547     }
548     else {
549       gradleSystemDir = StartParameter.DEFAULT_GRADLE_USER_HOME;
550     }
551     if (!gradleSystemDir.isDirectory()) {
552       return null;
553     }
554
555     PathAssembler.LocalDistribution localDistribution = new PathAssembler(gradleSystemDir).getDistribution(wrapperConfiguration);
556
557     if (localDistribution.getDistributionDir() == null) {
558       return null;
559     }
560
561     File[] distFiles = localDistribution.getDistributionDir().listFiles(
562       f -> f.isDirectory() && StringUtil.startsWith(f.getName(), "gradle-"));
563
564     return distFiles == null || distFiles.length == 0 ? null : distFiles[0];
565   }
566 }