IDEA-150835: Provide an API which tells which production module corresponding to...
authornik <Nikolay.Chashnikov@jetbrains.com>
Mon, 25 Jan 2016 13:45:38 +0000 (16:45 +0300)
committernik <Nikolay.Chashnikov@jetbrains.com>
Mon, 25 Jan 2016 13:46:07 +0000 (16:46 +0300)
java/java-tests/testSrc/com/intellij/roots/ModuleTestPropertiesTest.java [new file with mode: 0644]
platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ModuleData.java
platform/external-system-api/src/com/intellij/openapi/externalSystem/service/project/IdeModifiableModelsProvider.java
platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/AbstractIdeModifiableModelsProvider.java
platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractModuleDataService.java
platform/platform-resources/src/META-INF/LangExtensions.xml
platform/projectModel-impl/src/com/intellij/openapi/roots/TestModuleProperties.java [new file with mode: 0644]
platform/projectModel-impl/src/com/intellij/openapi/roots/impl/TestModulePropertiesImpl.java [new file with mode: 0644]
plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java

diff --git a/java/java-tests/testSrc/com/intellij/roots/ModuleTestPropertiesTest.java b/java/java-tests/testSrc/com/intellij/roots/ModuleTestPropertiesTest.java
new file mode 100644 (file)
index 0000000..3abc565
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.roots;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.TestModuleProperties;
+import com.intellij.testFramework.ModuleTestCase;
+
+/**
+ * @author nik
+ */
+public class ModuleTestPropertiesTest extends ModuleTestCase {
+  public void testSetAndGet() throws Exception {
+    Module tests = createModule("tests");
+    TestModuleProperties moduleProperties = TestModuleProperties.getInstance(tests);
+    moduleProperties.setProductionModuleName(myModule.getName());
+    assertSame(myModule, moduleProperties.getProductionModule());
+  }
+}
index 0dd514a23718fac3a166dbb5fd74aa89d6dd89ee..4179681ab59ba6594a789320c3ca0568eb230f86 100644 (file)
@@ -33,6 +33,7 @@ public class ModuleData extends AbstractNamedData implements Named, ExternalConf
   @Nullable private String[] myIdeModuleGroup;
   @Nullable  private String mySourceCompatibility;
   @Nullable private String myTargetCompatibility;
+  @Nullable private String myProductionModuleId;
 
   private boolean myInheritProjectCompileOutputPath = true;
 
@@ -97,6 +98,19 @@ public class ModuleData extends AbstractNamedData implements Named, ExternalConf
     myModuleFileDirectoryPath = path;
   }
 
+  /**
+   * @return an internal id of production module corresponding to a test-only module, this information is used to populate
+   * {@link com.intellij.openapi.roots.TestModuleProperties}
+   */
+  @Nullable
+  public String getProductionModuleId() {
+    return myProductionModuleId;
+  }
+
+  public void setProductionModuleId(@Nullable String productionModuleId) {
+    myProductionModuleId = productionModuleId;
+  }
+
   public boolean isInheritProjectCompileOutputPath() {
     return myInheritProjectCompileOutputPath;
   }
index 33148bcea989b769e4475fcff9a8a23d3e6ed66f..a72dc7cc601cfff5d24201b707ec846f150cb709 100644 (file)
@@ -65,4 +65,6 @@ public interface IdeModifiableModelsProvider extends IdeModelsProvider {
   void commit();
 
   void dispose();
+
+  void setTestModuleProperties(Module testModule, String productionModuleName);
 }
index 1721400de6078653c0aac2484d8b943580c180c8..cc2de2f9af28bcd5949178960742eb5e7b25c9e4 100644 (file)
@@ -55,6 +55,7 @@ public abstract class AbstractIdeModifiableModelsProvider extends IdeModelsProvi
   private ModifiableModuleModel myModifiableModuleModel;
   private Map<Module, ModifiableRootModel> myModifiableRootModels = new THashMap<Module, ModifiableRootModel>();
   private Map<Module, ModifiableFacetModel> myModifiableFacetModels = new THashMap<Module, ModifiableFacetModel>();
+  private Map<Module, String> myProductionModulesForTestModules = new THashMap<Module, String>();
   private Map<Library, Library.ModifiableModel> myModifiableLibraryModels = new IdentityHashMap<Library, Library.ModifiableModel>();
   private ModifiableArtifactModel myModifiableArtifactModel;
   private AbstractIdeModifiableModelsProvider.MyPackagingElementResolvingContext myPackagingElementResolvingContext;
@@ -361,6 +362,9 @@ public abstract class AbstractIdeModifiableModelsProvider extends IdeModelsProvi
             model.commit();
           }
         }
+        for (Map.Entry<Module, String> entry : myProductionModulesForTestModules.entrySet()) {
+          TestModuleProperties.getInstance(entry.getKey()).setProductionModuleName(entry.getValue());
+        }
 
         for (Map.Entry<Module, ModifiableFacetModel> each : myModifiableFacetModels.entrySet()) {
           if(!each.getKey().isDisposed()) {
@@ -397,4 +401,9 @@ public abstract class AbstractIdeModifiableModelsProvider extends IdeModelsProvi
     myModifiableFacetModels.clear();
     myModifiableLibraryModels.clear();
   }
+
+  @Override
+  public void setTestModuleProperties(Module testModule, String productionModuleName) {
+    myProductionModulesForTestModules.put(testModule, productionModuleName);
+  }
 }
index 1a60ecb643e244e65b4d77aa6a15e09de511c33e..60c9d7d4e7b626c6b74de7d43f08f1ecfa008ec9 100644 (file)
@@ -123,6 +123,10 @@ public abstract class AbstractModuleDataService<E extends ModuleData> extends Ab
       ModuleData data = module.getData();
       final Module created = modelsProvider.newModule(data.getModuleFilePath(), data.getModuleTypeId());
       module.putUserData(MODULE_KEY, created);
+      String productionModuleId = data.getProductionModuleId();
+      if (productionModuleId != null) {
+        modelsProvider.setTestModuleProperties(created, productionModuleId);
+      }
       Set<String> orphanFiles = project.getUserData(ORPHAN_MODULE_FILES);
       if (orphanFiles != null) {
         orphanFiles.remove(created.getModuleFilePath());
index bfc607c14c5b6658b77d883022baa645db5593ac..9ba616080ea8a13e60619f8e7fe1df98c12853bd 100644 (file)
     <moduleService serviceInterface="com.intellij.openapi.components.impl.stores.IComponentStore"
                    serviceImplementation="com.intellij.configurationStore.ModuleStoreImpl"
                    testServiceImplementation="com.intellij.configurationStore.ModuleStoreImpl$TestModuleStore"/>
+    <moduleService serviceInterface="com.intellij.openapi.roots.TestModuleProperties"
+                   serviceImplementation="com.intellij.openapi.roots.impl.TestModulePropertiesImpl"/>
 
     <moduleService serviceImplementation="com.intellij.openapi.module.impl.ModuleImpl$DeprecatedModuleOptionManager"/>
     <moduleService serviceInterface="com.intellij.openapi.components.PathMacroManager" serviceImplementation="com.intellij.openapi.components.impl.ModulePathMacroManager"/>
diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/TestModuleProperties.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/TestModuleProperties.java
new file mode 100644 (file)
index 0000000..750ecbe
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.roots;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleServiceManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * In some cases tests need to be extracted to a separate module (because they have a different classpath, output folder or JDK). E.g. when
+ * the project is imported from Gradle IDEA creates separate modules for each source set of a Gradle project.
+ * <p/>
+ * This service allows to specify to which production module the tests module belongs. This information may be used for example by
+ * 'Create Test' feature.
+ * <p/>
+ * <strong>This API isn't stable for now and may be changed in future. Also it isn't possible to change this in UI.</strong>
+ * @author nik
+ */
+public abstract class TestModuleProperties {
+  public static TestModuleProperties getInstance(@NotNull Module module) {
+    return ModuleServiceManager.getService(module, TestModuleProperties.class);
+  }
+
+  @Nullable
+  public abstract String getProductionModuleName();
+
+  @Nullable
+  public abstract Module getProductionModule();
+
+  public abstract void setProductionModuleName(@Nullable String moduleName);
+}
diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/TestModulePropertiesImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/TestModulePropertiesImpl.java
new file mode 100644 (file)
index 0000000..221806b
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.roots.impl;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.module.ModulePointerManager;
+import com.intellij.openapi.roots.TestModuleProperties;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+@State(
+  name = "TestModuleProperties",
+  storages = @Storage(file = StoragePathMacros.MODULE_FILE)
+)
+public class TestModulePropertiesImpl extends TestModuleProperties implements PersistentStateComponent<TestModulePropertiesImpl.TestModulePropertiesState> {
+  private final ModulePointerManager myModulePointerManager;
+  private ModulePointer myProductionModulePointer;
+
+  public TestModulePropertiesImpl(@NotNull ModulePointerManager modulePointerManager) {
+    myModulePointerManager = modulePointerManager;
+  }
+
+  @Nullable
+  @Override
+  public String getProductionModuleName() {
+    return myProductionModulePointer != null ? myProductionModulePointer.getModuleName() : null;
+  }
+
+  @Nullable
+  @Override
+  public Module getProductionModule() {
+    return myProductionModulePointer != null ? myProductionModulePointer.getModule() : null;
+  }
+
+  @Override
+  public void setProductionModuleName(@Nullable String moduleName) {
+    myProductionModulePointer = moduleName != null ? myModulePointerManager.create(moduleName) : null;
+  }
+
+  @Nullable
+  @Override
+  public TestModulePropertiesState getState() {
+    TestModulePropertiesState state = new TestModulePropertiesState();
+    state.moduleName = getProductionModuleName();
+    return state;
+  }
+
+  @Override
+  public void loadState(TestModulePropertiesState state) {
+
+  }
+
+  public static class TestModulePropertiesState {
+    @Attribute("production-module")
+    public String moduleName;
+  }
+}
index 09ce935ed1018c7d6cab69448b09b69755a80027..2a7f6b2eb6c2575a44ed662fdff859250913c316 100644 (file)
@@ -178,7 +178,7 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver
       for (ExternalSourceSet sourceSet : externalProject.getSourceSets().values()) {
         final String moduleId = getModuleId(externalProject, sourceSet);
         final String moduleExternalName = gradleModule.getName() + ":" + sourceSet.getName();
-        final String moduleInternalName = gradleModule.getName() + "_" + sourceSet.getName();
+        final String moduleInternalName = getInternalModuleName(gradleModule, sourceSet.getName());
 
         GradleSourceSetData sourceSetData = new GradleSourceSetData(
           moduleId, moduleExternalName, moduleInternalName, mainModuleFileDirectoryPath, mainModuleConfigPath);
@@ -204,10 +204,14 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver
             }
             artifacts.addAll(archivesArtifacts);
           }
-        } else if("test".equals(sourceSet.getName())) {
-          final Set<File> testsArtifacts = externalProject.getArtifactsByConfiguration().get("tests");
-          if (testsArtifacts != null) {
-            artifacts.addAll(testsArtifacts);
+        }
+        else {
+          sourceSetData.setProductionModuleId(getInternalModuleName(gradleModule, "main"));
+          if ("test".equals(sourceSet.getName())) {
+            final Set<File> testsArtifacts = externalProject.getArtifactsByConfiguration().get("tests");
+            if (testsArtifacts != null) {
+              artifacts.addAll(testsArtifacts);
+            }
           }
         }
         sourceSetData.setArtifacts(ContainerUtil.newArrayList(artifacts));
@@ -229,6 +233,11 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver
     return mainModuleNode;
   }
 
+  @NotNull
+  public String getInternalModuleName(@NotNull IdeaModule gradleModule, @NotNull String sourceSetName) {
+    return gradleModule.getName() + "_" + sourceSetName;
+  }
+
   @Override
   public void populateModuleExtraModels(@NotNull IdeaModule gradleModule, @NotNull DataNode<ModuleData> ideModule) {
     final BuildScriptClasspathModel buildScriptClasspathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel.class);