diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index a74b08b2f039..aa510e3be835 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -177,6 +177,12 @@ public def layoutCommunityPlugins(String home) { fileset(dir: "$home/plugins/maven/maven2-server-impl/lib") } + layoutPlugin("gradle") { + jar("gradle.jar") { + module("gradle") + } + } + layoutPlugin("git4idea") { fileset(dir: "$home/plugins/git4idea/lib", excludes: "**/trilead-ssh2-src.zip") } diff --git a/plugins/gradle/gradle.iml b/plugins/gradle/gradle.iml index 4ba3c58beb15..c9bf1c328807 100644 --- a/plugins/gradle/gradle.iml +++ b/plugins/gradle/gradle.iml @@ -17,6 +17,15 @@ + + + + + + + + + diff --git a/plugins/gradle/lib/gradle-tooling-api.jar b/plugins/gradle/lib/gradle-tooling-api.jar new file mode 100644 index 000000000000..2b38bc80a055 Binary files /dev/null and b/plugins/gradle/lib/gradle-tooling-api.jar differ diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index 2fc00d71000f..12d0a07c6b10 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -1,8 +1,15 @@ gradle.name=Gradle -gradle.import.label.select.project=Gradle &project +gradle.import.label.select.project=Gradle project: gradle.import.title.select.project=Select project to import gradle.import.text.home.path=Gradle home: +gradle.import.progress.text=Building gradle project info + +gradle.import.title.error.resolve.generic=Resolve error +gradle.import.text.error.resolve.generic=Can't resolve target gradle project at '{0}' gradle.import.text.error.project.undefined=No project file is defined gradle.import.text.error.directory.instead.file=Given path points to directory instead of gradle build file +gradle.import.text.error.invalid.path=Can't resolve gradle project. Reason: given path ({0}) doesn't point to a file gradle.import.text.error.cannot.parse.project=Can not parse gradle project + +gradle.generic.text.error.sdk.undefined=Gradle installation is unknown \ No newline at end of file diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java index 13a6a3b869c6..21bdd1b3125c 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleConfigurable.java @@ -94,7 +94,11 @@ public class GradleConfigurable implements SearchableConfigurable { } String newPath = myPathComponent.getPath(); String oldPath = GradleSettings.getInstance(myProject).GRADLE_HOME; - return newPath == null ? oldPath == null : newPath.equals(oldPath); + boolean modified = newPath == null ? oldPath == null : !newPath.equals(oldPath); + if (modified) { + useNormalColorForPath(); + } + return modified; } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java index d147a24af95f..cf01402eb8da 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleSettings.java @@ -30,7 +30,7 @@ import org.jetbrains.annotations.NotNull; @Storage( file = "$PROJECT_CONFIG_DIR$/gradle.xml", scheme = StorageScheme.DIRECTORY_BASED) } ) -public class GradleSettings implements PersistentStateComponent { +public class GradleSettings implements PersistentStateComponent, Cloneable { public String GRADLE_HOME; @@ -48,4 +48,16 @@ public class GradleSettings implements PersistentStateComponent public static GradleSettings getInstance(@NotNull Project project) { return ServiceManager.getService(project, GradleSettings.class); } + + @Override + protected GradleSettings clone() throws CloneNotSupportedException { + GradleSettings result = new GradleSettings(); + result.GRADLE_HOME = GRADLE_HOME; + return result; + } + + @Override + public String toString() { + return "home: " + GRADLE_HOME; + } } \ No newline at end of file diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProject.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProject.java deleted file mode 100644 index a16c3764af62..000000000000 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProject.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.jetbrains.plugins.gradle.importing; - -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -/** - * Application-level representation of the 'build.gradle' file. - * - * @author Denis Zhdanov - * @since 8/1/11 1:30 PM - */ -public class GradleProject { - - private final File myFile; - - /** - * Creates new GradleProject object for the given gradle project file. - * - * @param projectFile target gradle project file - */ - public GradleProject(@NotNull File projectFile) { - myFile = projectFile; - } - - /** - * Parses underlying 'build.gradle' file and populates current object within that information. - */ - public void build() { - // TODO den implement - } -} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java index 55b68a321f35..46bb92b4ec9c 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java @@ -2,17 +2,25 @@ package org.jetbrains.plugins.gradle.importing; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.projectWizard.WizardContext; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.configuration.ModulesProvider; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.projectImport.ProjectImportBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager; +import org.jetbrains.plugins.gradle.resolve.GradleProject; +import org.jetbrains.plugins.gradle.resolve.GradleProjectResolver; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.util.GradleIcons; +import org.jetbrains.plugins.gradle.util.GradleLog; import javax.swing.*; import java.io.File; @@ -29,8 +37,7 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder'build.gradle' -> {@link GradleProject} conversion. - *

- * Thread-safe. - * - * @author Denis Zhdanov - * @since 8/3/11 12:04 PM - */ -public class GradleProjectParser { - - /** - * // TODO den add doc - * - * @param file - * @return - * @throws IllegalArgumentException if given file handle doesn't point to valid gradle project - */ - @NotNull - public GradleProject parse(@NotNull File file) throws IllegalArgumentException{ - if (!file.isFile()) { - - } - // TODO den implement - return new GradleProject(file); - } -} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleSelectProjectStep.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleSelectProjectStep.java index de091d774efc..364c3b056a89 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleSelectProjectStep.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleSelectProjectStep.java @@ -1,8 +1,10 @@ package org.jetbrains.plugins.gradle.importing; -import com.intellij.ide.util.projectWizard.NamePathComponent; import com.intellij.ide.util.projectWizard.WizardContext; +import com.intellij.openapi.fileChooser.FileTypeDescriptor; import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.util.GradleBundle; @@ -24,23 +26,27 @@ import java.awt.*; public class GradleSelectProjectStep extends GradleImportWizardStep { private final JPanel myComponent = new JPanel(new GridBagLayout()); - private final NamePathComponent myRootPathComponent; + private final TextFieldWithBrowseButton myProjectPathComponent; public GradleSelectProjectStep(@NotNull WizardContext context) { super(context); GridBagConstraints constraints = new GridBagConstraints(); + JLabel label = new JLabel(GradleBundle.message("gradle.import.label.select.project")); + myComponent.add(label); + constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.weightx = 1; constraints.fill = GridBagConstraints.HORIZONTAL; - myRootPathComponent = new NamePathComponent( - "", GradleBundle.message("gradle.import.label.select.project"), GradleBundle.message("gradle.import.title.select.project"), "", - false, - false + myProjectPathComponent = new TextFieldWithBrowseButton(); + myProjectPathComponent.addBrowseFolderListener( + "", + GradleBundle.message("gradle.import.title.select.project"), + null, + new FileTypeDescriptor(GradleBundle.message("gradle.import.label.select.project"), "gradle") ); - myRootPathComponent.setNameComponentVisible(false); - myComponent.add(myRootPathComponent, constraints); + myComponent.add(myProjectPathComponent, constraints); myComponent.add(Box.createVerticalGlue()); } @@ -51,8 +57,8 @@ public class GradleSelectProjectStep extends GradleImportWizardStep { @Override public void updateStep() { - if (!myRootPathComponent.isPathChangedByUser()) { - myRootPathComponent.setPath(getBuilder().getRootDirectoryPath(getWizardContext())); + if (isPathChanged()) { + myProjectPathComponent.setText(getBuilder().getProjectPath(getWizardContext())); } } @@ -73,8 +79,12 @@ public class GradleSelectProjectStep extends GradleImportWizardStep { } private void storeCurrentSettings() { - if (myRootPathComponent.isPathChangedByUser()) { - getBuilder().setCurrentProjectPath(myRootPathComponent.getPath()); + if (isPathChanged()) { + getBuilder().setCurrentProjectPath(myProjectPathComponent.getText()); } } + + private boolean isPathChanged() { + return !StringUtil.equals(myProjectPathComponent.getText(), getBuilder().getProjectPath(getWizardContext())); + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java new file mode 100644 index 000000000000..f140f35676b0 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java @@ -0,0 +1,38 @@ +package org.jetbrains.plugins.gradle.remote; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.resolve.GradleProjectResolver; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +/** + * Serves as a facade for working with + * gradle tooling api. + *

+ * The main idea is that we don't want to use it directly from IntelliJ IDEA process (to avoid unnecessary heap/perm gen pollution, + * memory leaks etc). So, we use it at external process and current class works as a facade to it from IntelliJ process. + * + * @author Denis Zhdanov + * @since 8/8/11 10:52 AM + */ +public interface GradleApiFacade extends Remote { + + /** + * Exposes 'resolve gradle project' service that works at another process. + * + * @return 'resolve gradle project' service + * @throws RemoteException in case of unexpected I/O exception during processing + * @throws IllegalStateException in case of inability to create the service + */ + @NotNull + GradleProjectResolver getResolver() throws RemoteException, IllegalStateException; + + /** + * Asks remote gradle process to apply given settings. + * + * @param settings settings to apply + * @throws RemoteException in case of unexpected I/O exception during processing + */ + void applySettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException; +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeImpl.java new file mode 100644 index 000000000000..27205b2d37c2 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeImpl.java @@ -0,0 +1,85 @@ +package org.jetbrains.plugins.gradle.remote; + +import com.intellij.execution.rmi.RemoteServer; +import com.intellij.util.containers.ConcurrentHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.resolve.GradleProjectResolver; +import org.jetbrains.plugins.gradle.resolve.GradleProjectResolverImpl; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Denis Zhdanov + * @since 8/8/11 12:51 PM + */ +public class GradleApiFacadeImpl extends RemoteServer implements GradleApiFacade { + + private final ConcurrentMap, RemoteGradleService> myRemotes = new ConcurrentHashMap, RemoteGradleService>(); + private final AtomicReference mySettings = new AtomicReference(); + + public static void main(String[] args) throws Exception { + start(new GradleApiFacadeImpl()); + } + + @NotNull + @Override + public GradleProjectResolver getResolver() throws RemoteException, IllegalStateException { + try { + return getRemote(GradleProjectResolverImpl.class); + } + catch (Exception e) { + throw new IllegalStateException(String.format("Can't create '%s' service", GradleProjectResolver.class.getName()), e); + } + } + + /** + * Generic method to retrieve exposed implementations of the target interface. + *

+ * Uses cached value if it's found; creates new and caches it otherwise. + * + * @param clazz target service implementation class + * @param service implementation class + * @return implementation of the target service + * @throws IllegalAccessException in case of incorrect assumptions about server class interface + * @throws InstantiationException in case of incorrect assumptions about server class interface + * @throws ClassNotFoundException in case of incorrect assumptions about server class interface + */ + @SuppressWarnings("unchecked") + private T getRemote(@NotNull Class clazz) + throws ClassNotFoundException, IllegalAccessException, InstantiationException + { + Object cachedResult = myRemotes.get(clazz); + if (cachedResult != null) { + return (T)cachedResult; + } + T result = clazz.newInstance(); + RemoteGradleProcessSettings settings = mySettings.get(); + if (settings != null) { + result.setSettings(settings); + } + try { + UnicastRemoteObject.exportObject(result, 0); + T stored = (T)myRemotes.putIfAbsent(clazz, result); + return stored == null ? result : stored; + } + catch (RemoteException e) { + Object raceResult = myRemotes.get(clazz); + if (raceResult != null) { + // Race condition occurred + return (T)raceResult; + } + else { + throw new IllegalStateException(String.format("Can't prepare remote service for interface '%s'", clazz), e); + } + } + } + + @Override + public void applySettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { + mySettings.set(settings); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java new file mode 100644 index 000000000000..eaca740d36c9 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java @@ -0,0 +1,238 @@ +package org.jetbrains.plugins.gradle.remote; + +import com.intellij.execution.DefaultExecutionResult; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.CommandLineState; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.SimpleJavaParameters; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessTerminatedListener; +import com.intellij.execution.rmi.RemoteProcessSupport; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.JdkUtil; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SimpleJavaSdkType; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.ShutDownTracker; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiBundle; +import com.intellij.util.PathUtil; +import com.intellij.util.SystemProperties; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.gradle.util.GradleLibraryManager; +import org.jetbrains.plugins.gradle.util.GradleLog; + +import java.io.File; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.charset.Charset; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Entry point to work with remote {@link GradleApiFacade}. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 8/8/11 1:08 PM + */ +public class GradleApiFacadeManager { + + private static final String MAIN_CLASS_NAME = GradleApiFacadeImpl.class.getName(); + private static final int REMOTE_FAIL_RECOVERY_ATTEMPTS_NUMBER = 3; + + private final AtomicReference> myFacade + = new AtomicReference>(); + + private final GradleLibraryManager myGradleLibraryManager = GradleLibraryManager.INSTANCE; + + private final RemoteProcessSupport mySupport; + private final GradleApiFacade myApiFacade; + + public GradleApiFacadeManager() { + mySupport = new RemoteProcessSupport(GradleApiFacade.class) { + @Override + protected void fireModificationCountChanged() { + } + + @Override + protected String getName(Object o) { + return GradleApiFacade.class.getName(); + } + + @Override + protected RunProfileState getRunProfileState(Object o, Object configuration, Executor executor) throws ExecutionException { + return createRunProfileState(); + } + }; + myApiFacade = (GradleApiFacade)Proxy.newProxyInstance( + GradleApiFacadeManager.class.getClassLoader(), new Class[]{GradleApiFacade.class}, new MyHandler() + ); + + ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { + public void run() { + shutdown(false); + } + }); + } + + private RunProfileState createRunProfileState() { + return new CommandLineState(null) { + private SimpleJavaParameters createJavaParameters() throws ExecutionException { + Collection gradleLibraries = myGradleLibraryManager.getAllLibraries(null); + GradleLog.LOG.assertTrue(gradleLibraries != null, GradleBundle.message("gradle.generic.text.error.sdk.undefined")); + if (gradleLibraries == null) { + return null; + } + + final SimpleJavaParameters params = new SimpleJavaParameters(); + params.setJdk(new SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())); + + params.setWorkingDirectory(PathManager.getBinPath()); + final ArrayList classPath = new ArrayList(); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(NotNull.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(StringUtil.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(THashSet.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(LanguageLevel.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(PsiBundle.class), classPath); + ContainerUtil.addIfNotNull(PathUtil.getJarPathForClass(getClass()), classPath); + for (File library : gradleLibraries) { + classPath.add(library.getAbsolutePath()); + } + params.getClassPath().addAll(classPath); + params.getClassPath().add(PathManager.getResourceRoot(getClass(), "/messages/CommonBundle.properties")); + + params.setMainClass(MAIN_CLASS_NAME); + + params.getVMParametersList().addParametersString("-Djava.awt.headless=true -Xmx512m"); + //params.getVMParametersList().addParametersString("-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5009"); + return params; + } + + @Override + public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { + ProcessHandler processHandler = startProcess(); + return new DefaultExecutionResult(null, processHandler, AnAction.EMPTY_ARRAY); + } + + protected OSProcessHandler startProcess() throws ExecutionException { + SimpleJavaParameters params = createJavaParameters(); + Sdk sdk = params.getJdk(); + if (sdk == null) { + throw new ExecutionException("No sdk is defined. Params: " + params); + } + + final GeneralCommandLine commandLine = JdkUtil.setupJVMCommandLine( + ((JavaSdkType)sdk.getSdkType()).getVMExecutablePath(sdk), + params, + false // TODO den check + ); + final OSProcessHandler processHandler = new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString()) { + @Override + public Charset getCharset() { + return commandLine.getCharset(); + } + }; + ProcessTerminatedListener.attach(processHandler); + return processHandler; + } + }; + } + + public synchronized void shutdown(boolean wait) { + mySupport.stopAll(wait); + } + + /** + * @return gradle api facade to use + * @throws Exception in case of inability to return the facade + */ + @NotNull + public GradleApiFacade getFacade() throws Exception { + return myApiFacade; + } + + public Object doInvoke(Method method, Object[] args, int invocationNumber) throws Throwable { + GradleApiFacade facade = doGetFacade(); + try { + return method.invoke(facade, args); + } + catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RemoteException && invocationNumber > 0) { + Thread.sleep(1000); + return doInvoke(method, args, invocationNumber - 1); + } + else { + throw e; + } + } + } + + @NotNull + private GradleApiFacade doGetFacade() throws Exception { + Pair pair = myFacade.get(); + if (pair != null) { + if (isValid(pair)) { + return pair.first; + } + mySupport.stopAll(true); + } + + GradleApiFacade result = mySupport.acquire(this, ""); + if (result == null) { + throw new IllegalStateException("Can't obtain facade to working with gradle api at the remote process"); + } + Pair newPair + = new Pair(result, getRemoteSettings()); + if (!myFacade.compareAndSet(null, newPair)) { + GradleLog.LOG.warn("Detected unexpected duplicate tooling api facade instance creation"); + return myFacade.get().first; + } + result.applySettings(getRemoteSettings()); + return result; + } + + private boolean isValid(@NotNull Pair pair) { + // Check remote process is alive. + try { + pair.first.getResolver(); + } + catch (RemoteException e) { + return false; + } + + // Check that significant settings are not changed + RemoteGradleProcessSettings oldSettings = pair.second; + RemoteGradleProcessSettings currentSettings = getRemoteSettings(); + return StringUtil.equals(oldSettings.getGradleHome(), currentSettings.getGradleHome()); + } + + @NotNull + private RemoteGradleProcessSettings getRemoteSettings() { + File gradleHome = myGradleLibraryManager.getGradleHome(); + return new RemoteGradleProcessSettings(gradleHome.getAbsolutePath()); + } + + private class MyHandler implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return doInvoke(method, args, REMOTE_FAIL_RECOVERY_ATTEMPTS_NUMBER); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProcessSettings.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProcessSettings.java new file mode 100644 index 000000000000..0f8df656f684 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProcessSettings.java @@ -0,0 +1,35 @@ +package org.jetbrains.plugins.gradle.remote; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * Encapsulates settings important for the process that works with gradle tooling api. + *

+ * The main idea is that we don't want to use the tooling api at intellij process (avoid heap/cpu pollution) + * and run separate process instead. Current class encapsulates settings significant for it. + * + * @author Denis Zhdanov + * @since 8/9/11 12:12 PM + */ +public class RemoteGradleProcessSettings implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String myGradleHome; + + public RemoteGradleProcessSettings(@NotNull String gradleHome) { + myGradleHome = gradleHome; + } + + @NotNull + public String getGradleHome() { + return myGradleHome; + } + + @Override + public String toString() { + return "home: " + myGradleHome; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java new file mode 100644 index 000000000000..f81a5b8ce77a --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java @@ -0,0 +1,19 @@ +package org.jetbrains.plugins.gradle.remote; + +import org.jetbrains.annotations.NotNull; + +/** + * Generic interface with common functionality for all remote services that work with gradle tooling api. + * + * @author Denis Zhdanov + * @since 8/9/11 3:19 PM + */ +public interface RemoteGradleService { + + /** + * Provides the service with settings to use. + * + * @param settings settings to use + */ + void setSettings(@NotNull RemoteGradleProcessSettings settings); +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleModule.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleModule.java new file mode 100644 index 000000000000..13dcbc7c9f96 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleModule.java @@ -0,0 +1,12 @@ +package org.jetbrains.plugins.gradle.resolve; + +/** + * Defines IntelliJ module view to the application configured via gradle. + *

+ * Implementations of this interface are not obliged to be thread-safe. + * + * @author Denis Zhdanov + * @since 8/8/11 12:10 PM + */ +public interface GradleModule { +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleModuleImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleModuleImpl.java new file mode 100644 index 000000000000..7a359c178cd4 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleModuleImpl.java @@ -0,0 +1,12 @@ +package org.jetbrains.plugins.gradle.resolve; + +import java.io.Serializable; + +/** + * @author Denis Zhdanov + * @since 8/8/11 12:11 PM + */ +public class GradleModuleImpl implements GradleModule, Serializable { + + // TODO den implement +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProject.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProject.java new file mode 100644 index 000000000000..f8af423f7b74 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProject.java @@ -0,0 +1,26 @@ +package org.jetbrains.plugins.gradle.resolve; + +import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * Defines IntelliJ project view to the application configured via gradle. + *

+ * Implementations of this interface are not obliged to be thread-safe. + * + * @author Denis Zhdanov + * @since 8/8/11 12:05 PM + */ +public interface GradleProject { + + @NotNull + String getJdkName(); + + @NotNull + LanguageLevel getLanguageLevel(); + + @NotNull + Set getModules(); +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectImpl.java new file mode 100644 index 000000000000..2c5a133564aa --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectImpl.java @@ -0,0 +1,60 @@ +package org.jetbrains.plugins.gradle.resolve; + +import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +/** + * Not thread-safe. + * + * @author Denis Zhdanov + * @since 8/1/11 1:30 PM + */ +public class GradleProjectImpl implements Serializable, GradleProject { + + private static final long serialVersionUID = 1L; + private static final LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JDK_1_6; + private static final String DEFAULT_JDK = "1.6"; + + private final Set myModules = new HashSet(); + + private String myJdk = DEFAULT_JDK; + private LanguageLevel myLanguageLevel = DEFAULT_LANGUAGE_LEVEL; + + @NotNull + @Override + public String getJdkName() { + return myJdk; + } + + public void setJdk(@NotNull String jdk) { + myJdk = jdk; + } + + @NotNull + @Override + public LanguageLevel getLanguageLevel() { + return myLanguageLevel; + } + + public void setLanguageLevel(@Nullable String languageLevel) { + LanguageLevel level = LanguageLevel.parse(languageLevel); + if (level != null) { + myLanguageLevel = level; + } + } + + public void addModule(@NotNull GradleModuleImpl module) { + myModules.add(module); + } + + @NotNull + @Override + public Set getModules() { + return myModules; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectResolver.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectResolver.java new file mode 100644 index 000000000000..0042e3b99580 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectResolver.java @@ -0,0 +1,31 @@ +package org.jetbrains.plugins.gradle.resolve; + +import org.jetbrains.annotations.NotNull; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +/** + * Defines common interface for resolving gradle project, i.e. building object-level representation of 'build.gradle'. + * + * @author Denis Zhdanov + * @since 8/8/11 10:58 AM + */ +public interface GradleProjectResolver extends Remote { + + /** + * Builds object-level representation of the gradle project file contained at the target directory (dependencies are not resolved + * during that). + *

+ * Note: gradle api doesn't allow to explicitly define project file to use though it's possible to do that via + * command-line switch. So, we want to treat the argument as a target project file name when that is supported. + * + * @param projectPath absolute path to the gradle project file + * @return object-level representation of the target gradle project + * @throws RemoteException in case of unexpected exception during remote communications + * @throws IllegalArgumentException if given path doesn't point to directory that contains gradle project + * @throws IllegalStateException if it's not possible to resolve target project info + */ + @NotNull + GradleProject resolveProjectInfo(@NotNull String projectPath) throws RemoteException, IllegalArgumentException, IllegalStateException; +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectResolverImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectResolverImpl.java new file mode 100644 index 000000000000..a495b7e0f8ff --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/GradleProjectResolverImpl.java @@ -0,0 +1,108 @@ +package org.jetbrains.plugins.gradle.resolve; + +import com.intellij.execution.rmi.RemoteObject; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.gradle.tooling.model.idea.OfflineIdeaProject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; +import org.jetbrains.plugins.gradle.remote.RemoteGradleService; +import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.gradle.util.GradleLog; + +import java.io.File; +import java.rmi.RemoteException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Denis Zhdanov + * @since 8/8/11 11:09 AM + */ +public class GradleProjectResolverImpl extends RemoteObject implements GradleProjectResolver, RemoteGradleService { + + private final BlockingQueue myConnections = new LinkedBlockingQueue(); + private final AtomicReference mySettings = new AtomicReference(); + + @NotNull + @Override + public GradleProject resolveProjectInfo(@NotNull String projectPath) throws RemoteException { + try { + return doResolve(projectPath); + } + catch (Throwable e) { + throw new IllegalStateException(GradleBundle.message("gradle.import.text.error.resolve.generic", projectPath), e); + } + } + + @NotNull + private GradleProject doResolve(@NotNull String projectPath) { + ProjectConnection connection = getConnection(projectPath); + OfflineIdeaProject project = connection.getModel(OfflineIdeaProject.class); + GradleProjectImpl result = new GradleProjectImpl(); + result.setJdk(project.getJdkName()); + result.setLanguageLevel(project.getLanguageLevel().getLevel()); + // TODO den build modules here + return result; + } + + /** + * Allows to retrieve gradle api connection to use for the given project. + * + * @param projectPath target project path + * @return connection to use + * @throws IllegalStateException if it's not possible to create the connection + */ + @NotNull + private ProjectConnection getConnection(@NotNull String projectPath) throws IllegalStateException { + File projectFile = new File(projectPath); + if (!projectFile.isFile()) { + throw new IllegalArgumentException(GradleBundle.message("gradle.import.text.error.invalid.path", projectPath)); + } + File projectDir = projectFile.getParentFile(); + GradleConnector connector = GradleConnector.newConnector(); + RemoteGradleProcessSettings settings = mySettings.get(); + if (settings != null) { + connector.useInstallation(new File(settings.getGradleHome())); + } + connector.forProjectDirectory(projectDir); + ProjectConnection connection = connector.connect(); + if (connection == null) { + throw new IllegalStateException(String.format( + "Can't create connection to the target project via gradle tooling api. Project path: '%s'", projectPath + )); + } + myConnections.add(connection); + return connection; + } + + @SuppressWarnings("NonSynchronizedMethodOverridesSynchronizedMethod") + @Override + public void unreferenced() { + releaseConnectionIfPossible(); + super.unreferenced(); + } + + private void releaseConnectionIfPossible() { + while (!myConnections.isEmpty()) { + try { + ProjectConnection connection = myConnections.poll(1, TimeUnit.SECONDS); + connection.close(); + } + catch (InterruptedException e) { + GradleLog.LOG.warn("Detected unexpected thread interruption on releasing gradle connections", e); + Thread.currentThread().interrupt(); + } + catch (Throwable e) { + GradleLog.LOG.warn("Got unexpected exception on closing project connection created by the gradle tooling api", e); + } + } + } + + @Override + public void setSettings(@NotNull RemoteGradleProcessSettings settings) { + mySettings.set(settings); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/RemoteResolveEntryPoint.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/RemoteResolveEntryPoint.java new file mode 100644 index 000000000000..eb0de9ff6366 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/resolve/RemoteResolveEntryPoint.java @@ -0,0 +1,21 @@ +package org.jetbrains.plugins.gradle.resolve; + +import com.intellij.execution.rmi.RemoteServer; + +/** + * Entry point to the processing that resolves gradle project via its tooling api and exposes the result to IntelliJ process. + *

+ * Rationale: we want to import gradle project, i.e. setup IDE structure (project, modules, libraries etc) on the basis + * of existing 'build.gradle' file. We need to have object-level representation of the + * 'build.gradle' file data then. That is done via gradle api, i.e. it should be initialised and asked to + * perform the job. The thing is that we don't want to have that done at IJ process. The reason is that that is not our + * codebase, so, we can't be sure that it doesn't leaks memory, consumes perm gen, calls System.exit() etc. + * + * @author Denis Zhdanov + * @since 8/5/11 11:50 AM + */ +public class RemoteResolveEntryPoint extends RemoteServer { + public static void main(String[] args) throws Exception { + start(new GradleProjectResolverImpl()); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLibraryManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLibraryManager.java index 8670f9f01d62..c27bed2df53f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLibraryManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLibraryManager.java @@ -1,6 +1,7 @@ package org.jetbrains.plugins.gradle.util; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -52,7 +53,7 @@ public class GradleLibraryManager { // Manually defined gradle home File gradleHome = getGradleHome(project); - if (gradleHome == null || gradleHome.isDirectory()) { + if (gradleHome == null || !gradleHome.isDirectory()) { return null; } @@ -70,6 +71,17 @@ public class GradleLibraryManager { return result; } + /** + * Performs the same job as {@link #getGradleHome(Project)} but tries to deduce project to use. + * + * @return file handle that points to the gradle installation home (if any) + */ + public File getGradleHome() { + ProjectManager projectManager = ProjectManager.getInstance(); + Project[] openProjects = projectManager.getOpenProjects(); + return openProjects.length == 1 ? getGradleHome(openProjects[0]) : getGradleHome(projectManager.getDefaultProject()); + } + /** * Tries to return file handle that points to the gradle installation home. * @@ -96,7 +108,7 @@ public class GradleLibraryManager { @Nullable public File getManuallyDefinedGradleHome(@Nullable Project project) { if (project == null) { - return null; + project = ProjectManager.getInstance().getDefaultProject(); } GradleSettings settings = GradleSettings.getInstance(project); String path = settings.GRADLE_HOME; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLog.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLog.java new file mode 100644 index 000000000000..3eaf8b22485d --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleLog.java @@ -0,0 +1,15 @@ +package org.jetbrains.plugins.gradle.util; + +import com.intellij.openapi.diagnostic.Logger; + +/** + * @author Denis Zhdanov + * @since 8/8/11 11:44 AM + */ +public class GradleLog { + + public static final Logger LOG = Logger.getInstance("#" + GradleLog.class.getName()); + + private GradleLog() { + } +}