IDEA-150835: Provide an API which tells which production module corresponding to tests module

This commit is contained in:
nik
2016-01-25 16:46:07 +03:00
parent 13556f976c
commit 8d7d8a0384
9 changed files with 201 additions and 5 deletions
@@ -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());
}
}
@@ -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;
}
@@ -65,4 +65,6 @@ public interface IdeModifiableModelsProvider extends IdeModelsProvider {
void commit();
void dispose();
void setTestModuleProperties(Module testModule, String productionModuleName);
}
@@ -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);
}
}
@@ -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());
@@ -341,6 +341,8 @@
<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"/>
@@ -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);
}
@@ -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;
}
}
@@ -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);