IDEA-53476 Gradle integration (Maven's level - dependencies, modules, repositories)

1. Moved functional that works with gradle api to the separate process;
2. Setup infrastructure for communication with that remote process;
3. Started implemented 'import' functionality;
This commit is contained in:
Denis.Zhdanov
2011-08-09 17:56:30 +04:00
parent d7d2cb8de7
commit 7d142c1de7
24 changed files with 813 additions and 96 deletions
+6
View File
@@ -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")
}
+9
View File
@@ -17,6 +17,15 @@
<orderEntry type="module" module-name="execution-openapi" />
<orderEntry type="module" module-name="openapi" />
<orderEntry type="module" module-name="java-impl" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/gradle-tooling-api.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
Binary file not shown.
@@ -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
@@ -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
@@ -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<GradleSettings> {
public class GradleSettings implements PersistentStateComponent<GradleSettings>, Cloneable {
public String GRADLE_HOME;
@@ -48,4 +48,16 @@ public class GradleSettings implements PersistentStateComponent<GradleSettings>
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;
}
}
@@ -1,32 +0,0 @@
package org.jetbrains.plugins.gradle.importing;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Application-level representation of the <code>'build.gradle'</code> file.
*
* @author Denis Zhdanov
* @since 8/1/11 1:30 PM
*/
public class GradleProject {
private final File myFile;
/**
* Creates new <code>GradleProject</code> object for the given gradle project file.
*
* @param projectFile target gradle project file
*/
public GradleProject(@NotNull File projectFile) {
myFile = projectFile;
}
/**
* Parses underlying <code>'build.gradle'</code> file and populates current object within that information.
*/
public void build() {
// TODO den implement
}
}
@@ -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<GradleProje
private static final String GRADLE_IMPORT_ROOT_KEY = "gradle.import.root";
private final GradleProjectParser myProjectParser = new GradleProjectParser();
private GradleProject myProject;
private GradleProject myGradleProject;
private File myProjectFile;
@Override
@@ -82,9 +89,13 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder<GradleProje
* @return start path to use during importing gradle projects
*/
@NotNull
public String getRootDirectoryPath(@Nullable WizardContext context) {
public String getProjectPath(@Nullable WizardContext context) {
String result = PropertiesComponent.getInstance().getValue(GRADLE_IMPORT_ROOT_KEY, "");
if (new File(result).isDirectory()) {
File file = new File(result);
if (file.exists()) {
if (myProjectFile == null) {
myProjectFile = file;
}
return result;
}
if (context == null) {
@@ -106,17 +117,8 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder<GradleProje
}
if (file == null) {
return;
}
String pathToStore = null;
if (file.isDirectory()) {
pathToStore = file.getAbsolutePath();
}
else if (file.isFile()) {
pathToStore = file.getParentFile().getAbsolutePath();
}
if (pathToStore != null) {
PropertiesComponent.getInstance().setValue(GRADLE_IMPORT_ROOT_KEY, pathToStore);
}
PropertiesComponent.getInstance().setValue(GRADLE_IMPORT_ROOT_KEY, file.getAbsolutePath());
}
/**
@@ -132,10 +134,29 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder<GradleProje
throw new ConfigurationException(GradleBundle.message("gradle.import.text.error.directory.instead.file"));
}
try {
myProject = myProjectParser.parse(myProjectFile);
ProgressManager.getInstance().run(new Task.Modal(null, GradleBundle.message("gradle.import.progress.text"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
GradleApiFacadeManager manager = ServiceManager.getService(GradleApiFacadeManager.class);
try {
GradleProjectResolver resolver = manager.getFacade().getResolver();
myGradleProject = resolver.resolveProjectInfo(myProjectFile.getAbsolutePath());
}
catch (Exception e) {
// Ignore here because it will be reported on method exit.
GradleLog.LOG.warn("Can't resolve gradle project", e);
}
}
});
}
catch (IllegalArgumentException e) {
throw new ConfigurationException(e.getMessage(), GradleBundle.message("gradle.import.text.error.cannot.parse.project"));
}
if (myGradleProject == null) {
throw new ConfigurationException(
GradleBundle.message("gradle.import.text.error.resolve.generic", myProjectFile.getPath()),
GradleBundle.message("gradle.import.title.error.resolve.generic")
);
}
}
}
@@ -1,32 +0,0 @@
package org.jetbrains.plugins.gradle.importing;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Encapsulates functionality of performing <code>'build.gradle' -&gt; {@link GradleProject}</code> conversion.
* <p/>
* 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);
}
}
@@ -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()));
}
}
@@ -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
* <a href="http://gradle.org/current/docs/javadoc/org/gradle/tooling/package-summary.html">gradle tooling api</a>.
* <p/>
* 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 <code>'resolve gradle project'</code> service that works at another process.
*
* @return <code>'resolve gradle project'</code> 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;
}
@@ -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<Class<?>, RemoteGradleService> myRemotes = new ConcurrentHashMap<Class<?>, RemoteGradleService>();
private final AtomicReference<RemoteGradleProcessSettings> mySettings = new AtomicReference<RemoteGradleProcessSettings>();
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.
* <p/>
* Uses cached value if it's found; creates new and caches it otherwise.
*
* @param clazz target service implementation class
* @param <T> 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 extends Remote & RemoteGradleService> T getRemote(@NotNull Class<T> 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);
}
}
@@ -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}.
* <p/>
* 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<Pair<GradleApiFacade, RemoteGradleProcessSettings>> myFacade
= new AtomicReference<Pair<GradleApiFacade, RemoteGradleProcessSettings>>();
private final GradleLibraryManager myGradleLibraryManager = GradleLibraryManager.INSTANCE;
private final RemoteProcessSupport<Object, GradleApiFacade, Object> mySupport;
private final GradleApiFacade myApiFacade;
public GradleApiFacadeManager() {
mySupport = new RemoteProcessSupport<Object, GradleApiFacade, Object>(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<File> 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<String> classPath = new ArrayList<String>();
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<GradleApiFacade, RemoteGradleProcessSettings> 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<GradleApiFacade, RemoteGradleProcessSettings> newPair
= new Pair<GradleApiFacade, RemoteGradleProcessSettings>(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<GradleApiFacade, RemoteGradleProcessSettings> 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);
}
}
}
@@ -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.
* <p/>
* 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;
}
}
@@ -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);
}
@@ -0,0 +1,12 @@
package org.jetbrains.plugins.gradle.resolve;
/**
* Defines IntelliJ module view to the application configured via gradle.
* <p/>
* Implementations of this interface are not obliged to be thread-safe.
*
* @author Denis Zhdanov
* @since 8/8/11 12:10 PM
*/
public interface GradleModule {
}
@@ -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
}
@@ -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.
* <p/>
* 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<? extends GradleModule> getModules();
}
@@ -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<GradleModuleImpl> myModules = new HashSet<GradleModuleImpl>();
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<? extends GradleModule> getModules() {
return myModules;
}
}
@@ -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 <code>'build.gradle'</code>.
*
* @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).
* <p/>
* <b>Note:</b> 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;
}
@@ -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<ProjectConnection> myConnections = new LinkedBlockingQueue<ProjectConnection>();
private final AtomicReference<RemoteGradleProcessSettings> mySettings = new AtomicReference<RemoteGradleProcessSettings>();
@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);
}
}
@@ -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.
* <p/>
* Rationale: we want to import gradle project, i.e. setup IDE structure (project, modules, libraries etc) on the basis
* of existing <code>'build.gradle'</code> file. We need to have object-level representation of the
* <code>'build.gradle'</code> 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 <code>System.exit()</code> 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());
}
}
@@ -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;
@@ -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() {
}
}