Import of bazel plugin using copybara PiperOrigin-RevId: 145021219
diff --git a/BUILD b/BUILD index f87b1dc..99282bc 100644 --- a/BUILD +++ b/BUILD
@@ -10,6 +10,7 @@ tests = [ "//base:integration_tests", "//base:unit_tests", + "//golang:unit_tests", "//ijwb:integration_tests", "//ijwb:unit_tests", "//java:integration_tests",
diff --git a/WORKSPACE b/WORKSPACE index edb340b..bad62f5 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -2,6 +2,14 @@ # Long-lived download links available at: https://www.jetbrains.com/intellij-repository/releases +# The plugin api for IntelliJ 2016.3.1. This is required to build IJwB, +# and run integration tests. +new_http_archive( + name = "intellij_ce_2016_3_1", + build_file = "intellij_platform_sdk/BUILD.idea", + url = "https://www.jetbrains.com/intellij-repository/releases/com/jetbrains/intellij/idea/ideaIC/2016.3.1/ideaIC-2016.3.1.zip", +) + # The plugin api for IntelliJ 2016.2.4. This is required to build IJwB, # and run integration tests. new_http_archive( @@ -18,6 +26,30 @@ url = "https://download.jetbrains.com/cpp/CLion-2016.2.2.tar.gz", ) +# The plugin api for CLion 2016.3.2. This is required to build CLwB, +# and run integration tests. +new_http_archive( + name = "clion_2016_3_2", + build_file = "intellij_platform_sdk/BUILD.clion", + url = "https://download.jetbrains.com/cpp/CLion-2016.3.2.tar.gz", +) + +# The plugin api for Android Studio 2.3 Beta 1. This is required to build ASwB, +# and run integration tests. +new_http_archive( + name = "android_studio_2_3_0_3", + build_file = "intellij_platform_sdk/BUILD.android_studio", + url = "https://dl.google.com/dl/android/studio/ide-zips/2.3.0.3/android-studio-ide-162.3573574-linux.zip", +) + +# The plugin api for Android Studio 2.3 Beta 2. This is required to build ASwB, +# and run integration tests. +new_http_archive( + name = "android_studio_2_3_0_4", + build_file = "intellij_platform_sdk/BUILD.android_studio", + url = "https://dl.google.com/dl/android/studio/ide-zips/2.3.0.4/android-studio-ide-162.3616766-linux.zip", +) + # The plugin api for Android Studio 2.2 stable. This is required to build ASwB, # and run integration tests. new_http_archive(
diff --git a/aswb/2.2/src/com/google/idea/blaze/android/compatibility/Compatibility.java b/aswb/2.2/src/com/google/idea/blaze/android/compatibility/Compatibility.java new file mode 100644 index 0000000..d65d9f2 --- /dev/null +++ b/aswb/2.2/src/com/google/idea/blaze/android/compatibility/Compatibility.java
@@ -0,0 +1,138 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.compatibility; + +import com.android.sdklib.AndroidVersion; +import com.android.tools.idea.run.ConsolePrinter; +import com.android.tools.idea.run.editor.AndroidDebugger; +import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.tasks.DebugConnectorTask; +import com.android.tools.idea.run.util.LaunchStatus; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import java.io.File; +import java.util.List; +import java.util.Set; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.sdk.AndroidSdkAdditionalData; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.android.model.impl.JpsAndroidModuleProperties; + +/** Compatibility facades for Android Studio 2.2. */ +public class Compatibility { + private Compatibility() {} + /** + * Facade for {@link org.jetbrains.android.sdk.AndroidSdkUtils} and {@link + * com.android.tools.idea.sdk.AndroidSdks#getInstance()}. + */ + public static class AndroidSdkUtils { + private AndroidSdkUtils() {} + + public static Sdk findSuitableAndroidSdk(String targetHash) { + return org.jetbrains.android.sdk.AndroidSdkUtils.findSuitableAndroidSdk(targetHash); + } + + public static List<Sdk> getAllAndroidSdks() { + return org.jetbrains.android.sdk.AndroidSdkUtils.getAllAndroidSdks(); + } + + public static AndroidSdkAdditionalData getAndroidSdkAdditionalData(Sdk sdk) { + return org.jetbrains.android.sdk.AndroidSdkUtils.getAndroidSdkAdditionalData(sdk); + } + } + + /** + * Facade for {@link com.android.tools.idea.sdk.IdeSdks} and {@link + * com.android.tools.idea.sdk.IdeSdks#getInstance()}. + */ + public static class IdeSdks { + private IdeSdks() {} + + public static File getAndroidSdkPath() { + return com.android.tools.idea.sdk.IdeSdks.getAndroidSdkPath(); + } + + public static List<Sdk> createAndroidSdkPerAndroidTarget(File androidSdkPath) { + return com.android.tools.idea.sdk.IdeSdks.createAndroidSdkPerAndroidTarget(androidSdkPath); + } + } + + /** + * Facade for {@link com.android.tools.idea.run.testing.AndroidTestListener} and {@link + * com.android.tools.idea.testartifacts.instrumented.AndroidTestListener} + */ + public static class AndroidTestListener + extends com.android.tools.idea.run.testing.AndroidTestListener { + public AndroidTestListener(LaunchStatus launchStatus, ConsolePrinter consolePrinter) { + super(launchStatus, consolePrinter); + } + } + + /** + * Facade for {@link com.android.tools.idea.run.testing.AndroidTestConsoleProperties} and {@link + * com.android.tools.idea.testartifacts.instrumented.AndroidTestConsoleProperties} + */ + public static class AndroidTestConsoleProperties + extends com.android.tools.idea.run.testing.AndroidTestConsoleProperties { + public AndroidTestConsoleProperties(RunConfiguration runConfiguration, Executor executor) { + super(runConfiguration, executor); + } + } + + /** + * Facade for {@link com.android.tools.idea.run.testing.AndroidTestRunConfiguration} and {@link + * com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration} + */ + public static class AndroidTestRunConfiguration + extends com.android.tools.idea.run.testing.AndroidTestRunConfiguration { + public AndroidTestRunConfiguration(Project project, ConfigurationFactory configurationFactory) { + super(project, configurationFactory); + } + } + + /** Facade for {@link com.android.tools.idea.run.tasks.ConnectDebuggerTask}. */ + public abstract static class ConnectDebuggerTask + extends com.android.tools.idea.run.tasks.ConnectDebuggerTask { + protected ConnectDebuggerTask( + Set<String> applicationIds, + AndroidDebugger<?> debugger, + Project project, + boolean monitorRemoteProcess) { + super(applicationIds, debugger, project); + } + } + + public static <S extends AndroidDebuggerState> DebugConnectorTask getConnectDebuggerTask( + AndroidDebugger<S> androidDebugger, + ExecutionEnvironment env, + @Nullable AndroidVersion version, + Set<String> applicationIds, + AndroidFacet facet, + S state, + String runConfigTypeId, + boolean monitorRemoteProcess) { + return androidDebugger.getConnectDebuggerTask( + env, version, applicationIds, facet, state, runConfigTypeId); + } + + public static void setFacetStateIsLibraryProject(JpsAndroidModuleProperties facetState) { + facetState.LIBRARY_PROJECT = true; + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java b/aswb/2.2/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java similarity index 78% rename from aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java rename to aswb/2.2/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java index 037f4fa..20c2c22 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/NullClassJarProvider.java +++ b/aswb/2.2/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java
@@ -18,22 +18,23 @@ import com.android.tools.idea.model.ClassJarProvider; import com.google.common.collect.ImmutableList; import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import java.util.List; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** Returns no class jars. Used to disable the layout editor loading jars. */ -public class NullClassJarProvider extends ClassJarProvider { +public class BlazeClassJarProvider extends ClassJarProvider { + public BlazeClassJarProvider(Project project) {} + @Nullable @Override - public VirtualFile findModuleClassFile(@NotNull String className, @NotNull Module module) { + public VirtualFile findModuleClassFile(String className, Module module) { return null; } - @NotNull @Override - public List<VirtualFile> getModuleExternalLibraries(@NotNull Module module) { + public List<VirtualFile> getModuleExternalLibraries(Module module) { return ImmutableList.of(); } }
diff --git a/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java b/aswb/2.2/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java similarity index 100% rename from aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java rename to aswb/2.2/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java
diff --git a/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java b/aswb/2.2/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java similarity index 100% rename from aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java rename to aswb/2.2/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java
diff --git a/aswb/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java b/aswb/2.2/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java similarity index 100% rename from aswb/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java rename to aswb/2.2/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java
diff --git a/aswb/2.3/src/META-INF/aswb_beta.xml b/aswb/2.3/src/META-INF/aswb_beta.xml new file mode 100644 index 0000000..8df2945 --- /dev/null +++ b/aswb/2.3/src/META-INF/aswb_beta.xml
@@ -0,0 +1,35 @@ +<!-- + ~ Copyright 2016 The Bazel Authors. All rights reserved. + ~ + ~ 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. + --> +<idea-plugin> + <depends>com.google.gct.test.recorder</depends> + + <extensions defaultExtensionNs="com.google.idea.blaze"> + <BlazeUserSettingsContributor implementation="com.google.idea.blaze.android.settings.BlazeAndroidUserSettingsContributor$BlazeAndroidUserSettingsProvider"/> + </extensions> + + <extensions defaultExtensionNs="com.android.project"> + <buildSystemService implementation="com.google.idea.blaze.android.project.BlazeBuildSystemService"/> + <featureEnableService implementation="com.google.idea.blaze.android.project.BlazeFeatureEnableService"/> + </extensions> + + <extensions defaultExtensionNs="com.android.rendering"> + <renderErrorContributor implementation="com.google.idea.blaze.android.rendering.BlazeRenderErrorContributor$BlazeProvider"/> + </extensions> + + <extensions defaultExtensionNs="com.google.gct.testrecorder.run"> + <testRecorderRunConfigurationProxyProvider implementation="com.google.idea.blaze.android.run.testrecorder.TestRecorderBlazeCommandRunConfigurationProxyProvider" /> + </extensions> +</idea-plugin>
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/compatibility/Compatibility.java b/aswb/2.3/src/com/google/idea/blaze/android/compatibility/Compatibility.java new file mode 100644 index 0000000..e614959 --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/compatibility/Compatibility.java
@@ -0,0 +1,141 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.compatibility; + +import com.android.sdklib.AndroidVersion; +import com.android.tools.idea.run.ConsolePrinter; +import com.android.tools.idea.run.editor.AndroidDebugger; +import com.android.tools.idea.run.editor.AndroidDebuggerState; +import com.android.tools.idea.run.tasks.DebugConnectorTask; +import com.android.tools.idea.run.util.LaunchStatus; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import java.io.File; +import java.util.List; +import java.util.Set; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.sdk.AndroidSdkAdditionalData; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.android.model.impl.JpsAndroidModuleProperties; + +/** Compatibility facades for Android Studio 2.3. */ +public class Compatibility { + private Compatibility() {} + + /** + * Facade for {@link org.jetbrains.android.sdk.AndroidSdkUtils} and {@link + * com.android.tools.idea.sdk.AndroidSdks#getInstance()}. + */ + public static class AndroidSdkUtils { + private AndroidSdkUtils() {} + + public static Sdk findSuitableAndroidSdk(String targetHash) { + return com.android.tools.idea.sdk.AndroidSdks.getInstance() + .findSuitableAndroidSdk(targetHash); + } + + public static List<Sdk> getAllAndroidSdks() { + return com.android.tools.idea.sdk.AndroidSdks.getInstance().getAllAndroidSdks(); + } + + public static AndroidSdkAdditionalData getAndroidSdkAdditionalData(Sdk sdk) { + return com.android.tools.idea.sdk.AndroidSdks.getInstance().getAndroidSdkAdditionalData(sdk); + } + } + + /** + * Facade for {@link com.android.tools.idea.sdk.IdeSdks} and {@link + * com.android.tools.idea.sdk.IdeSdks#getInstance()}. + */ + public static class IdeSdks { + private IdeSdks() {} + + public static File getAndroidSdkPath() { + return com.android.tools.idea.sdk.IdeSdks.getInstance().getAndroidSdkPath(); + } + + public static List<Sdk> createAndroidSdkPerAndroidTarget(File androidSdkPath) { + return com.android.tools.idea.sdk.IdeSdks.getInstance() + .createAndroidSdkPerAndroidTarget(androidSdkPath); + } + } + + /** + * Facade for {@link com.android.tools.idea.run.testing.AndroidTestListener} and {@link + * com.android.tools.idea.testartifacts.instrumented.AndroidTestListener} + */ + public static class AndroidTestListener + extends com.android.tools.idea.testartifacts.instrumented.AndroidTestListener { + public AndroidTestListener(LaunchStatus launchStatus, ConsolePrinter consolePrinter) { + super(launchStatus, consolePrinter); + } + } + + /** + * Facade for {@link com.android.tools.idea.run.testing.AndroidTestConsoleProperties} and {@link + * com.android.tools.idea.testartifacts.instrumented.AndroidTestConsoleProperties} + */ + public static class AndroidTestConsoleProperties + extends com.android.tools.idea.testartifacts.instrumented.AndroidTestConsoleProperties { + public AndroidTestConsoleProperties(RunConfiguration runConfiguration, Executor executor) { + super(runConfiguration, executor); + } + } + + /** + * Facade for {@link com.android.tools.idea.run.testing.AndroidTestRunConfiguration} and {@link + * com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration} + */ + public static class AndroidTestRunConfiguration + extends com.android.tools.idea.testartifacts.instrumented.AndroidTestRunConfiguration { + public AndroidTestRunConfiguration(Project project, ConfigurationFactory configurationFactory) { + super(project, configurationFactory); + } + } + + /** Facade for {@link com.android.tools.idea.run.tasks.ConnectDebuggerTask}. */ + public abstract static class ConnectDebuggerTask + extends com.android.tools.idea.run.tasks.ConnectDebuggerTask { + protected ConnectDebuggerTask( + Set<String> applicationIds, + AndroidDebugger<?> debugger, + Project project, + boolean monitorRemoteProcess) { + super(applicationIds, debugger, project, monitorRemoteProcess); + } + } + + public static <S extends AndroidDebuggerState> DebugConnectorTask getConnectDebuggerTask( + AndroidDebugger<S> androidDebugger, + ExecutionEnvironment env, + @Nullable AndroidVersion version, + Set<String> applicationIds, + AndroidFacet facet, + S state, + String runConfigTypeId, + boolean monitorRemoteProcess) { + return androidDebugger.getConnectDebuggerTask( + env, version, applicationIds, facet, state, runConfigTypeId, monitorRemoteProcess); + } + + public static void setFacetStateIsLibraryProject(JpsAndroidModuleProperties facetState) { + facetState.PROJECT_TYPE = com.android.builder.model.AndroidProject.PROJECT_TYPE_LIBRARY; + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java b/aswb/2.3/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java new file mode 100644 index 0000000..36923d7 --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/project/BlazeBuildSystemService.java
@@ -0,0 +1,99 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.project; + +import com.android.tools.idea.project.BuildSystemService; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.actions.BlazeBuildService; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.BlazeSyncManager; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.ex.temp.TempFileSystem; +import com.intellij.psi.PsiElement; +import java.io.File; + +/** Blaze implementation of {@link BuildSystemService} for build system specific operations. */ +public class BlazeBuildSystemService extends BuildSystemService { + @Override + public boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public void buildProject(Project project) { + BlazeBuildService.getInstance().buildProject(project); + } + + @Override + public void syncProject(Project project) { + BlazeSyncManager.getInstance(project).incrementalProjectSync(); + } + + @Override + public void addDependency(Module module, String artifact) { + Project project = module.getProject(); + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (blazeProjectData == null) { + return; + } + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + TargetIdeInfo targetIdeInfo = blazeProjectData.targetMap.get(registry.getTargetKey(module)); + if (targetIdeInfo == null || targetIdeInfo.buildFile == null) { + return; + } + + // TODO: automagically edit deps instead of just opening the BUILD file? + // Need to translate Gradle coordinates into blaze targets. + // Will probably need to hardcode for each dependency. + FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); + PsiElement buildTargetPsi = + BuildReferenceManager.getInstance(project).resolveLabel(targetIdeInfo.key.label); + if (buildTargetPsi != null) { + // If we can find a PSI for the target, + // then we can jump straight to the target in the build file. + fileEditorManager.openTextEditor( + new OpenFileDescriptor( + project, + buildTargetPsi.getContainingFile().getVirtualFile(), + buildTargetPsi.getTextOffset()), + true); + } else { + // If not, just the build file is good enough. + File buildIoFile = blazeProjectData.artifactLocationDecoder.decode(targetIdeInfo.buildFile); + VirtualFile buildVirtualFile = findFileByIoFile(buildIoFile); + if (buildVirtualFile != null) { + fileEditorManager.openFile(buildVirtualFile, true); + } + } + } + + private static VirtualFile findFileByIoFile(File file) { + return ApplicationManager.getApplication().isUnitTestMode() + ? TempFileSystem.getInstance().findFileByIoFile(file) + : VfsUtil.findFileByIoFile(file, true); + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/project/BlazeFeatureEnableService.java b/aswb/2.3/src/com/google/idea/blaze/android/project/BlazeFeatureEnableService.java new file mode 100644 index 0000000..e59f375 --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/project/BlazeFeatureEnableService.java
@@ -0,0 +1,46 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.project; + +import com.android.tools.idea.project.FeatureEnableService; +import com.google.idea.blaze.android.settings.BlazeAndroidUserSettings; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.common.experiments.BoolExperiment; +import com.intellij.openapi.project.Project; + +/** Enable features supported by the blaze integration. */ +public class BlazeFeatureEnableService extends FeatureEnableService { + private static final BoolExperiment ENABLE_LAYOUT_EDITOR = + new BoolExperiment("enable.layout.editor", true); + + @Override + protected boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public boolean isLayoutEditorEnabled(Project project) { + return isLayoutEditorExperimentEnabled() + && BlazeAndroidUserSettings.getInstance().getUseLayoutEditor() + // Can't render if we don't have the data ready. + && BlazeProjectDataManager.getInstance(project).getBlazeProjectData() != null; + } + + public static boolean isLayoutEditorExperimentEnabled() { + return ENABLE_LAYOUT_EDITOR.getValue(); + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java b/aswb/2.3/src/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java new file mode 100644 index 0000000..b4c8498 --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributor.java
@@ -0,0 +1,338 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.rendering; + +import static com.android.SdkConstants.ANDROID_MANIFEST_XML; + +import com.android.tools.idea.rendering.HtmlLinkManager; +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderLogger; +import com.android.tools.idea.rendering.RenderResult; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.android.utils.HtmlBuilder; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Maps; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.GlobalSearchScope; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.SortedMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.jetbrains.annotations.Nullable; + +/** Contribute blaze specific render errors. */ +public class BlazeRenderErrorContributor extends RenderErrorContributor { + private RenderLogger logger; + private Module module; + private Project project; + + public BlazeRenderErrorContributor(RenderResult result, @Nullable DataContext dataContext) { + super(result, dataContext); + logger = result.getLogger(); + module = result.getModule(); + project = module.getProject(); + } + + @Override + public Collection<RenderErrorModel.Issue> reportIssues() { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + + if (blazeProjectData == null || !logger.hasErrors()) { + return getIssues(); + } + + TargetMap targetMap = blazeProjectData.targetMap; + ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder; + AndroidResourceModule resourceModule = + AndroidResourceModuleRegistry.getInstance(project).get(module); + if (resourceModule == null) { + return getIssues(); + } + + TargetIdeInfo target = targetMap.get(resourceModule.targetKey); + if (target == null) { + return getIssues(); + } + + reportGeneratedResources(resourceModule, targetMap, decoder); + reportNonStandardAndroidManifestName(target, decoder); + reportResourceTargetShouldDependOnClassTarget(target, targetMap, decoder); + return getIssues(); + } + + /** + * We can't find generated resources. If a layout uses them, the layout won't render correctly. + */ + private void reportGeneratedResources( + AndroidResourceModule resourceModule, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Map<String, Throwable> brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedMap<ArtifactLocation, TargetIdeInfo> generatedResources = + Maps.newTreeMap(getGeneratedResources(targetMap.get(resourceModule.targetKey))); + + for (TargetKey dependency : resourceModule.transitiveResourceDependencies) { + generatedResources.putAll(getGeneratedResources(targetMap.get(dependency))); + } + + if (generatedResources.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + builder.add("Generated resources will not be discovered by the IDE:"); + builder.beginList(); + for (Map.Entry<ArtifactLocation, TargetIdeInfo> entry : generatedResources.entrySet()) { + ArtifactLocation resource = entry.getKey(); + TargetIdeInfo target = entry.getValue(); + builder.listItem().add(resource.getRelativePath()).add(" from "); + addTargetLink(builder, target, decoder); + } + builder + .endList() + .add("Please avoid using generated resources, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes + .setSummary("Generated resources") + .setHtmlContent(builder) + .build(); + } + + private static SortedMap<ArtifactLocation, TargetIdeInfo> getGeneratedResources( + TargetIdeInfo target) { + if (target == null || target.androidIdeInfo == null) { + return Collections.emptySortedMap(); + } + SortedMap<ArtifactLocation, TargetIdeInfo> generatedResources = Maps.newTreeMap(); + generatedResources.putAll( + target + .androidIdeInfo + .resources + .stream() + .filter(ArtifactLocation::isGenerated) + .collect(Collectors.toMap(Function.identity(), resource -> target))); + return generatedResources; + } + + /** + * When the Android manifest isn't AndroidManifest.xml, resolving resource IDs would fail. This + * doesn't seem to be an issue if the manifest belongs to one of the target's dependencies. + */ + private void reportNonStandardAndroidManifestName( + TargetIdeInfo target, ArtifactLocationDecoder decoder) { + if (target.androidIdeInfo == null || target.androidIdeInfo.manifest == null) { + return; + } + + Map<String, Throwable> brokenClasses = logger.getBrokenClasses(); + if (brokenClasses == null || brokenClasses.isEmpty()) { + return; + } + + File manifest = decoder.decode(target.androidIdeInfo.manifest); + if (manifest.getName().equals(ANDROID_MANIFEST_XML)) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" uses a non-standard name for the Android manifest: "); + String linkToManifest = HtmlLinkManager.createFilePositionUrl(manifest, -1, 0); + if (linkToManifest != null) { + builder.addLink(manifest.getName(), linkToManifest); + } else { + builder.newline().add(manifest.getPath()); + } + // TODO: add a link to automatically rename the file and refactor all references. + builder + .newline() + .add("Please rename it to ") + .add(ANDROID_MANIFEST_XML) + .addLink(", then ", "sync the project", "", getLinkManager().createSyncProjectUrl()) + .addLink(" and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()); + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above broken classes. + .setSummary("Non-standard manifest name") + .setHtmlContent(builder) + .build(); + } + + /** + * Blaze doesn't resolve class dependencies from resources until building the final + * android_binary, so we could end up with resources that ultimately build correctly, but fail to + * find their class dependencies during rendering in the layout editor. + */ + private void reportResourceTargetShouldDependOnClassTarget( + TargetIdeInfo target, TargetMap targetMap, ArtifactLocationDecoder decoder) { + Collection<String> missingClasses = logger.getMissingClasses(); + if (missingClasses == null || missingClasses.isEmpty()) { + return; + } + + // Sorted entries for deterministic error message. + SortedSetMultimap<String, TargetKey> missingClassToTargetMap = TreeMultimap.create(); + + SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project); + ImmutableCollection transitiveDependencies = + TransitiveDependencyMap.getInstance(project).getTransitiveDependencies(target.key); + + for (String missingClass : missingClasses) { + File sourceFile = getSourceFileForClass(missingClass); + if (sourceFile == null) { + continue; + } + ImmutableCollection<TargetKey> sourceTargets = + sourceToTargetMap.getRulesForSourceFile(sourceFile); + if (sourceTargets + .stream() + .noneMatch( + sourceTarget -> + sourceTarget.equals(target.key) + || transitiveDependencies.contains(sourceTarget))) { + missingClassToTargetMap.putAll(missingClass, sourceTargets); + } + } + + if (missingClassToTargetMap.isEmpty()) { + return; + } + + HtmlBuilder builder = new HtmlBuilder(); + addTargetLink(builder, target, decoder) + .add(" contains resource files that reference these classes:") + .beginList(); + for (String missingClass : missingClassToTargetMap.keySet()) { + builder + .listItem() + .addLink(missingClass, getLinkManager().createOpenClassUrl(missingClass)) + .add(" from "); + for (TargetKey targetKey : missingClassToTargetMap.get(missingClass)) { + addTargetLink(builder, targetMap.get(targetKey), decoder).add(" "); + } + } + builder.endList().add("Please fix your dependencies so that "); + addTargetLink(builder, target, decoder) + .add(" correctly depends on these classes, ") + .addLink("then ", "sync the project", " ", getLinkManager().createSyncProjectUrl()) + .addLink("and ", "refresh the layout", ".", getLinkManager().createRefreshRenderUrl()) + .newline() + .newline() + .addBold( + "NOTE: blaze can still build with the incorrect dependencies " + + "due to the way it handles resources, " + + "but the layout editor needs them to be correct."); + + addIssue() + .setSeverity(HighlightSeverity.ERROR, HIGH_PRIORITY + 1) // Reported above missing classes. + .setSummary("Missing class dependencies") + .setHtmlContent(builder) + .build(); + } + + private File getSourceFileForClass(String className) { + return ApplicationManager.getApplication() + .runReadAction( + (Computable<File>) + () -> { + try { + PsiClass psiClass = + JavaPsiFacade.getInstance(project) + .findClass(className, GlobalSearchScope.projectScope(project)); + if (psiClass == null) { + return null; + } + return VfsUtilCore.virtualToIoFile( + psiClass.getContainingFile().getVirtualFile()); + } catch (IndexNotReadyException ignored) { + // We're in dumb mode. Abort! Abort! + return null; + } + }); + } + + private HtmlBuilder addTargetLink( + HtmlBuilder builder, TargetIdeInfo target, ArtifactLocationDecoder decoder) { + File buildFile = decoder.decode(target.buildFile); + int line = + ApplicationManager.getApplication() + .runReadAction( + (Computable<Integer>) + () -> { + PsiElement buildTargetPsi = + BuildReferenceManager.getInstance(project).resolveLabel(target.key.label); + if (buildTargetPsi == null) { + return -1; + } + return StringUtil.offsetToLineNumber( + buildTargetPsi.getContainingFile().getText(), + buildTargetPsi.getTextOffset()); + }); + String url = HtmlLinkManager.createFilePositionUrl(buildFile, line, 0); + if (url != null) { + return builder.addLink(target.toString(), url); + } + return builder.add(target.toString()); + } + + /** Extension to provide {@link BlazeRenderErrorContributor}. */ + public static class BlazeProvider extends Provider { + @Override + public boolean isApplicable(Project project) { + return Blaze.isBlazeProject(project); + } + + @Override + public RenderErrorContributor getContributor( + RenderResult result, @Nullable DataContext dataContext) { + return new BlazeRenderErrorContributor(result, dataContext); + } + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfiguration.java b/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfiguration.java new file mode 100644 index 0000000..2f6c62e --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfiguration.java
@@ -0,0 +1,40 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.run.testrecorder; + +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.openapi.diagnostic.Logger; +import org.jdom.Element; + +/** Blaze integration with test recorder. */ +public class TestRecorderBlazeCommandRunConfiguration extends BlazeCommandRunConfiguration { + private static final Logger LOGGER = + Logger.getInstance(TestRecorderBlazeCommandRunConfiguration.class); + + public TestRecorderBlazeCommandRunConfiguration(BlazeCommandRunConfiguration baseConfiguration) { + super( + baseConfiguration.getProject(), + baseConfiguration.getFactory(), + "TestRecorder" + baseConfiguration.getName()); + Element element = new Element("toClone"); + try { + baseConfiguration.writeExternal(element); + this.readExternal(element); + } catch (Exception e) { + LOGGER.error(e); + } + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java b/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java new file mode 100644 index 0000000..b91a720 --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxy.java
@@ -0,0 +1,83 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.run.testrecorder; + +import com.android.annotations.Nullable; +import com.android.ddmlib.IDevice; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxy; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationHandler; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.configurations.LocatableConfigurationBase; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.module.Module; +import java.util.List; + +/** {@link TestRecorderRunConfigurationProxy} for blaze. */ +public class TestRecorderBlazeCommandRunConfigurationProxy + implements TestRecorderRunConfigurationProxy { + + private final BlazeCommandRunConfiguration myBaseConfiguration; + private final BlazeAndroidBinaryRunConfigurationHandler myBaseConfigurationHandler; + + public TestRecorderBlazeCommandRunConfigurationProxy( + BlazeCommandRunConfiguration baseConfiguration) { + myBaseConfiguration = baseConfiguration; + myBaseConfigurationHandler = + (BlazeAndroidBinaryRunConfigurationHandler) baseConfiguration.getHandler(); + } + + @Override + public LocatableConfigurationBase getTestRecorderRunConfiguration() { + return new TestRecorderBlazeCommandRunConfiguration(myBaseConfiguration); + } + + @Override + public Module getModule() { + return myBaseConfigurationHandler.getModule(); + } + + @Override + public boolean isLaunchActivitySupported() { + String mode = myBaseConfigurationHandler.getState().getMode(); + + // Supported launch activities are Default and Specified. + return BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY.equals(mode) + || BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY.equals(mode); + } + + @Override + public String getLaunchActivityClass() { + BlazeAndroidBinaryRunConfigurationState state = myBaseConfigurationHandler.getState(); + + if (BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY.equals(state.getMode())) { + return state.getActivityClass(); + } + + return ""; + } + + @Nullable + @Override + public List<ListenableFuture<IDevice>> getDeviceFutures(ExecutionEnvironment environment) { + return environment + .getCopyableUserData(BlazeAndroidRunConfigurationRunner.DEVICE_SESSION_KEY) + .deviceFutures + .get(); + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxyProvider.java b/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxyProvider.java new file mode 100644 index 0000000..8a2c206 --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/run/testrecorder/TestRecorderBlazeCommandRunConfigurationProxyProvider.java
@@ -0,0 +1,41 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.run.testrecorder; + +import com.android.annotations.Nullable; +import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxy; +import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxyProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationHandler; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.intellij.execution.configurations.RunConfiguration; + +/** Provides {@link TestRecorderBlazeCommandRunConfigurationProxy}. */ +public class TestRecorderBlazeCommandRunConfigurationProxyProvider + implements TestRecorderRunConfigurationProxyProvider { + + @Nullable + @Override + public TestRecorderRunConfigurationProxy getProxy(@Nullable RunConfiguration runConfiguration) { + if (runConfiguration instanceof BlazeCommandRunConfiguration + && ((BlazeCommandRunConfiguration) runConfiguration).getHandler() + instanceof BlazeAndroidBinaryRunConfigurationHandler) { + return new TestRecorderBlazeCommandRunConfigurationProxy( + (BlazeCommandRunConfiguration) runConfiguration); + } + + return null; + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettingsContributor.java b/aswb/2.3/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettingsContributor.java new file mode 100644 index 0000000..3ff141b --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettingsContributor.java
@@ -0,0 +1,96 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.settings; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.project.BlazeFeatureEnableService; +import com.google.idea.blaze.base.settings.ui.BlazeUserSettingsContributor; +import com.intellij.uiDesigner.core.GridConstraints; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JPanel; + +/** Contributes Android-specific settings. */ +public class BlazeAndroidUserSettingsContributor implements BlazeUserSettingsContributor { + private final ImmutableList<JComponent> components; + private JCheckBox useLayoutEditor; + + BlazeAndroidUserSettingsContributor() { + useLayoutEditor = new JCheckBox(); + useLayoutEditor.setSelected(false); + useLayoutEditor.setText("Use the layout editor for layout XML files. May freeze IDE."); + + ImmutableList.Builder<JComponent> builder = ImmutableList.builder(); + if (BlazeFeatureEnableService.isLayoutEditorExperimentEnabled()) { + builder.add(useLayoutEditor); + } + components = builder.build(); + } + + @Override + public void apply() { + BlazeAndroidUserSettings settings = BlazeAndroidUserSettings.getInstance(); + settings.setUseLayoutEditor(useLayoutEditor.isSelected()); + } + + @Override + public void reset() { + BlazeAndroidUserSettings settings = BlazeAndroidUserSettings.getInstance(); + useLayoutEditor.setSelected(settings.getUseLayoutEditor()); + } + + @Override + public boolean isModified() { + BlazeAndroidUserSettings settings = BlazeAndroidUserSettings.getInstance(); + return !Objects.equal(useLayoutEditor.isSelected(), settings.getUseLayoutEditor()); + } + + @Override + public int getRowCount() { + return components.size(); + } + + @Override + public int addComponents(JPanel panel, int rowi) { + for (JComponent contributedComponent : components) { + panel.add( + contributedComponent, + new GridConstraints( + rowi++, + 0, + 1, + 2, + GridConstraints.ANCHOR_NORTHWEST, + GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, + null, + null, + null, + 0, + false)); + } + return rowi; + } + + static class BlazeAndroidUserSettingsProvider implements Provider { + @Override + public BlazeUserSettingsContributor getContributor() { + return new BlazeAndroidUserSettingsContributor(); + } + } +}
diff --git a/aswb/2.3/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java b/aswb/2.3/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java new file mode 100644 index 0000000..15c2add --- /dev/null +++ b/aswb/2.3/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java
@@ -0,0 +1,224 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.sync.model.idea; + +import com.android.SdkConstants; +import com.android.tools.idea.model.ClassJarProvider; +import com.android.tools.idea.res.AppResourceRepository; +import com.android.tools.idea.res.ResourceClassRegistry; +import com.google.common.collect.Lists; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.JavaIdeInfo; +import com.google.idea.blaze.base.ideinfo.LibraryArtifact; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.ex.temp.TempFileSystem; +import com.intellij.util.containers.OrderedSet; +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.annotations.Nullable; + +/** Collects class jars from the user's build. */ +public class BlazeClassJarProvider extends ClassJarProvider { + + private final Project project; + private AtomicBoolean pendingModuleJarsRefresh; + private AtomicBoolean pendingDependencyJarsRefresh; + + public BlazeClassJarProvider(final Project project) { + this.project = project; + this.pendingModuleJarsRefresh = new AtomicBoolean(false); + this.pendingDependencyJarsRefresh = new AtomicBoolean(false); + } + + @Override + @Nullable + public VirtualFile findModuleClassFile(String className, Module module) { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (blazeProjectData == null) { + return null; + } + + ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder; + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + TargetIdeInfo target = blazeProjectData.targetMap.get(registry.getTargetKey(module)); + + if (target == null || target.javaIdeInfo == null) { + return null; + } + + // As a potential optimization, we could choose an arbitrary android_binary target + // that depends on the library to provide a single complete resource jar, + // instead of having to rely on dynamic class generation. + // TODO: benchmark to see if optimization is worthwhile. + + String classNamePath = className.replace('.', File.separatorChar) + SdkConstants.DOT_CLASS; + + List<File> missingClassJars = Lists.newArrayList(); + for (LibraryArtifact jar : target.javaIdeInfo.jars) { + if (jar.classJar == null) { + continue; + } + File classJarFile = decoder.decode(jar.classJar); + VirtualFile classJarVF = findFileByIoFile(classJarFile); + if (classJarVF == null) { + if (classJarFile.exists()) { + missingClassJars.add(classJarFile); + } + continue; + } + VirtualFile classFile = findClassInJar(classJarVF, classNamePath); + if (classFile != null) { + return classFile; + } + } + + maybeRefreshJars(missingClassJars, pendingModuleJarsRefresh); + return null; + } + + @Nullable + private static VirtualFile findClassInJar(final VirtualFile classJar, String classNamePath) { + VirtualFile jarRoot = getJarRootForLocalFile(classJar); + if (jarRoot == null) { + return null; + } + return jarRoot.findFileByRelativePath(classNamePath); + } + + @Override + public List<VirtualFile> getModuleExternalLibraries(Module module) { + OrderedSet<VirtualFile> results = new OrderedSet<>(); + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + + if (blazeProjectData == null) { + return results; + } + + TargetMap targetMap = blazeProjectData.targetMap; + ArtifactLocationDecoder decoder = blazeProjectData.artifactLocationDecoder; + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + TargetIdeInfo target = targetMap.get(registry.getTargetKey(module)); + + if (target == null) { + return results; + } + + AppResourceRepository repository = AppResourceRepository.getAppResources(module, true); + + List<File> missingClassJars = Lists.newArrayList(); + for (TargetKey dependencyTargetKey : + TransitiveDependencyMap.getInstance(project).getTransitiveDependencies(target.key)) { + TargetIdeInfo dependencyTarget = targetMap.get(dependencyTargetKey); + if (dependencyTarget == null) { + continue; + } + JavaIdeInfo javaIdeInfo = dependencyTarget.javaIdeInfo; + AndroidIdeInfo androidIdeInfo = dependencyTarget.androidIdeInfo; + + // Add all non-resource jars to be searched. + // Multiple resource jars will have ID conflicts unless generated dynamically. + if (javaIdeInfo != null) { + for (LibraryArtifact jar : javaIdeInfo.jars) { + if (androidIdeInfo != null && jar.equals(androidIdeInfo.resourceJar)) { + // No resource jars. + continue; + } + // Some of these could be empty class jars from resource only android_library targets. + // A potential optimization could be to filter out jars like these, + // so we don't waste time fetching and searching them. + // TODO: benchmark to see if optimization is worthwhile. + if (jar.classJar != null) { + File classJarFile = decoder.decode(jar.classJar); + VirtualFile classJar = findFileByIoFile(classJarFile); + if (classJar != null) { + results.add(classJar); + } else if (classJarFile.exists()) { + missingClassJars.add(classJarFile); + } + } + } + } + + // Tell ResourceClassRegistry which repository contains our resources and the java packages of + // the resources that we're interested in. + // When the class loader tries to load a custom view, and the view references resource + // classes, layoutlib will ask the class loader for these resource classes. + // If these resource classes are in a separate jar from the target (i.e., in a dependency), + // then offering their jars will lead to a conflict in the resource IDs. + // So instead, the resource class generator will produce dummy resource classes with + // non-conflicting IDs to satisfy the class loader. + // The resource repository remembers the dynamic IDs that it handed out and when the layoutlib + // calls to ask about the name and content of a given resource ID, the repository can just + // answer what it has already stored. + if (androidIdeInfo != null && repository != null) { + ResourceClassRegistry.get(module.getProject()) + .addLibrary(repository, androidIdeInfo.resourceJavaPackage); + } + } + + maybeRefreshJars(missingClassJars, pendingDependencyJarsRefresh); + return results; + } + + private static VirtualFile findFileByIoFile(File file) { + return ApplicationManager.getApplication().isUnitTestMode() + ? TempFileSystem.getInstance().findFileByIoFile(file) + : LocalFileSystem.getInstance().findFileByIoFile(file); + } + + private static void maybeRefreshJars(Collection<File> missingJars, AtomicBoolean pendingRefresh) { + // We probably need to refresh the virtual file system to find these files, but we can't refresh + // here because we're in a read action. We also can't use the async refreshIoFiles since it + // still tries to refresh the IO files synchronously. A global async refresh can't find new + // files in the ObjFS since we're not watching it. + // We need to do our own asynchronous refresh, and guard it with a flag to prevent the event + // queue from overflowing. + if (!missingJars.isEmpty() && !pendingRefresh.getAndSet(true)) { + ApplicationManager.getApplication() + .invokeLater( + () -> { + LocalFileSystem.getInstance().refreshIoFiles(missingJars); + pendingRefresh.set(false); + }, + ModalityState.NON_MODAL); + } + } + + private static VirtualFile getJarRootForLocalFile(VirtualFile file) { + return ApplicationManager.getApplication().isUnitTestMode() + ? TempFileSystem.getInstance().findFileByPath(file.getPath() + JarFileSystem.JAR_SEPARATOR) + : JarFileSystem.getInstance().getJarRootForLocalFile(file); + } +}
diff --git a/aswb/2.3/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java b/aswb/2.3/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java new file mode 100644 index 0000000..05f1ccf --- /dev/null +++ b/aswb/2.3/tests/integrationtests/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProviderIntegrationTest.java
@@ -0,0 +1,501 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.sync.model.idea; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import com.android.tools.idea.model.ClassJarProvider; +import com.android.tools.idea.res.AppResourceRepository; +import com.android.tools.idea.res.ResourceClassRegistry; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.BlazeIntegrationTestCase; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.JavaIdeInfo; +import com.google.idea.blaze.base.ideinfo.LibraryArtifact; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.Kind; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.intellij.facet.FacetManager; +import com.intellij.facet.ModifiableFacetModel; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import java.io.File; +import java.io.IOException; +import java.util.List; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.facet.AndroidFacetConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration test for {@link BlazeClassJarProvider}. */ +@RunWith(JUnit4.class) +public class BlazeClassJarProviderIntegrationTest extends BlazeIntegrationTestCase { + private static final String BLAZE_BIN = "blaze-out/crosstool/bin"; + + private Module module; + private ClassJarProvider classJarProvider; + + @Before + public void doSetup() { + module = testFixture.getModule(); + + ArtifactLocationDecoder decoder = + (location) -> new File("/src", location.getExecutionRootRelativePath()); + + mockBlazeProjectDataManager( + new BlazeProjectData( + 0L, buildTargetMap(), null, null, null, null, decoder, null, null, null)); + classJarProvider = new BlazeClassJarProvider(getProject()); + } + + @Test + public void testFindModuleClassFile() { + createClassesInJars(); + + // Make sure we can find classes in the main resource module. + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.MainActivity", module)) + .isEqualTo( + fileSystem.findFile( + BLAZE_BIN + + "/com/google/example/main.jar!" + + "/com/google/example/main/MainActivity.class")); + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R", module)) + .isEqualTo( + fileSystem.findFile( + BLAZE_BIN + + "/com/google/example/main_resources.jar!" + + "/com/google/example/main/R.class")); + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R$string", module)) + .isEqualTo( + fileSystem.findFile( + BLAZE_BIN + + "/com/google/example/main_resources.jar!" + + "/com/google/example/main/R$string.class")); + + // And not classes that are missing. + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.MissingClass", module)) + .isNull(); + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R$missing", module)) + .isNull(); + + // And not classes in other libraries. + assertThat(classJarProvider.findModuleClassFile("com.google.example.java.CustomView", module)) + .isNull(); + assertThat(classJarProvider.findModuleClassFile("com.google.example.android_res.R", module)) + .isNull(); + assertThat( + classJarProvider.findModuleClassFile("com.google.example.android_res.R$style", module)) + .isNull(); + assertThat( + classJarProvider.findModuleClassFile( + "com.google.unrelated.android_res.R$layout", module)) + .isNull(); + } + + @Test + public void testMissingMainJar() { + createClassesInJars(); + + ApplicationManager.getApplication() + .runWriteAction( + () -> { + try { + // Let's pretend that this hasn't been built yet. + fileSystem.findFile(BLAZE_BIN + "/com/google/example/main.jar").delete(this); + } catch (IOException ignored) { + // ignored + } + }); + // This hasn't been built yet, and shouldn't be found. + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.MainActivity", module)) + .isNull(); + // But these should still be found. + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R", module)) + .isEqualTo( + fileSystem.findFile( + BLAZE_BIN + + "/com/google/example/main_resources.jar!" + + "/com/google/example/main/R.class")); + assertThat(classJarProvider.findModuleClassFile("com.google.example.main.R$string", module)) + .isEqualTo( + fileSystem.findFile( + BLAZE_BIN + + "/com/google/example/main_resources.jar!" + + "/com/google/example/main/R$string.class")); + } + + @Test + public void testGetModuleExternalLibraries() { + // Need AndroidFact for AppResourceRepository. + ApplicationManager.getApplication() + .runWriteAction( + () -> { + ModifiableFacetModel model = FacetManager.getInstance(module).createModifiableModel(); + model.addFacet(new MockAndroidFacet(module)); + model.commit(); + }); + + List<VirtualFile> externalLibraries = classJarProvider.getModuleExternalLibraries(module); + assertThat(externalLibraries) + .containsExactly( + fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_lib.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res2.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/android_res.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/java.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/java.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared/java.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared2/java.jar"), + fileSystem.findFile("com/google/example/import.jar"), + fileSystem.findFile("com/google/example/transitive/import.jar"), + fileSystem.findFile("com/google/example/transitive/import2.jar")); + + // Make sure we can generate dynamic classes from all resource packages in dependencies. + ResourceClassRegistry registry = ResourceClassRegistry.get(getProject()); + AppResourceRepository repository = AppResourceRepository.getAppResources(module, false); + assertThat(repository).isNotNull(); + assertThat(registry.findClassDefinition("com.google.example.android_res.R", repository)) + .isNotNull(); + assertThat(registry.findClassDefinition("com.google.example.android_res.R$string", repository)) + .isNotNull(); + assertThat(registry.findClassDefinition("com.google.example.android_res2.R", repository)) + .isNotNull(); + assertThat(registry.findClassDefinition("com.google.example.android_res2.R$layout", repository)) + .isNotNull(); + assertThat( + registry.findClassDefinition("com.google.example.transitive.android_res.R", repository)) + .isNotNull(); + assertThat( + registry.findClassDefinition( + "com.google.example.transitive.android_res.R$style", repository)) + .isNotNull(); + + // And nothing else. + assertThat(registry.findClassDefinition("com.google.example.main.MainActivity", repository)) + .isNull(); + assertThat(registry.findClassDefinition("com.google.example.android_res.Bogus", repository)) + .isNull(); + assertThat(registry.findClassDefinition("com.google.example.main.R", repository)).isNull(); + assertThat(registry.findClassDefinition("com.google.example.main.R$string", repository)) + .isNull(); + assertThat(registry.findClassDefinition("com.google.example.java.CustomView", repository)) + .isNull(); + assertThat(registry.findClassDefinition("com.google.unrelated.android_res.R", repository)) + .isNull(); + assertThat( + registry.findClassDefinition("com.google.unrelated.android_res.R$layout", repository)) + .isNull(); + } + + @Test + public void testMissingExternalJars() { + ApplicationManager.getApplication() + .runWriteAction( + () -> { + try { + // Let's pretend that these haven't been built yet. + fileSystem.findFile(BLAZE_BIN + "/com/google/example/java.jar").delete(this); + fileSystem + .findFile(BLAZE_BIN + "/com/google/example/android_res2.jar") + .delete(this); + fileSystem + .findFile(BLAZE_BIN + "/com/google/example/shared2/java.jar") + .delete(this); + } catch (IOException ignored) { + // ignored + } + }); + List<VirtualFile> externalLibraries = classJarProvider.getModuleExternalLibraries(module); + assertThat(externalLibraries) + .containsExactly( + fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_lib.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res.jar"), + // This should be missing. + // fileSystem.findFile(BLAZE_BIN + "/com/google/example/android_res2.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/android_res.jar"), + // This should be missing. + // fileSystem.findFile(BLAZE_BIN + "/com/google/example/java.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/transitive/java.jar"), + fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared/java.jar"), + // This should be missing. + // fileSystem.findFile(BLAZE_BIN + "/com/google/example/shared2/java.jar"), + fileSystem.findFile("com/google/example/import.jar"), + fileSystem.findFile("com/google/example/transitive/import.jar"), + fileSystem.findFile("com/google/example/transitive/import2.jar")); + } + + private void createClassesInJars() { + fileSystem.createFile( + BLAZE_BIN + + "/com/google/example/main.jar!" + + "/com/google/example/main/MainActivity.class"); + fileSystem.createFile( + BLAZE_BIN + "/com/google/example/main_resources.jar!" + "/com/google/example/main/R.class"); + fileSystem.createFile( + BLAZE_BIN + + "/com/google/example/main_resources.jar!" + + "/com/google/example/main/R$string.class"); + fileSystem.createFile( + BLAZE_BIN + "/com/google/example/java.jar!" + "/com/google/example/java/CustomView.class"); + fileSystem.createFile( + BLAZE_BIN + + "/com/google/example/android_res_resources.jar!" + + "/com/google/example/android_res/R.class"); + fileSystem.createFile( + BLAZE_BIN + + "/com/google/example/android_res_resources.jar!" + + "/com/google/example/android_res/R$style.class"); + fileSystem.createFile( + BLAZE_BIN + + "/com/google/unrelated/android_res_resources.jar!" + + "/com/google/unrelated/android_res/R$layout.class"); + } + + private TargetMap buildTargetMap() { + Label mainResourceLibrary = new Label("//com/google/example:main"); + Label androidLibraryDependency = new Label("//com/google/example:android_lib"); + Label androidResourceDependency = new Label("//com/google/example:android_res"); + Label androidResourceDependency2 = new Label("//com/google/example:android_res2"); + Label transitiveResourceDependency = new Label("//com/google/example/transitive:android_res"); + Label javaDependency = new Label("//com/google/example:java"); + Label transitiveJavaDependency = new Label("//com/google/example/transitive:java"); + Label sharedJavaDependency = new Label("//com/google/example/shared:java"); + Label sharedJavaDependency2 = new Label("//com/google/example/shared2:java"); + Label importDependency = new Label("//com/google/example:import"); + Label transitiveImportDependency = new Label("//com/google/example/transitive:import"); + Label unrelatedJava = new Label("//com/google/unrelated:java"); + Label unrelatedAndroidLibrary = new Label("//com/google/unrelated:android_lib"); + Label unrelatedAndroidResource = new Label("//com/google/unrelated:android_res"); + + AndroidResourceModuleRegistry registry = new AndroidResourceModuleRegistry(); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourceLibrary)).build()); + // Not using these, but they should be in the registry. + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(androidResourceDependency)).build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(androidResourceDependency2)) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(transitiveResourceDependency)) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedAndroidResource)).build()); + registerProjectService(AndroidResourceModuleRegistry.class, registry); + + return TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(mainResourceLibrary) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo( + javaInfoWithJars( + "com/google/example/main.jar", "com/google/example/main_resources.jar")) + .setAndroidInfo( + androidInfoWithResourceAndJar( + "com.google.example.main", + "com/google/example/main/res", + "com/google/example/main_resources.jar")) + .addDependency(androidLibraryDependency) + .addDependency(androidResourceDependency) + .addDependency(androidResourceDependency2) + .addDependency(javaDependency) + .addDependency(importDependency)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(androidLibraryDependency) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/example/android_lib.jar")) + .addDependency(transitiveResourceDependency)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(androidResourceDependency) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo( + javaInfoWithJars( + "com/google/example/android_res.jar", + "com/google/example/android_res_resources.jar")) + .setAndroidInfo( + androidInfoWithResourceAndJar( + "com.google.example.android_res", + "com/google/example/android_res/res", + "com/google/example/android_res_resources.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(androidResourceDependency2) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo( + javaInfoWithJars( + "com/google/example/android_res2.jar", + "com/google/example/android_res2_resources.jar")) + .setAndroidInfo( + androidInfoWithResourceAndJar( + "com.google.example.android_res2", + "com/google/example/android_res2/res", + "com/google/example/android_res2_resources.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(transitiveResourceDependency) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo( + javaInfoWithJars( + "com/google/example/transitive/android_res.jar", + "com/google/example/transitive/android_res_resources.jar")) + .setAndroidInfo( + androidInfoWithResourceAndJar( + "com.google.example.transitive.android_res", + "com/google/example/transitive/android_res/res", + "com/google/example/transitive/android_res_resources.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(javaDependency) + .setKind(Kind.JAVA_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/example/java.jar")) + .addDependency(transitiveJavaDependency) + .addDependency(sharedJavaDependency) + .addDependency(sharedJavaDependency2) + .addDependency(transitiveImportDependency)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(transitiveJavaDependency) + .setKind(Kind.JAVA_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/example/transitive/java.jar")) + .addDependency(sharedJavaDependency) + .addDependency(sharedJavaDependency2)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(sharedJavaDependency) + .setKind(Kind.JAVA_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/example/shared/java.jar")) + .addDependency(sharedJavaDependency2)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(sharedJavaDependency2) + .setKind(Kind.JAVA_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/example/shared2/java.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(importDependency) + .setKind(Kind.JAVA_IMPORT) + .setJavaInfo(javaInfoWithCheckedInJars("com/google/example/import.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(transitiveImportDependency) + .setKind(Kind.JAVA_IMPORT) + .setJavaInfo( + javaInfoWithCheckedInJars( + "com/google/example/transitive/import.jar", + "com/google/example/transitive/import2.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(unrelatedJava) + .setKind(Kind.JAVA_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/unrelated/java.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(unrelatedAndroidLibrary) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo(javaInfoWithJars("com/google/unrelated/android_lib.jar"))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(unrelatedAndroidResource) + .setKind(Kind.ANDROID_LIBRARY) + .setJavaInfo( + javaInfoWithJars( + "com/google/unrelated/android_res.jar", + "com/google/unrelated/android_res_resources.jar")) + .setAndroidInfo( + androidInfoWithResourceAndJar( + "com.google.unrelated.android_res", + "com/google/unrelated/android_res/res", + "com/google/unrelated/android_res_resources.jar"))) + .build(); + } + + private JavaIdeInfo.Builder javaInfoWithJars(String... relativeJarPaths) { + JavaIdeInfo.Builder builder = JavaIdeInfo.builder(); + for (String relativeJarPath : relativeJarPaths) { + ArtifactLocation jar = + ArtifactLocation.builder() + .setRootExecutionPathFragment(BLAZE_BIN) + .setRelativePath(relativeJarPath) + .setIsSource(false) + .build(); + builder.addJar(LibraryArtifact.builder().setClassJar(jar)); + fileSystem.createFile(jar.getExecutionRootRelativePath()); + } + return builder; + } + + private JavaIdeInfo.Builder javaInfoWithCheckedInJars(String... relativeJarPaths) { + JavaIdeInfo.Builder builder = JavaIdeInfo.builder(); + for (String relativeJarPath : relativeJarPaths) { + ArtifactLocation jar = + ArtifactLocation.builder().setRelativePath(relativeJarPath).setIsSource(true).build(); + builder.addJar(LibraryArtifact.builder().setClassJar(jar)); + fileSystem.createFile(jar.getExecutionRootRelativePath()); + } + return builder; + } + + private static AndroidIdeInfo.Builder androidInfoWithResourceAndJar( + String javaPackage, String relativeResourcePath, String relativeJarPath) { + return AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setResourceJavaPackage(javaPackage) + .addResource( + ArtifactLocation.builder() + .setRelativePath(relativeResourcePath) + .setIsSource(true) + .build()) + .setResourceJar( + LibraryArtifact.builder() + .setClassJar( + // No need to createFile for this one since it should also be in the Java info. + ArtifactLocation.builder() + .setRootExecutionPathFragment(BLAZE_BIN) + .setRelativePath(relativeJarPath) + .setIsSource(false) + .build())); + } + + private static class MockAndroidFacet extends AndroidFacet { + public MockAndroidFacet(Module module) { + super(module, AndroidFacet.NAME, new AndroidFacetConfiguration()); + } + + @Override + public void initFacet() { + // We don't need this, but it causes trouble when it tries looking for project templates. + } + } +}
diff --git a/aswb/2.3/tests/unittests/com/google/idea/blaze/android/project/BlazeBuildSystemServiceTest.java b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/project/BlazeBuildSystemServiceTest.java new file mode 100755 index 0000000..72541b3 --- /dev/null +++ b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/project/BlazeBuildSystemServiceTest.java
@@ -0,0 +1,263 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.project; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.android.tools.idea.project.BuildSystemService; +import com.google.common.collect.Maps; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.actions.BlazeBuildService; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.sync.BlazeSyncManager; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; +import com.intellij.mock.MockModule; +import com.intellij.mock.MockVirtualFile; +import com.intellij.openapi.editor.LazyRangeMarkerFactory; +import com.intellij.openapi.editor.impl.LazyRangeMarkerFactoryImpl; +import com.intellij.openapi.extensions.ExtensionPoint; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.ex.temp.TempFileSystem; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import java.io.File; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +/** Test cases for {@link BlazeBuildSystemService}. */ +@RunWith(JUnit4.class) +public class BlazeBuildSystemServiceTest extends BlazeTestCase { + Module module; + BuildSystemService service; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + module = new MockModule(project, () -> {}); + + mockBlazeImportSettings(projectServices); // For Blaze.isBlazeProject. + createMocksForBuildProject(applicationServices); + createMocksForSyncProject(projectServices); + createMocksForAddDependency(applicationServices, projectServices); + + ExtensionPoint<BuildSystemService> extensionPoint = + registerExtensionPoint( + ExtensionPointName.create("com.android.project.buildSystemService"), + BuildSystemService.class); + extensionPoint.registerExtension(new BlazeBuildSystemService()); + + service = BuildSystemService.getInstance(project); + } + + @Test + public void testIsBlazeBuildSystemService() { + assertThat(service).isInstanceOf(BlazeBuildSystemService.class); + } + + @Test + public void testBuildProject() { + service.buildProject(project); + verify(BlazeBuildService.getInstance()).buildProject(project); + verifyNoMoreInteractions(BlazeBuildService.getInstance()); + } + + @Test + public void testSyncProject() { + service.syncProject(project); + verify(BlazeSyncManager.getInstance(project)).incrementalProjectSync(); + verifyNoMoreInteractions(BlazeSyncManager.getInstance(project)); + } + + @Test + public void testAddDependencyWithBuildTargetPsi() throws Exception { + PsiElement buildTargetPsi = mock(PsiElement.class); + PsiFile psiFile = mock(PsiFile.class); + + BuildReferenceManager buildReferenceManager = BuildReferenceManager.getInstance(project); + when(buildReferenceManager.resolveLabel(new Label("//foo:bar"))).thenReturn(buildTargetPsi); + when(buildTargetPsi.getContainingFile()).thenReturn(psiFile); + when(buildTargetPsi.getTextOffset()).thenReturn(1337); + + VirtualFile buildFile = TempFileSystem.getInstance().findFileByPath("/foo/BUILD"); + assertThat(buildFile).isNotNull(); + when(psiFile.getVirtualFile()).thenReturn(buildFile); + + String dependency = "com.android.foo:bar"; // Doesn't matter. + + service.addDependency(module, dependency); + + ArgumentCaptor<OpenFileDescriptor> descriptorCaptor = + ArgumentCaptor.forClass(OpenFileDescriptor.class); + verify(FileEditorManager.getInstance(project)) + .openTextEditor(descriptorCaptor.capture(), eq(true)); + OpenFileDescriptor descriptor = descriptorCaptor.getValue(); + assertThat(descriptor.getProject()).isEqualTo(project); + assertThat(descriptor.getFile()).isEqualTo(buildFile); + assertThat(descriptor.getOffset()).isEqualTo(1337); + verifyNoMoreInteractions(FileEditorManager.getInstance(project)); + } + + @Test + public void testAddDependencyWithoutBuildTargetPsi() throws Exception { + // Can't find PSI for the target. + when(BuildReferenceManager.getInstance(project).resolveLabel(new Label("//foo:bar"))) + .thenReturn(null); + + VirtualFile buildFile = TempFileSystem.getInstance().findFileByPath("/foo/BUILD"); + assertThat(buildFile).isNotNull(); + + String dependency = "com.android.foo:bar"; // Doesn't matter. + + service.addDependency(module, dependency); + + verify(FileEditorManager.getInstance(project)).openFile(buildFile, true); + verifyNoMoreInteractions(FileEditorManager.getInstance(project)); + } + + private void mockBlazeImportSettings(Container projectServices) { + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + importSettingsManager.setImportSettings( + new BlazeImportSettings("", "", "", "", "", Blaze.BuildSystem.Blaze)); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + } + + private static void createMocksForBuildProject(Container applicationServices) { + applicationServices.register(BlazeBuildService.class, mock(BlazeBuildService.class)); + } + + private static void createMocksForSyncProject(Container projectServices) { + projectServices.register(ProjectViewManager.class, new MockProjectViewManager()); + projectServices.register(BlazeSyncManager.class, mock(BlazeSyncManager.class)); + } + + private void createMocksForAddDependency( + Container applicationServices, Container projectServices) { + projectServices.register(BlazeProjectDataManager.class, new MockProjectDataManager()); + projectServices.register(FileEditorManager.class, mock(FileEditorManager.class)); + projectServices.register(BuildReferenceManager.class, mock(BuildReferenceManager.class)); + projectServices.register(LazyRangeMarkerFactory.class, mock(LazyRangeMarkerFactoryImpl.class)); + + applicationServices.register(TempFileSystem.class, new MockFileSystem("/foo/BUILD")); + + AndroidResourceModuleRegistry moduleRegistry = new AndroidResourceModuleRegistry(); + moduleRegistry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo:bar"))).build()); + projectServices.register(AndroidResourceModuleRegistry.class, moduleRegistry); + } + + private static class MockProjectViewManager extends ProjectViewManager { + private ProjectViewSet viewSet; + + public MockProjectViewManager() { + this.viewSet = ProjectViewSet.builder().build(); + } + + @Nullable + @Override + public ProjectViewSet getProjectViewSet() { + return viewSet; + } + + @Nullable + @Override + public ProjectViewSet reloadProjectView( + BlazeContext context, WorkspacePathResolver workspacePathResolver) { + return viewSet; + } + } + + private static class MockProjectDataManager implements BlazeProjectDataManager { + private BlazeProjectData projectData; + + public MockProjectDataManager() { + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(new Label("//foo:bar")) + .setBuildFile(ArtifactLocation.builder().setRelativePath("foo/BUILD").build()) + .build()) + .build(); + ArtifactLocationDecoder decoder = (location) -> new File("/", location.getRelativePath()); + + projectData = + new BlazeProjectData(0L, targetMap, null, null, null, null, decoder, null, null, null); + } + + @Nullable + @Override + public BlazeProjectData getBlazeProjectData() { + return projectData; + } + + @Override + public ModuleEditor editModules() { + return null; + } + } + + private static class MockFileSystem extends TempFileSystem { + private Map<String, VirtualFile> files; + + public MockFileSystem(String... paths) { + files = Maps.newHashMap(); + for (String path : paths) { + files.put(path, new MockVirtualFile(path)); + } + } + + @Override + public VirtualFile findFileByPath(String path) { + return files.get(path); + } + + @Override + public VirtualFile findFileByIoFile(File file) { + return findFileByPath(file.getPath()); + } + } +}
diff --git a/aswb/2.3/tests/unittests/com/google/idea/blaze/android/project/BlazeFeatureEnabledServiceTest.java b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/project/BlazeFeatureEnabledServiceTest.java new file mode 100644 index 0000000..e4fe775 --- /dev/null +++ b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/project/BlazeFeatureEnabledServiceTest.java
@@ -0,0 +1,158 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.project; + +import static com.google.common.truth.Truth.THROW_ASSERTION_ERROR; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import com.android.tools.idea.project.FeatureEnableService; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.settings.BlazeAndroidUserSettings; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManagerLegacy; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.common.experiments.ExperimentService; +import com.intellij.openapi.extensions.ExtensionPoint; +import com.intellij.openapi.extensions.ExtensionPointName; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test cases for {@link BlazeFeatureEnableService}. */ +@RunWith(JUnit4.class) +public class BlazeFeatureEnabledServiceTest extends BlazeTestCase { + private MockExperimentService experimentService; + private BlazeAndroidUserSettings userSettings; + private MockBlazeProjectDataManager projectDataManager; + private FeatureEnableService featureEnableService; + + @Override + protected void initTest( + @NotNull Container applicationServices, @NotNull Container projectServices) { + super.initTest(applicationServices, projectServices); + experimentService = new MockExperimentService(); + applicationServices.register(ExperimentService.class, experimentService); + + userSettings = new BlazeAndroidUserSettings(); + applicationServices.register(BlazeAndroidUserSettings.class, userSettings); + + projectDataManager = new MockBlazeProjectDataManager(); + projectServices.register(BlazeProjectDataManager.class, projectDataManager); + + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + importSettingsManager.setImportSettings( + new BlazeImportSettings(null, null, null, null, null, BuildSystem.Blaze)); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + projectServices.register( + BlazeImportSettingsManagerLegacy.class, new BlazeImportSettingsManagerLegacy(project)); + + ExtensionPoint<FeatureEnableService> extensionPoint = + registerExtensionPoint( + ExtensionPointName.create("com.android.project.featureEnableService"), + FeatureEnableService.class); + extensionPoint.registerExtension(new BlazeFeatureEnableService()); + + featureEnableService = FeatureEnableService.getInstance(project); + } + + @Test + public void testIsBlazeFeatureEnableService() { + assertThat(featureEnableService).isInstanceOf(BlazeFeatureEnableService.class); + } + + @Test + public void testIsNotBlazeFeatureEnableService() { + BlazeImportSettingsManager.getInstance(project).loadState(null); + assertThat(FeatureEnableService.getInstance(project)) + .isNotInstanceOf(BlazeFeatureEnableService.class); + } + + @Test + public void testGetLayoutEditorEnabled() { + for (boolean experimentEnabled : ImmutableList.of(true, false)) { + for (boolean settingEnabled : ImmutableList.of(true, false)) { + for (boolean projectDataReady : ImmutableList.of(true, false)) { + userSettings.setUseLayoutEditor(settingEnabled); + experimentService.setEnableLayoutEditor(experimentEnabled); + projectDataManager.setBlazeProjectData( + projectDataReady ? mock(BlazeProjectData.class) : null); + assertThat(featureEnableService.isLayoutEditorEnabled(project)) + .isEqualTo(settingEnabled && experimentEnabled && projectDataReady); + } + } + } + } + + private static class MockBlazeProjectDataManager implements BlazeProjectDataManager { + private BlazeProjectData blazeProjectData; + + public void setBlazeProjectData(BlazeProjectData blazeProjectData) { + this.blazeProjectData = blazeProjectData; + } + + @Nullable + @Override + public BlazeProjectData getBlazeProjectData() { + return blazeProjectData; + } + + @Override + public ModuleEditor editModules() { + return null; + } + } + + private static class MockExperimentService implements ExperimentService { + private boolean enableLayoutEditor; + + public void setEnableLayoutEditor(boolean enableLayoutEditor) { + this.enableLayoutEditor = enableLayoutEditor; + } + + @Override + public boolean getExperiment(String key, boolean defaultValue) { + assertThat(key).isEqualTo("enable.layout.editor"); + return enableLayoutEditor; + } + + @Nullable + @Override + public String getExperimentString(String key, @Nullable String defaultValue) { + THROW_ASSERTION_ERROR.fail("Should not be called."); + return null; + } + + @Override + public int getExperimentInt(String key, int defaultValue) { + THROW_ASSERTION_ERROR.fail("Should not be called."); + return 0; + } + + @Override + public void startExperimentScope() {} + + @Override + public void endExperimentScope() {} + } +}
diff --git a/aswb/2.3/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java new file mode 100644 index 0000000..5bbeec6 --- /dev/null +++ b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/rendering/BlazeRenderErrorContributorTest.java
@@ -0,0 +1,768 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.rendering; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import com.android.tools.idea.rendering.RenderErrorContributor; +import com.android.tools.idea.rendering.RenderErrorModelFactory; +import com.android.tools.idea.rendering.RenderResult; +import com.android.tools.idea.rendering.errors.ui.RenderErrorModel; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo; +import com.google.idea.blaze.base.ideinfo.ArtifactLocation; +import com.google.idea.blaze.base.ideinfo.JavaIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManagerLegacy; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap; +import com.intellij.mock.MockModule; +import com.intellij.mock.MockPsiFile; +import com.intellij.mock.MockPsiManager; +import com.intellij.mock.MockVirtualFile; +import com.intellij.openapi.extensions.ExtensionPoint; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.MockFileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.JavaPsiFacadeImpl; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.ProjectScopeBuilder; +import com.intellij.psi.search.ProjectScopeBuilderImpl; +import com.intellij.psi.util.PsiUtil.NullPsiClass; +import java.io.File; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BlazeRenderErrorContributor}. */ +@RunWith(JUnit4.class) +public class BlazeRenderErrorContributorTest extends BlazeTestCase { + private static final String BLAZE_BIN = "blaze-out/crosstool/bin"; + private static final String GENERATED_RESOURCES_ERROR = "Generated resources"; + private static final String NON_STANDARD_MANIFEST_NAME_ERROR = "Non-standard manifest name"; + private static final String MISSING_CLASS_DEPENDENCIES_ERROR = "Missing class dependencies"; + + private Module module; + private MockBlazeProjectDataManager projectDataManager; + private BlazeRenderErrorContributor.BlazeProvider provider; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + super.initTest(applicationServices, projectServices); + applicationServices.register(FileTypeManager.class, new MockFileTypeManager()); + + projectServices.register(BuildReferenceManager.class, new MockBuildReferenceManager(project)); + projectServices.register(TransitiveDependencyMap.class, new TransitiveDependencyMap(project)); + projectServices.register(ProjectScopeBuilder.class, new ProjectScopeBuilderImpl(project)); + projectServices.register( + AndroidResourceModuleRegistry.class, new AndroidResourceModuleRegistry()); + + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + BlazeImportSettings settings = new BlazeImportSettings("", "", "", "", "", BuildSystem.Blaze); + importSettingsManager.setImportSettings(settings); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + projectServices.register( + BlazeImportSettingsManagerLegacy.class, new BlazeImportSettingsManagerLegacy(project)); + + createPsiClassesAndSourceToTargetMap(projectServices); + + projectDataManager = new MockBlazeProjectDataManager(); + projectServices.register(BlazeProjectDataManager.class, projectDataManager); + + ExtensionPoint<RenderErrorContributor.Provider> extensionPoint = + registerExtensionPoint( + ExtensionPointName.create("com.android.rendering.renderErrorContributor"), + RenderErrorContributor.Provider.class); + extensionPoint.registerExtension(new RenderErrorContributor.Provider()); + extensionPoint.registerExtension(new BlazeRenderErrorContributor.BlazeProvider()); + + module = new MockModule(project, () -> {}); + + // For the isApplicable tests. + provider = new BlazeRenderErrorContributor.BlazeProvider(); + } + + @Test + public void testProviderIsApplicable() { + assertThat(provider.isApplicable(project)).isTrue(); + } + + @Test + public void testProviderNotApplicableIfNotBlaze() { + BlazeImportSettingsManager.getInstance(project).loadState(null); + assertThat(provider.isApplicable(project)).isFalse(); + } + + @Test + public void testNoIssuesIfNoErrors() { + PsiFile file = new MockPsiFile(new MockPsiManager(project)); + file.putUserData(ModuleUtilCore.KEY_MODULE, module); + RenderResult result = RenderResult.createBlank(file); + RenderErrorModel errorModel = RenderErrorModelFactory.createErrorModel(result, null); + assertThat(errorModel.getIssues()).isEmpty(); + } + + @Test + public void testNoBlazeIssuesIfNoRelatedErrors() { + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + errorModel + .getIssues() + .forEach( + issue -> + assertThat(issue.getSummary()) + .isNoneOf( + GENERATED_RESOURCES_ERROR, + NON_STANDARD_MANIFEST_NAME_ERROR, + MISSING_CLASS_DEPENDENCIES_ERROR)); + } + + @Test + public void testReportGeneratedResources() { + createTargetMapWithGeneratedResources(); + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + + RenderErrorModel.Issue generatedResourcesIssue = + Iterables.getOnlyElement( + errorModel + .getIssues() + .stream() + .filter(issue -> issue.getSummary().equals(GENERATED_RESOURCES_ERROR)) + .collect(Collectors.toList())); + + assertThat(generatedResourcesIssue.getHtmlContent()) + .isEqualTo( + "Generated resources will not be discovered by the IDE:" + + "<DL>" + + "<DD>-&NBSP;" + + "com/google/example/dependency/generated/res " + + "from <A HREF=\"file:///src/com/google/example/dependency/BUILD\">" + + "//com/google/example:generated</A>" + + "<DD>-&NBSP;" + + "com/google/example/main/generated/res " + + "from <A HREF=\"file:///src/com/google/example/main/BUILD\">" + + "//com/google/example:main</A>" + + "<DD>-&NBSP;" + + "com/google/example/transitive/generated/one/res " + + "from <A HREF=\"file:///src/com/google/example/transitive/BUILD\">" + + "//com/google/example/transitive:generated</A>" + + "<DD>-&NBSP;" + + "com/google/example/transitive/generated/two/res " + + "from <A HREF=\"file:///src/com/google/example/transitive/BUILD\">" + + "//com/google/example/transitive:generated</A>" + + "</DL>" + + "Please avoid using generated resources, then " + + "<A HREF=\"action:sync\">sync the project</A> and " + + "<A HREF=\"refreshRender\">refresh the layout</A>."); + } + + @Test + public void testReportNonStandardAndroidManifestName() { + createTargetMapWithNonStandardAndroidManifestName(); + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + + RenderErrorModel.Issue nonStandardManifestNameIssue = + Iterables.getOnlyElement( + errorModel + .getIssues() + .stream() + .filter(issue -> issue.getSummary().equals(NON_STANDARD_MANIFEST_NAME_ERROR)) + .collect(Collectors.toList())); + + assertThat(nonStandardManifestNameIssue.getHtmlContent()) + .isEqualTo( + "<A HREF=\"file:///src/com/google/example/main/BUILD\">" + + "//com/google/example:main</A> " + + "uses a non-standard name for the Android manifest: " + + "<A HREF=\"file:///src/com/google/example/main/WeirdManifest.xml\">" + + "WeirdManifest.xml</A>" + + "<BR/>" + + "Please rename it to AndroidManifest.xml, then " + + "<A HREF=\"action:sync\">sync the project</A> and " + + "<A HREF=\"refreshRender\">refresh the layout</A>."); + } + + @Test + public void testNoReportNonStandardAndroidManifestNameInDependency() { + createTargetMapWithNonStandardAndroidManifestNameInDependency(); + RenderErrorModel errorModel = createRenderErrorModelWithBrokenClasses(); + + errorModel + .getIssues() + .forEach( + issue -> assertThat(issue.getSummary()).isNotEqualTo(NON_STANDARD_MANIFEST_NAME_ERROR)); + } + + @Test + public void testReportMissingClassDependencies() { + createTargetMapWithMissingClassDependency(); + RenderErrorModel errorModel = + createRenderErrorModelWithMissingClasses( + "com.google.example.independent.LibraryView", + "com.google.example.independent.LibraryView2", + "com.google.example.independent.Library2View", + "com.google.example.dependent.LibraryView", + "com.google.example.ResourceView"); + + RenderErrorModel.Issue missingClassDependenciesIssue = + Iterables.getOnlyElement( + errorModel + .getIssues() + .stream() + .filter(issue -> issue.getSummary().equals(MISSING_CLASS_DEPENDENCIES_ERROR)) + .collect(Collectors.toList())); + + assertThat(missingClassDependenciesIssue.getHtmlContent()) + .isEqualTo( + "<A HREF=\"file:///src/com/google/example/BUILD\">" + + "//com/google/example:resources</A> " + + "contains resource files that reference these classes:" + + "<DL>" + + "<DD>-&NBSP;" + + "<A HREF=\"openClass:com.google.example.independent.Library2View\">" + + "com.google.example.independent.Library2View</A> " + + "from <A HREF=\"file:///src/com/google/example/BUILD\">" + + "//com/google/example/independent:library2</A> " + + "<DD>-&NBSP;" + + "<A HREF=\"openClass:com.google.example.independent.LibraryView\">" + + "com.google.example.independent.LibraryView</A> " + + "from <A HREF=\"file:///src/com/google/example/BUILD\">" + + "//com/google/example/independent:library</A> " + + "<DD>-&NBSP;" + + "<A HREF=\"openClass:com.google.example.independent.LibraryView2\">" + + "com.google.example.independent.LibraryView2</A> " + + "from <A HREF=\"file:///src/com/google/example/BUILD\">" + + "//com/google/example/independent:library</A> " + + "</DL>" + + "Please fix your dependencies so that " + + "<A HREF=\"file:///src/com/google/example/BUILD\">" + + "//com/google/example:resources</A> " + + "correctly depends on these classes, then " + + "<A HREF=\"action:sync\">sync the project</A> and " + + "<A HREF=\"refreshRender\">refresh the layout</A>." + + "<BR/>" + + "<BR/>" + + "<B>NOTE: blaze can still build with the incorrect dependencies " + + "due to the way it handles resources, " + + "but the layout editor needs them to be correct.</B>"); + } + + @Test + public void testNoReportMissingClassDependenciesIfClassInSameTarget() { + createTargetMapWithMissingClassDependency(); + RenderErrorModel errorModel = + createRenderErrorModelWithMissingClasses("com.google.example.ResourceView"); + + errorModel + .getIssues() + .forEach( + issue -> assertThat(issue.getSummary()).isNotEqualTo(MISSING_CLASS_DEPENDENCIES_ERROR)); + } + + @Test + public void testNoReportMissingClassDependenciesIfClassInDependency() { + createTargetMapWithMissingClassDependency(); + RenderErrorModel errorModel = + createRenderErrorModelWithMissingClasses("com.google.example.dependent.LibraryView"); + + errorModel + .getIssues() + .forEach( + issue -> assertThat(issue.getSummary()).isNotEqualTo(MISSING_CLASS_DEPENDENCIES_ERROR)); + } + + private RenderErrorModel createRenderErrorModelWithBrokenClasses() { + PsiFile file = new MockPsiFile(new MockPsiManager(project)); + file.putUserData(ModuleUtilCore.KEY_MODULE, module); + RenderResult result = RenderResult.createBlank(file); + result + .getLogger() + .addBrokenClass("com.google.example.CustomView", new Exception("resource not found")); + return RenderErrorModelFactory.createErrorModel(result, null); + } + + private RenderErrorModel createRenderErrorModelWithMissingClasses(String... classNames) { + PsiFile file = new MockPsiFile(new MockPsiManager(project)); + file.putUserData(ModuleUtilCore.KEY_MODULE, module); + RenderResult result = RenderResult.createBlank(file); + for (String className : classNames) { + result.getLogger().addMissingClass(className); + } + return RenderErrorModelFactory.createErrorModel(result, null); + } + + private static ArtifactLocation artifact(String relativePath, boolean isSource) { + return ArtifactLocation.builder() + .setIsSource(isSource) + .setRootExecutionPathFragment(isSource ? "" : BLAZE_BIN) + .setRelativePath(relativePath) + .build(); + } + + private void createTargetMapWithGeneratedResources() { + Label mainResourcesTarget = new Label("//com/google/example:main"); + Label dependencyGeneratedResourceTarget = new Label("//com/google/example:generated"); + Label dependencySourceResourceTarget = new Label("//com/google/example:source"); + Label transitiveGeneratedResourcesTarget = + new Label("//com/google/example/transitive:generated"); + Label transitiveSourceResourceTarget = new Label("//com/google/example/transitive:source"); + Label unrelatedGeneratedResourceTarget = new Label("//com/google/unrelated:generated"); + Label unrelatedSourceResourceTarget = new Label("//com/google/unrelated:source"); + + ArtifactLocation mainGeneratedResource = + artifact("com/google/example/main/generated/res", false); + ArtifactLocation mainSourceResource = artifact("com/google/example/main/source/res", true); + ArtifactLocation dependencyGeneratedResource = + artifact("com/google/example/dependency/generated/res", false); + ArtifactLocation dependencySourceResource = + artifact("com/google/example/dependency/source/res", true); + ArtifactLocation transitiveGeneratedResourceOne = + artifact("com/google/example/transitive/generated/one/res", false); + ArtifactLocation transitiveGeneratedResourceTwo = + artifact("com/google/example/transitive/generated/two/res", false); + ArtifactLocation transitiveSourceResource = + artifact("com/google/example/transitive/source/res", true); + ArtifactLocation unrelatedGeneratedResource = + artifact("com/google/unrelated/generated/res", false); + ArtifactLocation unrelatedSourceResource = artifact("com/google/unrelated/source/res", true); + + ArtifactLocation mainBuildFile = artifact("com/google/example/main/BUILD", true); + ArtifactLocation dependencyBuildFile = artifact("com/google/example/dependency/BUILD", true); + ArtifactLocation transitiveBuildFile = artifact("com/google/example/transitive/BUILD", true); + ArtifactLocation unrelatedBuildFile = artifact("com/google/unrelated/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourcesTarget)) + // .addResource(mainGeneratedResource) // Dropped. + .addResource(mainSourceResource) + .addTransitiveResourceDependency(dependencyGeneratedResourceTarget) + .addTransitiveResource(dependencyGeneratedResource) + .addTransitiveResourceDependency(dependencySourceResourceTarget) + .addTransitiveResource(dependencySourceResource) + .addTransitiveResourceDependency(transitiveGeneratedResourcesTarget) + .addTransitiveResource(transitiveGeneratedResourceOne) + .addTransitiveResource(transitiveGeneratedResourceTwo) + .addTransitiveResourceDependency(transitiveSourceResourceTarget) + .addTransitiveResource(transitiveSourceResource) + .build()); + // Not using these, but they should be in the registry. + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(dependencyGeneratedResourceTarget)) + // .addResource(dependencyGeneratedResource) // Dropped. + .addTransitiveResourceDependency(transitiveSourceResourceTarget) + .addTransitiveResource(transitiveSourceResource) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(dependencySourceResourceTarget)) + .addResource(dependencySourceResource) + .addTransitiveResourceDependency(transitiveGeneratedResourcesTarget) + .addTransitiveResource(transitiveGeneratedResourceOne) + .addTransitiveResource(transitiveGeneratedResourceTwo) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(transitiveGeneratedResourcesTarget)) + // .addResource(transitiveGeneratedResourceOne) // Dropped. + // .addResource(transitiveGeneratedResourceTwo) // Dropped. + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(transitiveSourceResourceTarget)) + .addResource(transitiveSourceResource) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedGeneratedResourceTarget)) + // .addResource(unrelatedGeneratedResource) // Dropped. + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(unrelatedSourceResourceTarget)) + .addResource(unrelatedSourceResource) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(mainResourcesTarget) + .setBuildFile(mainBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(mainGeneratedResource) + .addResource(mainSourceResource)) + .addDependency(dependencyGeneratedResourceTarget) + .addDependency(dependencySourceResourceTarget)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(dependencyGeneratedResourceTarget) + .setBuildFile(dependencyBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(dependencyGeneratedResource)) + .addDependency(transitiveSourceResourceTarget)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(dependencySourceResourceTarget) + .setBuildFile(dependencyBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(dependencySourceResource)) + .addDependency(transitiveGeneratedResourcesTarget)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(transitiveGeneratedResourcesTarget) + .setBuildFile(transitiveBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(transitiveGeneratedResourceOne) + .addResource(transitiveGeneratedResourceTwo))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(transitiveSourceResourceTarget) + .setBuildFile(transitiveBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(transitiveSourceResource))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(unrelatedGeneratedResourceTarget) + .setBuildFile(unrelatedBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(unrelatedGeneratedResource))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(unrelatedSourceResourceTarget) + .setBuildFile(unrelatedBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .addResource(unrelatedSourceResource))) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createTargetMapWithNonStandardAndroidManifestName() { + Label mainResourceTarget = new Label("//com/google/example:main"); + + ArtifactLocation mainManifest = artifact("com/google/example/main/WeirdManifest.xml", true); + ArtifactLocation mainResource = artifact("com/google/example/main/res", true); + ArtifactLocation mainBuildFile = artifact("com/google/example/main/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourceTarget)) + .addResource(mainResource) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(mainResourceTarget) + .setBuildFile(mainBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(mainManifest) + .addResource(mainResource))) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createTargetMapWithNonStandardAndroidManifestNameInDependency() { + Label mainResourceTarget = new Label("//com/google/example:main"); + Label dependencyResourceTarget = new Label("//com/google/example:dependency"); + + ArtifactLocation mainManifest = artifact("com/google/example/main/AndroidManifest.xml", true); + ArtifactLocation mainResource = artifact("com/google/example/main/res", true); + ArtifactLocation mainBuildFile = artifact("com/google/example/main/BUILD", true); + + ArtifactLocation dependencyManifest = + artifact("com/google/example/dependency/MyManifest.xml", true); + ArtifactLocation dependencyResource = artifact("com/google/example/dependency/res", true); + ArtifactLocation dependencyBuildFile = artifact("com/google/example/dependency/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(mainResourceTarget)) + .addResource(mainResource) + .addTransitiveResourceDependency(dependencyResourceTarget) + .addTransitiveResource(dependencyResource) + .build()); + registry.put( + mock(Module.class), + AndroidResourceModule.builder(TargetKey.forPlainTarget(dependencyResourceTarget)) + .addResource(dependencyResource) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(mainResourceTarget) + .setBuildFile(mainBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(mainManifest) + .addResource(mainResource))) + .addTarget( + TargetIdeInfo.builder() + .setLabel(dependencyResourceTarget) + .setBuildFile(dependencyBuildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(dependencyManifest) + .addResource(dependencyResource))) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createTargetMapWithMissingClassDependency() { + Label parentTarget = new Label("//com/google/example:app"); + Label independentLibraryTarget = new Label("//com/google/example/independent:library"); + Label independentLibrary2Target = new Label("//com/google/example/independent:library2"); + Label dependentLibraryTarget = new Label("//com/google/example/dependent:library"); + Label resourcesTarget = new Label("//com/google/example:resources"); + + ArtifactLocation manifest = artifact("com/google/example/AndroidManifest.xml", true); + ArtifactLocation resources = artifact("com/google/example/res", true); + ArtifactLocation buildFile = artifact("com/google/example/BUILD", true); + + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.put( + module, + AndroidResourceModule.builder(TargetKey.forPlainTarget(resourcesTarget)) + .addResource(resources) + .build()); + + TargetMap targetMap = + TargetMapBuilder.builder() + .addTarget( + TargetIdeInfo.builder() + .setLabel(parentTarget) + .setBuildFile(buildFile) + .addDependency(independentLibraryTarget) + .addDependency(resourcesTarget)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(independentLibraryTarget) + .setBuildFile(buildFile) + .setJavaInfo(JavaIdeInfo.builder()) + .addDependency(independentLibrary2Target)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(independentLibrary2Target) + .setBuildFile(buildFile) + .setJavaInfo(JavaIdeInfo.builder())) + .addTarget( + TargetIdeInfo.builder() + .setLabel(resourcesTarget) + .setBuildFile(buildFile) + .setAndroidInfo( + AndroidIdeInfo.builder() + .setGenerateResourceClass(true) + .setManifestFile(manifest) + .addResource(resources)) + .addDependency(dependentLibraryTarget)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(dependentLibraryTarget) + .setBuildFile(buildFile) + .setJavaInfo(JavaIdeInfo.builder())) + .build(); + + projectDataManager.setTargetMap(targetMap); + } + + private void createPsiClassesAndSourceToTargetMap(Container projectServices) { + PsiManager psiManager = new MockPsiManager(project); + + VirtualFile independentLibraryView = + new MockVirtualFile("src/com/google/example/independent/LibraryView.java"); + VirtualFile independentLibraryView2 = + new MockVirtualFile("src/com/google/example/independent/LibraryView2.java"); + VirtualFile independentLibrary2View = + new MockVirtualFile("src/com/google/example/independent/Library2View.java"); + VirtualFile dependentLibraryView = + new MockVirtualFile("src/com/google/example/dependent/LibraryView.java"); + VirtualFile resourceView = new MockVirtualFile("src/com/google/example/ResourceView.java"); + + ImmutableMap<String, PsiClass> classes = + ImmutableMap.of( + "com.google.example.independent.LibraryView", + new MockPsiClass(psiManager, independentLibraryView), + "com.google.example.independent.LibraryView2", + new MockPsiClass(psiManager, independentLibraryView2), + "com.google.example.independent.Library2View", + new MockPsiClass(psiManager, independentLibrary2View), + "com.google.example.dependent.LibraryView", + new MockPsiClass(psiManager, dependentLibraryView), + "com.google.example.ResourceView", + new MockPsiClass(psiManager, resourceView)); + + ImmutableMap<File, TargetKey> sourceToTarget = + ImmutableMap.of( + VfsUtilCore.virtualToIoFile(independentLibraryView), + TargetKey.forPlainTarget(new Label("//com/google/example/independent:library")), + VfsUtilCore.virtualToIoFile(independentLibraryView2), + TargetKey.forPlainTarget(new Label("//com/google/example/independent:library")), + VfsUtilCore.virtualToIoFile(independentLibrary2View), + TargetKey.forPlainTarget(new Label("//com/google/example/independent:library2")), + VfsUtilCore.virtualToIoFile(dependentLibraryView), + TargetKey.forPlainTarget(new Label("//com/google/example/dependent:library")), + VfsUtilCore.virtualToIoFile(resourceView), + TargetKey.forPlainTarget(new Label("//com/google/example:resources"))); + + projectServices.register( + JavaPsiFacade.class, new MockJavaPsiFacade(project, psiManager, classes)); + projectServices.register(SourceToTargetMap.class, new MockSourceToTargetMap(sourceToTarget)); + } + + private static class MockBlazeProjectDataManager implements BlazeProjectDataManager { + private BlazeProjectData blazeProjectData; + + public void setTargetMap(TargetMap targetMap) { + ArtifactLocationDecoder decoder = + (location) -> new File("/src", location.getExecutionRootRelativePath()); + this.blazeProjectData = + new BlazeProjectData(0L, targetMap, null, null, null, null, decoder, null, null, null); + } + + @Nullable + @Override + public BlazeProjectData getBlazeProjectData() { + return blazeProjectData; + } + + @Override + public ModuleEditor editModules() { + return null; + } + } + + private static class MockBuildReferenceManager extends BuildReferenceManager { + public MockBuildReferenceManager(Project project) { + super(project); + } + + @Nullable + @Override + public PsiElement resolveLabel(Label label) { + return null; + } + } + + private static class MockPsiClass extends NullPsiClass { + private PsiFile psiFile; + + public MockPsiClass(PsiManager psiManager, VirtualFile virtualFile) { + psiFile = + new MockPsiFile(psiManager) { + @Override + public VirtualFile getVirtualFile() { + return virtualFile; + } + }; + } + + @Override + public PsiFile getContainingFile() { + return psiFile; + } + } + + private static class MockJavaPsiFacade extends JavaPsiFacadeImpl { + private ImmutableMap<String, PsiClass> classes; + + public MockJavaPsiFacade( + Project project, PsiManager psiManager, ImmutableMap<String, PsiClass> classes) { + super(project, psiManager, null, null); + this.classes = classes; + } + + @Override + public PsiClass findClass(String qualifiedName, GlobalSearchScope scope) { + return classes.get(qualifiedName); + } + } + + private static class MockSourceToTargetMap implements SourceToTargetMap { + private ImmutableMap<File, TargetKey> sourceToTarget; + + public MockSourceToTargetMap(ImmutableMap<File, TargetKey> sourceToTarget) { + this.sourceToTarget = sourceToTarget; + } + + @Override + public ImmutableCollection<Label> getTargetsToBuildForSourceFile(File file) { + return ImmutableList.of(); + } + + @Override + public ImmutableCollection<TargetKey> getRulesForSourceFile(File file) { + return ImmutableList.of(sourceToTarget.get(file)); + } + } +}
diff --git a/aswb/2.3/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java new file mode 100644 index 0000000..86c20cf --- /dev/null +++ b/aswb/2.3/tests/unittests/com/google/idea/blaze/android/run/testrecorder/BlazeConfigurationsTest.java
@@ -0,0 +1,292 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.run.testrecorder; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.tools.idea.run.editor.AndroidDebugger; +import com.android.tools.idea.run.editor.AndroidJavaDebugger; +import com.android.tools.idea.run.editor.DeployTargetProvider; +import com.android.tools.idea.run.editor.ShowChooserTargetProvider; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxy; +import com.google.gct.testrecorder.run.TestRecorderRunConfigurationProxyProvider; +import com.google.gct.testrecorder.ui.TestRecorderAction; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationHandler; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationHandlerProvider; +import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.bazel.BuildSystemProvider; +import com.google.idea.blaze.base.bazel.WorkspaceRootProvider; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.model.primitives.Kind; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.run.BlazeBeforeRunTaskProvider; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler; +import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider; +import com.google.idea.blaze.base.run.targetfinder.TargetFinder; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.intellij.execution.BeforeRunTaskProvider; +import com.intellij.execution.RunManager; +import com.intellij.execution.RunManagerEx; +import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.ide.util.ProjectPropertiesComponentImpl; +import com.intellij.mock.MockModule; +import com.intellij.mock.MockProject; +import com.intellij.openapi.extensions.ExtensionPoint; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.extensions.impl.ExtensionPointImpl; +import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl; +import com.intellij.openapi.fileTypes.FileNameMatcher; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import java.io.File; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test cases for {@link TestRecorderBlazeCommandRunConfiguration}. */ +@RunWith(JUnit4.class) +public class BlazeConfigurationsTest extends BlazeTestCase { + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + super.initTest(applicationServices, projectServices); + + mockBlazeImportSettings(projectServices); + applicationServices.register(TargetFinder.class, new MockTargetFinder()); + + ExtensionPoint<ConfigurationType> configurationTypeExtensionPoint = + registerExtensionPoint(ConfigurationType.CONFIGURATION_TYPE_EP, ConfigurationType.class); + configurationTypeExtensionPoint.registerExtension(new BlazeCommandRunConfigurationType()); + + ExtensionPoint<BlazeCommandRunConfigurationHandlerProvider> handlerProviderExtensionPoint = + registerExtensionPoint( + BlazeCommandRunConfigurationHandlerProvider.EP_NAME, + BlazeCommandRunConfigurationHandlerProvider.class); + handlerProviderExtensionPoint.registerExtension( + new MockBlazeAndroidBinaryRunConfigurationHandlerProvider()); + + ExtensionPoint<BuildSystemProvider> buildSystemProviderExtensionPoint = + registerExtensionPoint(BuildSystemProvider.EP_NAME, BuildSystemProvider.class); + buildSystemProviderExtensionPoint.registerExtension(new MockBuildSystemProvider()); + + ExtensionPoint<DeployTargetProvider> deployTargetProviderExtensionPoint = + registerExtensionPoint( + ExtensionPointName.create("com.android.run.deployTargetProvider"), + DeployTargetProvider.class); + deployTargetProviderExtensionPoint.registerExtension(new ShowChooserTargetProvider()); + + ExtensionPoint<AndroidDebugger> androidDebuggerExtensionPoint = + registerExtensionPoint(AndroidDebugger.EP_NAME, AndroidDebugger.class); + androidDebuggerExtensionPoint.registerExtension(new AndroidJavaDebugger()); + + ExtensionPoint<BeforeRunTaskProvider> beforeRunTaskProviderExtensionPoint = + registerExtensionPoint( + ExtensionPointName.create("com.intellij.stepsBeforeRunProvider"), + BeforeRunTaskProvider.class); + ((ExtensionsAreaImpl) Extensions.getArea(project)) + .registerExtensionPoint((ExtensionPointImpl) beforeRunTaskProviderExtensionPoint); + beforeRunTaskProviderExtensionPoint.registerExtension(new BlazeBeforeRunTaskProvider()); + + ExtensionPoint<TestRecorderRunConfigurationProxyProvider> + testRecorderRunConfigurationProxyProviderExtensionPoint = + registerExtensionPoint( + ExtensionPointName.create( + "com.google.gct.testrecorder.run.testRecorderRunConfigurationProxyProvider"), + TestRecorderRunConfigurationProxyProvider.class); + testRecorderRunConfigurationProxyProviderExtensionPoint.registerExtension( + new TestRecorderBlazeCommandRunConfigurationProxyProvider()); + + ((MockProject) project) + .addComponent( + RunManager.class, new RunManagerImpl(project, new ProjectPropertiesComponentImpl())); + } + + @Test + public void testSuitableRunConfigurations() { + addConfigurations(); + + List<RunConfiguration> allConfigurations = + RunManagerEx.getInstanceEx(project).getAllConfigurationsList(); + assertThat(allConfigurations.size()).isEqualTo(2); + + List<RunConfiguration> suitableConfigurations = + TestRecorderAction.getSuitableRunConfigurations(project); + assertThat(suitableConfigurations.size()).isEqualTo(1); + assertThat(suitableConfigurations.get(0).getName()).isEqualTo("AndroidBinaryConfiguration"); + } + + @Test + public void testLaunchActivityClass() { + BlazeCommandRunConfiguration blazeConfiguration = + BlazeCommandRunConfigurationType.getInstance() + .getFactory() + .createTemplateConfiguration(project); + blazeConfiguration.setTarget(new Label("//label:android_binary_rule")); + BlazeAndroidBinaryRunConfigurationState configurationState = + ((BlazeAndroidBinaryRunConfigurationHandler) blazeConfiguration.getHandler()).getState(); + configurationState.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); + configurationState.setActivityClass("MyAppMainActivity"); + + TestRecorderRunConfigurationProxy proxy = + TestRecorderRunConfigurationProxy.getInstance(blazeConfiguration); + assertThat(proxy).isNotNull(); + assertThat(proxy.getLaunchActivityClass()).isEqualTo("MyAppMainActivity"); + } + + private void mockBlazeImportSettings(Container projectServices) { + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + importSettingsManager.setImportSettings( + new BlazeImportSettings("", "", "", "", "", Blaze.BuildSystem.Blaze)); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + } + + private void addConfigurations() { + RunManagerImpl runManager = (RunManagerImpl) RunManagerEx.getInstanceEx(project); + BlazeCommandRunConfigurationType.BlazeCommandRunConfigurationFactory configurationFactory = + BlazeCommandRunConfigurationType.getInstance().getFactory(); + + BlazeCommandRunConfiguration blazeAndroidBinaryConfiguration = + configurationFactory.createTemplateConfiguration(project); + blazeAndroidBinaryConfiguration.setName("AndroidBinaryConfiguration"); + blazeAndroidBinaryConfiguration.setTarget(new Label("//label:android_binary_rule")); + + BlazeCommandRunConfiguration blazeAndroidTestConfiguration = + configurationFactory.createTemplateConfiguration(project); + blazeAndroidTestConfiguration.setName("AndroidTestConfiguration"); + blazeAndroidTestConfiguration.setTarget(new Label("//label:android_test_rule")); + + runManager.addConfiguration( + runManager.createConfiguration(blazeAndroidBinaryConfiguration, configurationFactory), + true); + runManager.addConfiguration( + runManager.createConfiguration(blazeAndroidTestConfiguration, configurationFactory), true); + } + + private class MockTargetFinder extends TargetFinder { + @Override + public List<TargetIdeInfo> findTargets(Project project, Predicate<TargetIdeInfo> predicate) { + return null; + } + + @Override + public TargetIdeInfo targetForLabel(Project project, final Label label) { + TargetIdeInfo.Builder builder = TargetIdeInfo.builder().setLabel(label); + if (label.equals(new Label("//label:android_binary_rule"))) { + builder.setKind(Kind.ANDROID_BINARY); + } else if (label.equals(new Label("//label:android_test_rule"))) { + builder.setKind(Kind.ANDROID_TEST); + } + return builder.build(); + } + } + + private class MockBlazeAndroidBinaryRunConfigurationHandlerProvider + extends BlazeAndroidBinaryRunConfigurationHandlerProvider { + @Override + public boolean canHandleKind(Kind kind) { + return true; + } + + @Override + public BlazeCommandRunConfigurationHandler createHandler(BlazeCommandRunConfiguration config) { + return new MockBlazeAndroidBinaryRunConfigurationHandler(config); + } + } + + private class MockBlazeAndroidBinaryRunConfigurationHandler + extends BlazeAndroidBinaryRunConfigurationHandler { + private final MockModule mockModule; + + MockBlazeAndroidBinaryRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { + super(configuration); + mockModule = new MockModule(project, () -> {}); + } + + @Nullable + @Override + public Module getModule() { + Label label = getLabel(); + if (label != null && label.equals(new Label("//label:android_binary_rule"))) { + return mockModule; + } + + return null; + } + } + + private class MockBuildSystemProvider implements BuildSystemProvider { + @Override + public Blaze.BuildSystem buildSystem() { + return Blaze.BuildSystem.Blaze; + } + + @Override + public WorkspaceRootProvider getWorkspaceRootProvider() { + return null; + } + + @Override + public ImmutableList<String> buildArtifactDirectories(WorkspaceRoot root) { + return null; + } + + @Nullable + @Override + public String getRuleDocumentationUrl(RuleDefinition rule) { + return null; + } + + @Override + public boolean isBuildFile(String fileName) { + return false; + } + + @Nullable + @Override + public File findBuildFileInDirectory(File directory) { + return null; + } + + @Override + public FileNameMatcher buildFileMatcher() { + return null; + } + + @Override + public void populateBlazeVersionData( + BuildSystem buildSystem, + WorkspaceRoot workspaceRoot, + ImmutableMap<String, String> blazeInfo, + BlazeVersionData.Builder builder) {} + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java b/aswb/2.3/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java similarity index 65% rename from aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java rename to aswb/2.3/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java index 148b1d9..74459a0 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java +++ b/aswb/2.3/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java
@@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.idea.blaze.android.run.binary.instantrun; -import com.google.idea.common.experiments.BoolExperiment; +package com.google.idea.blaze.android; -/** Holds the instant run experiment */ -public class InstantRunExperiment { - public static final BoolExperiment INSTANT_RUN_ENABLED = - new BoolExperiment("instant.run.enabled", false); -} +import com.google.idea.blaze.base.BlazeIntegrationTestCase; + +/** Compatibility test class for Blaze Android integration tests. */ +public abstract class BlazeAndroidIntegrationTestCase extends BlazeIntegrationTestCase {}
diff --git a/aswb/BUILD b/aswb/BUILD index f3cfd00..16fc99b 100644 --- a/aswb/BUILD +++ b/aswb/BUILD
@@ -11,6 +11,7 @@ "stamped_plugin_xml", ) load("//:version.bzl", "VERSION") +load("//intellij_platform_sdk:build_defs.bzl", "select_for_plugin_api") merged_plugin_xml( name = "merged_plugin_xml_common", @@ -19,7 +20,12 @@ "//base:plugin_xml", "//cpp:plugin_xml", "//java:plugin_xml", - ], + ] + select_for_plugin_api({ + # TODO(chaorenl): remove when 2.2 is obsolete + "android-studio-145.1617.8": [], + "android-studio-2.3.0.3": ["2.3/src/META-INF/aswb_beta.xml"], + "android-studio-2.3.0.4": ["2.3/src/META-INF/aswb_beta.xml"], + }), visibility = [ "//visibility:public", ], @@ -45,7 +51,12 @@ java_library( name = "aswb_lib", - srcs = glob(["src/**/*.java"]), + srcs = glob(["src/**/*.java"]) + select_for_plugin_api({ + # TODO(chaorenl): remove when 2.2 is obsolete + "android-studio-145.1617.8": glob(["2.2/src/**/*.java"]), + "android-studio-2.3.0.3": glob(["2.3/src/**/*.java"]), + "android-studio-2.3.0.4": glob(["2.3/src/**/*.java"]), + }), resources = glob(["resources/**/*"]), visibility = [ "//visibility:public", @@ -61,10 +72,15 @@ ], ) +# TODO(chaorenl): remove when 2.2 is obsolete java_library( name = "integration_test_utils", testonly = 1, - srcs = glob(["tests/utils/integration/**/*.java"]), + srcs = select_for_plugin_api({ + "android-studio-145.1617.8": glob(["2.2/tests/utils/integration/**/*.java"]), + "android-studio-2.3.0.3": glob(["2.3/tests/utils/integration/**/*.java"]), + "android-studio-2.3.0.4": glob(["2.3/tests/utils/integration/**/*.java"]), + }), deps = [ "//base", "//base:integration_test_utils", @@ -83,7 +99,12 @@ intellij_unit_test_suite( name = "unit_tests", - srcs = glob(["tests/unittests/**/*.java"]), + srcs = glob(["tests/unittests/**/*.java"]) + select_for_plugin_api({ + # TODO(chaorenl): remove when 2.2 is obsolete + "android-studio-145.1617.8": [], + "android-studio-2.3.0.3": glob(["2.3/tests/unittests/**/*.java"]), + "android-studio-2.3.0.4": glob(["2.3/tests/unittests/**/*.java"]), + }), test_package_root = "com.google.idea.blaze.android", deps = [ ":aswb_lib", @@ -101,7 +122,12 @@ intellij_integration_test_suite( name = "integration_tests", - srcs = glob(["tests/integrationtests/**/*.java"]), + srcs = glob(["tests/integrationtests/**/*.java"]) + select_for_plugin_api({ + # TODO(chaorenl): remove when 2.2 is obsolete + "android-studio-145.1617.8": [], + "android-studio-2.3.0.3": glob(["2.3/tests/integrationtests/**/*.java"]), + "android-studio-2.3.0.4": glob(["2.3/tests/integrationtests/**/*.java"]), + }), platform_prefix = "AndroidStudio", required_plugins = "com.google.idea.bazel.aswb", test_package_root = "com.google.idea.blaze.android",
diff --git a/aswb/aswb.bazelproject b/aswb/aswb.bazelproject index 34cbb52..c2f7d36 100644 --- a/aswb/aswb.bazelproject +++ b/aswb/aswb.bazelproject
@@ -4,6 +4,8 @@ -plugin_dev -clwb -cpp/src/com/google/idea/blaze/cpp/versioned/v162 + # TODO(chaorenl): remove when 2.2 is obsolete. + -aswb/2.3 targets: //aswb:aswb_bazel
diff --git a/aswb/src/META-INF/aswb.xml b/aswb/src/META-INF/aswb.xml index ca22494..48f701d 100644 --- a/aswb/src/META-INF/aswb.xml +++ b/aswb/src/META-INF/aswb.xml
@@ -18,7 +18,8 @@ <depends>com.intellij.modules.androidstudio</depends> <depends>org.jetbrains.android</depends> - <depends>com.android.tools.idea.updater</depends> + <!-- TODO(chaorenl): remove when 2.2 is obsolete --> + <depends optional="true">com.android.tools.idea.updater</depends> <extensions defaultExtensionNs="com.intellij"> <java.elementFinder implementation="com.google.idea.blaze.android.resources.AndroidResourceClassFinder" @@ -31,14 +32,14 @@ <runConfigurationProducer implementation="com.google.idea.blaze.android.run.test.BlazeAndroidTestMethodRunConfigurationProducer" order="first"/> - <configurationType implementation="com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationType"/> - <configurationType implementation="com.google.idea.blaze.android.run.test.BlazeAndroidTestRunConfigurationType"/> <programRunner implementation="com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryProgramRunner" order="first"/> <executor implementation="com.google.idea.blaze.android.run.binary.mobileinstall.IncrementalInstallRunExecutor" order="last"/> <executor implementation="com.google.idea.blaze.android.run.binary.mobileinstall.IncrementalInstallDebugExecutor" order="last"/> <applicationService serviceInterface="com.google.idea.blaze.base.plugin.BlazePluginId" serviceImplementation="com.google.idea.blaze.android.plugin.AswbPlugin"/> <projectService serviceImplementation="com.google.idea.blaze.android.manifest.ManifestParser"/> + <projectService serviceImplementation="com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry"/> + <applicationService serviceImplementation="com.google.idea.blaze.android.settings.BlazeAndroidUserSettings"/> </extensions> <extensions defaultExtensionNs="org.jetbrains.android.actions"> @@ -88,4 +89,4 @@ </component> </application-components> -</idea-plugin> \ No newline at end of file +</idea-plugin>
diff --git a/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java b/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java index 056f692..0c14f79 100644 --- a/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java +++ b/aswb/src/com/google/idea/blaze/android/projectview/AndroidSdkPlatformSection.java
@@ -15,6 +15,7 @@ */ package com.google.idea.blaze.android.projectview; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidSdkUtils; import com.google.idea.blaze.android.sync.sdk.AndroidSdkFromProjectView; import com.google.idea.blaze.base.projectview.ProjectView; import com.google.idea.blaze.base.projectview.parser.ParseContext; @@ -28,7 +29,6 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.text.StringUtil; import java.util.Collection; -import org.jetbrains.android.sdk.AndroidSdkUtils; import org.jetbrains.annotations.Nullable; /** Allows manual override of the android sdk. */
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java index 5bd0c77..6926023 100644 --- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java +++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
@@ -215,5 +215,11 @@ } return UiUtil.createBox(result); } + + @Override + public void setComponentEnabled(boolean enabled) { + userFlagsEditor.setComponentEnabled(enabled); + enableNativeDebuggingCheckBox.setEnabled(enabled); + } } }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java index b90cc35..5351213 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java
@@ -33,6 +33,7 @@ import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; +import com.google.idea.blaze.android.compatibility.Compatibility; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.deployinfo.BlazeApkProvider; import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; @@ -167,14 +168,22 @@ @Nullable @Override + @SuppressWarnings("unchecked") public DebugConnectorTask getDebuggerTask( AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState, - Set<String> packageIds) + Set<String> packageIds, + boolean monitorRemoteProcess) throws ExecutionException { - //noinspection unchecked - return androidDebugger.getConnectDebuggerTask( - env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId()); + return Compatibility.getConnectDebuggerTask( + androidDebugger, + env, + null, + packageIds, + facet, + androidDebuggerState, + runConfiguration.getType().getId(), + monitorRemoteProcess); } @Nullable
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java index a70da60..3b222f7 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationHandler.java
@@ -15,16 +15,13 @@ */ package com.google.idea.blaze.android.run.binary; -import com.android.sdklib.AndroidVersion; -import com.android.tools.idea.fd.InstantRunManager; -import com.android.tools.idea.run.AndroidSessionInfo; +import com.android.annotations.VisibleForTesting; import com.android.tools.idea.run.ValidationError; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationValidationUtil; -import com.google.idea.blaze.android.run.binary.instantrun.BlazeAndroidBinaryInstantRunContext; import com.google.idea.blaze.android.run.binary.mobileinstall.BlazeAndroidBinaryMobileInstallRunContext; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationRunner; import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; @@ -42,15 +39,12 @@ import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RuntimeConfigurationException; -import com.intellij.execution.executors.DefaultRunExecutor; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import icons.AndroidIcons; import java.util.List; import javax.swing.Icon; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -63,7 +57,8 @@ private final BlazeCommandRunConfiguration configuration; private final BlazeAndroidBinaryRunConfigurationState configState; - BlazeAndroidBinaryRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { + @VisibleForTesting + protected BlazeAndroidBinaryRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { this.configuration = configuration; configState = new BlazeAndroidBinaryRunConfigurationState( @@ -91,7 +86,7 @@ } @Nullable - private Module getModule() { + public Module getModule() { Label target = getLabel(); return target != null ? BlazeAndroidProjectStructureSyncer.ensureRunConfigurationModule( @@ -125,10 +120,7 @@ AndroidFacet facet, ExecutionEnvironment env, ImmutableList<String> buildFlags) { - if (configState.instantRun()) { - return new BlazeAndroidBinaryInstantRunContext( - project, facet, configuration, env, configState, getLabel(), buildFlags); - } else if (configState.mobileInstall()) { + if (configState.mobileInstall()) { return new BlazeAndroidBinaryMobileInstallRunContext( project, facet, configuration, env, configState, getLabel(), buildFlags); } else { @@ -187,27 +179,7 @@ @Override @Nullable - public Icon getExecutorIcon(@NotNull RunConfiguration configuration, @NotNull Executor executor) { - if (!configState.instantRun()) { - return null; - } - - AndroidSessionInfo info = - AndroidSessionInfo.findOldSession( - this.configuration.getProject(), null, this.configuration.getUniqueID()); - if (info == null || !info.isInstantRun() || !info.getExecutorId().equals(executor.getId())) { - return null; - } - - // Make sure instant run is supported on the relevant device, if found. - AndroidVersion androidVersion = - InstantRunManager.getMinDeviceApiLevel(info.getProcessHandler()); - if (!InstantRunManager.isInstantRunCapableDeviceVersion(androidVersion)) { - return null; - } - - return executor instanceof DefaultRunExecutor - ? AndroidIcons.RunIcons.Replay - : AndroidIcons.RunIcons.DebugReattach; + public Icon getExecutorIcon(RunConfiguration configuration, Executor executor) { + return null; } }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java index 6557f8b..cb368d4 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationState.java
@@ -42,12 +42,10 @@ private static final String MOBILE_INSTALL_ATTR = "blaze-mobile-install"; private static final String USE_SPLIT_APKS_IF_POSSIBLE = "use-split-apks-if-possible"; - private static final String INSTANT_RUN_ATTR = "instant-run"; private static final String WORK_PROFILE_ATTR = "use-work-profile-if-present"; private static final String USER_ID_ATTR = "user-id"; private boolean mobileInstall = false; private boolean useSplitApksIfPossible = false; - private boolean instantRun = false; private boolean useWorkProfileIfPresent = false; private Integer userId; @@ -85,14 +83,6 @@ this.useSplitApksIfPossible = useSplitApksIfPossible; } - boolean instantRun() { - return instantRun; - } - - void setInstantRun(boolean instantRun) { - this.instantRun = instantRun; - } - public boolean useWorkProfileIfPresent() { return useWorkProfileIfPresent; } @@ -156,7 +146,6 @@ setMobileInstall(Boolean.parseBoolean(element.getAttributeValue(MOBILE_INSTALL_ATTR))); setUseSplitApksIfPossible( Boolean.parseBoolean(element.getAttributeValue(USE_SPLIT_APKS_IF_POSSIBLE))); - setInstantRun(Boolean.parseBoolean(element.getAttributeValue(INSTANT_RUN_ATTR))); setUseWorkProfileIfPresent(Boolean.parseBoolean(element.getAttributeValue(WORK_PROFILE_ATTR))); String userIdString = element.getAttributeValue(USER_ID_ATTR); @@ -196,7 +185,6 @@ element.setAttribute(MODE, mode); element.setAttribute(MOBILE_INSTALL_ATTR, Boolean.toString(mobileInstall)); element.setAttribute(USE_SPLIT_APKS_IF_POSSIBLE, Boolean.toString(useSplitApksIfPossible)); - element.setAttribute(INSTANT_RUN_ATTR, Boolean.toString(instantRun)); element.setAttribute(WORK_PROFILE_ATTR, Boolean.toString(useWorkProfileIfPresent)); if (userId != null) {
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java index c7fbff6..8ce6279 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateEditor.java
@@ -16,7 +16,6 @@ package com.google.idea.blaze.android.run.binary; import com.android.tools.idea.run.activity.ActivityLocatorUtils; -import com.google.idea.blaze.android.run.binary.instantrun.InstantRunExperiment; import com.google.idea.blaze.base.run.state.RunConfigurationState; import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor; import com.google.idea.blaze.base.ui.IntegerTextField; @@ -75,11 +74,12 @@ private JRadioButton launchCustomButton; private JCheckBox mobileInstallCheckBox; private JCheckBox splitApksCheckBox; - private JCheckBox instantRunCheckBox; private JCheckBox useWorkProfileIfPresentCheckBox; private JLabel userIdLabel; private IntegerTextField userIdField; + private boolean componentEnabled = true; + BlazeAndroidBinaryRunConfigurationStateEditor( RunConfigurationStateEditor commonStateEditor, Project project) { this.commonStateEditor = commonStateEditor; @@ -126,34 +126,15 @@ } } }); - ActionListener listener = e -> activityField.setEnabled(launchCustomButton.isSelected()); + ActionListener listener = e -> updateEnabledState(); launchCustomButton.addActionListener(listener); launchDefaultButton.addActionListener(listener); launchNothingButton.addActionListener(listener); - instantRunCheckBox.setVisible(InstantRunExperiment.INSTANT_RUN_ENABLED.getValue()); - - /* Only one of mobile-install and instant run can be selected at any one time */ - mobileInstallCheckBox.addActionListener( - e -> { - if (mobileInstallCheckBox.isSelected()) { - instantRunCheckBox.setSelected(false); - } - }); - instantRunCheckBox.addActionListener( - e -> { - if (instantRunCheckBox.isSelected()) { - mobileInstallCheckBox.setSelected(false); - } - }); - mobileInstallCheckBox.addActionListener( e -> splitApksCheckBox.setVisible(mobileInstallCheckBox.isSelected())); - useWorkProfileIfPresentCheckBox.addActionListener( - e -> { - setUserIdEnabled(!useWorkProfileIfPresentCheckBox.isSelected()); - }); + useWorkProfileIfPresentCheckBox.addActionListener(e -> updateEnabledState()); } @Override @@ -170,24 +151,18 @@ } else { launchNothingButton.setSelected(true); } - activityField.setEnabled(launchSpecificActivity); if (launchSpecificActivity) { activityField.getChildComponent().setText(state.getActivityClass()); } mobileInstallCheckBox.setSelected(state.mobileInstall()); splitApksCheckBox.setSelected(state.useSplitApksIfPossible()); - instantRunCheckBox.setSelected(state.instantRun()); useWorkProfileIfPresentCheckBox.setSelected(state.useWorkProfileIfPresent()); userIdField.setValue(state.getUserId()); - setUserIdEnabled(!state.useWorkProfileIfPresent()); splitApksCheckBox.setVisible(state.mobileInstall()); - } - private void setUserIdEnabled(boolean enabled) { - userIdLabel.setEnabled(enabled); - userIdField.setEnabled(enabled); + updateEnabledState(); } @Override @@ -207,7 +182,6 @@ } state.setMobileInstall(mobileInstallCheckBox.isSelected()); state.setUseSplitApksIfPossible(splitApksCheckBox.isSelected()); - state.setInstantRun(instantRunCheckBox.isSelected()); state.setUseWorkProfileIfPresent(useWorkProfileIfPresentCheckBox.isSelected()); } @@ -216,6 +190,26 @@ return UiUtil.createBox(commonStateEditor.createComponent(), panel); } + private void updateEnabledState() { + boolean useWorkProfile = useWorkProfileIfPresentCheckBox.isSelected(); + userIdLabel.setEnabled(componentEnabled && !useWorkProfile); + userIdField.setEnabled(componentEnabled && !useWorkProfile); + commonStateEditor.setComponentEnabled(componentEnabled); + activityField.setEnabled(componentEnabled && launchCustomButton.isSelected()); + launchNothingButton.setEnabled(componentEnabled); + launchDefaultButton.setEnabled(componentEnabled); + launchCustomButton.setEnabled(componentEnabled); + mobileInstallCheckBox.setEnabled(componentEnabled); + splitApksCheckBox.setEnabled(componentEnabled); + useWorkProfileIfPresentCheckBox.setEnabled(componentEnabled); + } + + @Override + public void setComponentEnabled(boolean enabled) { + componentEnabled = enabled; + updateEnabledState(); + } + private void createUIComponents(Project project) { final EditorTextField editorTextField = new LanguageTextField(PlainTextLanguage.INSTANCE, project, "") { @@ -465,24 +459,6 @@ null, 0, false)); - instantRunCheckBox = new JCheckBox(); - instantRunCheckBox.setText(" Use InstantRun"); - panel.add( - instantRunCheckBox, - new GridConstraints( - 2, - 0, - 1, - 2, - GridConstraints.ANCHOR_WEST, - GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, - null, - null, - null, - 0, - false)); ButtonGroup buttonGroup; buttonGroup = new ButtonGroup(); buttonGroup.add(launchDefaultButton);
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java deleted file mode 100644 index bfc5a02..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationType.java +++ /dev/null
@@ -1,125 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary; - -import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; -import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; -import com.google.idea.blaze.base.settings.Blaze; -import com.intellij.execution.BeforeRunTask; -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.ConfigurationType; -import com.intellij.execution.configurations.ConfigurationTypeUtil; -import com.intellij.execution.configurations.UnknownConfigurationType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import icons.AndroidIcons; -import javax.swing.Icon; -import org.jetbrains.annotations.NotNull; - -/** - * A type for Android application run configurations adapted specifically to run android_binary - * targets. - * - * @deprecated See {@link com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType}. Retained - * in 1.9 for legacy purposes, to allow existing BlazeAndroidBinaryRunConfigurations to be - * updated to BlazeCommandRunConfigurations. Intended to be removed in 2.1. - */ -// Hack: extend UnknownConfigurationType to completely hide it in the Run/Debug Configurations UI. -@Deprecated -public class BlazeAndroidBinaryRunConfigurationType extends UnknownConfigurationType { - private final BlazeAndroidBinaryRunConfigurationFactory factory = - new BlazeAndroidBinaryRunConfigurationFactory(this); - - static class BlazeAndroidBinaryRunConfigurationFactory extends ConfigurationFactory { - - protected BlazeAndroidBinaryRunConfigurationFactory(@NotNull ConfigurationType type) { - super(type); - } - - @Override - public String getName() { - // Used to look up this ConfigurationFactory. - // Preserve value so legacy configurations can be loaded. - return Blaze.defaultBuildSystemName() + " Android Binary"; - } - - @Override - @NotNull - public BlazeCommandRunConfiguration createTemplateConfiguration(@NotNull Project project) { - // Create a BlazeCommandRunConfiguration instead, to update legacy configurations. - return BlazeCommandRunConfigurationType.getInstance() - .getFactory() - .createTemplateConfiguration(project); - } - - @Override - public boolean canConfigurationBeSingleton() { - return false; - } - - @Override - public boolean isApplicable(@NotNull Project project) { - return false; - } - - @Override - public void configureBeforeRunTaskDefaults( - Key<? extends BeforeRunTask> providerID, BeforeRunTask task) { - // Removed BlazeAndroidBeforeRunTaskProvider; this method won't be called anymore anyhow. - } - - @Override - public boolean isConfigurationSingletonByDefault() { - return false; - } - } - - @NotNull - public static BlazeAndroidBinaryRunConfigurationType getInstance() { - return ConfigurationTypeUtil.findConfigurationType( - BlazeAndroidBinaryRunConfigurationType.class); - } - - @Override - @NotNull - public String getDisplayName() { - return "Legacy " + Blaze.defaultBuildSystemName() + " Android Binary"; - } - - @Override - public String getConfigurationTypeDescription() { - return "Launch/debug configuration for android_binary rules. " - + "Use Blaze Command instead; this legacy configuration type is being removed."; - } - - @Override - public Icon getIcon() { - return AndroidIcons.Android; - } - - @Override - @NotNull - public String getId() { - // Used to look up this ConfigurationType. - // Preserve value so legacy configurations can be loaded. - return "BlazeAndroidBinaryRunConfigurationType"; - } - - @Override - public BlazeAndroidBinaryRunConfigurationFactory[] getConfigurationFactories() { - return new BlazeAndroidBinaryRunConfigurationFactory[] {factory}; - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java index d6fcd4a..50c2dfd 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeDefaultActivityLocator.java
@@ -45,7 +45,13 @@ @NotNull @Override public String getQualifiedActivityName(@NotNull IDevice device) throws ActivityLocatorException { - Manifest manifest = ManifestParser.getInstance(project).getManifest(mergedManifestFile); + // Run in a read action since otherwise, it might throw a read access exception. + Manifest manifest = + ApplicationManager.getApplication() + .runReadAction( + (Computable<Manifest>) + () -> ManifestParser.getInstance(project).getManifest(mergedManifestFile)); + if (manifest == null) { throw new ActivityLocatorException("Could not locate merged manifest"); }
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java deleted file mode 100644 index b35d0b2..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java +++ /dev/null
@@ -1,196 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.ddmlib.IDevice; -import com.android.tools.idea.fd.InstantRunBuildAnalyzer; -import com.android.tools.idea.fd.InstantRunUtils; -import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.ConsolePrinter; -import com.android.tools.idea.run.ConsoleProvider; -import com.android.tools.idea.run.LaunchOptions; -import com.android.tools.idea.run.activity.DefaultStartActivityFlagsProvider; -import com.android.tools.idea.run.activity.StartActivityFlagsProvider; -import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.editor.AndroidDebuggerState; -import com.android.tools.idea.run.tasks.DebugConnectorTask; -import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.tasks.LaunchTasksProvider; -import com.android.tools.idea.run.tasks.UpdateSessionTasksProvider; -import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.Futures; -import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider; -import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider; -import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryRunConfigurationState; -import com.google.idea.blaze.android.run.binary.UserIdHelper; -import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; -import com.google.idea.blaze.android.run.runner.BlazeAndroidLaunchTasksProvider; -import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDebuggerManager; -import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; -import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep; -import com.google.idea.blaze.base.model.primitives.Label; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; -import java.util.Set; -import javax.annotation.Nullable; -import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.annotations.NotNull; - -/** Run context for InstantRun. */ -public class BlazeAndroidBinaryInstantRunContext implements BlazeAndroidRunContext { - - private final Project project; - private final AndroidFacet facet; - private final RunConfiguration runConfiguration; - private final ExecutionEnvironment env; - private final BlazeAndroidBinaryRunConfigurationState configState; - - private final BlazeAndroidBinaryConsoleProvider consoleProvider; - private final BlazeApkBuildStepInstantRun buildStep; - - public BlazeAndroidBinaryInstantRunContext( - Project project, - AndroidFacet facet, - RunConfiguration runConfiguration, - ExecutionEnvironment env, - BlazeAndroidBinaryRunConfigurationState configState, - Label label, - ImmutableList<String> buildFlags) { - this.project = project; - this.facet = facet; - this.runConfiguration = runConfiguration; - this.env = env; - this.configState = configState; - this.consoleProvider = new BlazeAndroidBinaryConsoleProvider(project); - this.buildStep = new BlazeApkBuildStepInstantRun(project, env, label, buildFlags); - } - - @Override - public BlazeAndroidDeviceSelector getDeviceSelector() { - return new BlazeInstantRunDeviceSelector(); - } - - @Override - public void augmentEnvironment(ExecutionEnvironment env) { - InstantRunUtils.setInstantRunEnabled(env, true); - } - - @Override - public void augmentLaunchOptions(@NotNull LaunchOptions.Builder options) { - options.setDeploy(true).setOpenLogcatAutomatically(true); - } - - @NotNull - @Override - public ConsoleProvider getConsoleProvider() { - return consoleProvider; - } - - @Override - public ApplicationIdProvider getApplicationIdProvider() throws ExecutionException { - return Futures.get(buildStep.getApplicationIdProvider(), ExecutionException.class); - } - - @Override - public BlazeApkBuildStep getBuildStep() { - return buildStep; - } - - @Override - public LaunchTasksProvider getLaunchTasksProvider( - LaunchOptions.Builder launchOptionsBuilder, - boolean isDebug, - BlazeAndroidRunConfigurationDebuggerManager debuggerManager) - throws ExecutionException { - InstantRunBuildAnalyzer analyzer = - Futures.get(buildStep.getInstantRunBuildAnalyzer(), ExecutionException.class); - - if (analyzer.canReuseProcessHandler()) { - return new UpdateSessionTasksProvider(analyzer); - } - return new BlazeAndroidLaunchTasksProvider( - project, - this, - getApplicationIdProvider(), - launchOptionsBuilder, - isDebug, - true, - debuggerManager); - } - - @Override - public ImmutableList<LaunchTask> getDeployTasks(IDevice device, LaunchOptions launchOptions) - throws ExecutionException { - InstantRunBuildAnalyzer analyzer = - Futures.get(buildStep.getInstantRunBuildAnalyzer(), ExecutionException.class); - return ImmutableList.<LaunchTask>builder() - .addAll(analyzer.getDeployTasks(launchOptions)) - .add(analyzer.getNotificationTask()) - .build(); - } - - @Nullable - @Override - public LaunchTask getApplicationLaunchTask( - LaunchOptions launchOptions, - @Nullable Integer userId, - AndroidDebugger androidDebugger, - AndroidDebuggerState androidDebuggerState, - ProcessHandlerLaunchStatus processHandlerLaunchStatus) - throws ExecutionException { - BlazeApkBuildStepInstantRun.BuildResult buildResult = - Futures.get(buildStep.getBuildResult(), ExecutionException.class); - - final StartActivityFlagsProvider startActivityFlagsProvider = - new DefaultStartActivityFlagsProvider( - androidDebugger, - androidDebuggerState, - project, - launchOptions.isDebug(), - UserIdHelper.getFlagsFromUserId(userId)); - - ApplicationIdProvider applicationIdProvider = getApplicationIdProvider(); - return BlazeAndroidBinaryApplicationLaunchTaskProvider.getApplicationLaunchTask( - project, - applicationIdProvider, - buildResult.mergedManifestFile, - configState, - startActivityFlagsProvider, - processHandlerLaunchStatus); - } - - @Nullable - @Override - public DebugConnectorTask getDebuggerTask( - AndroidDebugger androidDebugger, - AndroidDebuggerState androidDebuggerState, - Set<String> packageIds) - throws ExecutionException { - //noinspection unchecked - return androidDebugger.getConnectDebuggerTask( - env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId()); - } - - @Nullable - @Override - public Integer getUserId(IDevice device, ConsolePrinter consolePrinter) - throws ExecutionException { - return UserIdHelper.getUserIdFromConfigurationState(device, consolePrinter, configState); - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java deleted file mode 100644 index 2830533..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java +++ /dev/null
@@ -1,384 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.ddmlib.IDevice; -import com.android.tools.idea.fd.InstantRunBuildAnalyzer; -import com.android.tools.idea.fd.InstantRunBuilder; -import com.android.tools.idea.fd.InstantRunContext; -import com.android.tools.idea.fd.InstantRunUtils; -import com.android.tools.idea.fd.RunAsValidityService; -import com.android.tools.idea.gradle.run.MakeBeforeRunTaskProvider; -import com.android.tools.idea.run.AndroidDevice; -import com.android.tools.idea.run.AndroidRunConfigContext; -import com.android.tools.idea.run.AndroidSessionInfo; -import com.android.tools.idea.run.ApkProvisionException; -import com.android.tools.idea.run.ApplicationIdProvider; -import com.android.tools.idea.run.DeviceFutures; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import com.google.idea.blaze.android.manifest.ManifestParser; -import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; -import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep; -import com.google.idea.blaze.base.async.executor.BlazeExecutor; -import com.google.idea.blaze.base.async.process.ExternalTask; -import com.google.idea.blaze.base.async.process.LineProcessingOutputStream; -import com.google.idea.blaze.base.command.BlazeCommand; -import com.google.idea.blaze.base.command.BlazeCommandName; -import com.google.idea.blaze.base.command.BlazeFlags; -import com.google.idea.blaze.base.command.ExperimentalShowArtifactsLineProcessor; -import com.google.idea.blaze.base.command.info.BlazeInfo; -import com.google.idea.blaze.base.filecache.FileCaches; -import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor; -import com.google.idea.blaze.base.metrics.Action; -import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.ScopedTask; -import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope; -import com.google.idea.blaze.base.settings.Blaze; -import com.google.idea.blaze.base.util.SaveUtil; -import com.google.repackaged.devtools.build.lib.rules.android.apkmanifest.ApkManifestOuterClass; -import com.intellij.execution.Executor; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; -import org.jetbrains.annotations.NotNull; - -/** Builds the APK using normal blaze build. */ -class BlazeApkBuildStepInstantRun implements BlazeApkBuildStep { - private static final Logger LOG = Logger.getInstance(BlazeApkBuildStepInstantRun.class); - - private final Project project; - private final Executor executor; - private final ExecutionEnvironment env; - private final Label label; - private final ImmutableList<String> buildFlags; - private final File instantRunArtifactDirectory; - private final File instantRunGradleBuildFile; - private final File instantRunBuildInfoFile; - private final File instantRunGradlePropertiesFile; - - public static class BuildResult { - public final File executionRoot; - public final File mergedManifestFile; - public final File apkManifestProtoFile; - public final ApkManifestOuterClass.ApkManifest apkManifestProto; - - public BuildResult( - File executionRoot, - File mergedManifestFile, - File apkManifestProtoFile, - ApkManifestOuterClass.ApkManifest apkManifestProto) { - this.executionRoot = executionRoot; - this.mergedManifestFile = mergedManifestFile; - this.apkManifestProtoFile = apkManifestProtoFile; - this.apkManifestProto = apkManifestProto; - } - } - - private final SettableFuture<BuildResult> buildResultFuture = SettableFuture.create(); - private final SettableFuture<ApplicationIdProvider> applicationIdProviderFuture = - SettableFuture.create(); - private final SettableFuture<InstantRunContext> instantRunContextFuture = SettableFuture.create(); - private final SettableFuture<InstantRunBuildAnalyzer> instantRunBuildAnalyzerFuture = - SettableFuture.create(); - - public BlazeApkBuildStepInstantRun( - Project project, ExecutionEnvironment env, Label label, ImmutableList<String> buildFlags) { - this.project = project; - this.executor = env.getExecutor(); - this.env = env; - this.label = label; - this.buildFlags = buildFlags; - this.instantRunArtifactDirectory = - BlazeInstantRunGradleIntegration.getInstantRunArtifactDirectory(project, label); - this.instantRunBuildInfoFile = - new File(instantRunArtifactDirectory, "build/reload-dex/debug/build-info.xml"); - this.instantRunGradleBuildFile = new File(instantRunArtifactDirectory, "build.gradle"); - this.instantRunGradlePropertiesFile = - new File(instantRunArtifactDirectory, "gradle.properties"); - } - - @Override - public boolean build( - BlazeContext context, BlazeAndroidDeviceSelector.DeviceSession deviceSession) { - if (!instantRunArtifactDirectory.exists() && !instantRunArtifactDirectory.mkdirs()) { - IssueOutput.error( - "Could not create instant run artifact directory: " + instantRunArtifactDirectory) - .submit(context); - return false; - } - - BuildResult buildResult = buildApkManifest(context); - if (buildResult == null) { - return false; - } - - String gradleUrl = BlazeInstantRunGradleIntegration.getGradleUrl(context); - if (gradleUrl == null) { - return false; - } - - ApplicationIdProvider applicationIdProvider = - new BlazeInstantRunApplicationIdProvider(project, buildResult); - applicationIdProviderFuture.set(applicationIdProvider); - - // Write build.gradle - try (PrintWriter printWriter = new PrintWriter(instantRunGradleBuildFile)) { - printWriter.print( - BlazeInstantRunGradleIntegration.getGradleBuildInfoString( - gradleUrl, buildResult.executionRoot, buildResult.apkManifestProtoFile)); - } catch (IOException e) { - IssueOutput.error("Could not write build.gradle file: " + e).submit(context); - return false; - } - - // Write gradle.properties - try (PrintWriter printWriter = new PrintWriter(instantRunGradlePropertiesFile)) { - printWriter.print(BlazeInstantRunGradleIntegration.getGradlePropertiesString()); - } catch (IOException e) { - IssueOutput.error("Could not write build.gradle file: " + e).submit(context); - return false; - } - - String applicationId = null; - try { - applicationId = applicationIdProvider.getPackageName(); - } catch (ApkProvisionException e) { - return false; - } - - return invokeGradleIrTasks(context, deviceSession, buildResult, applicationId); - } - - private BuildResult buildApkManifest(BlazeContext context) { - final ScopedTask buildTask = - new ScopedTask(context) { - @Override - protected void execute(@NotNull BlazeContext context) { - WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); - String executionRoot = getExecutionRoot(context, workspaceRoot); - if (executionRoot == null) { - IssueOutput.error("Could not get execution root").submit(context); - return; - } - - BlazeCommand.Builder command = - BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD); - - command - .addTargets(label) - .addBlazeFlags(buildFlags) - .addBlazeFlags("--output_groups=apk_manifest") - .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS); - - List<File> apkManifestFiles = Lists.newArrayList(); - - SaveUtil.saveAllFiles(); - int retVal = - ExternalTask.builder(workspaceRoot) - .addBlazeCommand(command.build()) - .context(context) - .stderr( - LineProcessingOutputStream.of( - new ExperimentalShowArtifactsLineProcessor( - apkManifestFiles, fileName -> fileName.endsWith("apk_manifest")), - new IssueOutputLineProcessor(project, context, workspaceRoot))) - .build() - .run(new LoggedTimingScope(project, Action.BLAZE_BUILD)); - FileCaches.refresh(project); - - if (retVal != 0) { - context.setHasError(); - return; - } - - File apkManifestFile = Iterables.getOnlyElement(apkManifestFiles, null); - if (apkManifestFile == null) { - IssueOutput.error("Could not find APK manifest file").submit(context); - return; - } - - ApkManifestOuterClass.ApkManifest apkManifestProto; - try (InputStream inputStream = new FileInputStream(apkManifestFile)) { - apkManifestProto = ApkManifestOuterClass.ApkManifest.parseFrom(inputStream); - } catch (IOException e) { - LOG.error(e); - IssueOutput.error("Error parsing apk proto").submit(context); - return; - } - - // Refresh the manifest - File mergedManifestFile = - new File(executionRoot, apkManifestProto.getAndroidManifest().getExecRootPath()); - ManifestParser.getInstance(project) - .refreshManifests(ImmutableList.of(mergedManifestFile)); - - BuildResult buildResult = - new BuildResult( - new File(executionRoot), mergedManifestFile, apkManifestFile, apkManifestProto); - buildResultFuture.set(buildResult); - } - }; - - BlazeExecutor.submitTask( - project, - String.format("Executing %s apk build", Blaze.buildSystemName(project)), - buildTask); - - try { - BuildResult buildResult = buildResultFuture.get(); - if (!context.shouldContinue()) { - return null; - } - return buildResult; - } catch (InterruptedException | ExecutionException e) { - context.setHasError(); - } catch (CancellationException e) { - context.setCancelled(); - } - return null; - } - - private boolean invokeGradleIrTasks( - BlazeContext context, - BlazeAndroidDeviceSelector.DeviceSession deviceSession, - BuildResult buildResult, - String applicationId) { - InstantRunContext instantRunContext = - new BlazeInstantRunContext( - project, buildResult.apkManifestProto, applicationId, instantRunBuildInfoFile); - instantRunContextFuture.set(instantRunContext); - ProcessHandler previousSessionProcessHandler = - deviceSession.sessionInfo != null ? deviceSession.sessionInfo.getProcessHandler() : null; - DeviceFutures deviceFutures = deviceSession.deviceFutures; - assert deviceFutures != null; - List<AndroidDevice> targetDevices = deviceFutures.getDevices(); - AndroidDevice androidDevice = targetDevices.get(0); - IDevice device = getLaunchedDevice(androidDevice); - - AndroidRunConfigContext runConfigContext = new AndroidRunConfigContext(); - runConfigContext.setTargetDevices(deviceFutures); - - AndroidSessionInfo info = deviceSession.sessionInfo; - runConfigContext.setSameExecutorAsPreviousSession( - info != null && executor.getId().equals(info.getExecutorId())); - runConfigContext.setCleanRerun(InstantRunUtils.isCleanReRun(env)); - - InstantRunBuilder instantRunBuilder = - new InstantRunBuilder( - device, - instantRunContext, - runConfigContext, - new BlazeInstantRunTasksProvider(), - RunAsValidityService.getInstance()); - - try { - List<String> cmdLineArgs = Lists.newArrayList(); - cmdLineArgs.addAll(MakeBeforeRunTaskProvider.getDeviceSpecificArguments(targetDevices)); - BlazeInstantRunGradleTaskRunner taskRunner = - new BlazeInstantRunGradleTaskRunner(project, context, instantRunGradleBuildFile); - boolean success = instantRunBuilder.build(taskRunner, cmdLineArgs); - LOG.info("Gradle invocation complete, success = " + success); - if (!success) { - return false; - } - } catch (InvocationTargetException e) { - LOG.info("Unexpected error while launching gradle before run tasks", e); - return false; - } catch (InterruptedException e) { - LOG.info("Interrupted while launching gradle before run tasks"); - Thread.currentThread().interrupt(); - return false; - } - - InstantRunBuildAnalyzer analyzer = - new InstantRunBuildAnalyzer(project, instantRunContext, previousSessionProcessHandler); - instantRunBuildAnalyzerFuture.set(analyzer); - return true; - } - - ListenableFuture<BuildResult> getBuildResult() { - return buildResultFuture; - } - - ListenableFuture<ApplicationIdProvider> getApplicationIdProvider() { - return applicationIdProviderFuture; - } - - ListenableFuture<InstantRunContext> getInstantRunContext() { - return instantRunContextFuture; - } - - ListenableFuture<InstantRunBuildAnalyzer> getInstantRunBuildAnalyzer() { - return instantRunBuildAnalyzerFuture; - } - - private String getExecutionRoot(BlazeContext context, WorkspaceRoot workspaceRoot) { - ListenableFuture<String> execRootFuture = - BlazeInfo.getInstance() - .runBlazeInfo( - context, - Blaze.getBuildSystem(project), - workspaceRoot, - buildFlags, - BlazeInfo.EXECUTION_ROOT_KEY); - try { - return execRootFuture.get(); - } catch (InterruptedException e) { - context.setCancelled(); - } catch (ExecutionException e) { - LOG.error(e); - context.setHasError(); - } - return null; - } - - @Nullable - private static IDevice getLaunchedDevice(@NotNull AndroidDevice device) { - if (!device.getLaunchedDevice().isDone()) { - // If we don't have access to the device (this happens if the AVD is still launching) - return null; - } - - try { - return device.getLaunchedDevice().get(1, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return null; - } catch (ExecutionException | TimeoutException e) { - return null; - } - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java deleted file mode 100644 index ab1461e..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunApplicationIdProvider.java +++ /dev/null
@@ -1,65 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.tools.idea.run.ApkProvisionException; -import com.android.tools.idea.run.ApplicationIdProvider; -import com.google.idea.blaze.android.manifest.ManifestParser; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import java.io.File; -import org.jetbrains.android.dom.manifest.Manifest; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Application id provider for blaze instant run. */ -public class BlazeInstantRunApplicationIdProvider implements ApplicationIdProvider { - private final Project project; - private final BlazeApkBuildStepInstantRun.BuildResult buildResult; - - public BlazeInstantRunApplicationIdProvider( - Project project, BlazeApkBuildStepInstantRun.BuildResult buildResult) { - this.project = project; - this.buildResult = buildResult; - } - - @NotNull - @Override - public String getPackageName() throws ApkProvisionException { - File manifestFile = - new File( - buildResult.executionRoot, - buildResult.apkManifestProto.getAndroidManifest().getExecRootPath()); - Manifest manifest = ManifestParser.getInstance(project).getManifest(manifestFile); - if (manifest == null) { - throw new ApkProvisionException("Could not find merged manifest: " + manifestFile); - } - String applicationId = - ApplicationManager.getApplication() - .runReadAction((Computable<String>) () -> manifest.getPackage().getValue()); - if (applicationId == null) { - throw new ApkProvisionException("No application id in merged manifest: " + manifestFile); - } - return applicationId; - } - - @Nullable - @Override - public String getTestPackageName() throws ApkProvisionException { - return null; - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java deleted file mode 100644 index 9ea7c03..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunContext.java +++ /dev/null
@@ -1,105 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.tools.fd.client.InstantRunBuildInfo; -import com.android.tools.idea.fd.BuildSelection; -import com.android.tools.idea.fd.FileChangeListener; -import com.android.tools.idea.fd.InstantRunContext; -import com.google.common.hash.HashCode; -import com.google.repackaged.devtools.build.lib.rules.android.apkmanifest.ApkManifestOuterClass; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Blaze implementation of instant run context. */ -public class BlazeInstantRunContext implements InstantRunContext { - private static final Logger LOG = Logger.getInstance(BlazeInstantRunContext.class); - private final Project project; - private final ApkManifestOuterClass.ApkManifest apkManifest; - private final String applicationId; - private final File instantRunBuildInfoFile; - private BuildSelection buildSelection; - - BlazeInstantRunContext( - Project project, - ApkManifestOuterClass.ApkManifest apkManifest, - String applicationId, - File instantRunBuildInfoFile) { - this.project = project; - this.apkManifest = apkManifest; - this.applicationId = applicationId; - this.instantRunBuildInfoFile = instantRunBuildInfoFile; - } - - @NotNull - @Override - public String getApplicationId() { - return applicationId; - } - - @NotNull - @Override - public HashCode getManifestResourcesHash() { - // TODO b/28373160 - return HashCode.fromInt(0); - } - - @Override - public boolean usesMultipleProcesses() { - // TODO(tomlu) -- does this make sense in blaze? We can of course just parse the manifest. - return false; - } - - @Nullable - @Override - public FileChangeListener.Changes getFileChangesAndReset() { - return null; - } - - @Nullable - @Override - public InstantRunBuildInfo getInstantRunBuildInfo() { - if (instantRunBuildInfoFile.exists()) { - try { - String xml = - new String( - Files.readAllBytes(Paths.get(instantRunBuildInfoFile.getPath())), - StandardCharsets.UTF_8); - return InstantRunBuildInfo.get(xml); - } catch (IOException e) { - LOG.error(e); - } - } - return null; - } - - @Override - public void setBuildSelection(@NotNull BuildSelection buildSelection) { - this.buildSelection = buildSelection; - } - - @Override - public BuildSelection getBuildSelection() { - return buildSelection; - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java deleted file mode 100644 index 8c26fc4..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunDeviceSelector.java +++ /dev/null
@@ -1,96 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.ddmlib.IDevice; -import com.android.tools.idea.fd.InstantRunManager; -import com.android.tools.idea.fd.InstantRunUtils; -import com.android.tools.idea.run.AndroidSessionInfo; -import com.android.tools.idea.run.DeviceFutures; -import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; -import com.google.idea.blaze.android.run.runner.BlazeAndroidRunConfigurationDeployTargetManager; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.Executor; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; -import java.util.List; -import javax.annotation.Nullable; -import org.jetbrains.android.facet.AndroidFacet; - -/** Tries to reuse devices from a previous session. */ -public class BlazeInstantRunDeviceSelector implements BlazeAndroidDeviceSelector { - NormalDeviceSelector normalDeviceSelector = new NormalDeviceSelector(); - - @Override - public DeviceSession getDevice( - Project project, - AndroidFacet facet, - BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager, - Executor executor, - ExecutionEnvironment env, - AndroidSessionInfo info, - boolean debug, - int runConfigId) - throws ExecutionException { - DeviceFutures deviceFutures = null; - if (info != null) { - // if there is an existing previous session, - // then see if we can detect devices to fast deploy to - deviceFutures = getFastDeployDevices(executor, info); - - if (InstantRunUtils.isReRun(env)) { - info.getProcessHandler().destroyProcess(); - info = null; - } - } - - if (deviceFutures != null) { - return new DeviceSession(null, deviceFutures, info); - } - - // Fall back to normal device selection - return normalDeviceSelector.getDevice( - project, facet, deployTargetManager, executor, env, info, debug, runConfigId); - } - - @Nullable - private static DeviceFutures getFastDeployDevices(Executor executor, AndroidSessionInfo info) { - if (!info.getExecutorId().equals(executor.getId())) { - String msg = - String.format( - "Cannot Instant Run since old executor (%1$s) doesn't match current executor (%2$s)", - info.getExecutorId(), executor.getId()); - InstantRunManager.LOG.info(msg); - return null; - } - - List<IDevice> devices = info.getDevices(); - if (devices == null || devices.isEmpty()) { - InstantRunManager.LOG.info( - "Cannot Instant Run since we could not locate " - + "the devices from the existing launch session"); - return null; - } - - if (devices.size() > 1) { - InstantRunManager.LOG.info( - "Last run was on > 1 device, not reusing devices and prompting again"); - return null; - } - - return DeviceFutures.forDevices(devices); - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java deleted file mode 100644 index a9873ca..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleIntegration.java +++ /dev/null
@@ -1,126 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.SdkConstants; -import com.google.common.base.Joiner; -import com.google.common.hash.Hashing; -import com.google.idea.blaze.base.async.process.ExternalTask; -import com.google.idea.blaze.base.async.process.LineProcessingOutputStream; -import com.google.idea.blaze.base.async.process.PrintOutputLineProcessor; -import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.output.IssueOutput; -import com.google.idea.blaze.base.scope.output.StatusOutput; -import com.google.idea.blaze.base.settings.BlazeImportSettings; -import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; -import com.google.idea.blaze.base.sync.data.BlazeDataStorage; -import com.google.idea.common.experiments.DeveloperFlag; -import com.google.idea.common.experiments.StringExperiment; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.project.Project; -import java.io.File; -import javax.annotation.Nullable; - -/** Defines where instant run storage and artifacts go. */ -class BlazeInstantRunGradleIntegration { - private static final String INSTANT_RUN_SUBDIRECTORY = "instantrun"; - - private static final StringExperiment LOCAL_GRADLE_VERSION = - new StringExperiment("use.local.gradle.version"); - private static final DeveloperFlag REBUILD_LOCAL_GRADLE = - new DeveloperFlag("rebuild.local.gradle"); - - /** Gets a unique directory for a given target that can be used for the build process. */ - static File getInstantRunArtifactDirectory(Project project, Label target) { - BlazeImportSettings importSettings = - BlazeImportSettingsManager.getInstance(project).getImportSettings(); - assert importSettings != null; - File dataSubDirectory = BlazeDataStorage.getProjectDataDir(importSettings); - File instantRunDirectory = new File(dataSubDirectory, INSTANT_RUN_SUBDIRECTORY); - String targetHash = Hashing.md5().hashUnencodedChars(target.toString()).toString(); - return new File(instantRunDirectory, targetHash); - } - - @Nullable - static String getGradleUrl(BlazeContext context) { - String localGradleVersion = LOCAL_GRADLE_VERSION.getValue(); - boolean isDevMode = localGradleVersion != null; - - if (isDevMode) { - String toolsIdeaPath = PathManager.getHomePath(); - File toolsDir = new File(toolsIdeaPath).getParentFile(); - File repoDir = toolsDir.getParentFile(); - File localGradleDirectory = - new File( - new File(repoDir, "out/repo/com/android/tools/build/builder"), localGradleVersion); - if (REBUILD_LOCAL_GRADLE.getValue() || !localGradleDirectory.exists()) { - // Build gradle - context.output(new StatusOutput("Building local Gradle...")); - int retVal = - ExternalTask.builder(toolsDir) - .args("./gradlew", ":init", ":publishLocal") - .stdout(LineProcessingOutputStream.of(new PrintOutputLineProcessor(context))) - .build() - .run(); - - if (retVal != 0) { - IssueOutput.error("Gradle build failed.").submit(context); - return null; - } - } - return new File(repoDir, "out/repo").getPath(); - } - - // Not supported yet - IssueOutput.error( - "You must specify 'use.local.gradle.version' experiment, " - + "non-local gradle not supported yet.") - .submit(context); - return null; - } - - static String getGradlePropertiesString() { - return Joiner.on('\n') - .join("org.gradle.daemon=true", "org.gradle.jvmargs=-XX:MaxPermSize=1024m -Xmx4096m"); - } - - static String getGradleBuildInfoString( - String gradleUrl, File executionRoot, File apkManifestFile) { - String template = - Joiner.on('\n') - .join( - "buildscript {", - " repositories {", - " jcenter()", - " maven { url '%s' }", - " }", - " dependencies {", - " classpath 'com.android.tools.build:gradle:%s'", - " }", - "}", - "apply plugin: 'com.android.external.build'", - "externalBuild {", - " executionRoot = '%s'", - " buildManifestPath = '%s'", - "}"); - String gradleVersion = LOCAL_GRADLE_VERSION.getValue(); - gradleVersion = gradleVersion != null ? gradleVersion : SdkConstants.GRADLE_LATEST_VERSION; - - return String.format( - template, gradleUrl, gradleVersion, executionRoot.getPath(), apkManifestFile.getPath()); - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java deleted file mode 100644 index bdeab5a..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunGradleTaskRunner.java +++ /dev/null
@@ -1,130 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import static com.android.tools.idea.gradle.util.GradleUtil.GRADLE_SYSTEM_ID; -import static com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType.EXECUTE_TASK; - -import com.android.builder.model.AndroidProject; -import com.android.tools.idea.gradle.invoker.GradleInvocationResult; -import com.android.tools.idea.gradle.invoker.GradleInvoker; -import com.android.tools.idea.gradle.run.GradleTaskRunner; -import com.android.tools.idea.gradle.util.AndroidGradleSettings; -import com.android.tools.idea.gradle.util.BuildMode; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.output.PrintOutput; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter; -import com.intellij.openapi.project.Project; -import com.intellij.util.concurrency.Semaphore; -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import javax.swing.SwingUtilities; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -class BlazeInstantRunGradleTaskRunner implements GradleTaskRunner { - private final Project project; - private final BlazeContext context; - private final File instantRunGradleBuildFile; - - public BlazeInstantRunGradleTaskRunner( - Project project, BlazeContext context, File instantRunGradleBuildFile) { - this.project = project; - this.context = context; - this.instantRunGradleBuildFile = instantRunGradleBuildFile; - } - - @Override - public boolean run( - @NotNull List<String> tasks, - @Nullable BuildMode buildMode, - @NotNull List<String> commandLineArguments) - throws InvocationTargetException, InterruptedException { - assert !ApplicationManager.getApplication().isDispatchThread(); - - final GradleInvoker gradleInvoker = GradleInvoker.getInstance(project); - - final AtomicBoolean success = new AtomicBoolean(); - final Semaphore done = new Semaphore(); - done.down(); - - final GradleInvoker.AfterGradleInvocationTask afterTask = - new GradleInvoker.AfterGradleInvocationTask() { - @Override - public void execute(@NotNull GradleInvocationResult result) { - success.set(result.isBuildSuccessful()); - gradleInvoker.removeAfterGradleInvocationTask(this); - done.up(); - } - }; - - ExternalSystemTaskId taskId = - ExternalSystemTaskId.create(GRADLE_SYSTEM_ID, EXECUTE_TASK, project); - List<String> jvmArguments = ImmutableList.of(); - - // https://code.google.com/p/android/issues/detail?id=213040 - - // make split apks only available if an env var is set - List<String> args = new ArrayList<>(commandLineArguments); - if (!Boolean.valueOf(System.getenv(GradleTaskRunner.USE_SPLIT_APK))) { - // force multi dex when the env var is not set to true - args.add( - AndroidGradleSettings.createProjectProperty( - AndroidProject.PROPERTY_SIGNING_COLDSWAP_MODE, "MULTIDEX")); - } - - // To ensure that the "Run Configuration" waits for the Gradle tasks to be executed, - // we use SwingUtilities.invokeAndWait. I tried - // using Application.invokeAndWait but it never worked. - // IDEA also uses SwingUtilities in this scenario (see CompileStepBeforeRun.) - SwingUtilities.invokeAndWait( - () -> { - gradleInvoker.addAfterGradleInvocationTask(afterTask); - gradleInvoker.executeTasks( - tasks, - jvmArguments, - args, - taskId, - new GradleNotificationListener(), - instantRunGradleBuildFile, - false, - true); - }); - - done.waitFor(); - return success.get(); - } - - class GradleNotificationListener extends ExternalSystemTaskNotificationListenerAdapter { - @Override - public void onTaskOutput( - @NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) { - super.onTaskOutput(id, text, stdOut); - String toPrint = text.trim(); - if (!Strings.isNullOrEmpty(toPrint)) { - context.output( - new PrintOutput( - toPrint, stdOut ? PrintOutput.OutputType.NORMAL : PrintOutput.OutputType.ERROR)); - } - } - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java deleted file mode 100644 index 52a82f7..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeInstantRunTasksProvider.java +++ /dev/null
@@ -1,36 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.binary.instantrun; - -import com.android.tools.idea.fd.InstantRunTasksProvider; -import com.google.common.collect.ImmutableList; -import java.util.List; -import org.jetbrains.annotations.NotNull; - -/** Returns blaze-specific instant run tasks. */ -public class BlazeInstantRunTasksProvider implements InstantRunTasksProvider { - @NotNull - @Override - public List<String> getCleanAndGenerateSourcesTasks() { - return ImmutableList.of(); - } - - @NotNull - @Override - public List<String> getFullBuildTasks() { - return ImmutableList.of("process"); - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java index a25eba9..2ea7ebc 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java
@@ -30,6 +30,7 @@ import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; +import com.google.idea.blaze.android.compatibility.Compatibility; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationIdProvider; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryApplicationLaunchTaskProvider; import com.google.idea.blaze.android.run.binary.BlazeAndroidBinaryConsoleProvider; @@ -159,14 +160,22 @@ @Nullable @Override + @SuppressWarnings("unchecked") public DebugConnectorTask getDebuggerTask( AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState, - Set<String> packageIds) + Set<String> packageIds, + boolean monitorRemoteProcess) throws ExecutionException { - //noinspection unchecked - return androidDebugger.getConnectDebuggerTask( - env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId()); + return Compatibility.getConnectDebuggerTask( + androidDebugger, + env, + null, + packageIds, + facet, + androidDebuggerState, + runConfiguration.getType().getId(), + monitorRemoteProcess); } @Nullable
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java index 24d49b6..1256ff4 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java +++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeApkBuildStepMobileInstall.java
@@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidSdkUtils; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.deployinfo.BlazeApkDeployInfoProtoHelper; import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; @@ -56,8 +57,6 @@ import java.nio.file.Paths; import java.util.concurrent.CancellationException; import javax.annotation.Nullable; -import org.jetbrains.android.sdk.AndroidSdkUtils; -import org.jetbrains.annotations.NotNull; /** Builds and installs the APK using mobile-install. */ public class BlazeApkBuildStepMobileInstall implements BlazeApkBuildStep { @@ -89,7 +88,7 @@ final ScopedTask buildTask = new ScopedTask(context) { @Override - protected void execute(@NotNull BlazeContext context) { + protected void execute(BlazeContext context) { boolean incrementalInstall = env.getExecutor() instanceof IncrementalInstallExecutor; DeviceFutures deviceFutures = deviceSession.deviceFutures; @@ -203,8 +202,7 @@ } @Nullable - private static IDevice resolveDevice( - @NotNull BlazeContext context, @NotNull DeviceFutures deviceFutures) { + private static IDevice resolveDevice(BlazeContext context, DeviceFutures deviceFutures) { if (deviceFutures.get().size() != 1) { IssueOutput.error("Only one device can be used with mobile-install.").submit(context); return null;
diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java index e5f5ad3..afb7909 100644 --- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java +++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeAndroidDeployInfo.java
@@ -18,7 +18,9 @@ import com.google.common.collect.Lists; import com.google.idea.blaze.android.manifest.ManifestParser; import com.google.repackaged.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import java.io.File; import java.util.List; import java.util.Objects; @@ -49,7 +51,11 @@ @Nullable public Manifest getMergedManifest() { File manifestFile = getMergedManifestFile(); - return ManifestParser.getInstance(project).getManifest(manifestFile); + // Run in a read action since otherwise, it might throw a read access exception. + return ApplicationManager.getApplication() + .runReadAction( + (Computable<Manifest>) + () -> ManifestParser.getInstance(project).getManifest(manifestFile)); } public List<File> getAdditionalMergedManifestFiles() {
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java index 7354b25..dacc20b 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java
@@ -169,7 +169,8 @@ } try { - return runContext.getDebuggerTask(androidDebugger, androidDebuggerState, packageIds); + return runContext.getDebuggerTask( + androidDebugger, androidDebuggerState, packageIds, monitorRemoteProcess()); } catch (ExecutionException e) { launchStatus.terminateLaunch(e.getMessage()); return null;
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java index 76e3bfc..6d8feb4 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunConfigurationRunner.java
@@ -36,6 +36,7 @@ import com.google.idea.blaze.base.experiments.ExperimentScope; import com.google.idea.blaze.base.metrics.Action; import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; +import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; import com.google.idea.blaze.base.scope.Scope; import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope; @@ -79,7 +80,7 @@ private static final Key<BlazeAndroidRunContext> RUN_CONTEXT_KEY = Key.create("blaze.run.context"); - private static final Key<BlazeAndroidDeviceSelector.DeviceSession> DEVICE_SESSION_KEY = + public static final Key<BlazeAndroidDeviceSelector.DeviceSession> DEVICE_SESSION_KEY = Key.create("blaze.device.session"); private final Module module; @@ -251,7 +252,17 @@ @Nullable @Override - public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) + public ExecutionResult execute(Executor executor, ProgramRunner runner) + throws ExecutionException { + DefaultExecutionResult result = executeInner(executor, runner); + if (result == null) { + return null; + } + return SmRunnerUtils.attachRerunFailedTestsAction(result); + } + + @Nullable + private DefaultExecutionResult executeInner(Executor executor, @NotNull ProgramRunner<?> runner) throws ExecutionException { ProcessHandler processHandler; ConsoleView console;
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java index 1778a63..a0653a1 100644 --- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java +++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidRunContext.java
@@ -72,7 +72,8 @@ DebugConnectorTask getDebuggerTask( AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState, - Set<String> packageIds) + Set<String> packageIds, + boolean monitorRemoteProcess) throws ExecutionException; @Nullable
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java b/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java index 56ff338..d91419a 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/AndroidTestConsoleProvider.java
@@ -16,10 +16,13 @@ package com.google.idea.blaze.android.run.test; import com.android.tools.idea.run.ConsoleProvider; -import com.android.tools.idea.run.testing.AndroidTestConsoleProperties; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestConsoleProperties; +import com.google.idea.blaze.android.run.test.smrunner.BlazeAndroidTestEventsHandler; +import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; @@ -27,37 +30,50 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; -import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; /** Console provider for android_test */ class AndroidTestConsoleProvider implements ConsoleProvider { private final Project project; private final RunConfiguration runConfiguration; private final BlazeAndroidTestRunConfigurationState configState; + @Nullable private final BlazeAndroidTestEventsHandler testEventsHandler; AndroidTestConsoleProvider( Project project, RunConfiguration runConfiguration, - BlazeAndroidTestRunConfigurationState configState) { + BlazeAndroidTestRunConfigurationState configState, + @Nullable BlazeAndroidTestEventsHandler testEventsHandler) { this.project = project; this.runConfiguration = runConfiguration; this.configState = configState; + this.testEventsHandler = testEventsHandler; } - @NotNull @Override - public ConsoleView createAndAttach( - @NotNull Disposable parent, @NotNull ProcessHandler handler, @NotNull Executor executor) + public ConsoleView createAndAttach(Disposable parent, ProcessHandler handler, Executor executor) throws ExecutionException { if (!configState.isRunThroughBlaze()) { return getStockConsoleProvider().createAndAttach(parent, handler, executor); } - ConsoleView console = - TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole(); + ConsoleView console = createBlazeTestConsole(executor); console.attachToProcess(handler); return console; } + private ConsoleView createBlazeTestConsole(Executor executor) { + if (testEventsHandler == null || isDebugging(executor)) { + // SM runner console not yet supported when debugging, because we're calling this once per + // test case (see ConnectBlazeTestDebuggerTask::setUpForReattachingDebugger) + return TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole(); + } + return SmRunnerUtils.getConsoleView(project, runConfiguration, executor, testEventsHandler); + } + + private static boolean isDebugging(Executor executor) { + return executor instanceof DefaultDebugExecutor; + } + private ConsoleProvider getStockConsoleProvider() { return (parent, handler, executor) -> { AndroidTestConsoleProperties properties =
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java index 64e8239..8d08ec0 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java
@@ -15,8 +15,8 @@ */ package com.google.idea.blaze.android.run.test; -import com.android.tools.idea.run.testing.AndroidTestRunConfiguration; import com.google.common.base.Strings; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.model.primitives.Kind; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java index c6f4a3b..e5f2b1d 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestFilter.java
@@ -15,8 +15,8 @@ */ package com.google.idea.blaze.android.run.test; -import com.android.tools.idea.run.testing.AndroidTestRunConfiguration; import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration; import com.google.idea.blaze.base.command.BlazeFlags; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java index 0c42ea9..e37f07f 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java
@@ -15,8 +15,8 @@ */ package com.google.idea.blaze.android.run.test; -import com.android.tools.idea.run.testing.AndroidTestRunConfiguration; import com.google.common.base.Strings; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.model.primitives.Kind; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java index eb8293a..5df4512 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateEditor.java
@@ -16,10 +16,10 @@ package com.google.idea.blaze.android.run.test; -import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_ALL_IN_MODULE; -import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_ALL_IN_PACKAGE; -import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_CLASS; -import static com.android.tools.idea.run.testing.AndroidTestRunConfiguration.TEST_METHOD; +import static com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration.TEST_ALL_IN_MODULE; +import static com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration.TEST_ALL_IN_PACKAGE; +import static com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration.TEST_CLASS; +import static com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestRunConfiguration.TEST_METHOD; import com.google.idea.blaze.base.run.state.RunConfigurationState; import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor; @@ -64,6 +64,8 @@ private JCheckBox runThroughBlazeTestCheckBox; private final JRadioButton[] testingType2RadioButton = new JRadioButton[4]; + private boolean componentEnabled = true; + BlazeAndroidTestRunConfigurationStateEditor( RunConfigurationStateEditor commonStateEditor, Project project) { this.commonStateEditor = commonStateEditor; @@ -478,4 +480,29 @@ public JComponent createComponent() { return UiUtil.createBox(commonStateEditor.createComponent(), panel); } + + @Override + public void setComponentEnabled(boolean enabled) { + componentEnabled = enabled; + updateEnabledState(); + } + + private void updateEnabledState() { + commonStateEditor.setComponentEnabled(componentEnabled); + allInPackageButton.setEnabled(componentEnabled); + classButton.setEnabled(componentEnabled); + testMethodButton.setEnabled(componentEnabled); + allInTargetButton.setEnabled(componentEnabled); + packageComponent.setEnabled(componentEnabled); + classComponent.setEnabled(componentEnabled); + methodComponent.setEnabled(componentEnabled); + runnerComponent.setEnabled(componentEnabled); + labelTest.setEnabled(componentEnabled); + runThroughBlazeTestCheckBox.setEnabled(componentEnabled); + for (JComponent button : testingType2RadioButton) { + if (button != null) { + button.setEnabled(componentEnabled); + } + } + } }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java deleted file mode 100644 index 2104d75..0000000 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationType.java +++ /dev/null
@@ -1,132 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.run.test; - -import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; -import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; -import com.google.idea.blaze.base.settings.Blaze; -import com.intellij.execution.BeforeRunTask; -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.ConfigurationType; -import com.intellij.execution.configurations.ConfigurationTypeUtil; -import com.intellij.execution.configurations.UnknownConfigurationType; -import com.intellij.icons.AllIcons; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.ui.LayeredIcon; -import icons.AndroidIcons; -import javax.swing.Icon; -import org.jetbrains.annotations.NotNull; - -/** - * A type for Android test run configurations adapted specifically to run android_test targets. - * - * @deprecated See {@link com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType}. Retained - * in 1.9 for legacy purposes, to allow existing BlazeAndroidTestRunConfigurations to be updated - * to BlazeCommandRunConfigurations. Intended to be removed in 2.1. - */ -// Hack: extend UnknownConfigurationType to completely hide it in the Run/Debug Configurations UI. -@Deprecated -public class BlazeAndroidTestRunConfigurationType extends UnknownConfigurationType { - private static final Icon ANDROID_TEST_ICON; - - static { - LayeredIcon icon = new LayeredIcon(2); - icon.setIcon(AndroidIcons.Android, 0); - icon.setIcon(AllIcons.Nodes.JunitTestMark, 1); - ANDROID_TEST_ICON = icon; - } - - private final BlazeAndroidTestRunConfigurationFactory factory = - new BlazeAndroidTestRunConfigurationFactory(this); - - static class BlazeAndroidTestRunConfigurationFactory extends ConfigurationFactory { - - protected BlazeAndroidTestRunConfigurationFactory(@NotNull ConfigurationType type) { - super(type); - } - - @Override - public String getName() { - // Used to look up this ConfigurationFactory. - // Preserve value so legacy configurations can be loaded. - return Blaze.defaultBuildSystemName() + " Android Test"; - } - - @Override - @NotNull - public BlazeCommandRunConfiguration createTemplateConfiguration(@NotNull Project project) { - // Create a BlazeCommandRunConfiguration instead, to update legacy configurations. - return BlazeCommandRunConfigurationType.getInstance() - .getFactory() - .createTemplateConfiguration(project); - } - - @Override - public boolean canConfigurationBeSingleton() { - return false; - } - - @Override - public boolean isApplicable(@NotNull Project project) { - return Blaze.isBlazeProject(project); - } - - @Override - public void configureBeforeRunTaskDefaults( - Key<? extends BeforeRunTask> providerID, BeforeRunTask task) { - // Removed BlazeAndroidBeforeRunTaskProvider; this method won't be called anymore anyhow. - } - - @Override - public boolean isConfigurationSingletonByDefault() { - return true; - } - } - - public static BlazeAndroidTestRunConfigurationType getInstance() { - return ConfigurationTypeUtil.findConfigurationType(BlazeAndroidTestRunConfigurationType.class); - } - - @Override - public String getDisplayName() { - return "Legacy " + Blaze.defaultBuildSystemName() + " Android Test"; - } - - @Override - public String getConfigurationTypeDescription() { - return "Launch/debug configuration for android_test rules " - + "Use Blaze Command instead; this legacy configuration type is being removed."; - } - - @Override - public Icon getIcon() { - return ANDROID_TEST_ICON; - } - - @Override - @NotNull - public String getId() { - // Used to look up this ConfigurationType. - // Preserve value so legacy configurations can be loaded. - return "BlazeAndroidTestRunConfigurationType"; - } - - @Override - public BlazeAndroidTestRunConfigurationFactory[] getConfigurationFactories() { - return new BlazeAndroidTestRunConfigurationFactory[] {factory}; - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java index ee1013b..cf418e9 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java
@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; +import com.google.idea.blaze.android.compatibility.Compatibility; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.google.idea.blaze.android.run.deployinfo.BlazeApkProvider; import com.google.idea.blaze.android.run.runner.BlazeAndroidDeviceSelector; @@ -41,9 +42,13 @@ import com.google.idea.blaze.android.run.runner.BlazeAndroidRunContext; import com.google.idea.blaze.android.run.runner.BlazeApkBuildStep; import com.google.idea.blaze.android.run.runner.BlazeApkBuildStepNormalBuild; +import com.google.idea.blaze.android.run.test.smrunner.BlazeAndroidTestEventsHandler; import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.common.experiments.BoolExperiment; import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.project.Project; import java.util.Collection; @@ -55,6 +60,10 @@ /** Run context for android_test. */ class BlazeAndroidTestRunContext implements BlazeAndroidRunContext { + + static final BoolExperiment smRunnerUiEnabled = + new BoolExperiment("use.smrunner.ui.android", true); + private final Project project; private final AndroidFacet facet; private final RunConfiguration runConfiguration; @@ -82,12 +91,28 @@ this.env = env; this.label = label; this.configState = configState; - this.buildFlags = buildFlags; - this.consoleProvider = new AndroidTestConsoleProvider(project, runConfiguration, configState); this.buildStep = new BlazeApkBuildStepNormalBuild(project, label, buildFlags); this.applicationIdProvider = new BlazeAndroidTestApplicationIdProvider(project, buildStep.getDeployInfo()); this.apkProvider = new BlazeApkProvider(project, buildStep.getDeployInfo()); + + BlazeAndroidTestEventsHandler testEventsHandler = null; + if (smRunnerUiEnabled.getValue() && !isDebugging(env.getExecutor())) { + testEventsHandler = new BlazeAndroidTestEventsHandler(); + this.buildFlags = + ImmutableList.<String>builder() + .addAll(testEventsHandler.getBlazeFlags()) + .addAll(buildFlags) + .build(); + } else { + this.buildFlags = buildFlags; + } + this.consoleProvider = + new AndroidTestConsoleProvider(project, runConfiguration, configState, testEventsHandler); + } + + private static boolean isDebugging(Executor executor) { + return executor instanceof DefaultDebugExecutor; } @Override @@ -192,18 +217,26 @@ } @Override + @SuppressWarnings("unchecked") public DebugConnectorTask getDebuggerTask( AndroidDebugger androidDebugger, AndroidDebuggerState androidDebuggerState, - @NotNull Set<String> packageIds) + @NotNull Set<String> packageIds, + boolean monitorRemoteProcess) throws ExecutionException { if (configState.isRunThroughBlaze()) { return new ConnectBlazeTestDebuggerTask( env.getProject(), androidDebugger, packageIds, applicationIdProvider, this); } - //noinspection unchecked - return androidDebugger.getConnectDebuggerTask( - env, null, packageIds, facet, androidDebuggerState, runConfiguration.getType().getId()); + return Compatibility.getConnectDebuggerTask( + androidDebugger, + env, + null, + packageIds, + facet, + androidDebuggerState, + runConfiguration.getType().getId(), + monitorRemoteProcess); } void onLaunchTaskComplete() {
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java b/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java index 7153880..8e2328e 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java
@@ -28,9 +28,9 @@ import com.android.tools.idea.run.LaunchInfo; import com.android.tools.idea.run.ProcessHandlerConsolePrinter; import com.android.tools.idea.run.editor.AndroidDebugger; -import com.android.tools.idea.run.tasks.ConnectDebuggerTask; import com.android.tools.idea.run.tasks.ConnectJavaDebuggerTask; import com.android.tools.idea.run.util.ProcessHandlerLaunchStatus; +import com.google.idea.blaze.android.compatibility.Compatibility.ConnectDebuggerTask; import com.intellij.debugger.engine.RemoteDebugProcessHandler; import com.intellij.debugger.ui.DebuggerPanelsManager; import com.intellij.execution.ExecutionException; @@ -62,7 +62,7 @@ Set<String> applicationIds, ApplicationIdProvider applicationIdProvider, BlazeAndroidTestRunContext runContext) { - super(applicationIds, debugger, project); + super(applicationIds, debugger, project, true); this.project = project; this.applicationIdProvider = applicationIdProvider; this.runContext = runContext;
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java b/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java index cf3664b..2430f3c 100644 --- a/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java +++ b/aswb/src/com/google/idea/blaze/android/run/test/StockAndroidTestLaunchTask.java
@@ -21,9 +21,9 @@ import com.android.tools.idea.run.ApplicationIdProvider; import com.android.tools.idea.run.ConsolePrinter; import com.android.tools.idea.run.tasks.LaunchTask; -import com.android.tools.idea.run.testing.AndroidTestListener; import com.android.tools.idea.run.util.LaunchStatus; import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidTestListener; import com.google.idea.blaze.android.run.deployinfo.BlazeAndroidDeployInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger;
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java b/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java new file mode 100644 index 0000000..f37359a --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestEventsHandler.java
@@ -0,0 +1,110 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.run.test.smrunner; + +import com.google.idea.blaze.base.command.BlazeFlags; +import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler; +import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; +import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags; +import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags.JUnitVersion; +import com.intellij.execution.Location; +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.execution.testframework.sm.runner.SMTestLocator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.io.URLUtil; +import java.util.List; +import javax.annotation.Nullable; + +/** Provides java-specific methods needed by the SM-runner test UI. */ +public class BlazeAndroidTestEventsHandler extends BlazeTestEventsHandler { + + public BlazeAndroidTestEventsHandler() { + super("Blaze Android Test"); + } + + @Override + public SMTestLocator getTestLocator() { + return BlazeAndroidTestLocator.INSTANCE; + } + + @Override + public String suiteLocationUrl(String name) { + return SmRunnerUtils.GENERIC_SUITE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + name; + } + + @Override + public String testLocationUrl(String name, @Nullable String className) { + // ignore initial value of className -- it's the test runner class. + name = StringUtil.trimTrailing(name, '-'); + if (!name.contains("-")) { + return SmRunnerUtils.GENERIC_SUITE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + name; + } + int ix = name.lastIndexOf('-'); + className = name.substring(0, ix); + String methodName = name.substring(ix + 1); + return SmRunnerUtils.GENERIC_SUITE_PROTOCOL + + URLUtil.SCHEME_SEPARATOR + + className + + SmRunnerUtils.TEST_NAME_PARTS_SPLITTER + + methodName; + } + + @Override + public String testDisplayName(String rawName) { + String name = StringUtil.trimTrailing(rawName, '-'); + if (name.contains("-")) { + int ix = name.lastIndexOf('-'); + return name.substring(ix + 1); + } + return name; + } + + @Nullable + @Override + public String getTestFilter(Project project, List<AbstractTestProxy> failedTests) { + GlobalSearchScope projectScope = GlobalSearchScope.allScope(project); + MultiMap<PsiClass, PsiMethod> failedMethodsPerClass = new MultiMap<>(); + for (AbstractTestProxy test : failedTests) { + appendTest(failedMethodsPerClass, test.getLocation(project, projectScope)); + } + // the android test runner always runs with JUnit4 + String filter = + BlazeJUnitTestFilterFlags.testFilterForClassesAndMethods( + failedMethodsPerClass, JUnitVersion.JUNIT_4); + return filter != null ? BlazeFlags.TEST_FILTER + "=" + filter : null; + } + + private void appendTest( + MultiMap<PsiClass, PsiMethod> testMap, @Nullable Location<?> testLocation) { + if (testLocation == null) { + return; + } + PsiElement method = testLocation.getPsiElement(); + if (!(method instanceof PsiMethod)) { + return; + } + PsiClass psiClass = ((PsiMethod) method).getContainingClass(); + if (psiClass != null) { + testMap.putValue(psiClass, (PsiMethod) method); + } + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestLocator.java b/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestLocator.java new file mode 100644 index 0000000..c03f759 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/run/test/smrunner/BlazeAndroidTestLocator.java
@@ -0,0 +1,96 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.run.test.smrunner; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; +import com.intellij.execution.Location; +import com.intellij.execution.PsiLocation; +import com.intellij.execution.junit.JUnitUtil; +import com.intellij.execution.testframework.sm.runner.SMTestLocator; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** Locate android test classes / methods for test UI navigation. */ +public class BlazeAndroidTestLocator implements SMTestLocator { + + public static final BlazeAndroidTestLocator INSTANCE = new BlazeAndroidTestLocator(); + + private BlazeAndroidTestLocator() {} + + @Override + public List<Location> getLocation( + String protocol, String path, Project project, GlobalSearchScope scope) { + if (!protocol.equals(SmRunnerUtils.GENERIC_SUITE_PROTOCOL)) { + return ImmutableList.of(); + } + String[] split = path.split(SmRunnerUtils.TEST_NAME_PARTS_SPLITTER); + List<PsiClass> classes = findClasses(project, scope, split[0]); + if (classes.isEmpty()) { + return ImmutableList.of(); + } + if (split.length <= 1) { + return classes.stream().map(PsiLocation::new).collect(Collectors.toList()); + } + + String methodName = split[1]; + List<Location> results = new ArrayList<>(); + for (PsiClass psiClass : classes) { + PsiMethod method = findTestMethod(psiClass, methodName); + if (method != null) { + results.add(new PsiLocation<>(method)); + } + } + return results.isEmpty() ? ImmutableList.of(new PsiLocation<>(classes.get(0))) : results; + } + + private static List<PsiClass> findClasses( + Project project, GlobalSearchScope scope, String className) { + PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(className, scope); + if (psiClass != null) { + return ImmutableList.of(psiClass); + } + // handle unqualified class names + return Arrays.stream(PsiShortNamesCache.getInstance(project).getClassesByName(className, scope)) + .filter(JUnitUtil::isTestClass) + .collect(Collectors.toList()); + } + + private static PsiMethod findTestMethod(PsiClass psiClass, String methodName) { + final PsiMethod[] methods = psiClass.findMethodsByName(methodName, true); + + if (methods.length == 0) { + return null; + } + if (methods.length == 1) { + return methods[0]; + } + for (PsiMethod method : methods) { + if (method.getParameterList().getParametersCount() == 0) { + return method; + } + } + return methods[0]; + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java b/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java new file mode 100644 index 0000000..6d9487b --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/settings/BlazeAndroidUserSettings.java
@@ -0,0 +1,54 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.settings; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; + +/** Android-specific user settings. */ +@State(name = "BlazeAndroidUserSettings", storages = @Storage("blaze.android.user.settings.xml")) +public class BlazeAndroidUserSettings + implements PersistentStateComponent<BlazeAndroidUserSettings> { + + private boolean useLayoutEditor = false; + + public static BlazeAndroidUserSettings getInstance() { + return ServiceManager.getService(BlazeAndroidUserSettings.class); + } + + @Override + @NotNull + public BlazeAndroidUserSettings getState() { + return this; + } + + @Override + public void loadState(BlazeAndroidUserSettings state) { + XmlSerializerUtil.copyBean(state, this); + } + + public boolean getUseLayoutEditor() { + return useLayoutEditor; + } + + public void setUseLayoutEditor(boolean useLayoutEditor) { + this.useLayoutEditor = useLayoutEditor; + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BazelAndroidJdkProvider.java b/aswb/src/com/google/idea/blaze/android/sync/BazelAndroidJdkProvider.java index f45b368..d603ff3 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BazelAndroidJdkProvider.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BazelAndroidJdkProvider.java
@@ -15,9 +15,8 @@ */ package com.google.idea.blaze.android.sync; -import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.settings.Blaze.BuildSystem; -import com.intellij.openapi.project.Project; import com.intellij.pom.java.LanguageLevel; import javax.annotation.Nullable; @@ -26,8 +25,11 @@ @Nullable @Override - public LanguageLevel getLanguageLevel(Project project) { - BuildSystem buildSystem = Blaze.getBuildSystem(project); - return buildSystem == BuildSystem.Bazel ? LanguageLevel.JDK_1_7 : null; + public LanguageLevel getLanguageLevel( + BuildSystem buildSystem, BlazeVersionData blazeVersionData) { + if (buildSystem != BuildSystem.Bazel) { + return null; + } + return LanguageLevel.JDK_1_7; } }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java index 815c4c9..253b4db 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncListener.java
@@ -27,7 +27,7 @@ @Override public void afterSync( Project project, BlazeContext context, SyncMode syncMode, SyncResult syncResult) { - if (syncResult == SyncResult.SUCCESS || syncResult == SyncResult.PARTIAL_SUCCESS) { + if (syncResult.successful()) { DumbService dumbService = DumbService.getInstance(project); dumbService.queueTask(new ResourceFolderRegistry.PopulateCachesTask(project)); }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java index 72b352d..74d41e7 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java
@@ -15,9 +15,10 @@ */ package com.google.idea.blaze.android.sync; -import com.android.tools.idea.sdk.IdeSdks; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidSdkUtils; +import com.google.idea.blaze.android.compatibility.Compatibility.IdeSdks; import com.google.idea.blaze.android.cppapi.NdkSupport; import com.google.idea.blaze.android.projectview.AndroidSdkPlatformSection; import com.google.idea.blaze.android.projectview.GeneratedAndroidResourcesSection; @@ -29,6 +30,7 @@ import com.google.idea.blaze.android.sync.sdk.AndroidSdkFromProjectView; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.LanguageClass; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -40,6 +42,7 @@ import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.scope.output.StatusOutput; import com.google.idea.blaze.base.scope.scopes.TimingScope; +import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.BlazeSyncPlugin; import com.google.idea.blaze.base.sync.SourceFolderProvider; import com.google.idea.blaze.base.sync.libraries.LibrarySource; @@ -69,7 +72,6 @@ import java.util.Set; import javax.annotation.Nullable; import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.sdk.AndroidSdkUtils; /** ASwB sync plugin. */ public class BlazeAndroidSyncPlugin extends BlazeSyncPlugin.Adapter { @@ -160,6 +162,7 @@ Project project, BlazeContext context, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, BlazeProjectData blazeProjectData) { if (!isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)) { return; @@ -180,7 +183,9 @@ return; } - LanguageLevel defaultLanguageLevel = BuildSystemAndroidJdkProvider.languageLevel(project); + LanguageLevel defaultLanguageLevel = + BuildSystemAndroidJdkProvider.languageLevel( + Blaze.getBuildSystem(project), blazeVersionData); LanguageLevel javaLanguageLevel = JavaLanguageLevelSection.getLanguageLevel(projectViewSet, defaultLanguageLevel); setProjectSdkAndLanguageLevel(project, sdk, javaLanguageLevel); @@ -200,7 +205,6 @@ BlazeAndroidProjectStructureSyncer.updateProjectStructure( project, context, - workspaceRoot, projectViewSet, blazeProjectData, moduleEditor, @@ -209,6 +213,23 @@ isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)); } + @Override + public void updateInMemoryState( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + Module workspaceModule) { + BlazeAndroidProjectStructureSyncer.updateInMemoryState( + project, + workspaceRoot, + projectViewSet, + blazeProjectData, + workspaceModule, + isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)); + } + @Nullable @Override public SourceFolderProvider getSourceFolderProvider(BlazeProjectData projectData) {
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BuildSystemAndroidJdkProvider.java b/aswb/src/com/google/idea/blaze/android/sync/BuildSystemAndroidJdkProvider.java index d9b53bf..94c4c9a 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/BuildSystemAndroidJdkProvider.java +++ b/aswb/src/com/google/idea/blaze/android/sync/BuildSystemAndroidJdkProvider.java
@@ -15,8 +15,9 @@ */ package com.google.idea.blaze.android.sync; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.project.Project; import com.intellij.pom.java.LanguageLevel; import javax.annotation.Nullable; @@ -28,9 +29,9 @@ LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JDK_1_7; - static LanguageLevel languageLevel(Project project) { + static LanguageLevel languageLevel(BuildSystem buildSystem, BlazeVersionData blazeVersionData) { for (BuildSystemAndroidJdkProvider provider : EP_NAME.getExtensions()) { - LanguageLevel level = provider.getLanguageLevel(project); + LanguageLevel level = provider.getLanguageLevel(buildSystem, blazeVersionData); if (level != null) { return level; } @@ -43,5 +44,5 @@ * unable to determine it. */ @Nullable - LanguageLevel getLanguageLevel(Project project); + LanguageLevel getLanguageLevel(BuildSystem buildSystem, BlazeVersionData blazeVersionData); }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java new file mode 100644 index 0000000..a1f6bf8 --- /dev/null +++ b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModuleRegistry.java
@@ -0,0 +1,55 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.sync.model; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.model.primitives.Label; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; + +/** Keeps track of which resource modules correspond to which resource target. */ +public class AndroidResourceModuleRegistry { + private final BiMap<Module, AndroidResourceModule> moduleMap = HashBiMap.create(); + + public static AndroidResourceModuleRegistry getInstance(Project project) { + return ServiceManager.getService(project, AndroidResourceModuleRegistry.class); + } + + public Label getLabel(Module module) { + TargetKey targetKey = getTargetKey(module); + return targetKey == null ? null : targetKey.label; + } + + public TargetKey getTargetKey(Module module) { + AndroidResourceModule resourceModule = get(module); + return resourceModule == null ? null : resourceModule.targetKey; + } + + public AndroidResourceModule get(Module module) { + return moduleMap.get(module); + } + + public void put(Module module, AndroidResourceModule resourceModule) { + moduleMap.put(module, resourceModule); + } + + public void clear() { + moduleMap.clear(); + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java index 7a13e1e..8e19be7 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java +++ b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeAndroidModel.java
@@ -22,16 +22,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.idea.blaze.android.manifest.ManifestParser; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import java.io.File; import java.util.List; import java.util.Set; import org.jetbrains.android.dom.manifest.Manifest; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -39,10 +39,7 @@ * user-selected build variant. */ public class BlazeAndroidModel implements AndroidModel { - private static final Logger LOG = Logger.getInstance(BlazeAndroidModel.class); - private Project project; - private Module module; private final File rootDirPath; private final SourceProvider sourceProvider; private final List<SourceProvider> sourceProviders; // Singleton list of sourceProvider @@ -60,7 +57,6 @@ String resourceJavaPackage, int androidSdkApiLevel) { this.project = project; - this.module = module; this.rootDirPath = rootDirPath; this.sourceProvider = sourceProvider; this.sourceProviders = ImmutableList.of(sourceProvider); @@ -69,45 +65,43 @@ this.androidSdkApiLevel = androidSdkApiLevel; } - @NotNull @Override public SourceProvider getDefaultSourceProvider() { return sourceProvider; } - @NotNull @Override public List<SourceProvider> getActiveSourceProviders() { return sourceProviders; } - @NotNull @Override public List<SourceProvider> getTestSourceProviders() { return sourceProviders; } - @NotNull @Override public List<SourceProvider> getAllSourceProviders() { return sourceProviders; } @Override - @NotNull public String getApplicationId() { - String result = null; - Manifest manifest = ManifestParser.getInstance(project).getManifest(moduleManifest); - if (manifest != null) { - result = manifest.getPackage().getValue(); - } - if (result == null) { - result = resourceJavaPackage; - } - return result; + // Run in a read action since otherwise, it might throw a read access exception. + return ApplicationManager.getApplication() + .runReadAction( + (Computable<String>) + () -> { + Manifest manifest = + ManifestParser.getInstance(project).getManifest(moduleManifest); + if (manifest == null) { + return resourceJavaPackage; + } + String packageName = manifest.getPackage().getValue(); + return packageName == null ? resourceJavaPackage : packageName; + }); } - @NotNull @Override public Set<String> getAllApplicationIds() { Set<String> applicationIds = Sets.newHashSet(); @@ -149,18 +143,16 @@ return null; } - @NotNull @Override public File getRootDirPath() { return rootDirPath; } @Override - public boolean isGenerated(@NotNull VirtualFile file) { + public boolean isGenerated(VirtualFile file) { return false; } - @NotNull @Override public VirtualFile getRootDir() { File rootDirPath = getRootDirPath(); @@ -175,14 +167,13 @@ } @Override - @NotNull public ClassJarProvider getClassJarProvider() { - return new NullClassJarProvider(); + return new BlazeClassJarProvider(project); } @Override @Nullable - public Long getLastBuildTimestamp(@NotNull Project project) { + public Long getLastBuildTimestamp(Project project) { // TODO(jvoung): Coordinate with blaze build actions to be able determine last build time. return null; }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java b/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java deleted file mode 100644 index dbd24d6..0000000 --- a/aswb/src/com/google/idea/blaze/android/sync/model/idea/BlazeClassJarProvider.java +++ /dev/null
@@ -1,112 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.android.sync.model.idea; - -import com.android.SdkConstants; -import com.android.tools.idea.model.ClassJarProvider; -import com.google.idea.blaze.base.ideinfo.ArtifactLocation; -import com.google.idea.blaze.base.ideinfo.LibraryArtifact; -import com.google.idea.blaze.base.model.BlazeProjectData; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; -import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; -import com.google.idea.blaze.java.sync.model.BlazeJarLibrary; -import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.JarFileSystem; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.OrderedSet; -import java.io.File; -import java.util.List; -import org.jetbrains.annotations.Nullable; - -/** Collects class jars from the user's build. */ -public class BlazeClassJarProvider extends ClassJarProvider { - - private final Project project; - - public BlazeClassJarProvider(final Project project) { - this.project = project; - } - - @Override - @Nullable - public VirtualFile findModuleClassFile(String className, Module module) { - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - if (blazeProjectData == null) { - return null; - } - LocalFileSystem localVfs = LocalFileSystem.getInstance(); - String classNamePath = className.replace('.', File.separatorChar) + SdkConstants.DOT_CLASS; - BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class); - if (syncData == null) { - return null; - } - ArtifactLocationDecoder artifactLocationDecoder = blazeProjectData.artifactLocationDecoder; - for (File classJar : artifactLocationDecoder.decodeAll(syncData.importResult.buildOutputJars)) { - VirtualFile classJarVF = localVfs.findFileByIoFile(classJar); - if (classJarVF == null) { - continue; - } - VirtualFile classFile = findClassInJar(classJarVF, classNamePath); - if (classFile != null) { - return classFile; - } - } - return null; - } - - @Nullable - private static VirtualFile findClassInJar(final VirtualFile classJar, String classNamePath) { - VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(classJar); - if (jarRoot == null) { - return null; - } - return jarRoot.findFileByRelativePath(classNamePath); - } - - @Override - public List<VirtualFile> getModuleExternalLibraries(Module module) { - OrderedSet<VirtualFile> results = new OrderedSet<VirtualFile>(); - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - if (blazeProjectData == null) { - return results; - } - BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class); - if (syncData == null) { - return null; - } - ArtifactLocationDecoder artifactLocationDecoder = blazeProjectData.artifactLocationDecoder; - LocalFileSystem localVfs = LocalFileSystem.getInstance(); - for (BlazeJarLibrary blazeLibrary : syncData.importResult.libraries.values()) { - LibraryArtifact libraryArtifact = blazeLibrary.libraryArtifact; - ArtifactLocation classJar = libraryArtifact.classJar; - if (classJar == null) { - continue; - } - VirtualFile libVF = localVfs.findFileByIoFile(artifactLocationDecoder.decode(classJar)); - if (libVF == null) { - continue; - } - results.add(libVF); - } - - return results; - } -}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java index db6443a..541be52 100755 --- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java +++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/AndroidFacetModuleCustomizer.java
@@ -16,6 +16,7 @@ package com.google.idea.blaze.android.sync.projectstructure; import com.android.builder.model.AndroidProject; +import com.google.idea.blaze.android.compatibility.Compatibility; import com.intellij.facet.FacetManager; import com.intellij.facet.ModifiableFacetModel; import com.intellij.openapi.module.Module; @@ -55,7 +56,7 @@ private static void configureFacet(AndroidFacet facet) { JpsAndroidModuleProperties facetState = facet.getProperties(); facetState.ALLOW_USER_CONFIGURATION = false; - facetState.LIBRARY_PROJECT = true; + Compatibility.setFacetStateIsLibraryProject(facetState); facetState.MANIFEST_FILE_RELATIVE_PATH = ""; facetState.RES_FOLDER_RELATIVE_PATH = ""; facetState.ASSETS_FOLDER_RELATIVE_PATH = "";
diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java index 20d5462..cd5ff8f 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java +++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java
@@ -15,14 +15,18 @@ */ package com.google.idea.blaze.android.sync.projectstructure; +import static java.util.stream.Collectors.toSet; + import com.android.builder.model.SourceProvider; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.idea.blaze.android.projectview.GeneratedAndroidResourcesSection; import com.google.idea.blaze.android.resources.LightResourceClassService; import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationHandler; import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform; import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel; @@ -54,6 +58,7 @@ import com.intellij.openapi.roots.ModifiableRootModel; import java.io.File; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; @@ -65,149 +70,141 @@ public static void updateProjectStructure( Project project, BlazeContext context, - WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeProjectData blazeProjectData, BlazeSyncPlugin.ModuleEditor moduleEditor, Module workspaceModule, ModifiableRootModel workspaceModifiableModel, boolean isAndroidWorkspace) { - LightResourceClassService.Builder rClassBuilder = - new LightResourceClassService.Builder(project); - - if (isAndroidWorkspace) { - BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class); - if (syncData == null) { - return; - } - - AndroidSdkPlatform androidSdkPlatform = syncData.androidSdkPlatform; - if (androidSdkPlatform != null) { - int totalOrderEntries = 0; - - // Create the workspace module - updateWorkspaceModule(project, workspaceRoot, workspaceModule, androidSdkPlatform); - - // Create android resource modules - // Because we're setting up dependencies, the modules have to exist before we configure them - Map<TargetKey, AndroidResourceModule> targetToAndroidResourceModule = Maps.newHashMap(); - for (AndroidResourceModule androidResourceModule : - syncData.importResult.androidResourceModules) { - targetToAndroidResourceModule.put(androidResourceModule.targetKey, androidResourceModule); - String moduleName = moduleNameForAndroidModule(androidResourceModule.targetKey); - moduleEditor.createModule(moduleName, StdModuleTypes.JAVA); - } - - // Configure android resource modules - for (AndroidResourceModule androidResourceModule : targetToAndroidResourceModule.values()) { - TargetIdeInfo target = blazeProjectData.targetMap.get(androidResourceModule.targetKey); - AndroidIdeInfo androidIdeInfo = target.androidIdeInfo; - assert androidIdeInfo != null; - - String moduleName = moduleNameForAndroidModule(target.key); - Module module = moduleEditor.findModule(moduleName); - assert module != null; - ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module); - - updateAndroidTargetModule( - project, - workspaceRoot, - blazeProjectData.artifactLocationDecoder, - androidSdkPlatform, - target, - module, - modifiableRootModel, - androidResourceModule); - - for (TargetKey resourceDependency : - androidResourceModule.transitiveResourceDependencies) { - if (!targetToAndroidResourceModule.containsKey(resourceDependency)) { - continue; - } - String dependencyModuleName = moduleNameForAndroidModule(resourceDependency); - Module dependency = moduleEditor.findModule(dependencyModuleName); - if (dependency == null) { - continue; - } - modifiableRootModel.addModuleOrderEntry(dependency); - ++totalOrderEntries; - } - rClassBuilder.addRClass(androidIdeInfo.resourceJavaPackage, module); - // Add a dependency from the workspace to the resource module - workspaceModifiableModel.addModuleOrderEntry(module); - } - - // Collect potential android run configuration targets - Set<Label> runConfigurationModuleTargets = Sets.newHashSet(); - - // Get all explicitly mentioned targets - // Doing this now will cut down on root changes later - for (TargetExpression targetExpression : projectViewSet.listItems(TargetSection.KEY)) { - if (!(targetExpression instanceof Label)) { - continue; - } - Label label = (Label) targetExpression; - runConfigurationModuleTargets.add(label); - } - // Get any pre-existing targets - for (RunConfiguration runConfiguration : - RunManager.getInstance(project).getAllConfigurationsList()) { - BlazeAndroidRunConfigurationHandler handler = - BlazeAndroidRunConfigurationHandler.getHandlerFrom(runConfiguration); - if (handler == null) { - continue; - } - runConfigurationModuleTargets.add(handler.getLabel()); - } - - int totalRunConfigurationModules = 0; - for (Label label : runConfigurationModuleTargets) { - TargetKey targetKey = TargetKey.forPlainTarget(label); - // If it's a resource module, it will already have been created - if (targetToAndroidResourceModule.containsKey(targetKey)) { - continue; - } - // Ensure the label is a supported android rule that exists - TargetIdeInfo target = blazeProjectData.targetMap.get(targetKey); - if (target == null) { - continue; - } - if (!target.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST)) { - continue; - } - - String moduleName = moduleNameForAndroidModule(targetKey); - Module module = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA); - ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module); - updateAndroidTargetModule( - project, - workspaceRoot, - blazeProjectData.artifactLocationDecoder, - androidSdkPlatform, - target, - module, - modifiableRootModel, - null); - ++totalRunConfigurationModules; - } - - int whitelistedGenResources = - projectViewSet.listItems(GeneratedAndroidResourcesSection.KEY).size(); - context.output( - PrintOutput.log( - String.format( - "Android resource module count: %d, run config modules: %d, order entries: %d, " - + "generated resources: %d", - syncData.importResult.androidResourceModules.size(), - totalRunConfigurationModules, - totalOrderEntries, - whitelistedGenResources))); - } - } else { + if (!isAndroidWorkspace) { AndroidFacetModuleCustomizer.removeAndroidFacet(workspaceModule); + return; } - LightResourceClassService.getInstance(project).installRClasses(rClassBuilder); + BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class); + if (syncData == null) { + return; + } + AndroidSdkPlatform androidSdkPlatform = syncData.androidSdkPlatform; + if (androidSdkPlatform == null) { + return; + } + + // Configure workspace module as an android module + AndroidFacetModuleCustomizer.createAndroidFacet(workspaceModule); + + // Create android resource modules + // Because we're setting up dependencies, the modules have to exist before we configure them + Map<TargetKey, AndroidResourceModule> targetToAndroidResourceModule = Maps.newHashMap(); + for (AndroidResourceModule androidResourceModule : + syncData.importResult.androidResourceModules) { + targetToAndroidResourceModule.put(androidResourceModule.targetKey, androidResourceModule); + String moduleName = moduleNameForAndroidModule(androidResourceModule.targetKey); + Module module = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA); + AndroidFacetModuleCustomizer.createAndroidFacet(module); + } + + // Configure android resource modules + int totalOrderEntries = 0; + for (AndroidResourceModule androidResourceModule : targetToAndroidResourceModule.values()) { + TargetIdeInfo target = blazeProjectData.targetMap.get(androidResourceModule.targetKey); + AndroidIdeInfo androidIdeInfo = target.androidIdeInfo; + assert androidIdeInfo != null; + + String moduleName = moduleNameForAndroidModule(target.key); + Module module = moduleEditor.findModule(moduleName); + assert module != null; + ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module); + + Collection<File> resources = + blazeProjectData.artifactLocationDecoder.decodeAll(androidResourceModule.resources); + ResourceModuleContentRootCustomizer.setupContentRoots(modifiableRootModel, resources); + + for (TargetKey resourceDependency : androidResourceModule.transitiveResourceDependencies) { + if (!targetToAndroidResourceModule.containsKey(resourceDependency)) { + continue; + } + String dependencyModuleName = moduleNameForAndroidModule(resourceDependency); + Module dependency = moduleEditor.findModule(dependencyModuleName); + if (dependency == null) { + continue; + } + modifiableRootModel.addModuleOrderEntry(dependency); + ++totalOrderEntries; + } + // Add a dependency from the workspace to the resource module + workspaceModifiableModel.addModuleOrderEntry(module); + } + + List<TargetIdeInfo> runConfigurationTargets = + getRunConfigurationTargets( + project, projectViewSet, blazeProjectData, targetToAndroidResourceModule.keySet()); + for (TargetIdeInfo target : runConfigurationTargets) { + TargetKey targetKey = target.key; + String moduleName = moduleNameForAndroidModule(targetKey); + Module module = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA); + AndroidFacetModuleCustomizer.createAndroidFacet(module); + } + + int whitelistedGenResources = + projectViewSet.listItems(GeneratedAndroidResourcesSection.KEY).size(); + context.output( + PrintOutput.log( + String.format( + "Android resource module count: %d, run config modules: %d, order entries: %d, " + + "generated resources: %d", + syncData.importResult.androidResourceModules.size(), + runConfigurationTargets.size(), + totalOrderEntries, + whitelistedGenResources))); + } + + // Collect potential android run configuration targets + private static List<TargetIdeInfo> getRunConfigurationTargets( + Project project, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + Set<TargetKey> androidResourceModules) { + List<TargetIdeInfo> result = Lists.newArrayList(); + Set<Label> runConfigurationModuleTargets = Sets.newHashSet(); + + // Get all explicitly mentioned targets + // Doing this now will cut down on root changes later + for (TargetExpression targetExpression : projectViewSet.listItems(TargetSection.KEY)) { + if (!(targetExpression instanceof Label)) { + continue; + } + Label label = (Label) targetExpression; + runConfigurationModuleTargets.add(label); + } + // Get any pre-existing targets + for (RunConfiguration runConfiguration : + RunManager.getInstance(project).getAllConfigurationsList()) { + BlazeAndroidRunConfigurationHandler handler = + BlazeAndroidRunConfigurationHandler.getHandlerFrom(runConfiguration); + if (handler == null) { + continue; + } + runConfigurationModuleTargets.add(handler.getLabel()); + } + + for (Label label : runConfigurationModuleTargets) { + TargetKey targetKey = TargetKey.forPlainTarget(label); + // If it's a resource module, it will already have been created + if (androidResourceModules.contains(targetKey)) { + continue; + } + // Ensure the label is a supported android rule that exists + TargetIdeInfo target = blazeProjectData.targetMap.get(targetKey); + if (target == null) { + continue; + } + if (!target.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST)) { + continue; + } + result.add(target); + } + return result; } /** Ensures a suitable module exists for the given android target. */ @@ -220,7 +217,6 @@ return module; } - WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData == null) { @@ -246,20 +242,10 @@ BlazeSyncPlugin.ModuleEditor moduleEditor = BlazeProjectDataManager.getInstance(project).editModules(); Module newModule = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA); - ModifiableRootModel modifiableRootModel = moduleEditor.editModule(newModule); - ApplicationManager.getApplication() .runWriteAction( () -> { - updateAndroidTargetModule( - project, - workspaceRoot, - blazeProjectData.artifactLocationDecoder, - androidSdkPlatform, - target, - newModule, - modifiableRootModel, - null); + AndroidFacetModuleCustomizer.createAndroidFacet(newModule); moduleEditor.commit(); }); return newModule; @@ -273,8 +259,122 @@ .replace(':', '.'); } + public static void updateInMemoryState( + Project project, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + Module workspaceModule, + boolean isAndroidWorkspace) { + LightResourceClassService.Builder rClassBuilder = + new LightResourceClassService.Builder(project); + AndroidResourceModuleRegistry registry = AndroidResourceModuleRegistry.getInstance(project); + registry.clear(); + if (isAndroidWorkspace) { + updateInMemoryState( + project, + workspaceRoot, + projectViewSet, + blazeProjectData, + workspaceModule, + registry, + rClassBuilder); + } + LightResourceClassService.getInstance(project).installRClasses(rClassBuilder); + } + + private static void updateInMemoryState( + Project project, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + Module workspaceModule, + AndroidResourceModuleRegistry registry, + LightResourceClassService.Builder rClassBuilder) { + BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class); + if (syncData == null) { + return; + } + AndroidSdkPlatform androidSdkPlatform = syncData.androidSdkPlatform; + if (androidSdkPlatform == null) { + return; + } + + updateWorkspaceModuleFacetInMemoryState( + project, workspaceRoot, workspaceModule, androidSdkPlatform); + + ArtifactLocationDecoder artifactLocationDecoder = blazeProjectData.artifactLocationDecoder; + ModuleManager moduleManager = ModuleManager.getInstance(project); + for (AndroidResourceModule androidResourceModule : + syncData.importResult.androidResourceModules) { + TargetIdeInfo target = blazeProjectData.targetMap.get(androidResourceModule.targetKey); + String moduleName = moduleNameForAndroidModule(target.key); + Module module = moduleManager.findModuleByName(moduleName); + registry.put(module, androidResourceModule); + + AndroidIdeInfo androidIdeInfo = target.androidIdeInfo; + assert androidIdeInfo != null; + + updateModuleFacetInMemoryState( + project, + androidSdkPlatform, + module, + moduleDirectoryForAndroidTarget(workspaceRoot, target), + manifestFileForAndroidTarget( + artifactLocationDecoder, + androidIdeInfo, + moduleDirectoryForAndroidTarget(workspaceRoot, target)), + androidIdeInfo.resourceJavaPackage, + artifactLocationDecoder.decodeAll(androidResourceModule.transitiveResources)); + rClassBuilder.addRClass(androidIdeInfo.resourceJavaPackage, module); + } + + Set<TargetKey> androidResourceModules = + syncData + .importResult + .androidResourceModules + .stream() + .map(androidResourceModule -> androidResourceModule.targetKey) + .collect(toSet()); + List<TargetIdeInfo> runConfigurationTargets = + getRunConfigurationTargets( + project, projectViewSet, blazeProjectData, androidResourceModules); + for (TargetIdeInfo target : runConfigurationTargets) { + String moduleName = moduleNameForAndroidModule(target.key); + Module module = moduleManager.findModuleByName(moduleName); + AndroidIdeInfo androidIdeInfo = target.androidIdeInfo; + assert androidIdeInfo != null; + updateModuleFacetInMemoryState( + project, + androidSdkPlatform, + module, + moduleDirectoryForAndroidTarget(workspaceRoot, target), + manifestFileForAndroidTarget( + artifactLocationDecoder, + androidIdeInfo, + moduleDirectoryForAndroidTarget(workspaceRoot, target)), + androidIdeInfo.resourceJavaPackage, + ImmutableList.of()); + } + } + + private static File moduleDirectoryForAndroidTarget( + WorkspaceRoot workspaceRoot, TargetIdeInfo target) { + return workspaceRoot.fileForPath(target.key.label.blazePackage()); + } + + private static File manifestFileForAndroidTarget( + ArtifactLocationDecoder artifactLocationDecoder, + AndroidIdeInfo androidIdeInfo, + File moduleDirectory) { + ArtifactLocation manifestArtifactLocation = androidIdeInfo.manifest; + return manifestArtifactLocation != null + ? artifactLocationDecoder.decode(manifestArtifactLocation) + : new File(moduleDirectory, "AndroidManifest.xml"); + } + /** Updates the shared workspace module with android info. */ - private static void updateWorkspaceModule( + private static void updateWorkspaceModuleFacetInMemoryState( Project project, WorkspaceRoot workspaceRoot, Module workspaceModule, @@ -283,8 +383,7 @@ File manifest = new File(workspaceRoot.directory(), "AndroidManifest.xml"); String resourceJavaPackage = ":workspace"; ImmutableList<File> transitiveResources = ImmutableList.of(); - - createAndroidModel( + updateModuleFacetInMemoryState( project, androidSdkPlatform, workspaceModule, @@ -294,49 +393,7 @@ transitiveResources); } - /** Updates a module from an android rule. */ - private static void updateAndroidTargetModule( - Project project, - WorkspaceRoot workspaceRoot, - ArtifactLocationDecoder artifactLocationDecoder, - AndroidSdkPlatform androidSdkPlatform, - TargetIdeInfo target, - Module module, - ModifiableRootModel modifiableRootModel, - @Nullable AndroidResourceModule androidResourceModule) { - - Collection<File> resources = - androidResourceModule != null - ? artifactLocationDecoder.decodeAll(androidResourceModule.resources) - : ImmutableList.of(); - Collection<File> transitiveResources = - androidResourceModule != null - ? artifactLocationDecoder.decodeAll(androidResourceModule.transitiveResources) - : ImmutableList.of(); - - AndroidIdeInfo androidIdeInfo = target.androidIdeInfo; - assert androidIdeInfo != null; - - File moduleDirectory = workspaceRoot.fileForPath(target.key.label.blazePackage()); - ArtifactLocation manifestArtifactLocation = androidIdeInfo.manifest; - File manifest = - manifestArtifactLocation != null - ? artifactLocationDecoder.decode(manifestArtifactLocation) - : new File(moduleDirectory, "AndroidManifest.xml"); - String resourceJavaPackage = androidIdeInfo.resourceJavaPackage; - ResourceModuleContentRootCustomizer.setupContentRoots(modifiableRootModel, resources); - - createAndroidModel( - project, - androidSdkPlatform, - module, - moduleDirectory, - manifest, - resourceJavaPackage, - transitiveResources); - } - - private static void createAndroidModel( + private static void updateModuleFacetInMemoryState( Project project, AndroidSdkPlatform androidSdkPlatform, Module module, @@ -344,7 +401,6 @@ File manifest, String resourceJavaPackage, Collection<File> transitiveResources) { - AndroidFacetModuleCustomizer.createAndroidFacet(module); SourceProvider sourceProvider = new SourceProviderImpl(module.getName(), manifest, transitiveResources); BlazeAndroidModel androidModel =
diff --git a/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java b/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java index 05b52e4..e4ae1b5 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java +++ b/aswb/src/com/google/idea/blaze/android/sync/sdk/AndroidSdkFromProjectView.java
@@ -17,6 +17,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.Lists; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidSdkUtils; import com.google.idea.blaze.android.projectview.AndroidSdkPlatformSection; import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform; import com.google.idea.blaze.base.projectview.ProjectViewSet; @@ -29,7 +30,6 @@ import javax.annotation.Nullable; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.android.sdk.AndroidSdkAdditionalData; -import org.jetbrains.android.sdk.AndroidSdkUtils; /** Calculates AndroidSdkPlatform. */ public class AndroidSdkFromProjectView {
diff --git a/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java b/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java index 933679d..fcbe1f9 100644 --- a/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java +++ b/aswb/src/com/google/idea/blaze/android/sync/sdk/SdkUtil.java
@@ -16,6 +16,7 @@ package com.google.idea.blaze.android.sync.sdk; import com.android.tools.idea.updater.configure.SdkUpdaterConfigurableProvider; +import com.google.idea.blaze.android.compatibility.Compatibility.AndroidSdkUtils; import com.google.idea.blaze.android.sync.model.AndroidSdkPlatform; import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData; import com.google.idea.blaze.base.model.BlazeProjectData; @@ -26,7 +27,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import org.jetbrains.android.sdk.AndroidPlatform; -import org.jetbrains.android.sdk.AndroidSdkUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java index 0a222a7..7eca0c6 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java
@@ -90,58 +90,6 @@ } @Test - public void readAndWriteShouldMigrate() throws Exception { - String oldXml = - "<?xml version=\"1.0\"?>" - + "<configuration blaze-native-debug=\"true\">" - + " <blaze-user-flag>--flag1</blaze-user-flag>" - + " <blaze-user-flag>--flag2</blaze-user-flag>" - + " <option name=\"USE_LAST_SELECTED_DEVICE\" value=\"true\" />" - + " <option name=\"PREFERRED_AVD\" value=\"some avd\" />" - + DEBUGGER_STATE_AUTO_RAW_XML - + DEBUGGER_STATE_NATIVE_RAW_XML - + DEBUGGER_STATE_JAVA_RAW_XML - + DEBUGGER_STATE_HYBRID_RAW_XML - + DEBUGGER_STATE_BLAZE_AUTO_RAW_XML - + "</configuration>"; - Element oldElement = saxBuilder.build(new StringReader(oldXml)).getRootElement(); - - state.readExternal(oldElement); - Element migratedElement = new Element("configuration"); - state.writeExternal(migratedElement); - - assertThat(migratedElement.getChildren()).hasSize(4); - assertThat(migratedElement.getAttribute("blaze-native-debug").getValue()).isEqualTo("true"); - - List<Element> flagElements = migratedElement.getChildren("blaze-user-flag"); - assertThat(flagElements).hasSize(2); - assertThat(flagElements.get(0).getText()).isEqualTo("--flag1"); - assertThat(flagElements.get(1).getText()).isEqualTo("--flag2"); - - Element deployTargetStatesElement = migratedElement.getChild("android-deploy-target-states"); - assertThat(xmlOutputter.outputString(deployTargetStatesElement)) - .isEqualTo(formatRawXml(DEPLOY_TARGET_STATES_RAW_XML)); - - Element debuggerStatesElement = migratedElement.getChild("android-debugger-states"); - assertThat(debuggerStatesElement.getChildren()).hasSize(5); - Element debuggerStateElement = debuggerStatesElement.getChild("Auto"); - assertThat(xmlOutputter.outputString(debuggerStateElement)) - .isEqualTo(formatRawXml(DEBUGGER_STATE_AUTO_RAW_XML)); - debuggerStateElement = debuggerStatesElement.getChild("Native"); - assertThat(xmlOutputter.outputString(debuggerStateElement)) - .isEqualTo(formatRawXml(DEBUGGER_STATE_NATIVE_RAW_XML)); - debuggerStateElement = debuggerStatesElement.getChild("Java"); - assertThat(xmlOutputter.outputString(debuggerStateElement)) - .isEqualTo(formatRawXml(DEBUGGER_STATE_JAVA_RAW_XML)); - debuggerStateElement = debuggerStatesElement.getChild("Hybrid"); - assertThat(xmlOutputter.outputString(debuggerStateElement)) - .isEqualTo(formatRawXml(DEBUGGER_STATE_HYBRID_RAW_XML)); - debuggerStateElement = debuggerStatesElement.getChild("BlazeAuto"); - assertThat(xmlOutputter.outputString(debuggerStateElement)) - .isEqualTo(formatRawXml(DEBUGGER_STATE_BLAZE_AUTO_RAW_XML)); - } - - @Test public void readAndWriteShouldRemoveExtraElements() throws Exception { String oldXml = "<?xml version=\"1.0\"?>"
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java index 76cd2e5..05de9a5 100644 --- a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java +++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java
@@ -63,7 +63,6 @@ state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); state.setMobileInstall(true); state.setUseSplitApksIfPossible(false); - state.setInstantRun(true); state.setUseWorkProfileIfPresent(true); state.setUserId(2); state.setDeepLink("http://deeplink"); @@ -83,7 +82,6 @@ .isEqualTo(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); assertThat(readState.mobileInstall()).isTrue(); assertThat(readState.useSplitApksIfPossible()).isFalse(); - assertThat(readState.instantRun()).isTrue(); assertThat(readState.useWorkProfileIfPresent()).isTrue(); assertThat(readState.getUserId()).isEqualTo(2); assertThat(readState.getDeepLink()).isEqualTo("http://deeplink"); @@ -107,7 +105,6 @@ assertThat(readState.getMode()).isEqualTo(state.getMode()); assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall()); assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible()); - assertThat(readState.instantRun()).isEqualTo(state.instantRun()); assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent()); assertThat(readState.getUserId()).isEqualTo(state.getUserId()); assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink()); @@ -125,7 +122,6 @@ state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); state.setMobileInstall(true); state.setUseSplitApksIfPossible(false); - state.setInstantRun(true); state.setUseWorkProfileIfPresent(true); state.setUserId(2); state.setDeepLink("http://deeplink"); @@ -151,7 +147,6 @@ state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY); state.setMobileInstall(true); state.setUseSplitApksIfPossible(false); - state.setInstantRun(true); state.setUseWorkProfileIfPresent(true); state.setUserId(2); // We don't test DeepLink because it is not exposed in the editor. @@ -171,7 +166,6 @@ assertThat(readState.getMode()).isEqualTo(state.getMode()); assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall()); assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible()); - assertThat(readState.instantRun()).isEqualTo(state.instantRun()); assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent()); assertThat(readState.getUserId()).isEqualTo(state.getUserId()); // We don't test DeepLink because it is not exposed in the editor. @@ -197,7 +191,6 @@ assertThat(readState.getMode()).isEqualTo(state.getMode()); assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall()); assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible()); - assertThat(readState.instantRun()).isEqualTo(state.instantRun()); assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent()); assertThat(readState.getUserId()).isEqualTo(state.getUserId()); // We don't test DeepLink because it is not exposed in the editor.
diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/model/idea/AndroidResourceModuleRegistryTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/model/idea/AndroidResourceModuleRegistryTest.java new file mode 100644 index 0000000..9a8e66c --- /dev/null +++ b/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/model/idea/AndroidResourceModuleRegistryTest.java
@@ -0,0 +1,117 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.android.sync.importer.model.idea; + +import static com.google.common.truth.Truth.THROW_ASSERTION_ERROR; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import com.google.idea.blaze.android.sync.model.AndroidResourceModule; +import com.google.idea.blaze.android.sync.model.AndroidResourceModuleRegistry; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.model.primitives.Label; +import com.intellij.openapi.module.Module; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link AndroidResourceModuleRegistry}. */ +@RunWith(JUnit4.class) +public class AndroidResourceModuleRegistryTest extends BlazeTestCase { + private AndroidResourceModuleRegistry registry; + + @Override + protected void initTest( + @NotNull Container applicationServices, @NotNull Container projectServices) { + super.initTest(applicationServices, projectServices); + projectServices.register( + AndroidResourceModuleRegistry.class, new AndroidResourceModuleRegistry()); + + registry = AndroidResourceModuleRegistry.getInstance(getProject()); + } + + @Test + public void testPutAndGet() { + Module moduleOne = mock(Module.class); + Module moduleTwo = mock(Module.class); + Module moduleThree = mock(Module.class); + AndroidResourceModule resourceModuleOne = + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo/bar:one"))).build(); + AndroidResourceModule resourceModuleTwo = + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo/bar:two"))).build(); + AndroidResourceModule resourceModuleThree = + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo/bar:three"))) + .build(); + registry.put(moduleOne, resourceModuleOne); + registry.put(moduleTwo, resourceModuleTwo); + registry.put(moduleThree, resourceModuleThree); + + assertThat(registry.get(moduleOne)).isEqualTo(resourceModuleOne); + assertThat(registry.get(moduleTwo)).isEqualTo(resourceModuleTwo); + assertThat(registry.get(moduleThree)).isEqualTo(resourceModuleThree); + assertThat(registry.getTargetKey(moduleOne)).isEqualTo(resourceModuleOne.targetKey); + assertThat(registry.getTargetKey(moduleTwo)).isEqualTo(resourceModuleTwo.targetKey); + assertThat(registry.getTargetKey(moduleThree)).isEqualTo(resourceModuleThree.targetKey); + assertThat(registry.getLabel(moduleOne)).isEqualTo(resourceModuleOne.targetKey.label); + assertThat(registry.getLabel(moduleTwo)).isEqualTo(resourceModuleTwo.targetKey.label); + assertThat(registry.getLabel(moduleThree)).isEqualTo(resourceModuleThree.targetKey.label); + } + + @Test + public void testPutSameKeyDifferentValues() { + Module module = mock(Module.class); + AndroidResourceModule resourceModuleOne = + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo/bar:one"))).build(); + AndroidResourceModule resourceModuleTwo = + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo/bar:two"))).build(); + registry.put(module, resourceModuleOne); + registry.put(module, resourceModuleTwo); + assertThat(registry.get(module)).isEqualTo(resourceModuleTwo); + } + + @Test + public void testPutDifferentKeysSameValue() { + Module moduleOne = mock(Module.class); + Module moduleTwo = mock(Module.class); + AndroidResourceModule resourceModule = + AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//foo/bar:one"))).build(); + registry.put(moduleOne, resourceModule); + try { + registry.put(moduleTwo, resourceModule); + THROW_ASSERTION_ERROR.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException ignored) { + // ignored + } + assertThat(registry.get(moduleOne)).isEqualTo(resourceModule); + assertThat(registry.get(moduleTwo)).isNull(); + } + + @Test + public void testGetNull() { + assertThat(registry.get(null)).isNull(); + assertThat(registry.getTargetKey(null)).isNull(); + assertThat(registry.getLabel(null)).isNull(); + } + + @Test + public void testGetWithoutPut() { + assertThat(registry.get(mock(Module.class))).isNull(); + assertThat(registry.getTargetKey(mock(Module.class))).isNull(); + assertThat(registry.getLabel(mock(Module.class))).isNull(); + } +}
diff --git a/base/BUILD b/base/BUILD index 6902fd5..850f1dd 100644 --- a/base/BUILD +++ b/base/BUILD
@@ -6,11 +6,13 @@ resources = glob(["resources/**/*"]), visibility = ["//visibility:public"], deps = [ + "//common/actionhelper", "//common/binaryhelper", "//common/experiments", "//common/formatter", "//intellij_platform_sdk:plugin_api", "//proto_deps", + "//sdkcompat", "@jsr305_annotations//jar", ], ) @@ -29,6 +31,7 @@ deps = [ "//base", "//intellij_platform_sdk:plugin_api_for_tests", + "//sdkcompat", "@jsr305_annotations//jar", "@junit//jar", ],
diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml index 1e12599..34d33f4 100644 --- a/base/src/META-INF/blaze-base.xml +++ b/base/src/META-INF/blaze-base.xml
@@ -15,46 +15,86 @@ --> <idea-plugin> <actions> - <action id="MakeBlazeProject" class="com.google.idea.blaze.base.actions.BlazeMakeProjectAction" use-shortcut-of="CompileDirty" icon="AllIcons.Actions.Compile"> + <action id="MakeBlazeProject" + class="com.google.idea.blaze.base.actions.BlazeMakeProjectAction" + text="Compile Project" + use-shortcut-of="CompileDirty" + icon="AllIcons.Actions.Compile"> </action> - <action id="MakeBlazeModule" class="com.google.idea.blaze.base.actions.BlazeCompileFileAction"> + <action id="MakeBlazeModule" + class="com.google.idea.blaze.base.actions.BlazeCompileFileAction" + text="Compile File"> </action> - <action id="Blaze.IncrementalSyncProject" class="com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction" icon="BlazeIcons.Blaze"> + <action id="Blaze.IncrementalSyncProject" + class="com.google.idea.blaze.base.sync.actions.IncrementalSyncProjectAction" + text="Sync Project with BUILD Files" + icon="BlazeIcons.Blaze"> </action> - <action id="Blaze.FullSyncProject" class="com.google.idea.blaze.base.sync.actions.FullSyncProjectAction" icon="BlazeIcons.BlazeSlow"> + <action id="Blaze.FullSyncProject" + class="com.google.idea.blaze.base.sync.actions.FullSyncProjectAction" + text="Non-Incrementally Sync Project with BUILD Files" + icon="BlazeIcons.BlazeSlow"> </action> - <action id="Blaze.SyncWorkingSet" class="com.google.idea.blaze.base.sync.actions.SyncWorkingSetAction" icon="BlazeIcons.Blaze" text="Sync Working Set"> + <action id="Blaze.SyncWorkingSet" + class="com.google.idea.blaze.base.sync.actions.SyncWorkingSetAction" + text="Sync Working Set" + icon="BlazeIcons.Blaze"> </action> - <action id="Blaze.ExpandSyncToWorkingSet" class="com.google.idea.blaze.base.sync.actions.ExpandSyncToWorkingSetAction" text="Expand Sync to Working Set"> + <action id="Blaze.ExpandSyncToWorkingSet" + class="com.google.idea.blaze.base.sync.actions.ExpandSyncToWorkingSetAction" + text="Expand Sync to Working Set"> </action> - <action id="Blaze.ShowPerformanceWarnings" class="com.google.idea.blaze.base.sync.actions.ShowPerformanceWarningsToggleAction" text="Show Performance Warnings"> + <action id="Blaze.ShowPerformanceWarnings" + class="com.google.idea.blaze.base.sync.actions.ShowPerformanceWarningsToggleAction" + text="Show Performance Warnings"> </action> - <action id="Blaze.EditProjectView" class="com.google.idea.blaze.base.settings.ui.EditProjectViewAction" text="Edit Project View..." icon="BlazeIcons.Blaze"> + <action id="Blaze.EditProjectView" + class="com.google.idea.blaze.base.settings.ui.EditProjectViewAction" + text="Open Project View Files"> </action> - <action class="com.google.idea.blaze.base.buildmap.OpenCorrespondingBuildFile" - id="Blaze.OpenCorrespondingBuildFile" - icon="BlazeIcons.Blaze" - text="Open Corresponding BUILD File"> + id="Blaze.OpenCorrespondingBuildFile" + text="Open Corresponding BUILD File"> </action> <action class="com.google.idea.blaze.base.sync.actions.PartialSyncAction" - id="Blaze.PartialSync" - icon="BlazeIcons.Blaze"> + id="Blaze.PartialSync" + text="Partially Sync File" + icon="BlazeIcons.Blaze"> </action> + <action id="Blaze.ExportRunConfigurations" + class="com.google.idea.blaze.base.run.exporter.ExportRunConfigurationAction" + text="Export Run Configurations" + icon="AllIcons.Actions.Export"> + </action> + <action id="Blaze.NewPackageAction" + class="com.google.idea.blaze.base.ide.NewBlazePackageAction" + text="New Package"/> + <action id="Blaze.NewRuleAction" + class="com.google.idea.blaze.base.ide.NewBlazeRuleAction" + text="New Rule" + popup="true"/> <group id="Blaze.MainMenuActionGroup" class="com.google.idea.blaze.base.actions.BlazeMenuGroup"> <add-to-group group-id="MainMenu" anchor="before" relative-to-action="HelpMenu"/> - <reference id="MakeBlazeProject"/> - <reference id="MakeBlazeModule"/> - <separator/> <reference id="Blaze.EditProjectView"/> + <group id ="Blaze.SyncMenuGroup" text="Sync" popup="true"> + <reference id="Blaze.IncrementalSyncProject"/> + <reference id="Blaze.FullSyncProject"/> + <reference id="Blaze.SyncWorkingSet"/> + <reference id="Blaze.PartialSync"/> + <reference id="Blaze.ExpandSyncToWorkingSet"/> + <reference id="Blaze.ShowPerformanceWarnings"/> + </group> + <group id="Blaze.BuildMenuGroup" text="Build" popup="true"> + <reference id="MakeBlazeProject"/> + <reference id="MakeBlazeModule"/> + </group> + <!--Add popup groups anchored after this bookmark--> + <group id="Blaze.MenuGroupsBookmark"/> <separator/> - <reference id="Blaze.IncrementalSyncProject"/> - <reference id="Blaze.FullSyncProject"/> - <reference id="Blaze.SyncWorkingSet"/> - <reference id="Blaze.PartialSync"/> - <reference id="Blaze.ExpandSyncToWorkingSet"/> - <reference id="Blaze.ShowPerformanceWarnings"/> + <reference id="Blaze.ExportRunConfigurations"/> + <!--Add single menu items anchored after this bookmark--> + <group id="Blaze.MenuFooter"/> </group> <group id="Blaze.MainToolBarActionGroup"> @@ -65,8 +105,8 @@ <group id="Blaze.NewActions" text="Edit Blaze structure" description="Create new Blaze packages, rules, etc."> <add-to-group group-id="NewGroup" anchor="first"/> - <action id="Blaze.NewPackageAction" class="com.google.idea.blaze.base.ide.NewBlazePackageAction" popup="true"/> - <action id="Blaze.NewRuleAction" class="com.google.idea.blaze.base.ide.NewBlazeRuleAction" popup="true"/> + <reference id="Blaze.NewPackageAction"/> + <reference id="Blaze.NewRuleAction"/> <separator/> </group> @@ -137,6 +177,8 @@ <projectService serviceImplementation="com.google.idea.blaze.base.buildmap.FileToBuildMap"/> <projectService serviceInterface="com.google.idea.blaze.base.targetmaps.SourceToTargetMap" serviceImplementation="com.google.idea.blaze.base.targetmaps.SourceToTargetMapImpl"/> + <projectService serviceInterface="com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap" + serviceImplementation="com.google.idea.blaze.base.targetmaps.TransitiveDependencyMap"/> <projectService serviceImplementation="com.google.idea.blaze.base.settings.BlazeImportSettingsManager"/> <projectService serviceImplementation="com.google.idea.blaze.base.settings.BlazeImportSettingsManagerLegacy"/> <applicationService serviceImplementation="com.google.idea.blaze.base.settings.BlazeUserSettings"/> @@ -147,6 +189,8 @@ <applicationService serviceInterface="com.google.idea.blaze.base.prefetch.PrefetchService" serviceImplementation="com.google.idea.blaze.base.prefetch.PrefetchServiceImpl"/> <applicationService serviceImplementation="com.google.idea.blaze.base.wizard2.BlazeWizardUserSettingsStorage"/> + <applicationService serviceInterface="com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider" + serviceImplementation="com.google.idea.blaze.base.wizard2.BazelWizardOptionProvider"/> <projectService serviceInterface="com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider" serviceImplementation="com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProviderImpl"/> <configurationType implementation="com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType"/> @@ -159,6 +203,7 @@ <stepsBeforeRunProvider implementation="com.google.idea.blaze.base.run.BlazeBeforeRunTaskProvider"/> <applicationService serviceInterface="com.google.idea.blaze.base.help.BlazeHelpHandler" serviceImplementation="com.google.idea.blaze.base.help.BlazeHelpHandlerImpl"/> + <applicationService serviceImplementation="com.google.idea.blaze.base.actions.BlazeBuildService"/> <additionalTextAttributes scheme="Default" file="base/resources/colorSchemes/BuildDefault.xml"/> <typedHandler implementation="com.google.idea.blaze.base.lang.buildfile.completion.BuildCompletionAutoPopupHandler"/> @@ -254,8 +299,6 @@ <extensionPoint qualifiedName="com.google.idea.blaze.PsiFileProvider" interface="com.google.idea.blaze.base.lang.buildfile.search.PsiFileProvider"/> <extensionPoint qualifiedName="com.google.idea.blaze.VcsHandler" interface="com.google.idea.blaze.base.vcs.BlazeVcsHandler"/> - <extensionPoint qualifiedName="com.google.idea.blaze.BlazeWizardOptionProvider" - interface="com.google.idea.blaze.base.wizard2.BlazeWizardOptionProvider"/> <extensionPoint qualifiedName="com.google.idea.blaze.DefaultSdkProvider" interface="com.google.idea.blaze.base.sync.sdk.DefaultSdkProvider"/> <extensionPoint qualifiedName="com.google.idea.blaze.BuildFlagsProvider" interface="com.google.idea.blaze.base.command.BuildFlagsProvider"/> @@ -269,6 +312,7 @@ <extensionPoint qualifiedName="com.google.idea.blaze.TestTargetHeuristic" interface="com.google.idea.blaze.base.run.TestTargetHeuristic"/> <extensionPoint qualifiedName="com.google.idea.blaze.ProjectDataDirectoryValidator" interface="com.google.idea.blaze.base.wizard2.ProjectDataDirectoryValidator"/> <extensionPoint qualifiedName="com.google.idea.blaze.AspectStrategyProvider" interface="com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProvider"/> + <extensionPoint qualifiedName="com.google.idea.blaze.DistributedExecutorSupport" interface="com.google.idea.blaze.base.run.DistributedExecutorSupport"/> </extensionPoints> <extensions defaultExtensionNs="com.google.idea.blaze"> @@ -276,9 +320,7 @@ <SyncListener implementation="com.google.idea.blaze.base.sync.status.BlazeSyncStatusListener"/> <SyncListener implementation="com.google.idea.blaze.base.run.testmap.TestTargetFilterImpl$ClearTestMap"/> <SyncListener implementation="com.google.idea.blaze.base.targetmaps.SourceToTargetMapImpl$ClearSourceToTargetMap"/> - <SyncListener implementation="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProviderImpl"/> <SyncPlugin implementation="com.google.idea.blaze.base.lang.buildfile.sync.BuildLangSyncPlugin"/> - <BlazeWizardOptionProvider implementation="com.google.idea.blaze.base.wizard2.BazelWizardOptionProvider"/> <BuildFlagsProvider implementation="com.google.idea.blaze.base.command.BuildFlagsProviderImpl"/> <VcsHandler implementation="com.google.idea.blaze.base.vcs.git.GitBlazeVcsHandler"/> <VcsHandler implementation="com.google.idea.blaze.base.vcs.FallbackBlazeVcsHandler" order="last" id="fallback"/>
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeAction.java deleted file mode 100644 index ed31396..0000000 --- a/base/src/com/google/idea/blaze/base/actions/BlazeAction.java +++ /dev/null
@@ -1,59 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.base.actions; - -import com.google.idea.blaze.base.settings.Blaze; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.project.Project; -import javax.swing.Icon; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** Base class action that hides for non-blaze projects. */ -public abstract class BlazeAction extends AnAction { - protected BlazeAction() {} - - protected BlazeAction(Icon icon) { - super(icon); - } - - protected BlazeAction(@Nullable String text) { - super(text); - } - - protected BlazeAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) { - super(text, description, icon); - } - - @Override - public final void update(AnActionEvent e) { - if (!isBlazeProject(e)) { - e.getPresentation().setEnabledAndVisible(false); - return; - } - - e.getPresentation().setEnabledAndVisible(true); - doUpdate(e); - } - - protected void doUpdate(@NotNull AnActionEvent e) {} - - private static boolean isBlazeProject(@NotNull AnActionEvent e) { - Project project = e.getProject(); - return project != null && Blaze.isBlazeProject(project); - } -}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java b/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java new file mode 100644 index 0000000..4da44f1 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/actions/BlazeBuildService.java
@@ -0,0 +1,141 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.actions; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Lists; +import com.google.idea.blaze.base.async.executor.BlazeExecutor; +import com.google.idea.blaze.base.experiments.ExperimentScope; +import com.google.idea.blaze.base.filecache.FileCaches; +import com.google.idea.blaze.base.metrics.Action; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.TargetExpression; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.projectview.section.sections.TargetSection; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.ScopedTask; +import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope; +import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; +import com.google.idea.blaze.base.scope.scopes.IssuesScope; +import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope; +import com.google.idea.blaze.base.scope.scopes.NotificationScope; +import com.google.idea.blaze.base.scope.scopes.TimingScope; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.util.SaveUtil; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import java.util.List; + +/** Utility to build various collections of targets. */ +public class BlazeBuildService { + public static BlazeBuildService getInstance() { + return ServiceManager.getService(BlazeBuildService.class); + } + + public void buildFile(Project project, String fileName, ImmutableCollection<Label> targets) { + if (project == null || !Blaze.isBlazeProject(project) || fileName == null) { + return; + } + buildTargetExpressions( + project, + Lists.newArrayList(targets), + ProjectViewManager.getInstance(project).getProjectViewSet(), + new LoggedTimingScope(project, Action.MAKE_MODULE_TOTAL_TIME), + new NotificationScope( + project, + "Make", + "Make " + fileName, + "Make " + fileName + " completed successfully", + "Make " + fileName + " failed")); + } + + public void buildProject(Project project) { + if (project == null || !Blaze.isBlazeProject(project)) { + return; + } + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + if (projectViewSet == null) { + return; + } + buildTargetExpressions( + project, + projectViewSet.listItems(TargetSection.KEY), + projectViewSet, + new LoggedTimingScope(project, Action.MAKE_PROJECT_TOTAL_TIME), + new NotificationScope( + project, + "Make", + "Make project", + "Make project completed successfully", + "Make project failed")); + } + + @VisibleForTesting + void buildTargetExpressions( + Project project, + List<TargetExpression> targets, + ProjectViewSet projectViewSet, + LoggedTimingScope loggedTimingScope, + NotificationScope notificationScope) { + if (targets.isEmpty() || projectViewSet == null) { + return; + } + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (blazeProjectData == null) { + return; + } + BlazeExecutor.submitTask( + project, + new ScopedTask() { + @Override + public void execute(BlazeContext context) { + context + .push(new ExperimentScope()) + .push(new BlazeConsoleScope.Builder(project).build()) + .push(new IssuesScope(project)) + .push(new IdeaLogScope()) + .push(new TimingScope("Make")) + .push(loggedTimingScope) + .push(notificationScope); + + WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); + BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance(); + + SaveUtil.saveAllFiles(); + BlazeIdeInterface.BuildResult buildResult = + blazeIdeInterface.compileIdeArtifacts( + project, + context, + workspaceRoot, + projectViewSet, + blazeProjectData.blazeVersionData, + targets); + FileCaches.refresh(project); + + if (buildResult != BlazeIdeInterface.BuildResult.SUCCESS) { + context.setHasError(); + } + } + }); + } +}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java index 00ad470..130de1b 100644 --- a/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java +++ b/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
@@ -17,65 +17,29 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.idea.blaze.base.async.executor.BlazeExecutor; -import com.google.idea.blaze.base.experiments.ExperimentScope; -import com.google.idea.blaze.base.filecache.FileCaches; -import com.google.idea.blaze.base.metrics.Action; -import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.primitives.Label; -import com.google.idea.blaze.base.model.primitives.TargetExpression; -import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; -import com.google.idea.blaze.base.projectview.ProjectViewManager; -import com.google.idea.blaze.base.projectview.ProjectViewSet; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.ScopedTask; -import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope; -import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; -import com.google.idea.blaze.base.scope.scopes.IssuesScope; -import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope; -import com.google.idea.blaze.base.scope.scopes.NotificationScope; -import com.google.idea.blaze.base.scope.scopes.TimingScope; -import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface; -import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface.BuildResult; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; -import com.google.idea.blaze.base.util.SaveUtil; +import com.google.idea.common.actionhelper.ActionPresentationHelper; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import java.io.File; -import java.util.List; -import org.jetbrains.annotations.NotNull; -class BlazeCompileFileAction extends BlazeAction { - private static final Logger LOG = Logger.getInstance(BlazeCompileFileAction.class); +class BlazeCompileFileAction extends BlazeProjectAction { - public BlazeCompileFileAction() { - super("Compile file"); + @Override + protected void updateForBlazeProject(Project project, AnActionEvent e) { + ActionPresentationHelper.of(e) + .disableIf(getTargets(e).isEmpty()) + .setTextWithSubject("Compile File", "Compile %s", e.getData(CommonDataKeys.VIRTUAL_FILE)) + .disableWithoutSubject() + .commit(); } @Override - protected void doUpdate(@NotNull AnActionEvent e) { - // IntelliJ uses different logic for 1 vs many module selection. When many modules are selected - // modules with more than 1 content root are ignored - // (ProjectViewImpl#moduleBySingleContentRoot). - if (getTargets(e).isEmpty()) { - Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - } - } - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project != null) { - ImmutableCollection<Label> targets = getTargets(e); - buildSourceFile(project, targets); - } + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + BlazeBuildService.getInstance().buildFile(project, getFileName(e), getTargets(e)); } private ImmutableCollection<Label> getTargets(AnActionEvent e) { @@ -88,53 +52,8 @@ return ImmutableList.of(); } - private static void buildSourceFile( - @NotNull Project project, @NotNull ImmutableCollection<Label> targets) { - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - if (blazeProjectData == null || targets.isEmpty()) { - return; - } - final ProjectViewSet projectViewSet = - ProjectViewManager.getInstance(project).getProjectViewSet(); - if (projectViewSet == null) { - return; - } - BlazeExecutor.submitTask( - project, - new ScopedTask() { - @Override - public void execute(@NotNull BlazeContext context) { - context - .push(new ExperimentScope()) - .push(new BlazeConsoleScope.Builder(project).build()) - .push(new IssuesScope(project)) - .push(new IdeaLogScope()) - .push(new TimingScope("Make")) - .push(new LoggedTimingScope(project, Action.MAKE_MODULE_TOTAL_TIME)) - .push( - new NotificationScope( - project, - "Make", - "Make module", - "Make module completed successfully", - "Make module failed")); - - WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); - - BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance(); - List<TargetExpression> targetExpressions = Lists.newArrayList(targets); - - SaveUtil.saveAllFiles(); - BuildResult buildResult = - blazeIdeInterface.resolveIdeArtifacts( - project, context, workspaceRoot, projectViewSet, targetExpressions); - FileCaches.refresh(project); - - if (buildResult != BuildResult.SUCCESS) { - context.setHasError(); - } - } - }); + private static String getFileName(AnActionEvent e) { + VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE); + return virtualFile == null ? null : virtualFile.getName(); } }
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java index 64964c5..a124759 100644 --- a/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java +++ b/base/src/com/google/idea/blaze/base/actions/BlazeMakeProjectAction.java
@@ -15,102 +15,13 @@ */ package com.google.idea.blaze.base.actions; -import com.google.common.collect.Lists; -import com.google.idea.blaze.base.async.executor.BlazeExecutor; -import com.google.idea.blaze.base.experiments.ExperimentScope; -import com.google.idea.blaze.base.filecache.FileCaches; -import com.google.idea.blaze.base.metrics.Action; -import com.google.idea.blaze.base.model.BlazeProjectData; -import com.google.idea.blaze.base.model.primitives.TargetExpression; -import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; -import com.google.idea.blaze.base.projectview.ProjectViewManager; -import com.google.idea.blaze.base.projectview.ProjectViewSet; -import com.google.idea.blaze.base.projectview.section.sections.TargetSection; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.scope.ScopedTask; -import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope; -import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; -import com.google.idea.blaze.base.scope.scopes.IssuesScope; -import com.google.idea.blaze.base.scope.scopes.LoggedTimingScope; -import com.google.idea.blaze.base.scope.scopes.NotificationScope; -import com.google.idea.blaze.base.scope.scopes.TimingScope; -import com.google.idea.blaze.base.settings.Blaze; -import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface; -import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface.BuildResult; -import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; -import com.google.idea.blaze.base.util.SaveUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; -import java.util.List; -import org.jetbrains.annotations.NotNull; -class BlazeMakeProjectAction extends BlazeAction { - - public BlazeMakeProjectAction() { - super("Make Project"); - } +class BlazeMakeProjectAction extends BlazeProjectAction { @Override - public final void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project != null && Blaze.isBlazeProject(project)) { - buildBlazeProject(project); - } - } - - protected void buildBlazeProject(@NotNull final Project project) { - - BlazeExecutor.submitTask( - project, - new ScopedTask() { - @Override - public void execute(@NotNull BlazeContext context) { - context - .push(new ExperimentScope()) - .push(new BlazeConsoleScope.Builder(project).build()) - .push(new IssuesScope(project)) - .push(new IdeaLogScope()) - .push(new TimingScope("Make")) - .push(new LoggedTimingScope(project, Action.MAKE_PROJECT_TOTAL_TIME)) - .push( - new NotificationScope( - project, - "Make", - "Make project", - "Make project completed successfully", - "Make project failed")); - - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - if (blazeProjectData == null) { - return; - } - SaveUtil.saveAllFiles(); - - ProjectViewSet projectViewSet = - ProjectViewManager.getInstance(project) - .reloadProjectView(context, blazeProjectData.workspacePathResolver); - if (projectViewSet == null) { - return; - } - - WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); - - List<TargetExpression> targets = Lists.newArrayList(); - targets.addAll(projectViewSet.listItems(TargetSection.KEY)); - - BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance(); - - BuildResult buildResult = - blazeIdeInterface.resolveIdeArtifacts( - project, context, workspaceRoot, projectViewSet, targets); - FileCaches.refresh(project); - - if (buildResult != BuildResult.SUCCESS) { - context.setHasError(); - ; - } - } - }); + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + BlazeBuildService.getInstance().buildProject(project); } }
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeProjectAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeProjectAction.java new file mode 100644 index 0000000..fdf81be --- /dev/null +++ b/base/src/com/google/idea/blaze/base/actions/BlazeProjectAction.java
@@ -0,0 +1,86 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.actions; + +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; +import javax.swing.Icon; + +/** Base class action that hides for non-blaze projects. */ +public abstract class BlazeProjectAction extends AnAction { + protected BlazeProjectAction() {} + + protected BlazeProjectAction(Icon icon) { + super(icon); + } + + protected BlazeProjectAction(@Nullable String text) { + super(text); + } + + protected BlazeProjectAction( + @Nullable String text, @Nullable String description, @Nullable Icon icon) { + super(text, description, icon); + } + + @Override + public final void update(AnActionEvent e) { + Project project = e.getProject(); + if (project == null || !Blaze.isBlazeProject(project)) { + e.getPresentation().setEnabledAndVisible(false); + return; + } + + e.getPresentation().setEnabledAndVisible(true); + + if (!compatibleBuildSystem(project)) { + e.getPresentation().setEnabled(false); + return; + } + + updateForBlazeProject(project, e); + } + + @Override + public final void actionPerformed(AnActionEvent anActionEvent) { + Project project = anActionEvent.getProject(); + if (project == null) { + return; + } + actionPerformedInBlazeProject(project, anActionEvent); + } + + protected void updateForBlazeProject(Project project, AnActionEvent e) {} + + protected abstract void actionPerformedInBlazeProject(Project project, AnActionEvent e); + + private boolean compatibleBuildSystem(Project project) { + BuildSystem requiredBuildSystem = requiredBuildSystem(); + if (requiredBuildSystem == null) { + return true; + } + return Blaze.getBuildSystem(project) == requiredBuildSystem; + } + + @Nullable + protected BuildSystem requiredBuildSystem() { + return null; + } +}
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeProjectToggleAction.java similarity index 73% rename from base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java rename to base/src/com/google/idea/blaze/base/actions/BlazeProjectToggleAction.java index 0413761..270f067 100644 --- a/base/src/com/google/idea/blaze/base/actions/BlazeToggleAction.java +++ b/base/src/com/google/idea/blaze/base/actions/BlazeProjectToggleAction.java
@@ -19,39 +19,34 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; import javax.swing.Icon; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** Base class toggle action that hides for non-blaze projects. */ -public abstract class BlazeToggleAction extends ToggleAction { - protected BlazeToggleAction() {} +public abstract class BlazeProjectToggleAction extends ToggleAction { + protected BlazeProjectToggleAction() {} - protected BlazeToggleAction(@Nullable String text) { + protected BlazeProjectToggleAction(@Nullable String text) { super(text); } - protected BlazeToggleAction( + protected BlazeProjectToggleAction( @Nullable String text, @Nullable String description, @Nullable Icon icon) { super(text, description, icon); } @Override public final void update(AnActionEvent e) { - if (!isBlazeProject(e)) { + Project project = e.getProject(); + if (project == null || !Blaze.isBlazeProject(project)) { e.getPresentation().setEnabledAndVisible(false); return; } e.getPresentation().setEnabledAndVisible(true); super.update(e); - doUpdate(e); + updateForBlazeProject(project, e); } - protected void doUpdate(@NotNull AnActionEvent e) {} - - private static boolean isBlazeProject(@NotNull AnActionEvent e) { - Project project = e.getProject(); - return project != null && Blaze.isBlazeProject(project); - } + protected void updateForBlazeProject(Project project, AnActionEvent e) {} }
diff --git a/base/src/com/google/idea/blaze/base/async/AsyncUtil.java b/base/src/com/google/idea/blaze/base/async/AsyncUtil.java deleted file mode 100644 index 153f995..0000000 --- a/base/src/com/google/idea/blaze/base/async/AsyncUtil.java +++ /dev/null
@@ -1,58 +0,0 @@ -/* - * Copyright 2016 The Bazel Authors. All rights reserved. - * - * 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.google.idea.blaze.base.async; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; - -/** Async utilities. */ -public class AsyncUtil { - public static void executeProjectChangeAction(@NotNull final Runnable task) throws Throwable { - final ValueHolder<Throwable> error = new ValueHolder<Throwable>(); - - executeOnEdt( - new Runnable() { - @Override - public void run() { - ApplicationManager.getApplication() - .runWriteAction( - new Runnable() { - @Override - public void run() { - try { - task.run(); - } catch (Throwable t) { - error.value = t; - } - } - }); - } - }); - - if (error.value != null) { - throw error.value; - } - } - - private static void executeOnEdt(@NotNull Runnable task) { - if (ApplicationManager.getApplication().isDispatchThread()) { - task.run(); - } else { - UIUtil.invokeAndWaitIfNeeded(task); - } - } -}
diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java index d207ff2..b3d4598 100644 --- a/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java +++ b/base/src/com/google/idea/blaze/base/bazel/BazelBuildSystemProvider.java
@@ -16,8 +16,10 @@ package com.google.idea.blaze.base.bazel; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.idea.blaze.base.io.FileAttributeProvider; import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.openapi.fileTypes.ExactFileNameMatcher; @@ -27,6 +29,7 @@ /** Provides the bazel build system name string. */ public class BazelBuildSystemProvider implements BuildSystemProvider { + @Override public BuildSystem buildSystem() { return BuildSystem.Bazel; @@ -72,4 +75,16 @@ public FileNameMatcher buildFileMatcher() { return new ExactFileNameMatcher("BUILD"); } + + @Override + public void populateBlazeVersionData( + BuildSystem buildSystem, + WorkspaceRoot workspaceRoot, + ImmutableMap<String, String> blazeInfo, + BlazeVersionData.Builder builder) { + if (buildSystem != BuildSystem.Bazel) { + return; + } + builder.setBazelVersion(BazelVersion.parseVersion(blazeInfo)); + } }
diff --git a/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java b/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java new file mode 100644 index 0000000..44582cb --- /dev/null +++ b/base/src/com/google/idea/blaze/base/bazel/BazelVersion.java
@@ -0,0 +1,109 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.bazel; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; +import com.google.idea.blaze.base.command.info.BlazeInfo; +import com.intellij.openapi.util.text.StringUtil; +import java.io.Serializable; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +/** Bazel version */ +public class BazelVersion implements Serializable { + private static final long serialVersionUID = 1L; + + public static final BazelVersion UNKNOWN = new BazelVersion(0, 0, 0); + private static final Pattern PATTERN = Pattern.compile("([[0-9]\\.]+)"); + + public final int major; + public final int minor; + public final int bugfix; + + BazelVersion(int major, int minor, int bugfix) { + this.bugfix = bugfix; + this.minor = minor; + this.major = major; + } + + @VisibleForTesting + static BazelVersion parseVersion(@Nullable String string) { + if (string == null) { + return UNKNOWN; + } + Matcher matcher = PATTERN.matcher(string); + if (!matcher.find()) { + return UNKNOWN; + } + try { + BazelVersion version = parseVersion(matcher.group(1).split("\\.")); + if (version == null) { + return UNKNOWN; + } + return version; + } catch (Exception e) { + return UNKNOWN; + } + } + + @Nullable + private static BazelVersion parseVersion(String[] numbers) { + if (numbers.length < 1) { + return null; + } + int major = StringUtil.parseInt(numbers[0], -1); + if (major < 0) { + return null; + } + int minor = numbers.length > 1 ? StringUtil.parseInt(numbers[1], 0) : 0; + int bugfix = numbers.length > 2 ? StringUtil.parseInt(numbers[2], 0) : 0; + return new BazelVersion(major, minor, bugfix); + } + + public static BazelVersion parseVersion(Map<String, String> blazeInfo) { + return parseVersion(blazeInfo.get(BlazeInfo.RELEASE)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BazelVersion that = (BazelVersion) o; + return major == that.major && minor == that.minor && bugfix == that.bugfix; + } + + @Override + public int hashCode() { + return Objects.hashCode(major, minor, bugfix); + } + + public boolean isAtLeast(int major, int minor, int bugfix) { + return ComparisonChain.start() + .compare(this.major, major) + .compare(this.minor, minor) + .compare(this.bugfix, bugfix) + .result() + >= 0; + } +}
diff --git a/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java b/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java index 4b47716..6ce5aca 100644 --- a/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java +++ b/base/src/com/google/idea/blaze/base/bazel/BuildSystemProvider.java
@@ -16,7 +16,9 @@ package com.google.idea.blaze.base.bazel; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.openapi.components.ServiceManager; @@ -105,4 +107,11 @@ } FileNameMatcher buildFileMatcher(); + + /** Populates the passed builder with version data. */ + void populateBlazeVersionData( + BuildSystem buildSystem, + WorkspaceRoot workspaceRoot, + ImmutableMap<String, String> blazeInfo, + BlazeVersionData.Builder builder); }
diff --git a/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java b/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java index 2754da2..592b7b4 100644 --- a/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java +++ b/base/src/com/google/idea/blaze/base/buildmap/OpenCorrespondingBuildFile.java
@@ -16,7 +16,7 @@ package com.google.idea.blaze.base.buildmap; import com.google.common.collect.Iterables; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.metrics.Action; import com.google.idea.blaze.base.metrics.LoggingService; import com.intellij.ide.actions.OpenFileAction; @@ -29,16 +29,12 @@ import com.intellij.openapi.vfs.VirtualFile; import java.io.File; import java.util.Collection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import javax.annotation.Nullable; -class OpenCorrespondingBuildFile extends BlazeAction { +class OpenCorrespondingBuildFile extends BlazeProjectAction { + @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE); File file = getBuildFile(project, virtualFile); if (file == null) { @@ -49,10 +45,7 @@ } @Nullable - private File getBuildFile(@Nullable Project project, @Nullable VirtualFile virtualFile) { - if (project == null) { - return null; - } + private File getBuildFile(Project project, @Nullable VirtualFile virtualFile) { if (virtualFile == null) { return null; } @@ -62,12 +55,11 @@ } @Override - protected void doUpdate(@NotNull AnActionEvent e) { + protected void updateForBlazeProject(Project project, AnActionEvent e) { Presentation presentation = e.getPresentation(); DataContext dataContext = e.getDataContext(); VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); - Project project = CommonDataKeys.PROJECT.getData(dataContext); - boolean visible = (project != null && virtualFile != null); + boolean visible = virtualFile != null; boolean enabled = getBuildFile(project, virtualFile) != null; presentation.setVisible(visible || ActionPlaces.isMainMenuOrActionSearch(e.getPlace())); presentation.setEnabled(enabled);
diff --git a/base/src/com/google/idea/blaze/base/command/BlazeFlags.java b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java index 2338f24..1f24f62 100644 --- a/base/src/com/google/idea/blaze/base/command/BlazeFlags.java +++ b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
@@ -41,13 +41,6 @@ public static final String TEST_OUTPUT_STREAMED = "--test_output=streamed"; // Filters the unit tests that are run (used with regexp for Java/Robolectric tests). public static final String TEST_FILTER = "--test_filter"; - // Skips checking for output file modifications (reduced statting -> faster). - public static final String NO_CHECK_OUTPUTS = "--noexperimental_check_output_files"; - // Ignores implicit dependencies (e.g. java_library rules depending implicitly on - // "//transconsole/tools:aggregate_messages" in order to support translations). - public static final String NO_IMPLICIT_DEPS = "--noimplicit_deps"; - // Ignores host dependencies. - public static final String NO_HOST_DEPS = "--nohost_deps"; // When used with mobile-install, deploys the an app incrementally. public static final String INCREMENTAL = "--incremental"; // When used with mobile-install, deploys the an app incrementally
diff --git a/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java index 290f001..2e40662 100644 --- a/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java +++ b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
@@ -15,7 +15,6 @@ */ package com.google.idea.blaze.base.command; -import static com.google.idea.blaze.base.command.BlazeFlags.NO_CHECK_OUTPUTS; import static com.google.idea.blaze.base.command.BlazeFlags.VERSION_WINDOW_FOR_DIRTY_NODE_GC; import com.google.idea.blaze.base.projectview.ProjectViewSet; @@ -28,8 +27,6 @@ private static final BoolExperiment experimentUseVersionWindowForDirtyNodeGc = new BoolExperiment("ide_build_info.use_version_window_for_dirty_node_gc", false); - private static final BoolExperiment experimentNoExperimentalCheckOutputFiles = - new BoolExperiment("build.noexperimental_check_output_files", false); @Override public void addBuildFlags( @@ -37,9 +34,6 @@ if (experimentUseVersionWindowForDirtyNodeGc.getValue()) { flags.add(VERSION_WINDOW_FOR_DIRTY_NODE_GC); } - if (experimentNoExperimentalCheckOutputFiles.getValue()) { - flags.add(NO_CHECK_OUTPUTS); - } flags.add("--curses=no"); flags.add("--color=no"); }
diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java index 5346e8c..6099fc2 100644 --- a/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java +++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
@@ -31,6 +31,7 @@ public static final String BUILD_LANGUAGE = "build-language"; public static final String OUTPUT_BASE_KEY = "output_base"; public static final String MASTER_LOG = "master-log"; + public static final String RELEASE = "release"; public static String blazeBinKey(BuildSystem buildSystem) { switch (buildSystem) {
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java index 3780bf1..2d0f519 100644 --- a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java +++ b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageAction.java
@@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.buildmodifier.BuildFileModifier; import com.google.idea.blaze.base.buildmodifier.FileSystemModifier; import com.google.idea.blaze.base.metrics.Action; @@ -41,7 +41,6 @@ import com.intellij.ide.IdeView; import com.intellij.ide.util.DirectoryChooserUtil; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.diagnostic.Logger; @@ -60,7 +59,7 @@ import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; -class NewBlazePackageAction extends BlazeAction implements DumbAware { +class NewBlazePackageAction extends BlazeProjectAction implements DumbAware { private static final Logger LOG = Logger.getInstance(NewBlazePackageAction.class); private static final String BUILD_FILE_NAME = "BUILD"; @@ -70,9 +69,8 @@ } @Override - public void actionPerformed(AnActionEvent event) { + protected void actionPerformedInBlazeProject(Project project, AnActionEvent event) { final IdeView view = event.getData(LangDataKeys.IDE_VIEW); - final Project project = event.getData(CommonDataKeys.PROJECT); Scope.root( new ScopedOperation() { @Override @@ -160,10 +158,10 @@ } @Override - protected void doUpdate(@NotNull AnActionEvent event) { + protected void updateForBlazeProject(Project project, @NotNull AnActionEvent event) { Presentation presentation = event.getPresentation(); if (isEnabled(event)) { - String text = String.format("New %s Package", Blaze.buildSystemName(event.getProject())); + String text = String.format("New %s Package", Blaze.buildSystemName(project)); presentation.setEnabledAndVisible(true); presentation.setText(text); presentation.setDescription(text);
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java index d67dccf..422dada 100644 --- a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java +++ b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleAction.java
@@ -16,7 +16,7 @@ package com.google.idea.blaze.base.ide; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.experiments.ExperimentScope; import com.google.idea.blaze.base.metrics.Action; import com.google.idea.blaze.base.scope.Scope; @@ -34,20 +34,15 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import javax.annotation.Nullable; -import org.jetbrains.annotations.NotNull; -class NewBlazeRuleAction extends BlazeAction implements DumbAware { +class NewBlazeRuleAction extends BlazeProjectAction implements DumbAware { public NewBlazeRuleAction() { super(); } @Override - public void actionPerformed(AnActionEvent event) { - final Project project = event.getData(CommonDataKeys.PROJECT); - if (project == null) { - return; - } + protected void actionPerformedInBlazeProject(Project project, AnActionEvent event) { final VirtualFile virtualFile = event.getData(CommonDataKeys.VIRTUAL_FILE); if (virtualFile == null) { return; @@ -68,15 +63,12 @@ } @Override - protected void doUpdate(@NotNull AnActionEvent event) { + protected void updateForBlazeProject(Project project, AnActionEvent event) { Presentation presentation = event.getPresentation(); DataContext dataContext = event.getDataContext(); VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); - Project project = CommonDataKeys.PROJECT.getData(dataContext); boolean enabled = - (project != null - && file != null - && Blaze.getBuildSystemProvider(project).isBuildFile(file.getName())); + file != null && Blaze.getBuildSystemProvider(project).isBuildFile(file.getName()); presentation.setVisible(enabled || ActionPlaces.isMainMenuOrActionSearch(event.getPlace())); presentation.setEnabled(enabled); presentation.setText(getText(project));
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java b/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java index 0a347c6..d1e99a1 100644 --- a/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java +++ b/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java
@@ -15,36 +15,42 @@ */ package com.google.idea.blaze.base.ideinfo; +import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Ordering; import com.google.idea.blaze.base.model.primitives.Label; import java.io.Serializable; -import javax.annotation.Nullable; +import java.util.List; /** A key that uniquely idenfifies a target in the target map */ public class TargetKey implements Serializable, Comparable<TargetKey> { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; public final Label label; - @Nullable private final String aspectId; + private final ImmutableList<String> aspectIds; - private TargetKey(Label label, @Nullable String aspectId) { + private TargetKey(Label label, ImmutableList<String> aspectIds) { this.label = label; - this.aspectId = aspectId; + this.aspectIds = aspectIds; } /** Returns a key identifying a plain target */ public static TargetKey forPlainTarget(Label label) { - return new TargetKey(label, null); + return new TargetKey(label, ImmutableList.of()); } /** Returns a key identifying a general target */ - public static TargetKey forGeneralTarget(Label label, @Nullable String aspectId) { - return new TargetKey(label, aspectId); + public static TargetKey forGeneralTarget(Label label, List<String> aspectIds) { + if (aspectIds.isEmpty()) { + return forPlainTarget(label); + } + return new TargetKey(label, ImmutableList.copyOf(aspectIds)); } public boolean isPlainTarget() { - return aspectId == null; + return aspectIds.isEmpty(); } @Override @@ -56,24 +62,27 @@ return false; } TargetKey key = (TargetKey) o; - return Objects.equal(label, key.label) && Objects.equal(aspectId, key.aspectId); + return Objects.equal(label, key.label) && Objects.equal(aspectIds, key.aspectIds); } @Override public int hashCode() { - return Objects.hashCode(label, aspectId); + return Objects.hashCode(label, aspectIds); } @Override public String toString() { - if (aspectId == null) { + if (aspectIds.isEmpty()) { return label.toString(); } - return label.toString() + "#" + aspectId; + return label.toString() + "#" + Joiner.on('#').join(aspectIds); } @Override public int compareTo(TargetKey o) { - return ComparisonChain.start().compare(label, o.label).compare(aspectId, o.aspectId).result(); + return ComparisonChain.start() + .compare(label, o.label) + .compare(aspectIds, o.aspectIds, Ordering.natural().lexicographical()) + .result(); } }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java index c57a138..f443957 100644 --- a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java +++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuildLanguageSpecProviderImpl.java
@@ -15,41 +15,25 @@ */ package com.google.idea.blaze.base.lang.buildfile.language.semantics; -import com.google.common.collect.Maps; import com.google.idea.blaze.base.lang.buildfile.sync.LanguageSpecResult; import com.google.idea.blaze.base.model.BlazeProjectData; -import com.google.idea.blaze.base.projectview.ProjectViewSet; -import com.google.idea.blaze.base.scope.BlazeContext; -import com.google.idea.blaze.base.settings.BlazeImportSettings; -import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; -import com.google.idea.blaze.base.sync.SyncListener; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.intellij.openapi.project.Project; -import java.util.Map; /** Calls 'blaze info build-language', to retrieve the language spec. */ -public class BuildLanguageSpecProviderImpl extends SyncListener.Adapter - implements BuildLanguageSpecProvider { - - private static final Map<Project, LanguageSpecResult> calculatedSpecs = Maps.newHashMap(); +public class BuildLanguageSpecProviderImpl implements BuildLanguageSpecProvider { @Override public BuildLanguageSpec getLanguageSpec(Project project) { - LanguageSpecResult result = calculatedSpecs.get(project); - return result != null ? result.spec : null; - } - - @Override - public void onSyncComplete( - Project project, - BlazeContext context, - BlazeImportSettings importSettings, - ProjectViewSet projectViewSet, - BlazeProjectData blazeProjectData, - SyncMode syncMode, - SyncResult syncResult) { - LanguageSpecResult spec = blazeProjectData.syncState.get(LanguageSpecResult.class); - if (spec != null) { - calculatedSpecs.put(project, spec); + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (blazeProjectData == null) { + return null; } + LanguageSpecResult spec = blazeProjectData.syncState.get(LanguageSpecResult.class); + if (spec == null) { + return null; + } + return spec.spec; } }
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java b/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java index 84f6936..4a401c8 100644 --- a/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java +++ b/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
@@ -22,51 +22,46 @@ import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; -import com.google.idea.blaze.base.sync.workspace.WorkingSet; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import java.io.Serializable; -import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** The top-level object serialized to cache. */ @Immutable public class BlazeProjectData implements Serializable { - private static final long serialVersionUID = 25L; + private static final long serialVersionUID = 27L; public final long syncTime; public final TargetMap targetMap; public final ImmutableMap<String, String> blazeInfo; public final BlazeRoots blazeRoots; - @Nullable public final WorkingSet workingSet; + public final BlazeVersionData blazeVersionData; public final WorkspacePathResolver workspacePathResolver; public final ArtifactLocationDecoder artifactLocationDecoder; public final WorkspaceLanguageSettings workspaceLanguageSettings; public final SyncState syncState; public final ImmutableMultimap<TargetKey, TargetKey> reverseDependencies; - @Nullable public final String vcsName; public BlazeProjectData( long syncTime, TargetMap targetMap, ImmutableMap<String, String> blazeInfo, BlazeRoots blazeRoots, - @Nullable WorkingSet workingSet, + BlazeVersionData blazeVersionData, WorkspacePathResolver workspacePathResolver, ArtifactLocationDecoder artifactLocationDecoder, - WorkspaceLanguageSettings workspaceLangaugeSettings, + WorkspaceLanguageSettings workspaceLanguageSettings, SyncState syncState, - ImmutableMultimap<TargetKey, TargetKey> reverseDependencies, - String vcsName) { + ImmutableMultimap<TargetKey, TargetKey> reverseDependencies) { this.syncTime = syncTime; this.targetMap = targetMap; this.blazeInfo = blazeInfo; this.blazeRoots = blazeRoots; - this.workingSet = workingSet; + this.blazeVersionData = blazeVersionData; this.workspacePathResolver = workspacePathResolver; this.artifactLocationDecoder = artifactLocationDecoder; - this.workspaceLanguageSettings = workspaceLangaugeSettings; + this.workspaceLanguageSettings = workspaceLanguageSettings; this.syncState = syncState; this.reverseDependencies = reverseDependencies; - this.vcsName = vcsName; } }
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java b/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java new file mode 100644 index 0000000..1cbdbff --- /dev/null +++ b/base/src/com/google/idea/blaze/base/model/BlazeVersionData.java
@@ -0,0 +1,98 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.model; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.base.bazel.BazelVersion; +import com.google.idea.blaze.base.bazel.BuildSystemProvider; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import java.io.Serializable; +import javax.annotation.Nullable; + +/** + * Version data about the user's blaze/bazel and other info needed for switching behaviour + * dynamically. + */ +public class BlazeVersionData implements Serializable { + private static final long serialVersionUID = 2L; + + @Nullable private final Long blazeCl; + @Nullable private final Long clientCl; + @Nullable private final BazelVersion bazelVersion; + + @VisibleForTesting + public BlazeVersionData() { + this(null, null, null); + } + + private BlazeVersionData( + @Nullable Long blazeCl, @Nullable Long clientCl, @Nullable BazelVersion bazelVersion) { + this.blazeCl = blazeCl; + this.clientCl = clientCl; + this.bazelVersion = bazelVersion; + } + + public boolean blazeContainsCl(long cl) { + return blazeCl != null && blazeCl >= cl; + } + + public boolean blazeClientIsAtLeastCl(long cl) { + return clientCl != null && clientCl >= cl; + } + + public boolean bazelIsAtLeastVersion(int major, int minor, int bugfix) { + return bazelVersion != null && bazelVersion.isAtLeast(major, minor, bugfix); + } + + public static BlazeVersionData build( + BuildSystem buildSystem, + WorkspaceRoot workspaceRoot, + ImmutableMap<String, String> blazeInfo) { + Builder builder = new Builder(); + for (BuildSystemProvider provider : BuildSystemProvider.EP_NAME.getExtensions()) { + provider.populateBlazeVersionData(buildSystem, workspaceRoot, blazeInfo, builder); + } + return builder.build(); + } + + /** Builder class for constructing the blaze version data */ + public static class Builder { + public Long blazeCl; + public Long clientCl; + public BazelVersion bazelVersion; + + public Builder setBlazeCl(Long blazeCl) { + this.blazeCl = blazeCl; + return this; + } + + public Builder setClientCl(Long clientCl) { + this.clientCl = clientCl; + return this; + } + + public Builder setBazelVersion(BazelVersion bazelVersion) { + this.bazelVersion = bazelVersion; + return this; + } + + public BlazeVersionData build() { + return new BlazeVersionData(blazeCl, clientCl, bazelVersion); + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/Kind.java b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java index 88d5199..206a73e 100644 --- a/base/src/com/google/idea/blaze/base/model/primitives/Kind.java +++ b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
@@ -49,6 +49,13 @@ PY_TEST("py_test", LanguageClass.PYTHON), PY_APPENGINE_BINARY("py_appengine_binary", LanguageClass.PYTHON), PY_WRAP_CC("py_wrap_cc", LanguageClass.PYTHON), + GO_TEST("go_test", LanguageClass.GO), + GO_APPENGINE_TEST("go_appengine_test", LanguageClass.GO), + GO_BINARY("go_binary", LanguageClass.GO), + GO_APPENGINE_BINARY("go_appengine_binary", LanguageClass.GO), + GO_LIBRARY("go_library", LanguageClass.GO), + GO_APPENGINE_LIBRARY("go_appengine_library", LanguageClass.GO), + GO_WRAP_CC("go_wrap_cc", LanguageClass.GO), ; static final ImmutableMap<String, Kind> STRING_TO_KIND = makeStringToKindMap();
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java b/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java index 9addd48..cf030a2 100644 --- a/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java +++ b/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java
@@ -28,6 +28,7 @@ JAVASCRIPT("javascript", ImmutableSet.of("js", "applejs")), TYPESCRIPT("typescript", ImmutableSet.of("ts", "ats")), DART("dart", ImmutableSet.of("dart")), + GO("go", ImmutableSet.of("go")), PYTHON("python", ImmutableSet.of("py", "pyw")); private static final ImmutableMap<String, LanguageClass> RECOGNIZED_EXTENSIONS =
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java index 290118b..c88ad5a 100644 --- a/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java +++ b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java
@@ -15,6 +15,8 @@ */ package com.google.idea.blaze.base.model.primitives; +import com.google.common.collect.ImmutableSet; + /** * Workspace types. * @@ -27,22 +29,23 @@ JAVA("java", LanguageClass.JAVA), PYTHON("python", LanguageClass.PYTHON), JAVASCRIPT("javascript", LanguageClass.JAVASCRIPT), + GO("go", LanguageClass.GO), INTELLIJ_PLUGIN("intellij_plugin", LanguageClass.JAVA); private final String name; // the languages active by default for this WorkspaceType - private final LanguageClass[] languages; + private final ImmutableSet<LanguageClass> languages; WorkspaceType(String name, LanguageClass... languages) { this.name = name; - this.languages = languages; + this.languages = ImmutableSet.copyOf(languages); } public String getName() { return name; } - public LanguageClass[] getLanguages() { + public ImmutableSet<LanguageClass> getLanguages() { return languages; }
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java index 553900d..0953d07 100644 --- a/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java +++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
@@ -18,7 +18,6 @@ import com.google.common.collect.Lists; import com.google.idea.blaze.base.io.FileAttributeProvider; import com.google.idea.blaze.base.model.primitives.WorkspacePath; -import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.projectview.section.ListSection; import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry; import com.google.idea.blaze.base.projectview.section.sections.DirectorySection; @@ -27,7 +26,9 @@ import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.sync.BlazeSyncPlugin; import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.intellij.openapi.util.io.FileUtil; +import java.io.File; import java.util.List; /** Verifies project views. */ @@ -44,16 +45,7 @@ /** Verifies the project view. Any errors are output to the context as issues. */ public static boolean verifyProjectView( BlazeContext context, - WorkspaceRoot workspaceRoot, - ProjectViewSet projectViewSet, - WorkspaceLanguageSettings workspaceLanguageSettings) { - return verifyProjectViewNoDisk(context, projectViewSet, workspaceLanguageSettings) - && verifyIncludedPackagesExistOnDisk(context, workspaceRoot, projectViewSet); - } - - /** Verifies the project view, without hitting disk. */ - public static boolean verifyProjectViewNoDisk( - BlazeContext context, + WorkspacePathResolver workspacePathResolver, ProjectViewSet projectViewSet, WorkspaceLanguageSettings workspaceLanguageSettings) { if (!verifyIncludedPackagesAreNotExcluded(context, projectViewSet)) { @@ -69,6 +61,9 @@ .inFile(projectViewSet.getTopLevelProjectViewFile().projectViewFile) .submit(context); } + if (!verifyIncludedPackagesExistOnDisk(context, workspacePathResolver, projectViewSet)) { + return false; + } return true; } @@ -119,7 +114,9 @@ } private static boolean verifyIncludedPackagesExistOnDisk( - BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) { + BlazeContext context, + WorkspacePathResolver workspacePathResolver, + ProjectViewSet projectViewSet) { boolean ok = true; FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance(); @@ -135,12 +132,19 @@ continue; } WorkspacePath workspacePath = entry.directory; - if (!fileAttributeProvider.exists(workspaceRoot.fileForPath(workspacePath))) { + File file = workspacePathResolver.resolveToFile(workspacePath); + if (!fileAttributeProvider.exists(file)) { IssueOutput.error( String.format( - "Directory '%s' specified in import roots not found " - + "under workspace root '%s'", - workspacePath, workspaceRoot)) + "Directory '%s' specified in project view not found.", workspacePath)) + .inFile(projectViewFile.projectViewFile) + .withData(new MissingDirectoryIssueData(workspacePath)) + .submit(context); + ok = false; + } else if (!fileAttributeProvider.isDirectory(file)) { + IssueOutput.error( + String.format( + "Directory '%s' specified in project view is a file.", workspacePath)) .inFile(projectViewFile.projectViewFile) .withData(new MissingDirectoryIssueData(workspacePath)) .submit(context);
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/RunConfigurationsSection.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/RunConfigurationsSection.java new file mode 100644 index 0000000..9ffda84 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/RunConfigurationsSection.java
@@ -0,0 +1,63 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.projectview.section.sections; + +import com.google.common.collect.Lists; +import com.google.idea.blaze.base.model.primitives.WorkspacePath; +import com.google.idea.blaze.base.projectview.parser.ParseContext; +import com.google.idea.blaze.base.projectview.parser.ProjectViewParser; +import com.google.idea.blaze.base.projectview.section.ListSection; +import com.google.idea.blaze.base.projectview.section.ListSectionParser; +import com.google.idea.blaze.base.projectview.section.SectionKey; +import com.google.idea.blaze.base.projectview.section.SectionParser; +import com.google.idea.blaze.base.ui.BlazeValidationError; +import java.util.List; +import javax.annotation.Nullable; + +/** Allows users to import run configurations from XML files in their workspace. */ +public class RunConfigurationsSection { + public static final SectionKey<WorkspacePath, ListSection<WorkspacePath>> KEY = + SectionKey.of("import_run_configurations"); + public static final SectionParser PARSER = new RunConfigurationsSectionParser(); + + private static class RunConfigurationsSectionParser extends ListSectionParser<WorkspacePath> { + private RunConfigurationsSectionParser() { + super(KEY); + } + + @Nullable + @Override + protected WorkspacePath parseItem(ProjectViewParser parser, ParseContext parseContext) { + String text = parseContext.current().text; + List<BlazeValidationError> errors = Lists.newArrayList(); + if (!WorkspacePath.validate(text, errors)) { + parseContext.addErrors(errors); + return null; + } + return new WorkspacePath(text); + } + + @Override + protected void printItem(WorkspacePath item, StringBuilder sb) { + sb.append(item); + } + + @Override + public ItemType getItemType() { + return ItemType.FileSystemItem; + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java b/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java index 93b2fd9..ad0c35e 100644 --- a/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java +++ b/base/src/com/google/idea/blaze/base/projectview/section/sections/Sections.java
@@ -36,7 +36,8 @@ ImportTargetOutputSection.PARSER, ExcludeTargetSection.PARSER, ExcludedSourceSection.PARSER, - MetricsProjectSection.PARSER); + MetricsProjectSection.PARSER, + RunConfigurationsSection.PARSER); public static List<SectionParser> getParsers() { List<SectionParser> parsers = Lists.newArrayList(PARSERS);
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.java b/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.java index 7eb51e3..9b5e0e1 100644 --- a/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.java +++ b/base/src/com/google/idea/blaze/base/run/BlazeBeforeRunTaskProvider.java
@@ -20,6 +20,7 @@ import com.intellij.execution.BeforeRunTask; import com.intellij.execution.BeforeRunTaskProvider; import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.WrappingRunConfiguration; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.util.Key; @@ -91,6 +92,9 @@ @Override public boolean canExecuteTask(RunConfiguration configuration, Task task) { + if (configuration instanceof WrappingRunConfiguration) { + configuration = ((WrappingRunConfiguration) configuration).getPeer(); + } return configuration instanceof BlazeCommandRunConfiguration; }
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java index d6200ee..07fd7e3 100644 --- a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java +++ b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java
@@ -36,18 +36,21 @@ import com.intellij.execution.RunnerIconProvider; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.LocatableConfigurationBase; +import com.intellij.execution.configurations.ModuleRunProfile; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.configurations.RuntimeConfigurationError; import com.intellij.execution.configurations.RuntimeConfigurationException; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.ui.TextFieldWithAutoCompletion; import com.intellij.ui.TextFieldWithAutoCompletion.StringsCompletionProvider; +import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.UIUtil; import java.util.Collection; @@ -63,13 +66,15 @@ /** A run configuration which executes Blaze commands. */ public class BlazeCommandRunConfiguration extends LocatableConfigurationBase - implements BlazeRunConfiguration, RunnerIconProvider { + implements BlazeRunConfiguration, RunnerIconProvider, ModuleRunProfile { private static final Logger LOG = Logger.getInstance(BlazeCommandRunConfiguration.class); private static final String HANDLER_ATTR = "handler-id"; private static final String TARGET_TAG = "blaze-target"; private static final String KIND_ATTR = "kind"; + private static final String KEEP_IN_SYNC_TAG = "keep-in-sync"; + /** * This tag is actually written by {@link com.intellij.execution.impl.RunManagerImpl}; it * represents the before-run tasks of the configuration. We need to know about it to avoid writing @@ -83,6 +88,10 @@ @Nullable private TargetExpression target; // Null if the target is null, not a Label, or not a known rule. @Nullable private Kind targetKind; + + // for keeping imported configurations in sync with their source XML + @Nullable private Boolean keepInSync = null; + private BlazeCommandRunConfigurationHandlerProvider handlerProvider; private BlazeCommandRunConfigurationHandler handler; @@ -118,6 +127,17 @@ } @Override + public void setKeepInSync(@Nullable Boolean keepInSync) { + this.keepInSync = keepInSync; + } + + @Override + @Nullable + public Boolean getKeepInSync() { + return keepInSync; + } + + @Override @Nullable public TargetExpression getTarget() { return target; @@ -125,6 +145,10 @@ public void setTarget(@Nullable TargetExpression target) { this.target = target; + updateHandler(); + } + + private void updateHandler() { targetKind = getKindForTarget(); BlazeCommandRunConfigurationHandlerProvider handlerProvider = @@ -208,6 +232,9 @@ super.readExternal(element); element = element.clone(); + String keepInSyncString = element.getAttributeValue(KEEP_IN_SYNC_TAG); + keepInSync = keepInSyncString != null ? Boolean.parseBoolean(keepInSyncString) : null; + // Target is persisted as a tag to permit multiple targets in the future. Element targetElement = element.getChild(TARGET_TAG); if (targetElement != null && !Strings.isNullOrEmpty(targetElement.getTextTrim())) { @@ -234,6 +261,7 @@ element.removeAttribute(KIND_ATTR); element.removeAttribute(HANDLER_ATTR); element.removeChildren(TARGET_TAG); + element.removeAttribute(KEEP_IN_SYNC_TAG); // remove legacy attribute, if present element.removeAttribute(TARGET_TAG); @@ -253,7 +281,11 @@ } element.addContent(targetElement); } + if (keepInSync != null) { + element.setAttribute(KEEP_IN_SYNC_TAG, Boolean.toString(keepInSync)); + } element.setAttribute(HANDLER_ATTR, handlerProvider.getId()); + handler.getState().writeExternal(elementState); // copy our internal state to the provided Element, skipping items already present @@ -283,6 +315,7 @@ configuration.elementState = elementState.clone(); configuration.target = target; configuration.targetKind = targetKind; + configuration.keepInSync = keepInSync; configuration.handlerProvider = handlerProvider; configuration.handler = handlerProvider.createHandler(this); try { @@ -298,6 +331,11 @@ @Nullable public RunProfileState getState(Executor executor, ExecutionEnvironment environment) throws ExecutionException { + if (target != null) { + // We need to update the handler manually because it might otherwise be out of date (e.g. + // because the target map has changed since the last update). + updateHandler(); + } BlazeCommandRunConfigurationRunner runner = handler.createRunner(executor, environment); if (runner != null) { environment.putCopyableUserData(BlazeCommandRunConfigurationRunner.RUNNER_KEY, runner); @@ -323,6 +361,11 @@ return new BlazeCommandRunConfigurationSettingsEditor(this); } + @Override + public Module[] getModules() { + return new Module[0]; + } + static class BlazeCommandRunConfigurationSettingsEditor extends SettingsEditor<BlazeCommandRunConfiguration> { @@ -332,7 +375,9 @@ private JComponent handlerStateComponent; private Element elementState; + private final Box editorWithoutSyncCheckBox; private final Box editor; + private final JBCheckBox keepInSyncCheckBox; private final JBLabel targetExpressionLabel; private final TextFieldWithAutoCompletion<String> targetField; @@ -343,16 +388,35 @@ project, new TargetCompletionProvider(project), true, null); elementState = config.elementState.clone(); targetExpressionLabel = new JBLabel(UIUtil.ComponentStyle.LARGE); - editor = UiUtil.createBox(targetExpressionLabel, targetField); - updateTargetExpressionLabel(config); + keepInSyncCheckBox = new JBCheckBox("Keep in sync with source XML"); + editorWithoutSyncCheckBox = UiUtil.createBox(targetExpressionLabel, targetField); + editor = UiUtil.createBox(editorWithoutSyncCheckBox, keepInSyncCheckBox); + updateEditor(config); updateHandlerEditor(config); + keepInSyncCheckBox.addItemListener(e -> updateEnabledStatus()); } - private void updateTargetExpressionLabel(BlazeCommandRunConfiguration config) { + private void updateEditor(BlazeCommandRunConfiguration config) { targetExpressionLabel.setText( String.format( "Target expression (%s handled by %s):", config.getTargetKindName(), config.handler.getHandlerName())); + keepInSyncCheckBox.setVisible(config.keepInSync != null); + if (config.keepInSync != null) { + keepInSyncCheckBox.setSelected(config.keepInSync); + } + updateEnabledStatus(); + } + + private void updateEnabledStatus() { + setEnabled(!keepInSyncCheckBox.isVisible() || !keepInSyncCheckBox.isSelected()); + } + + private void setEnabled(boolean enabled) { + if (handlerStateEditor != null) { + handlerStateEditor.setComponentEnabled(enabled); + } + targetField.setEnabled(enabled); } private void updateHandlerEditor(BlazeCommandRunConfiguration config) { @@ -366,10 +430,10 @@ handlerStateEditor = handler.getState().getEditor(config.getProject()); if (handlerStateComponent != null) { - editor.remove(handlerStateComponent); + editorWithoutSyncCheckBox.remove(handlerStateComponent); } handlerStateComponent = handlerStateEditor.createComponent(); - editor.add(handlerStateComponent); + editorWithoutSyncCheckBox.add(handlerStateComponent); } @Override @@ -380,7 +444,7 @@ @Override protected void resetEditorFrom(BlazeCommandRunConfiguration config) { elementState = config.elementState.clone(); - updateTargetExpressionLabel(config); + updateEditor(config); if (config.handlerProvider != handlerProvider) { updateHandlerEditor(config); } @@ -397,6 +461,7 @@ } catch (WriteExternalException e) { LOG.error(e); } + config.keepInSync = keepInSyncCheckBox.isVisible() ? keepInSyncCheckBox.isSelected() : null; // now set the config's state, based on the editor's (possibly out of date) handler config.updateHandlerIfDifferentProvider(handlerProvider); @@ -411,7 +476,7 @@ String targetString = targetField.getText(); config.setTarget( Strings.isNullOrEmpty(targetString) ? null : TargetExpression.fromString(targetString)); - updateTargetExpressionLabel(config); + updateEditor(config); if (config.handlerProvider != handlerProvider) { updateHandlerEditor(config); handlerStateEditor.resetEditorFrom(config.handler.getState());
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java index f3409af..5a4daed 100644 --- a/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java +++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfiguration.java
@@ -22,4 +22,9 @@ public interface BlazeRunConfiguration { @Nullable TargetExpression getTarget(); + + /** Keep in sync with source XML */ + void setKeepInSync(@Nullable Boolean keepInSync); + + Boolean getKeepInSync(); }
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java index 74a3812..a1e0549 100644 --- a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java +++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationSyncListener.java
@@ -20,20 +20,28 @@ import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.projectview.section.sections.RunConfigurationsSection; import com.google.idea.blaze.base.projectview.section.sections.TargetSection; +import com.google.idea.blaze.base.run.exporter.RunConfigurationSerializer; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; import com.google.idea.blaze.base.sync.SyncListener; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; +import com.google.idea.sdkcompat.transactions.Transactions; import com.intellij.execution.RunManager; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.openapi.project.Project; -import com.intellij.util.ui.UIUtil; +import java.io.File; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; -/** Creates run configurations for project view targets, where appropriate. */ +/** + * Imports run configurations specified in the project view, and creates run configurations for + * project view targets, where appropriate. + */ public class BlazeRunConfigurationSyncListener extends SyncListener.Adapter { @Override @@ -45,23 +53,40 @@ BlazeProjectData blazeProjectData, SyncMode syncMode, SyncResult syncResult) { + if (syncMode == SyncMode.STARTUP) { + return; + } - UIUtil.invokeAndWaitIfNeeded( - (Runnable) - () -> { - Set<Label> labelsWithConfigs = labelsWithConfigs(project); - Set<TargetExpression> targetExpressions = - Sets.newHashSet(projectViewSet.listItems(TargetSection.KEY)); - // We only auto-generate configurations for rules listed in the project view. - for (TargetExpression target : targetExpressions) { - if (!(target instanceof Label) || labelsWithConfigs.contains(target)) { - continue; - } - Label label = (Label) target; - labelsWithConfigs.add(label); - maybeAddRunConfiguration(project, blazeProjectData, label); - } - }); + Set<File> xmlFiles = + getImportedRunConfigurations(projectViewSet, blazeProjectData.workspacePathResolver); + Transactions.submitTransactionAndWait( + () -> { + // First, import from specified XML files. Then auto-generate from targets. + xmlFiles.forEach( + (file) -> RunConfigurationSerializer.loadFromXmlIgnoreExisting(project, file)); + + Set<Label> labelsWithConfigs = labelsWithConfigs(project); + Set<TargetExpression> targetExpressions = + Sets.newHashSet(projectViewSet.listItems(TargetSection.KEY)); + // We only auto-generate configurations for rules listed in the project view. + for (TargetExpression target : targetExpressions) { + if (!(target instanceof Label) || labelsWithConfigs.contains(target)) { + continue; + } + Label label = (Label) target; + labelsWithConfigs.add(label); + maybeAddRunConfiguration(project, blazeProjectData, label); + } + }); + } + + private static Set<File> getImportedRunConfigurations( + ProjectViewSet projectViewSet, WorkspacePathResolver pathResolver) { + return projectViewSet + .listItems(RunConfigurationsSection.KEY) + .stream() + .map(pathResolver::resolveToFile) + .collect(Collectors.toSet()); } /** Collects a set of all the Blaze labels that have an associated run configuration. */
diff --git a/base/src/com/google/idea/blaze/base/run/DistributedExecutorSupport.java b/base/src/com/google/idea/blaze/base/run/DistributedExecutorSupport.java new file mode 100644 index 0000000..851eab6 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/DistributedExecutorSupport.java
@@ -0,0 +1,64 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import java.util.List; +import javax.annotation.Nullable; + +/** Information about any distributed executor available to the build system. */ +public interface DistributedExecutorSupport { + + ExtensionPointName<DistributedExecutorSupport> EP_NAME = + ExtensionPointName.create("com.google.idea.blaze.DistributedExecutorSupport"); + + /** + * Returns the name of an available distributed executor, if one exists for the given build + * system. + */ + @Nullable + static DistributedExecutorSupport getAvailableExecutor(BuildSystem buildSystem) { + for (DistributedExecutorSupport executor : EP_NAME.getExtensions()) { + if (executor.isAvailable(buildSystem)) { + return executor; + } + } + return null; + } + + /** Returns the blaze/bazel flags required to specify whether to run on a distributed executor. */ + static List<String> getBlazeFlags(Project project, @Nullable Boolean runDistributed) { + if (runDistributed == null) { + return ImmutableList.of(); + } + DistributedExecutorSupport executorInfo = getAvailableExecutor(Blaze.getBuildSystem(project)); + if (executorInfo == null) { + return ImmutableList.of(); + } + return ImmutableList.of(executorInfo.getBlazeFlag(runDistributed)); + } + + String executorName(); + + boolean isAvailable(BuildSystem buildSystem); + + /** Get blaze/bazel flag specifying whether to run on this distributed executor */ + String getBlazeFlag(boolean runDistributed); +}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java index 0080b3f..da3d641 100644 --- a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java +++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationHandler.java
@@ -20,6 +20,7 @@ import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RuntimeConfigurationException; @@ -38,8 +39,9 @@ private final BlazeCommandRunConfigurationCommonState state; public BlazeCommandGenericRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { - this.buildSystemName = Blaze.buildSystemName(configuration.getProject()); - this.state = new BlazeCommandRunConfigurationCommonState(buildSystemName); + BuildSystem buildSystem = Blaze.getBuildSystem(configuration.getProject()); + this.buildSystemName = buildSystem.getName(); + this.state = new BlazeCommandRunConfigurationCommonState(buildSystem); } @Override
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java index 79a5147..d2c6512 100644 --- a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java +++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.idea.blaze.base.async.process.LineProcessingOutputStream; import com.google.idea.blaze.base.command.BlazeCommand; +import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.command.BlazeFlags; import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor; import com.google.idea.blaze.base.metrics.Action; @@ -25,8 +26,12 @@ import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.DistributedExecutorSupport; import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter; import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler; +import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler; +import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandlerProvider; +import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; @@ -35,15 +40,22 @@ import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +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.RunProfile; import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.WrappingRunConfiguration; +import com.intellij.execution.filters.TextConsoleBuilderImpl; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessListener; import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.project.Project; +import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; /** @@ -55,7 +67,7 @@ @Override public RunProfileState getRunProfileState(Executor executor, ExecutionEnvironment environment) { - return new BlazeCommandRunProfileState(environment); + return new BlazeCommandRunProfileState(environment, null); } @Override @@ -68,13 +80,31 @@ public static class BlazeCommandRunProfileState extends CommandLineState { private final BlazeCommandRunConfiguration configuration; private final BlazeCommandRunConfigurationCommonState handlerState; + @Nullable private final BlazeTestEventsHandlerProvider testEventsHandlerProvider; - public BlazeCommandRunProfileState(ExecutionEnvironment environment) { + public BlazeCommandRunProfileState( + ExecutionEnvironment environment, + @Nullable BlazeTestEventsHandlerProvider testEventsHandlerProvider) { super(environment); - RunProfile runProfile = environment.getRunProfile(); - configuration = (BlazeCommandRunConfiguration) runProfile; - handlerState = + this.configuration = getConfiguration(environment); + this.handlerState = (BlazeCommandRunConfigurationCommonState) configuration.getHandler().getState(); + this.testEventsHandlerProvider = testEventsHandlerProvider; + } + + private static BlazeCommandRunConfiguration getConfiguration(ExecutionEnvironment environment) { + RunProfile runProfile = environment.getRunProfile(); + if (runProfile instanceof WrappingRunConfiguration) { + runProfile = ((WrappingRunConfiguration) runProfile).getPeer(); + } + return (BlazeCommandRunConfiguration) runProfile; + } + + @Override + public ExecutionResult execute(Executor executor, ProgramRunner runner) + throws ExecutionException { + DefaultExecutionResult result = (DefaultExecutionResult) super.execute(executor, runner); + return SmRunnerUtils.attachRerunFailedTestsAction(result); } @Override @@ -88,14 +118,24 @@ ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); assert projectViewSet != null; - BlazeCommand blazeCommand = - BlazeCommand.builder(Blaze.getBuildSystem(project), handlerState.getCommand()) - .setBlazeBinary(handlerState.getBlazeBinary()) - .addTargets(configuration.getTarget()) - .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)) - .addBlazeFlags(handlerState.getBlazeFlags()) - .addExeFlags(handlerState.getExeFlags()) - .build(); + ImmutableList<String> testHandlerFlags = ImmutableList.of(); + BlazeTestEventsHandler testEventsHandler = + canUseTestUi() && testEventsHandlerProvider != null + ? testEventsHandlerProvider.getHandler() + : null; + if (testEventsHandler != null) { + testHandlerFlags = testEventsHandler.getBlazeFlags(); + setConsoleBuilder( + new TextConsoleBuilderImpl(project) { + @Override + protected ConsoleView createConsole() { + return SmRunnerUtils.getConsoleView( + project, configuration, getEnvironment().getExecutor(), testEventsHandler); + } + }); + } + + BlazeCommand blazeCommand = getBlazeCommand(project, testHandlerFlags); WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings); return new ScopedBlazeProcessHandler( @@ -120,5 +160,28 @@ } }); } + + private BlazeCommand getBlazeCommand(Project project, ImmutableList<String> testHandlerFlags) { + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + assert projectViewSet != null; + + return BlazeCommand.builder(Blaze.getBuildSystem(project), handlerState.getCommand()) + .setBlazeBinary(handlerState.getBlazeBinary()) + .addTargets(configuration.getTarget()) + .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)) + .addBlazeFlags(testHandlerFlags) + .addBlazeFlags(handlerState.getBlazeFlags()) + .addBlazeFlags( + DistributedExecutorSupport.getBlazeFlags( + project, handlerState.getRunOnDistributedExecutor())) + .addExeFlags(handlerState.getExeFlags()) + .build(); + } + + private boolean canUseTestUi() { + return testEventsHandlerProvider != null + && BlazeCommandName.TEST.equals(handlerState.getCommand()) + && !Boolean.TRUE.equals(handlerState.getRunOnDistributedExecutor()); + } } }
diff --git a/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationAction.java b/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationAction.java new file mode 100644 index 0000000..bc97225 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationAction.java
@@ -0,0 +1,46 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.exporter; + +import com.google.idea.blaze.base.actions.BlazeProjectAction; +import com.google.idea.blaze.base.run.BlazeRunConfiguration; +import com.intellij.execution.RunManager; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; + +/** + * Export selected run configurations to file, so they can be checked in and shared between users. + */ +public class ExportRunConfigurationAction extends BlazeProjectAction implements DumbAware { + + @Override + protected void updateForBlazeProject(Project project, AnActionEvent e) { + boolean hasBlazeConfigs = + RunManager.getInstance(project) + .getAllConfigurationsList() + .stream() + .anyMatch((config) -> config instanceof BlazeRunConfiguration); + if (!hasBlazeConfigs) { + e.getPresentation().setEnabled(false); + } + } + + @Override + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + new ExportRunConfigurationDialog(project).show(); + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationDialog.java b/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationDialog.java new file mode 100644 index 0000000..c4802c1 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationDialog.java
@@ -0,0 +1,270 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.exporter; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.io.FileAttributeProvider; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.projectview.ProjectViewSet.ProjectViewFile; +import com.google.idea.blaze.base.run.BlazeRunConfiguration; +import com.google.idea.blaze.base.settings.Blaze; +import com.intellij.execution.RunManager; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.fileChooser.FileChooserDialog; +import com.intellij.openapi.fileChooser.FileChooserFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.BooleanTableCellEditor; +import com.intellij.ui.BooleanTableCellRenderer; +import com.intellij.ui.ColoredTableCellRenderer; +import com.intellij.ui.FieldPanel; +import com.intellij.ui.GuiUtils; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.table.JBTable; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.swing.DefaultCellEditor; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.table.TableColumn; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; + +/** UI for exporting blaze run configurations. */ +public class ExportRunConfigurationDialog extends DialogWrapper { + + private final ImmutableList<RunConfiguration> blazeConfigurations; + private final ExportRunConfigurationTableModel tableModel; + private final JBTable table; + private final FieldPanel outputDirectoryPanel; + + ExportRunConfigurationDialog(Project project) { + super(project, true); + blazeConfigurations = + ImmutableList.copyOf( + RunManager.getInstance(project) + .getAllConfigurationsList() + .stream() + .filter((config) -> config instanceof BlazeRunConfiguration) + .collect(Collectors.toList())); + tableModel = new ExportRunConfigurationTableModel(blazeConfigurations); + table = new JBTable(tableModel); + + TableColumn booleanColumn = table.getColumnModel().getColumn(0); + booleanColumn.setCellRenderer(new BooleanTableCellRenderer()); + booleanColumn.setCellEditor(new BooleanTableCellEditor()); + int width = table.getFontMetrics(table.getFont()).stringWidth(table.getColumnName(0)) + 10; + booleanColumn.setPreferredWidth(width); + booleanColumn.setMinWidth(width); + booleanColumn.setMaxWidth(width); + + table + .getColumnModel() + .getColumn(2) + .setCellEditor(new DefaultCellEditor(GuiUtils.createUndoableTextField())); + + TableColumn nameColumn = table.getColumnModel().getColumn(1); + nameColumn.setCellRenderer( + new ColoredTableCellRenderer() { + @Override + protected void customizeCellRenderer( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { + RunConfiguration config = blazeConfigurations.get(row); + setIcon(config.getType().getIcon()); + append(config.getName()); + } + }); + + table.setPreferredSize(new Dimension(700, 700)); + table.setShowColumns(true); + + final ActionListener browseAction = e -> chooseDirectory(); + outputDirectoryPanel = + new FieldPanel("Export configurations to directory:", null, browseAction, null); + File defaultExportDirectory = defaultExportDirectory(project); + if (defaultExportDirectory != null) { + outputDirectoryPanel.setText(defaultExportDirectory.getPath()); + } + + String buildSystem = Blaze.buildSystemName(project); + setTitle(String.format("Export %s Run Configurations", buildSystem)); + init(); + } + + /** Try to find a checked-in project view file. Otherwise, fall back to the workspace root. */ + @Nullable + private static File defaultExportDirectory(Project project) { + WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProjectSafe(project); + if (workspaceRoot == null) { + return null; + } + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + if (projectViewSet != null) { + for (ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) { + File file = projectViewFile.projectViewFile; + if (file != null && FileUtil.isAncestor(workspaceRoot.directory(), file, false)) { + return file.getParentFile(); + } + } + } + return workspaceRoot.directory(); + } + + private String getOutputDirectoryPath() { + return Strings.nullToEmpty(outputDirectoryPanel.getText()).trim(); + } + + private void chooseDirectory() { + FileChooserDescriptor descriptor = + FileChooserDescriptorFactory.createSingleFolderDescriptor() + .withTitle("Export Directory Location") + .withDescription("Choose directory to export run configurations to") + .withHideIgnored(false); + FileChooserDialog chooser = + FileChooserFactory.getInstance().createFileChooser(descriptor, null, null); + + final VirtualFile[] files; + File existingLocation = new File(getOutputDirectoryPath()); + if (existingLocation.exists()) { + VirtualFile toSelect = + LocalFileSystem.getInstance().refreshAndFindFileByPath(existingLocation.getPath()); + files = chooser.choose(null, toSelect); + } else { + files = chooser.choose(null); + } + if (files.length == 0) { + return; + } + VirtualFile file = files[0]; + outputDirectoryPanel.setText(file.getPath()); + } + + @Nullable + @Override + protected ValidationInfo doValidate() { + String outputDir = getOutputDirectoryPath(); + if (outputDir.isEmpty()) { + return new ValidationInfo("Choose an output directory"); + } + if (!FileAttributeProvider.getInstance().exists(new File(outputDir))) { + return new ValidationInfo("Invalid output directory"); + } + Set<String> names = new HashSet<>(); + for (int i = 0; i < blazeConfigurations.size(); i++) { + if (!tableModel.enabled[i]) { + continue; + } + if (!names.add(tableModel.paths[i])) { + return new ValidationInfo("Duplicate output file name '" + tableModel.paths[i] + "'"); + } + } + return null; + } + + @Override + protected void doOKAction() { + File outputDir = new File(getOutputDirectoryPath()); + List<File> outputFiles = new ArrayList<>(); + for (int i = 0; i < blazeConfigurations.size(); i++) { + if (!tableModel.enabled[i]) { + continue; + } + File outputFile = new File(outputDir, tableModel.paths[i]); + writeConfiguration(blazeConfigurations.get(i), outputFile); + outputFiles.add(outputFile); + } + LocalFileSystem.getInstance().refreshIoFiles(outputFiles); + super.doOKAction(); + } + + private static void writeConfiguration(RunConfiguration configuration, File outputFile) { + try (FileOutputStream writer = new FileOutputStream(outputFile, false)) { + XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat()); + xmlOutputter.output(RunConfigurationSerializer.writeToXml(configuration), writer); + } catch (IOException e) { + throw new RuntimeException("Error exporting run configuration to file: " + outputFile); + } + } + + @Override + protected JComponent createNorthPanel() { + return outputDirectoryPanel; + } + + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(IdeBorderFactory.createTitledBorder("Run Configurations", false)); + panel.add( + ToolbarDecorator.createDecorator(table).addExtraAction(new SelectAllButton()).createPanel(), + BorderLayout.CENTER); + return panel; + } + + private class SelectAllButton extends AnActionButton { + + boolean allSelected = false; + + private SelectAllButton() { + super("Select All", AllIcons.Actions.Selectall); + } + + @Override + public synchronized void actionPerformed(AnActionEvent anActionEvent) { + boolean newState = !allSelected; + for (int i = 0; i < tableModel.enabled.length; i++) { + table.setValueAt(newState, i, 0); + } + allSelected = newState; + Presentation presentation = anActionEvent.getPresentation(); + if (allSelected) { + presentation.setText("Deselect All"); + presentation.setIcon(AllIcons.Actions.Unselectall); + } else { + presentation.setText("Select All"); + presentation.setIcon(AllIcons.Actions.Selectall); + } + tableModel.fireTableDataChanged(); + table.revalidate(); + table.repaint(); + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationTableModel.java b/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationTableModel.java new file mode 100644 index 0000000..cc00d52 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/exporter/ExportRunConfigurationTableModel.java
@@ -0,0 +1,107 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.exporter; + +import com.google.common.collect.ImmutableList; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.text.UniqueNameGenerator; +import java.util.List; +import javax.swing.table.AbstractTableModel; + +/** Table model used by the 'export run configurations' UI. */ +class ExportRunConfigurationTableModel extends AbstractTableModel { + + private static final ImmutableList<String> COLUMN_NAMES = + ImmutableList.of("Export", "Name", "Output filename"); + private static final ImmutableList<Class<?>> COLUMN_CLASSES = + ImmutableList.of(Boolean.class, String.class, String.class); + + final Boolean[] enabled; + final String[] names; + final String[] paths; + + ExportRunConfigurationTableModel(List<RunConfiguration> configurations) { + enabled = new Boolean[configurations.size()]; + names = new String[configurations.size()]; + paths = new String[configurations.size()]; + + UniqueNameGenerator nameGenerator = new UniqueNameGenerator(); + for (int i = 0; i < configurations.size(); i++) { + RunConfiguration config = configurations.get(i); + enabled[i] = false; + names[i] = config.getName(); + paths[i] = + nameGenerator.generateUniqueName(FileUtil.sanitizeFileName(config.getName()), "", ".xml"); + } + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public Class<?> getColumnClass(int columnIndex) { + return COLUMN_CLASSES.get(columnIndex); + } + + @Override + public String getColumnName(int column) { + return COLUMN_NAMES.get(column); + } + + @Override + public int getRowCount() { + return enabled.length; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex != 1; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return enabled[rowIndex]; + case 1: + return names[rowIndex]; + case 2: + return paths[rowIndex]; + default: + throw new RuntimeException("Invalid column index: " + columnIndex); + } + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + enabled[rowIndex] = (Boolean) aValue; + return; + case 1: + names[rowIndex] = (String) aValue; + return; + case 2: + paths[rowIndex] = (String) aValue; + return; + default: + throw new RuntimeException("Invalid column index: " + columnIndex); + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java b/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java new file mode 100644 index 0000000..aed9db4 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializer.java
@@ -0,0 +1,125 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.exporter; + +import com.google.common.annotations.VisibleForTesting; +import com.google.idea.blaze.base.run.BlazeRunConfiguration; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.WriteExternalException; +import java.io.File; +import java.io.IOException; +import javax.annotation.Nullable; +import org.jdom.Element; +import org.jdom.JDOMException; + +/** Utility methods for converting run configuration to/from XML. */ +public class RunConfigurationSerializer { + + private static final Logger logger = Logger.getInstance(RunConfigurationSerializer.class); + + public static Element writeToXml(RunConfiguration configuration) { + RunnerAndConfigurationSettings settings = + RunManagerImpl.getInstanceImpl(configuration.getProject()).getSettings(configuration); + Element element = new Element("configuration"); + try { + ((RunnerAndConfigurationSettingsImpl) settings).writeExternal(element); + } catch (WriteExternalException e) { + logger.warn("Error serializing run configuration to XML", e); + } + return element; + } + + /** + * Parses a RunConfiguration from the given XML file, and adds it to the project, if there's not + * already a run configuration with the same name and type, + */ + public static void loadFromXmlIgnoreExisting(Project project, File xmlFile) { + try { + loadFromXmlElementIgnoreExisting(project, JDOMUtil.load(xmlFile)); + } catch (InvalidDataException | JDOMException | IOException e) { + logger.warn("Error parsing run configuration from XML", e); + } + } + + /** + * Parses a RunConfiguration from the given XML element, and adds it to the project, if there's + * not already a run configuration with the same name and type, + */ + @VisibleForTesting + static void loadFromXmlElementIgnoreExisting(Project project, Element element) + throws InvalidDataException { + if (!shouldLoadConfiguration(project, element)) { + return; + } + RunnerAndConfigurationSettings settings = + RunManagerImpl.getInstanceImpl(project).loadConfiguration(element, false); + RunConfiguration config = settings != null ? settings.getConfiguration() : null; + if (config instanceof BlazeRunConfiguration) { + ((BlazeRunConfiguration) config).setKeepInSync(true); + } + } + + /** + * Deserializes the configuration represented by the given XML element, then searches for an + * existing run configuration in the project with the same name and type. + */ + @Nullable + @VisibleForTesting + static RunnerAndConfigurationSettings findExisting(Project project, Element element) + throws InvalidDataException { + RunManagerImpl manager = RunManagerImpl.getInstanceImpl(project); + RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(manager); + settings.readExternal(element); + RunConfiguration config = settings.getConfiguration(); + if (config == null) { + return null; + } + return manager.findConfigurationByTypeAndName(config.getType().getId(), config.getName()); + } + + /** + * Returns true if there's either no matching configuration altready in the project, or the + * matching configuration is marked as 'keep in sync'. + */ + @VisibleForTesting + static boolean shouldLoadConfiguration(Project project, Element element) + throws InvalidDataException { + RunnerAndConfigurationSettings existing = findExisting(project, element); + if (existing == null) { + return true; + } + RunConfiguration config = existing.getConfiguration(); + if (!(config instanceof BlazeRunConfiguration)) { + return false; + } + BlazeRunConfiguration blazeConfig = (BlazeRunConfiguration) config; + Boolean keepInSync = blazeConfig.getKeepInSync(); + if (keepInSync == null) { + // if the matching configuration was never previously imported, don't overwrite, but activate + // the UI option to keep it in sync. + blazeConfig.setKeepInSync(false); + return false; + } + return keepInSync; + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java new file mode 100644 index 0000000..bc90989 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeRerunFailedTestsAction.java
@@ -0,0 +1,100 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.smrunner; + +import com.google.idea.blaze.base.command.BlazeCommandName; +import com.google.idea.blaze.base.command.BlazeFlags; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.testframework.TestFrameworkRunningModel; +import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.ui.ComponentContainer; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +/** Re-run failed tests. */ +public class BlazeRerunFailedTestsAction extends AbstractRerunFailedTestsAction { + + private final BlazeTestEventsHandler eventsHandler; + + public BlazeRerunFailedTestsAction( + BlazeTestEventsHandler eventsHandler, ComponentContainer componentContainer) { + super(componentContainer); + this.eventsHandler = eventsHandler; + } + + @Override + @Nullable + protected MyRunProfile getRunProfile(ExecutionEnvironment environment) { + final TestFrameworkRunningModel model = getModel(); + if (model == null) { + return null; + } + BlazeCommandRunConfiguration config = + (BlazeCommandRunConfiguration) model.getProperties().getConfiguration(); + return new BlazeRerunTestRunProfile(config.clone()); + } + + class BlazeRerunTestRunProfile extends MyRunProfile { + + private final BlazeCommandRunConfiguration configuration; + + BlazeRerunTestRunProfile(BlazeCommandRunConfiguration configuration) { + super(configuration); + this.configuration = configuration; + } + + @Override + public Module[] getModules() { + return Module.EMPTY_ARRAY; + } + + @Nullable + @Override + public RunProfileState getState(Executor executor, ExecutionEnvironment environment) + throws ExecutionException { + BlazeCommandRunConfigurationCommonState handlerState = + configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class); + if (handlerState == null || !BlazeCommandName.TEST.equals(handlerState.getCommand())) { + return null; + } + String testFilter = eventsHandler.getTestFilter(getProject(), getFailedTests(getProject())); + List<String> blazeFlags = setTestFilter(handlerState.getBlazeFlags(), testFilter); + handlerState.setBlazeFlags(blazeFlags); + return configuration.getState(executor, environment); + } + + /** Replaces existing test_filter flag, or appends if none exists. */ + private List<String> setTestFilter(List<String> flags, String testFilter) { + List<String> copy = new ArrayList<>(flags); + for (int i = 0; i < copy.size(); i++) { + String flag = copy.get(i); + if (flag.startsWith(BlazeFlags.TEST_FILTER)) { + copy.set(i, testFilter); + return copy; + } + } + copy.add(testFilter); + return copy; + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java new file mode 100644 index 0000000..6582c23 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestConsoleProperties.java
@@ -0,0 +1,57 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.smrunner; + +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.testframework.TestConsoleProperties; +import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction; +import com.intellij.execution.testframework.sm.SMCustomMessagesParsing; +import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter; +import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; +import com.intellij.execution.testframework.sm.runner.SMTestLocator; +import com.intellij.execution.ui.ConsoleView; +import javax.annotation.Nullable; + +/** Integrates blaze test results with the SM-runner test UI. */ +public class BlazeTestConsoleProperties extends SMTRunnerConsoleProperties + implements SMCustomMessagesParsing { + + private final BlazeTestEventsHandler eventsHandler; + + public BlazeTestConsoleProperties( + RunConfiguration runConfiguration, Executor executor, BlazeTestEventsHandler eventsHandler) { + super(runConfiguration, eventsHandler.frameworkName, executor); + this.eventsHandler = eventsHandler; + } + + @Override + public OutputToGeneralTestEventsConverter createTestEventsConverter( + String framework, TestConsoleProperties consoleProperties) { + return new BlazeXmlToTestEventsConverter(framework, consoleProperties, eventsHandler); + } + + @Override + public SMTestLocator getTestLocator() { + return eventsHandler.getTestLocator(); + } + + @Nullable + @Override + public AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) { + return eventsHandler.createRerunFailedTestsAction(consoleView); + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java new file mode 100644 index 0000000..87e1b7f --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandler.java
@@ -0,0 +1,103 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.smrunner; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction; +import com.intellij.execution.testframework.sm.runner.SMTestLocator; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.openapi.project.Project; +import com.intellij.util.io.URLUtil; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import javax.annotation.Nullable; + +/** Language-specific handling of SM runner test protocol */ +public abstract class BlazeTestEventsHandler { + + public final String frameworkName; + public final File testOutputXml; + + protected BlazeTestEventsHandler(String frameworkName) { + this.frameworkName = frameworkName; + this.testOutputXml = generateTempTestXmlFile(); + } + + /** + * Blaze/Bazel flags required for test UI.<br> + * Forces local test execution, without sharding, and sets the output test xml path. + */ + public ImmutableList<String> getBlazeFlags() { + return ImmutableList.of( + "--test_env=XML_OUTPUT_FILE=" + testOutputXml, + "--test_sharding_strategy=disabled", + "--runs_per_test=1", + "--flaky_test_attempts=1", + "--test_strategy=local"); + } + + public abstract SMTestLocator getTestLocator(); + + /** + * The --test_filter flag passed to blaze to rerun the given tests. + * + * @return null if no filter can be constructed for these tests. + */ + @Nullable + public abstract String getTestFilter(Project project, List<AbstractTestProxy> failedTests); + + @Nullable + public AbstractRerunFailedTestsAction createRerunFailedTestsAction(ConsoleView consoleView) { + return new BlazeRerunFailedTestsAction(this, consoleView); + } + + /** Converts the testsuite name in the blaze test XML to a user-friendly format */ + public String suiteDisplayName(String rawName) { + return rawName; + } + + /** Converts the testcase name in the blaze test XML to a user-friendly format */ + public String testDisplayName(String rawName) { + return rawName; + } + + public String suiteLocationUrl(String name) { + return SmRunnerUtils.GENERIC_SUITE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + name; + } + + public String testLocationUrl(String name, @Nullable String className) { + String base = SmRunnerUtils.GENERIC_TEST_PROTOCOL + URLUtil.SCHEME_SEPARATOR + name; + if (Strings.isNullOrEmpty(className)) { + return base; + } + return base + SmRunnerUtils.TEST_NAME_PARTS_SPLITTER + className; + } + + private static File generateTempTestXmlFile() { + try { + File file = Files.createTempFile("blazeTest", ".xml").toFile(); + file.deleteOnExit(); + return file; + + } catch (IOException e) { + throw new RuntimeException(e); + } + } +}
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandlerProvider.java similarity index 61% copy from aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java copy to base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandlerProvider.java index 148b1d9..c02a0cb 100644 --- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/InstantRunExperiment.java +++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeTestEventsHandlerProvider.java
@@ -5,7 +5,7 @@ * 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 + * 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, @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.idea.blaze.android.run.binary.instantrun; +package com.google.idea.blaze.base.run.smrunner; -import com.google.idea.common.experiments.BoolExperiment; +/** Provides a {@link BlazeTestEventsHandler}. */ +public interface BlazeTestEventsHandlerProvider { -/** Holds the instant run experiment */ -public class InstantRunExperiment { - public static final BoolExperiment INSTANT_RUN_ENABLED = - new BoolExperiment("instant.run.enabled", false); + BlazeTestEventsHandler getHandler(); }
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlSchema.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlSchema.java new file mode 100644 index 0000000..8f6d1f0 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlSchema.java
@@ -0,0 +1,110 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.smrunner; + +import com.google.common.collect.Lists; +import java.io.InputStream; +import java.util.List; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlValue; + +/** Used to parse the test.xml generated by the blaze/bazel testing framework. */ +public class BlazeXmlSchema { + + private static final JAXBContext CONTEXT; + + static { + try { + CONTEXT = JAXBContext.newInstance(TestSuite.class); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + + static TestSuite parse(InputStream input) { + try { + return (TestSuite) CONTEXT.createUnmarshaller().unmarshal(input); + } catch (JAXBException e) { + throw new RuntimeException("Failed to parse test XML", e); + } + } + + @XmlRootElement(name = "testsuites") + static class TestSuite { + @XmlAttribute String name; + @XmlAttribute String classname; + @XmlAttribute int tests; + @XmlAttribute int failures; + @XmlAttribute int errors; + @XmlAttribute int skipped; + @XmlAttribute int disabled; + @XmlAttribute double time; + + @XmlAttribute(name = "system-out") + String sysOut; + + @XmlAttribute(name = "system-err") + String sysErr; + + @XmlElement(name = "error", type = ErrorOrFailureOrSkipped.class) + ErrorOrFailureOrSkipped error; + + @XmlElement(name = "failure", type = ErrorOrFailureOrSkipped.class) + ErrorOrFailureOrSkipped failure; + + @XmlElement(name = "testsuite") + List<TestSuite> testSuites = Lists.newArrayList(); + + @XmlElement(name = "testdecorator") + List<TestSuite> testDecorators = Lists.newArrayList(); + + @XmlElement(name = "testcase") + List<TestCase> testCases = Lists.newArrayList(); + } + + static class TestCase { + @XmlAttribute String name; + @XmlAttribute String classname; + @XmlAttribute String status; + @XmlAttribute String result; + @XmlAttribute String time; + + @XmlAttribute(name = "system-out") + String sysOut; + + @XmlAttribute(name = "system-err") + String sysErr; + + @XmlElement(name = "error", type = ErrorOrFailureOrSkipped.class) + ErrorOrFailureOrSkipped error; + + @XmlElement(name = "failure", type = ErrorOrFailureOrSkipped.class) + ErrorOrFailureOrSkipped failure; + + @XmlElement(name = "skipped", type = ErrorOrFailureOrSkipped.class) + ErrorOrFailureOrSkipped skipped; + } + + static class ErrorOrFailureOrSkipped { + @XmlValue String content; + @XmlAttribute String message; + @XmlAttribute String type; + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java new file mode 100644 index 0000000..82dcae8 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/smrunner/BlazeXmlToTestEventsConverter.java
@@ -0,0 +1,207 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.smrunner; + +import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.ErrorOrFailureOrSkipped; +import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.TestCase; +import com.google.idea.blaze.base.run.smrunner.BlazeXmlSchema.TestSuite; +import com.google.idea.sdkcompat.smrunner.SmRunnerCompatUtils; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.testframework.TestConsoleProperties; +import com.intellij.execution.testframework.sm.runner.GeneralTestEventsProcessor; +import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter; +import com.intellij.execution.testframework.sm.runner.events.TestFinishedEvent; +import com.intellij.execution.testframework.sm.runner.events.TestIgnoredEvent; +import com.intellij.execution.testframework.sm.runner.events.TestOutputEvent; +import com.intellij.execution.testframework.sm.runner.events.TestStartedEvent; +import com.intellij.execution.testframework.sm.runner.events.TestSuiteFinishedEvent; +import com.intellij.execution.testframework.sm.runner.events.TestSuiteStartedEvent; +import com.intellij.openapi.util.Key; +import java.io.FileInputStream; +import java.io.InputStream; +import java.text.ParseException; +import javax.annotation.Nullable; +import jetbrains.buildServer.messages.serviceMessages.ServiceMessageVisitor; +import jetbrains.buildServer.messages.serviceMessages.TestSuiteStarted; + +/** Converts blaze test runner xml logs to smRunner events. */ +public class BlazeXmlToTestEventsConverter extends OutputToGeneralTestEventsConverter { + + private static final ErrorOrFailureOrSkipped NO_ERROR = new ErrorOrFailureOrSkipped(); + + private final BlazeTestEventsHandler eventsHandler; + + public BlazeXmlToTestEventsConverter( + String testFrameworkName, + TestConsoleProperties testConsoleProperties, + BlazeTestEventsHandler eventsHandler) { + super(testFrameworkName, testConsoleProperties); + this.eventsHandler = eventsHandler; + } + + @Override + protected boolean processServiceMessages( + String s, Key key, ServiceMessageVisitor serviceMessageVisitor) throws ParseException { + return super.processServiceMessages(s, key, serviceMessageVisitor); + } + + @Override + public void process(String text, Key outputType) { + super.process(text, outputType); + } + + @Override + public void dispose() { + super.dispose(); + } + + @Override + public void flushBufferBeforeTerminating() { + super.flushBufferBeforeTerminating(); + onStartTesting(); + try (InputStream input = new FileInputStream(eventsHandler.testOutputXml)) { + parseXmlInput(getProcessor(), input); + } catch (Exception e) { + // ignore parsing errors -- most common cause is user cancellation, which we can't easily + // recognize. + } + } + + private void parseXmlInput(GeneralTestEventsProcessor processor, InputStream input) { + TestSuite testResult = BlazeXmlSchema.parse(input); + processor.onTestsReporterAttached(); + processTestSuite(processor, testResult); + } + + private void processTestSuite(GeneralTestEventsProcessor processor, TestSuite suite) { + if (!hasRunChild(suite)) { + return; + } + // don't include the outermost 'testsuites' element. + boolean logSuite = suite.testSuites.isEmpty(); + if (suite.name != null && logSuite) { + TestSuiteStarted suiteStarted = + new TestSuiteStarted(eventsHandler.suiteDisplayName(suite.name)); + String locationUrl = eventsHandler.suiteLocationUrl(suite.name); + processor.onSuiteStarted(new TestSuiteStartedEvent(suiteStarted, locationUrl)); + } + + for (TestSuite child : suite.testSuites) { + processTestSuite(processor, child); + } + for (TestSuite decorator : suite.testDecorators) { + processTestSuite(processor, decorator); + } + for (TestCase test : suite.testCases) { + processTestCase(processor, test); + } + + if (suite.sysOut != null) { + processor.onUncapturedOutput(suite.sysOut, ProcessOutputTypes.STDOUT); + } + if (suite.sysErr != null) { + processor.onUncapturedOutput(suite.sysErr, ProcessOutputTypes.STDERR); + } + + if (suite.name != null && logSuite) { + processor.onSuiteFinished( + new TestSuiteFinishedEvent(eventsHandler.suiteDisplayName(suite.name))); + } + } + + /** + * Does the test suite have at least one child which wasn't skipped? <br> + * This prevents spurious warnings from entirely filtered test classes. + */ + private static boolean hasRunChild(TestSuite suite) { + for (TestSuite child : suite.testSuites) { + if (hasRunChild(child)) { + return true; + } + } + for (TestSuite child : suite.testDecorators) { + if (hasRunChild(child)) { + return true; + } + } + for (TestCase test : suite.testCases) { + if ("run".equals(test.status)) { + return true; + } + } + return false; + } + + private static boolean isCancelled(TestCase test) { + return "interrupted".equalsIgnoreCase(test.result) || "cancelled".equalsIgnoreCase(test.result); + } + + private static boolean isIgnored(TestCase test) { + if (test.skipped != null) { + return true; + } + return "suppressed".equalsIgnoreCase(test.result) + || "skipped".equalsIgnoreCase(test.result) + || "filtered".equalsIgnoreCase(test.result); + } + + private static boolean isFailed(TestCase test) { + return test.failure != null || test.error != null; + } + + private void processTestCase(GeneralTestEventsProcessor processor, TestCase test) { + if (test.name == null || "notrun".equals(test.status) || isCancelled(test)) { + return; + } + String displayName = eventsHandler.testDisplayName(test.name); + String locationUrl = eventsHandler.testLocationUrl(test.name, test.classname); + processor.onTestStarted(new TestStartedEvent(displayName, locationUrl)); + + if (test.sysOut != null) { + processor.onTestOutput(new TestOutputEvent(displayName, test.sysOut, true)); + } + if (test.sysErr != null) { + processor.onTestOutput(new TestOutputEvent(displayName, test.sysErr, true)); + } + + if (isIgnored(test)) { + ErrorOrFailureOrSkipped err = test.skipped != null ? test.skipped : NO_ERROR; + processor.onTestIgnored(new TestIgnoredEvent(displayName, err.message, err.content)); + } else if (isFailed(test)) { + ErrorOrFailureOrSkipped err = + test.failure != null ? test.failure : test.error != null ? test.error : NO_ERROR; + processor.onTestFailure( + SmRunnerCompatUtils.getTestFailedEvent( + displayName, err.message, err.content, parseTimeMillis(test.time))); + } + processor.onTestFinished(new TestFinishedEvent(displayName, parseTimeMillis(test.time))); + } + + private static long parseTimeMillis(@Nullable String time) { + if (time == null) { + return -1; + } + // if the number contains a decimal point, it's a value in seconds. Otherwise in milliseconds. + try { + if (time.contains(".")) { + return Math.round(Float.parseFloat(time) * 1000); + } + return Long.parseLong(time); + } catch (NumberFormatException e) { + return -1; + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java b/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java new file mode 100644 index 0000000..04715a7 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/smrunner/SmRunnerUtils.java
@@ -0,0 +1,76 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.smrunner; + +import com.intellij.execution.DefaultExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.testframework.TestConsoleProperties; +import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction; +import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; +import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; +import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView; +import com.intellij.execution.ui.ExecutionConsole; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import javax.swing.tree.TreeSelectionModel; + +/** Utility methods for setting up the SM runner test UI. */ +public class SmRunnerUtils { + + public static final String GENERIC_SUITE_PROTOCOL = "blaze:suite"; + public static final String GENERIC_TEST_PROTOCOL = "blaze:test"; + public static final String TEST_NAME_PARTS_SPLITTER = "::"; + + public static SMTRunnerConsoleView getConsoleView( + Project project, + RunConfiguration configuration, + Executor executor, + BlazeTestEventsHandler eventsHandler) { + SMTRunnerConsoleProperties properties = + new BlazeTestConsoleProperties(configuration, executor, eventsHandler); + SMTRunnerConsoleView console = + (SMTRunnerConsoleView) + SMTestRunnerConnectionUtil.createConsole(eventsHandler.frameworkName, properties); + Disposer.register(project, console); + console + .getResultsViewer() + .getTreeView() + .getSelectionModel() + .setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + return console; + } + + public static DefaultExecutionResult attachRerunFailedTestsAction(DefaultExecutionResult result) { + ExecutionConsole console = result.getExecutionConsole(); + if (!(console instanceof SMTRunnerConsoleView)) { + return result; + } + SMTRunnerConsoleView smConsole = (SMTRunnerConsoleView) console; + TestConsoleProperties consoleProperties = smConsole.getProperties(); + if (!(consoleProperties instanceof BlazeTestConsoleProperties)) { + return result; + } + BlazeTestConsoleProperties properties = (BlazeTestConsoleProperties) consoleProperties; + AbstractRerunFailedTestsAction action = properties.createRerunFailedTestsAction(smConsole); + if (action != null) { + action.init(properties); + action.setModelProvider(smConsole::getResultsViewer); + result.setRestartActions(action); + } + return result; + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeBinaryState.java b/base/src/com/google/idea/blaze/base/run/state/BlazeBinaryState.java index 2ddeffc..da96aad 100644 --- a/base/src/com/google/idea/blaze/base/run/state/BlazeBinaryState.java +++ b/base/src/com/google/idea/blaze/base/run/state/BlazeBinaryState.java
@@ -72,6 +72,11 @@ } @Override + public void setComponentEnabled(boolean enabled) { + blazeBinaryField.setEnabled(enabled); + } + + @Override public void resetEditorFrom(RunConfigurationState genericState) { BlazeBinaryState state = (BlazeBinaryState) genericState; blazeBinaryField.setText(Strings.nullToEmpty(state.getBlazeBinary()));
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java b/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java index ab44e47..1991ae6 100644 --- a/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java +++ b/base/src/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonState.java
@@ -17,8 +17,11 @@ import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.command.BlazeFlags; +import com.google.idea.blaze.base.run.state.BlazeRunOnDistributedExecutorState.RunOnExecutorStateEditor; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.execution.configurations.RuntimeConfigurationError; import com.intellij.execution.configurations.RuntimeConfigurationException; +import com.intellij.openapi.project.Project; import java.io.File; import java.util.List; import javax.annotation.Nullable; @@ -35,13 +38,15 @@ private final RunConfigurationFlagsState blazeFlags; private final RunConfigurationFlagsState exeFlags; private final BlazeBinaryState blazeBinary; + private final BlazeRunOnDistributedExecutorState runOnDistributedExecutor; - public BlazeCommandRunConfigurationCommonState(String buildSystemName) { + public BlazeCommandRunConfigurationCommonState(BuildSystem buildSystem) { command = new BlazeCommandState(); - blazeFlags = new RunConfigurationFlagsState(USER_BLAZE_FLAG_TAG, buildSystemName + " flags:"); + blazeFlags = new RunConfigurationFlagsState(USER_BLAZE_FLAG_TAG, buildSystem + " flags:"); exeFlags = new RunConfigurationFlagsState(USER_EXE_FLAG_TAG, "Executable flags:"); blazeBinary = new BlazeBinaryState(); - addStates(command, blazeFlags, exeFlags, blazeBinary); + runOnDistributedExecutor = new BlazeRunOnDistributedExecutorState(buildSystem); + addStates(command, blazeFlags, exeFlags, blazeBinary, runOnDistributedExecutor); } @Nullable @@ -91,6 +96,14 @@ return null; } + public Boolean getRunOnDistributedExecutor() { + return runOnDistributedExecutor.runOnDistributedExecutor; + } + + public void setRunOnDistributedExecutor(Boolean runOnDistributedExecutor) { + this.runOnDistributedExecutor.runOnDistributedExecutor = runOnDistributedExecutor; + } + public void validate(String buildSystemName) throws RuntimeConfigurationException { if (getCommand() == null) { throw new RuntimeConfigurationError("You must specify a command."); @@ -100,4 +113,32 @@ throw new RuntimeConfigurationError(buildSystemName + " binary does not exist"); } } + + @Override + public RunConfigurationStateEditor getEditor(Project project) { + return new RunConfigurationCompositeStateEditor(project, getStates()) { + + @Nullable + private final RunOnExecutorStateEditor runOnExecutorEditor = + (RunOnExecutorStateEditor) + editors + .stream() + .filter(editor -> editor instanceof RunOnExecutorStateEditor) + .findFirst() + .orElse(null); + + @Override + public void applyEditorTo(RunConfigurationState genericState) { + BlazeCommandRunConfigurationCommonState state = + (BlazeCommandRunConfigurationCommonState) genericState; + super.applyEditorTo(genericState); + + // this editor needs to update based on state provided by other children. + if (runOnExecutorEditor != null) { + boolean isTest = BlazeCommandName.TEST.equals(state.getCommand()); + runOnExecutorEditor.updateVisibility(isTest); + } + } + }; + } }
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeCommandState.java b/base/src/com/google/idea/blaze/base/run/state/BlazeCommandState.java index 491acb7..221fe10 100644 --- a/base/src/com/google/idea/blaze/base/run/state/BlazeCommandState.java +++ b/base/src/com/google/idea/blaze/base/run/state/BlazeCommandState.java
@@ -79,6 +79,11 @@ } @Override + public void setComponentEnabled(boolean enabled) { + commandCombo.setEnabled(enabled); + } + + @Override public void resetEditorFrom(RunConfigurationState genericState) { BlazeCommandState state = (BlazeCommandState) genericState; commandCombo.setSelectedItem(state.getCommand());
diff --git a/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorState.java b/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorState.java new file mode 100644 index 0000000..b251d3f --- /dev/null +++ b/base/src/com/google/idea/blaze/base/run/state/BlazeRunOnDistributedExecutorState.java
@@ -0,0 +1,127 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.state; + +import com.google.idea.blaze.base.run.DistributedExecutorSupport; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; +import com.google.idea.blaze.base.ui.UiUtil; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.ui.components.JBCheckBox; +import javax.annotation.Nullable; +import javax.swing.JComponent; +import javax.swing.JLabel; +import org.jdom.Element; + +/** + * Provides an option to run blaze/bazel on a distributed executor, if available, rather than + * locally. + */ +public class BlazeRunOnDistributedExecutorState implements RunConfigurationState { + + private static final String RUN_ON_DISTRIBUTED_EXECUTOR_ATTR = + "blaze-run-on-distributed-executor"; + + @Nullable private final DistributedExecutorSupport executorInfo; + + public Boolean runOnDistributedExecutor = null; + + BlazeRunOnDistributedExecutorState(BuildSystem buildSystem) { + this.executorInfo = DistributedExecutorSupport.getAvailableExecutor(buildSystem); + } + + @Override + public void readExternal(Element element) throws InvalidDataException { + String string = element.getAttributeValue(RUN_ON_DISTRIBUTED_EXECUTOR_ATTR); + if (string != null) { + runOnDistributedExecutor = Boolean.parseBoolean(string); + } + } + + @Override + public void writeExternal(Element element) throws WriteExternalException { + if (executorInfo != null && runOnDistributedExecutor != null) { + element.setAttribute( + RUN_ON_DISTRIBUTED_EXECUTOR_ATTR, Boolean.toString(runOnDistributedExecutor)); + } else { + element.removeAttribute(RUN_ON_DISTRIBUTED_EXECUTOR_ATTR); + } + } + + @Override + public RunOnExecutorStateEditor getEditor(Project project) { + return new RunOnExecutorStateEditor(); + } + + /** Editor for {@link BlazeRunOnDistributedExecutorState} */ + class RunOnExecutorStateEditor implements RunConfigurationStateEditor { + + private final JBCheckBox checkBox = + new JBCheckBox("Run on " + (executorInfo != null ? executorInfo.executorName() : null)); + private final JLabel warning = + new JLabel( + "Warning: test UI integration is not available when running on distributed " + + "executor"); + + private boolean componentVisible = executorInfo != null; + private boolean isTest = false; + + private RunOnExecutorStateEditor() { + warning.setIcon(AllIcons.RunConfigurations.ConfigurationWarning); + checkBox.addItemListener(e -> setVisibility()); + setVisibility(); + } + + @Override + public void resetEditorFrom(RunConfigurationState genericState) { + BlazeRunOnDistributedExecutorState state = (BlazeRunOnDistributedExecutorState) genericState; + if (state.runOnDistributedExecutor != null) { + checkBox.setSelected(state.runOnDistributedExecutor); + } + } + + @Override + public void applyEditorTo(RunConfigurationState genericState) { + BlazeRunOnDistributedExecutorState state = (BlazeRunOnDistributedExecutorState) genericState; + if (checkBox.isVisible()) { + state.runOnDistributedExecutor = checkBox.isSelected(); + } + } + + @Override + public JComponent createComponent() { + return UiUtil.createBox(checkBox, warning); + } + + @Override + public void setComponentEnabled(boolean enabled) { + checkBox.setEnabled(enabled); + } + + void updateVisibility(boolean isTest) { + this.componentVisible = executorInfo != null; + this.isTest = isTest; + setVisibility(); + } + + private void setVisibility() { + warning.setVisible(componentVisible && isTest && checkBox.isSelected()); + checkBox.setVisible(componentVisible); + } + } +}
diff --git a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationCompositeState.java b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationCompositeState.java index 077e02d..a317232 100644 --- a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationCompositeState.java +++ b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationCompositeState.java
@@ -15,6 +15,7 @@ */ package com.google.idea.blaze.base.run.state; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.idea.blaze.base.ui.UiUtil; import com.intellij.openapi.project.Project; @@ -30,10 +31,6 @@ public class RunConfigurationCompositeState implements RunConfigurationState { private final List<RunConfigurationState> states; - public RunConfigurationCompositeState(List<RunConfigurationState> states) { - this.states = states; - } - protected RunConfigurationCompositeState() { this.states = Lists.newArrayList(); } @@ -42,6 +39,10 @@ Collections.addAll(this.states, states); } + protected ImmutableList<RunConfigurationState> getStates() { + return ImmutableList.copyOf(states); + } + @Override public final void readExternal(Element element) throws InvalidDataException { for (RunConfigurationState state : states) { @@ -60,11 +61,11 @@ /** @return A {@link RunConfigurationStateEditor} for this state. */ @Override - public final RunConfigurationStateEditor getEditor(Project project) { + public RunConfigurationStateEditor getEditor(Project project) { return new RunConfigurationCompositeStateEditor(project, states); } - private static class RunConfigurationCompositeStateEditor implements RunConfigurationStateEditor { + static class RunConfigurationCompositeStateEditor implements RunConfigurationStateEditor { List<RunConfigurationStateEditor> editors; public RunConfigurationCompositeStateEditor( @@ -96,5 +97,10 @@ .map(RunConfigurationStateEditor::createComponent) .collect(Collectors.toList())); } + + @Override + public void setComponentEnabled(boolean enabled) { + editors.forEach(editor -> editor.setComponentEnabled(enabled)); + } } }
diff --git a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java index c0edaa4..87d306c 100644 --- a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java +++ b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationFlagsState.java
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.idea.blaze.base.ui.UiUtil; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.execution.ParametersListUtil; import java.util.List; import javax.swing.JComponent; @@ -85,27 +86,38 @@ this.fieldLabel = fieldLabel; } + /** Identical to {@link ParametersListUtil#join}, except args are newline-delimited. */ private static String makeFlagString(List<String> flags) { - StringBuilder flagString = new StringBuilder(); + StringBuilder builder = new StringBuilder(); for (String flag : flags) { - if (flagString.length() > 0) { - flagString.append('\n'); + if (builder.length() > 0) { + builder.append('\n'); } - if (flag.isEmpty() || flag.contains(" ") || flag.contains("|")) { - flagString.append('"'); - flagString.append(flag); - flagString.append('"'); - } else { - flagString.append(flag); - } + builder.append(encode(flag)); } - return flagString.toString(); + return builder.toString(); + } + + private static String encode(String flag) { + StringBuilder builder = new StringBuilder(); + builder.append(flag); + StringUtil.escapeQuotes(builder); + if (builder.length() == 0 + || StringUtil.indexOf(builder, ' ') >= 0 + || StringUtil.indexOf(builder, '|') >= 0) { + StringUtil.quote(builder); + } + return builder.toString(); + } + + @Override + public void setComponentEnabled(boolean enabled) { + flagsField.setEnabled(enabled); } @Override public void resetEditorFrom(RunConfigurationState genericState) { RunConfigurationFlagsState state = (RunConfigurationFlagsState) genericState; - // Normally we could just use ParametersListUtils.join, but that will only space-delimit args. flagsField.setText(makeFlagString(state.getFlags())); }
diff --git a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationStateEditor.java b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationStateEditor.java index b04020e..3a905fc 100644 --- a/base/src/com/google/idea/blaze/base/run/state/RunConfigurationStateEditor.java +++ b/base/src/com/google/idea/blaze/base/run/state/RunConfigurationStateEditor.java
@@ -28,4 +28,6 @@ /** @return A component to display for the editor. */ JComponent createComponent(); + + void setComponentEnabled(boolean enabled); }
diff --git a/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java b/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java index 0b336f4..99d490f 100644 --- a/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java +++ b/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
@@ -134,7 +134,8 @@ Kind.JAVA_TEST, Kind.GWT_TEST, Kind.CC_TEST, - Kind.PY_TEST); + Kind.PY_TEST, + Kind.GO_TEST); } }
diff --git a/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java b/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java index b2e86be..926483b 100644 --- a/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java +++ b/base/src/com/google/idea/blaze/base/settings/ui/EditProjectViewAction.java
@@ -15,7 +15,7 @@ */ package com.google.idea.blaze.base.settings.ui; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -27,14 +27,10 @@ import java.io.File; /** Opens all the user's project views. */ -public class EditProjectViewAction extends BlazeAction { +public class EditProjectViewAction extends BlazeProjectAction { @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); if (projectViewSet == null) { return;
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java index db3cba2..c1c36b7 100644 --- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java +++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncManager.java
@@ -16,29 +16,31 @@ package com.google.idea.blaze.base.sync; import com.google.idea.blaze.base.async.executor.BlazeExecutor; +import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.settings.BlazeUserSettings; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupManager; -import org.jetbrains.annotations.NotNull; +import java.util.List; /** Manages syncing and its listeners. */ public class BlazeSyncManager { - @NotNull private final Project project; + private final Project project; - public BlazeSyncManager(@NotNull Project project) { + public BlazeSyncManager(Project project) { this.project = project; } - public static BlazeSyncManager getInstance(@NotNull Project project) { + public static BlazeSyncManager getInstance(Project project) { return ServiceManager.getService(project, BlazeSyncManager.class); } /** Requests a project sync with Blaze. */ - public void requestProjectSync(@NotNull final BlazeSyncParams syncParams) { + public void requestProjectSync(final BlazeSyncParams syncParams) { StartupManager.getInstance(project) .runWhenProjectIsInitialized( new Runnable() { @@ -59,4 +61,38 @@ } }); } + + public void fullProjectSync() { + BlazeSyncParams syncParams = + new BlazeSyncParams.Builder("Full Sync", BlazeSyncParams.SyncMode.FULL) + .addProjectViewTargets(true) + .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()) + .build(); + requestProjectSync(syncParams); + } + + public void incrementalProjectSync() { + BlazeSyncParams syncParams = + new BlazeSyncParams.Builder("Sync", BlazeSyncParams.SyncMode.INCREMENTAL) + .addProjectViewTargets(true) + .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()) + .build(); + requestProjectSync(syncParams); + } + + public void partialSync(List<TargetExpression> targetExpressions) { + BlazeSyncParams syncParams = + new BlazeSyncParams.Builder("Partial Sync", BlazeSyncParams.SyncMode.PARTIAL) + .addTargetExpressions(targetExpressions) + .build(); + requestProjectSync(syncParams); + } + + public void workingSetSync() { + BlazeSyncParams syncParams = + new BlazeSyncParams.Builder("Sync Working Set", BlazeSyncParams.SyncMode.PARTIAL) + .addWorkingSet(true) + .build(); + requestProjectSync(syncParams); + } }
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java index 2ee84c7..22fba9c 100644 --- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java +++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
@@ -27,7 +27,7 @@ /** The kind of sync. */ public enum SyncMode { /** Happens on startup, restores in-memory state */ - RESTORE_EPHEMERAL_STATE, + STARTUP, /** Partial / working set sync */ PARTIAL, /** This is the standard incremental sync */
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java index 0b55343..aa429f3 100644 --- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java +++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableSet; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.LanguageClass; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -119,6 +120,7 @@ Project project, BlazeContext context, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, BlazeProjectData blazeProjectData); @Nullable @@ -136,6 +138,19 @@ Module workspaceModule, ModifiableRootModel workspaceModifiableModel); + /** + * Updates in-memory state that isn't serialized by IntelliJ. + * + * <p>Called on sync and on startup, after updateProjectStructure. May not do any write actions. + */ + void updateInMemoryState( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + Module workspaceModule); + /** Validates the project. */ boolean validate(Project project, BlazeContext context, BlazeProjectData blazeProjectData); @@ -203,6 +218,7 @@ Project project, BlazeContext context, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, BlazeProjectData blazeProjectData) {} @Nullable @@ -224,6 +240,15 @@ ModifiableRootModel workspaceModifiableModel) {} @Override + public void updateInMemoryState( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + Module workspaceModule) {} + + @Override public boolean validate( Project project, BlazeContext context, BlazeProjectData blazeProjectData) { return true;
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java index 5b6a8ee..0fe7dac 100644 --- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java +++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncStartupActivity.java
@@ -43,8 +43,7 @@ .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()) .build(); } - return new BlazeSyncParams.Builder( - "Sync Project", BlazeSyncParams.SyncMode.RESTORE_EPHEMERAL_STATE) + return new BlazeSyncParams.Builder("Sync Project", BlazeSyncParams.SyncMode.STARTUP) .addProjectViewTargets(true) .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()) .build();
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java index 3d4e3ec..1fb59c9 100644 --- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java +++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
@@ -24,7 +24,6 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.idea.blaze.base.async.AsyncUtil; import com.google.idea.blaze.base.async.FutureUtil; import com.google.idea.blaze.base.async.executor.BlazeExecutor; import com.google.idea.blaze.base.command.info.BlazeInfo; @@ -32,10 +31,13 @@ import com.google.idea.blaze.base.filecache.FileCaches; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.io.FileAttributeProvider; import com.google.idea.blaze.base.metrics.Action; import com.google.idea.blaze.base.model.BlazeLibrary; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; +import com.google.idea.blaze.base.model.SyncState.Builder; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -66,6 +68,7 @@ import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface; import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface.BuildResult; import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl; import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector; import com.google.idea.blaze.base.sync.libraries.LibraryEditor; @@ -84,9 +87,11 @@ import com.google.idea.blaze.base.targetmaps.ReverseDependencyMap; import com.google.idea.blaze.base.util.SaveUtil; import com.google.idea.blaze.base.vcs.BlazeVcsHandler; +import com.google.idea.sdkcompat.transactions.Transactions; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Progressive; @@ -173,7 +178,18 @@ } onSyncStart(project, context, syncMode); - syncResult = doSyncProject(context, syncMode, oldBlazeProjectData); + if (syncMode != SyncMode.STARTUP) { + syncResult = doSyncProject(context, syncMode, oldBlazeProjectData); + } else { + syncResult = SyncResult.SUCCESS; + } + if (syncResult.successful()) { + ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + updateInMemoryState(project, context, projectViewSet, blazeProjectData); + onSyncComplete(project, context, projectViewSet, blazeProjectData, syncMode, syncResult); + } } catch (AssertionError | Exception e) { LOG.error(e); IssueOutput.error("Internal error: " + e.getMessage()).submit(context); @@ -188,13 +204,13 @@ BlazeContext context, SyncMode syncMode, @Nullable BlazeProjectData oldBlazeProjectData) { this.syncStartTime = System.currentTimeMillis(); - BlazeVcsHandler vcsHandler = null; - for (BlazeVcsHandler candidate : BlazeVcsHandler.EP_NAME.getExtensions()) { - if (candidate.handlesProject(importSettings.getBuildSystem(), workspaceRoot)) { - vcsHandler = candidate; - break; - } + if (!FileAttributeProvider.getInstance().exists(workspaceRoot.directory())) { + IssueOutput.error(String.format("Workspace '%s' doesn't exist.", workspaceRoot.directory())) + .submit(context); + return SyncResult.FAILURE; } + + BlazeVcsHandler vcsHandler = BlazeVcsHandler.vcsHandlerForProject(project); if (vcsHandler == null) { IssueOutput.error("Could not find a VCS handler").submit(context); return SyncResult.FAILURE; @@ -222,6 +238,8 @@ } BlazeRoots blazeRoots = BlazeRoots.build(importSettings.getBuildSystem(), workspaceRoot, blazeInfo); + BlazeVersionData blazeVersionData = + BlazeVersionData.build(importSettings.getBuildSystem(), workspaceRoot, blazeInfo); WorkspacePathResolverAndProjectView workspacePathResolverAndProjectView = computeWorkspacePathResolverAndProjectView(context, blazeRoots, vcsHandler, executor); @@ -245,7 +263,7 @@ } if (!ProjectViewVerifier.verifyProjectView( - context, workspaceRoot, projectViewSet, workspaceLanguageSettings)) { + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings)) { return SyncResult.FAILURE; } @@ -269,120 +287,112 @@ printWorkingSet(context, workingSet); } - BuildResult ideInfoResult = BuildResult.SUCCESS; - BuildResult ideResolveResult = BuildResult.SUCCESS; - if (syncMode != SyncMode.RESTORE_EPHEMERAL_STATE || oldBlazeProjectData == null) { - SyncState.Builder syncStateBuilder = new SyncState.Builder(); - SyncState previousSyncState = - oldBlazeProjectData != null ? oldBlazeProjectData.syncState : null; + SyncState.Builder syncStateBuilder = new SyncState.Builder(); + SyncState previousSyncState = + oldBlazeProjectData != null ? oldBlazeProjectData.syncState : null; - List<TargetExpression> targets = Lists.newArrayList(); - if (syncParams.addProjectViewTargets || oldBlazeProjectData == null) { - Collection<TargetExpression> projectViewTargets = - projectViewSet.listItems(TargetSection.KEY); - if (!projectViewTargets.isEmpty()) { - targets.addAll(projectViewTargets); - printTargets(context, "project view", projectViewTargets); - } + List<TargetExpression> targets = Lists.newArrayList(); + if (syncParams.addProjectViewTargets || oldBlazeProjectData == null) { + Collection<TargetExpression> projectViewTargets = projectViewSet.listItems(TargetSection.KEY); + if (!projectViewTargets.isEmpty()) { + targets.addAll(projectViewTargets); + printTargets(context, "project view", projectViewTargets); } - if (syncParams.addWorkingSet && workingSet != null) { - Collection<? extends TargetExpression> workingSetTargets = - getWorkingSetTargets(projectViewSet, workingSet); - if (!workingSetTargets.isEmpty()) { - targets.addAll(workingSetTargets); - printTargets(context, "working set", workingSetTargets); - } - } - if (!syncParams.targetExpressions.isEmpty()) { - targets.addAll(syncParams.targetExpressions); - printTargets(context, syncParams.title, syncParams.targetExpressions); - } - - boolean mergeWithOldState = !syncParams.addProjectViewTargets; - BlazeIdeInterface.IdeResult ideQueryResult = - getIdeQueryResult( - project, - context, - projectViewSet, - targets, - workspaceLanguageSettings, - artifactLocationDecoder, - syncStateBuilder, - previousSyncState, - mergeWithOldState); - if (context.isCancelled()) { - return SyncResult.CANCELLED; - } - if (ideQueryResult.targetMap == null - || ideQueryResult.buildResult == BuildResult.FATAL_ERROR) { - context.setHasError(); - return SyncResult.FAILURE; - } - - TargetMap targetMap = ideQueryResult.targetMap; - ideInfoResult = ideQueryResult.buildResult; - - ListenableFuture<ImmutableMultimap<TargetKey, TargetKey>> reverseDependenciesFuture = - BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(targetMap)); - - ideResolveResult = - resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targets); - if (ideResolveResult == BuildResult.FATAL_ERROR) { - context.setHasError(); - return SyncResult.FAILURE; - } - if (context.isCancelled()) { - return SyncResult.CANCELLED; - } - - Scope.push( - context, - (childContext) -> { - childContext.push(new TimingScope("UpdateSyncState")); - for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { - syncPlugin.updateSyncState( - project, - childContext, - workspaceRoot, - projectViewSet, - workspaceLanguageSettings, - blazeRoots, - workingSet, - workspacePathResolver, - artifactLocationDecoder, - targetMap, - syncStateBuilder, - previousSyncState); - } - }); - - ImmutableMultimap<TargetKey, TargetKey> reverseDependencies = - FutureUtil.waitForFuture(context, reverseDependenciesFuture) - .timed("ReverseDependencies") - .onError("Failed to compute reverse dependency map") - .run() - .result(); - if (reverseDependencies == null) { - return SyncResult.FAILURE; - } - - newBlazeProjectData = - new BlazeProjectData( - syncStartTime, - targetMap, - blazeInfo, - blazeRoots, - workingSet, - workspacePathResolver, - artifactLocationDecoder, - workspaceLanguageSettings, - syncStateBuilder.build(), - reverseDependencies, - vcsHandler.getVcsName()); - } else { - // Restore project based on old blaze project data - newBlazeProjectData = oldBlazeProjectData; } + if (syncParams.addWorkingSet && workingSet != null) { + Collection<? extends TargetExpression> workingSetTargets = + getWorkingSetTargets(projectViewSet, workingSet); + if (!workingSetTargets.isEmpty()) { + targets.addAll(workingSetTargets); + printTargets(context, "working set", workingSetTargets); + } + } + if (!syncParams.targetExpressions.isEmpty()) { + targets.addAll(syncParams.targetExpressions); + printTargets(context, syncParams.title, syncParams.targetExpressions); + } + + boolean mergeWithOldState = !syncParams.addProjectViewTargets; + BlazeIdeInterface.IdeResult ideQueryResult = + getIdeQueryResult( + project, + context, + projectViewSet, + blazeVersionData, + targets, + workspaceLanguageSettings, + artifactLocationDecoder, + syncStateBuilder, + previousSyncState, + mergeWithOldState); + if (context.isCancelled()) { + return SyncResult.CANCELLED; + } + if (ideQueryResult.targetMap == null || ideQueryResult.buildResult == BuildResult.FATAL_ERROR) { + context.setHasError(); + return SyncResult.FAILURE; + } + + TargetMap targetMap = ideQueryResult.targetMap; + BuildResult ideInfoResult = ideQueryResult.buildResult; + + ListenableFuture<ImmutableMultimap<TargetKey, TargetKey>> reverseDependenciesFuture = + BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(targetMap)); + + BuildResult ideResolveResult = + resolveIdeArtifacts( + project, context, workspaceRoot, projectViewSet, blazeVersionData, targets); + if (ideResolveResult == BuildResult.FATAL_ERROR) { + context.setHasError(); + return SyncResult.FAILURE; + } + if (context.isCancelled()) { + return SyncResult.CANCELLED; + } + + Scope.push( + context, + (childContext) -> { + childContext.push(new TimingScope("UpdateSyncState")); + for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { + syncPlugin.updateSyncState( + project, + childContext, + workspaceRoot, + projectViewSet, + workspaceLanguageSettings, + blazeRoots, + workingSet, + workspacePathResolver, + artifactLocationDecoder, + targetMap, + syncStateBuilder, + previousSyncState); + } + }); + + ImmutableMultimap<TargetKey, TargetKey> reverseDependencies = + FutureUtil.waitForFuture(context, reverseDependenciesFuture) + .timed("ReverseDependencies") + .onError("Failed to compute reverse dependency map") + .run() + .result(); + if (reverseDependencies == null) { + return SyncResult.FAILURE; + } + + newBlazeProjectData = + new BlazeProjectData( + syncStartTime, + targetMap, + blazeInfo, + blazeRoots, + blazeVersionData, + workspacePathResolver, + artifactLocationDecoder, + workspaceLanguageSettings, + syncStateBuilder.build(), + reverseDependencies); FileCaches.onSync(project, context, projectViewSet, newBlazeProjectData, syncMode); ListenableFuture<?> prefetch = @@ -394,7 +404,13 @@ .run(); boolean success = - updateProject(project, context, projectViewSet, oldBlazeProjectData, newBlazeProjectData); + updateProject( + project, + context, + projectViewSet, + blazeVersionData, + oldBlazeProjectData, + newBlazeProjectData); if (!success) { return SyncResult.FAILURE; } @@ -417,7 +433,6 @@ syncResult = SyncResult.PARTIAL_SUCCESS; } - onSyncComplete(project, context, projectViewSet, newBlazeProjectData, syncMode, syncResult); return syncResult; } @@ -555,10 +570,11 @@ Project project, BlazeContext parentContext, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets, WorkspaceLanguageSettings workspaceLanguageSettings, ArtifactLocationDecoder artifactLocationDecoder, - SyncState.Builder syncStateBuilder, + Builder syncStateBuilder, @Nullable SyncState previousSyncState, boolean mergeWithOldState) { @@ -575,6 +591,7 @@ context, workspaceRoot, projectViewSet, + blazeVersionData, targets, workspaceLanguageSettings, artifactLocationDecoder, @@ -589,6 +606,7 @@ BlazeContext parentContext, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targetExpressions) { return Scope.push( parentContext, @@ -606,7 +624,7 @@ } BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance(); return blazeIdeInterface.resolveIdeArtifacts( - project, context, workspaceRoot, projectViewSet, targetExpressions); + project, context, workspaceRoot, projectViewSet, blazeVersionData, targetExpressions); }); } @@ -614,6 +632,7 @@ Project project, BlazeContext parentContext, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, @Nullable BlazeProjectData oldBlazeProjectData, BlazeProjectData newBlazeProjectData) { return Scope.push( @@ -625,19 +644,29 @@ context.output(new StatusOutput("Committing project structure...")); try { - AsyncUtil.executeProjectChangeAction( - () -> - ProjectRootManagerEx.getInstanceEx(this.project) - .mergeRootsChangesDuring( - () -> { - updateProjectSdk(context, projectViewSet, newBlazeProjectData); - updateProjectStructure( - context, - importSettings, - projectViewSet, - newBlazeProjectData, - oldBlazeProjectData); - })); + Transactions.submitTransactionAndWait( + () -> { + ApplicationManager.getApplication() + .runWriteAction( + (Runnable) + () -> { + ProjectRootManagerEx.getInstanceEx(this.project) + .mergeRootsChangesDuring( + () -> { + updateProjectSdk( + context, + projectViewSet, + blazeVersionData, + newBlazeProjectData); + updateProjectStructure( + context, + importSettings, + projectViewSet, + newBlazeProjectData, + oldBlazeProjectData); + }); + }); + }); } catch (Throwable t) { IssueOutput.error("Internal error. Error: " + t).submit(context); LOG.error(t); @@ -651,9 +680,13 @@ } private void updateProjectSdk( - BlazeContext context, ProjectViewSet projectViewSet, BlazeProjectData newBlazeProjectData) { + BlazeContext context, + ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, + BlazeProjectData newBlazeProjectData) { for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) { - syncPlugin.updateProjectSdk(project, context, projectViewSet, newBlazeProjectData); + syncPlugin.updateProjectSdk( + project, context, projectViewSet, blazeVersionData, newBlazeProjectData); } } @@ -716,6 +749,36 @@ moduleEditor.commitWithGc(context); } + private void updateInMemoryState( + Project project, + BlazeContext parentContext, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData) { + Scope.push( + parentContext, + context -> { + context.push(new TimingScope("UpdateInMemoryState")); + context.output(new StatusOutput("Updating in-memory state...")); + ApplicationManager.getApplication() + .runReadAction( + () -> { + Module workspaceModule = + ModuleManager.getInstance(project) + .findModuleByName(BlazeDataStorage.WORKSPACE_MODULE_NAME); + for (BlazeSyncPlugin blazeSyncPlugin : + BlazeSyncPlugin.EP_NAME.getExtensions()) { + blazeSyncPlugin.updateInMemoryState( + project, + context, + workspaceRoot, + projectViewSet, + blazeProjectData, + workspaceModule); + } + }); + }); + } + /** * Creates a module that includes the user's data directory. *
diff --git a/base/src/com/google/idea/blaze/base/sync/SyncListener.java b/base/src/com/google/idea/blaze/base/sync/SyncListener.java index a3512cb..7a0cf03 100644 --- a/base/src/com/google/idea/blaze/base/sync/SyncListener.java +++ b/base/src/com/google/idea/blaze/base/sync/SyncListener.java
@@ -31,13 +31,23 @@ /** Result of the sync operation */ enum SyncResult { /** Full success */ - SUCCESS, + SUCCESS(true), /** The user has errors in their BUILD files or compilation errors */ - PARTIAL_SUCCESS, + PARTIAL_SUCCESS(true), /** The user cancelled */ - CANCELLED, + CANCELLED(false), /** Failure -- sync could not complete */ - FAILURE, + FAILURE(false); + + private final boolean success; + + SyncResult(boolean success) { + this.success = success; + } + + public boolean successful() { + return success; + } } /** Called after open documents have been saved, prior to starting the blaze sync. */
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java b/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java index 0509cca..54f2ca1 100644 --- a/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java +++ b/base/src/com/google/idea/blaze/base/sync/actions/ExpandSyncToWorkingSetAction.java
@@ -15,12 +15,12 @@ */ package com.google.idea.blaze.base.sync.actions; -import com.google.idea.blaze.base.actions.BlazeToggleAction; +import com.google.idea.blaze.base.actions.BlazeProjectToggleAction; import com.google.idea.blaze.base.settings.BlazeUserSettings; import com.intellij.openapi.actionSystem.AnActionEvent; /** Manages a tick box of whether to expand the sync targets to the working set. */ -public class ExpandSyncToWorkingSetAction extends BlazeToggleAction { +public class ExpandSyncToWorkingSetAction extends BlazeProjectToggleAction { @Override public boolean isSelected(AnActionEvent e) { return BlazeUserSettings.getInstance().getExpandSyncToWorkingSet();
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java b/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java index 2c1f625..dfe752e 100644 --- a/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java +++ b/base/src/com/google/idea/blaze/base/sync/actions/FullSyncProjectAction.java
@@ -15,38 +15,29 @@ */ package com.google.idea.blaze.base.sync.actions; -import com.google.idea.blaze.base.actions.BlazeAction; -import com.google.idea.blaze.base.settings.BlazeUserSettings; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.sync.BlazeSyncManager; -import com.google.idea.blaze.base.sync.BlazeSyncParams; -import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; +import com.google.idea.blaze.base.sync.status.BlazeSyncStatus; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.project.Project; -/** Re-imports (syncs) an Android-Blaze project, without showing the "Import Project" wizard. */ -public class FullSyncProjectAction extends BlazeAction { +/** Performs a full sync. This is like an incremental sync with some additional invalidation. */ +public class FullSyncProjectAction extends BlazeProjectAction { - public FullSyncProjectAction() { - super("Non-Incrementally Sync Project with BUILD Files"); + @Override + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + BlazeSyncManager.getInstance(project).fullProjectSync(); + updateStatus(project, e); } @Override - public void actionPerformed(final AnActionEvent e) { - Project project = e.getProject(); - if (project != null) { - Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - try { - BlazeSyncParams syncParams = - new BlazeSyncParams.Builder("Full Sync", SyncMode.FULL) - .addProjectViewTargets(true) - .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()) - .build(); - BlazeSyncManager.getInstance(project).requestProjectSync(syncParams); - } finally { - presentation.setEnabled(true); - } - } + protected void updateForBlazeProject(Project project, AnActionEvent e) { + updateStatus(project, e); + } + + private static void updateStatus(Project project, AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(!BlazeSyncStatus.getInstance(project).syncInProgress()); } }
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java b/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java index 134acfb..f772b67 100644 --- a/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java +++ b/base/src/com/google/idea/blaze/base/sync/actions/IncrementalSyncProjectAction.java
@@ -15,14 +15,11 @@ */ package com.google.idea.blaze.base.sync.actions; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.BlazeUserSettings; import com.google.idea.blaze.base.sync.BlazeSyncManager; -import com.google.idea.blaze.base.sync.BlazeSyncParams; -import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; import com.google.idea.blaze.base.sync.status.BlazeSyncStatus; -import com.google.idea.blaze.base.sync.status.BlazeSyncStatusImpl; import com.intellij.notification.Notification; import com.intellij.notification.NotificationDisplayType; import com.intellij.notification.NotificationGroup; @@ -34,45 +31,27 @@ import icons.BlazeIcons; import javax.swing.Icon; -/** Re-imports (syncs) an Android-Blaze project, without showing the "Import Project" wizard. */ -public class IncrementalSyncProjectAction extends BlazeAction { +/** Syncs the project with BUILD files. */ +public class IncrementalSyncProjectAction extends BlazeProjectAction { - public IncrementalSyncProjectAction() { - super("Sync Project with BUILD Files"); + @Override + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + BlazeSyncManager.getInstance(project).incrementalProjectSync(); + updateIcon(e); } @Override - public void actionPerformed(final AnActionEvent e) { - Project project = e.getProject(); - if (project != null) { - BlazeSyncManager.getInstance(project) - .requestProjectSync( - new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL) - .addProjectViewTargets(true) - .addWorkingSet(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()) - .build()); - updateIcon(e); - } - } - - @Override - protected void doUpdate(AnActionEvent e) { - super.doUpdate(e); + protected void updateForBlazeProject(Project project, AnActionEvent e) { updateIcon(e); } private static void updateIcon(AnActionEvent e) { Project project = e.getProject(); Presentation presentation = e.getPresentation(); - if (project == null) { - presentation.setIcon(BlazeIcons.Blaze); - presentation.setEnabled(true); - return; - } - BlazeSyncStatusImpl statusHelper = BlazeSyncStatusImpl.getImpl(project); + BlazeSyncStatus statusHelper = BlazeSyncStatus.getInstance(project); BlazeSyncStatus.SyncStatus status = statusHelper.getStatus(); presentation.setIcon(getIcon(status)); - presentation.setEnabled(!statusHelper.syncInProgress.get()); + presentation.setEnabled(!statusHelper.syncInProgress()); if (status == BlazeSyncStatus.SyncStatus.DIRTY && !BlazeUserSettings.getInstance().getSyncStatusPopupShown()) {
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java b/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java index e761f11..60e8484 100644 --- a/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java +++ b/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
@@ -15,8 +15,9 @@ */ package com.google.idea.blaze.base.sync.actions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.projectview.ProjectViewManager; @@ -24,13 +25,13 @@ import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.google.idea.blaze.base.sync.BlazeSyncManager; -import com.google.idea.blaze.base.sync.BlazeSyncParams; import com.google.idea.blaze.base.sync.BuildTargetFinder; import com.google.idea.blaze.base.sync.projectview.ImportRoots; +import com.google.idea.blaze.base.sync.status.BlazeSyncStatus; import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; +import com.google.idea.common.actionhelper.ActionPresentationHelper; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import java.io.File; @@ -38,57 +39,43 @@ import javax.annotation.Nullable; /** Allows a partial sync of the project depending on what's been selected. */ -public class PartialSyncAction extends BlazeAction { +public class PartialSyncAction extends BlazeProjectAction { + @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project != null) { - List<TargetExpression> targetExpressions = Lists.newArrayList(); - getTargets(e, targetExpressions); - - BlazeSyncParams syncParams = - new BlazeSyncParams.Builder("Partial Sync", BlazeSyncParams.SyncMode.PARTIAL) - .addTargetExpressions(targetExpressions) - .build(); - - BlazeSyncManager.getInstance(project).requestProjectSync(syncParams); - } + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + VirtualFile virtualFile = getSelectedFile(e); + List<TargetExpression> targets = getTargets(project, virtualFile); + BlazeSyncManager.getInstance(project).partialSync(targets); } @Override - protected void doUpdate(AnActionEvent e) { - super.doUpdate(e); - List<TargetExpression> targets = Lists.newArrayList(); - String objectName = getTargets(e, targets); - - boolean enabled = objectName != null && !targets.isEmpty(); - Presentation presentation = e.getPresentation(); - presentation.setEnabled(enabled); - - if (enabled) { - presentation.setText( - String.format("Partially Sync %s with %s", objectName, buildSystemName(e.getProject()))); - } else { - presentation.setText(String.format("Partial %s Sync", buildSystemName(e.getProject()))); - } + protected void updateForBlazeProject(Project project, AnActionEvent e) { + VirtualFile virtualFile = getSelectedFile(e); + List<TargetExpression> targets = getTargets(project, virtualFile); + ActionPresentationHelper.of(e) + .disableIf(BlazeSyncStatus.getInstance(project).syncInProgress()) + .disableIf(targets.isEmpty()) + .setTextWithSubject("Partially Sync File", "Partially Sync %s", virtualFile) + .disableWithoutSubject() + .commit(); } - private static String buildSystemName(@Nullable Project project) { - return Blaze.buildSystemName(project); - } - - @Nullable - private String getTargets(AnActionEvent e, List<TargetExpression> targets) { - Project project = e.getProject(); + private VirtualFile getSelectedFile(AnActionEvent e) { VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE); - if (project == null || virtualFile == null || !virtualFile.isInLocalFileSystem()) { + if (virtualFile == null || !virtualFile.isInLocalFileSystem()) { return null; } + return virtualFile; + } + private static List<TargetExpression> getTargets( + Project project, @Nullable VirtualFile virtualFile) { + if (virtualFile == null) { + return ImmutableList.of(); + } + List<TargetExpression> targets = Lists.newArrayList(); WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); SourceToTargetMap.getInstance(project); - - String objectName = virtualFile.isDirectory() ? "Package" : "File"; if (!virtualFile.isDirectory()) { targets.addAll( SourceToTargetMap.getInstance(project) @@ -109,7 +96,6 @@ } } } - - return objectName; + return targets; } }
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java b/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java index d7c6e96..9cb2b46 100644 --- a/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java +++ b/base/src/com/google/idea/blaze/base/sync/actions/ShowPerformanceWarningsToggleAction.java
@@ -15,12 +15,12 @@ */ package com.google.idea.blaze.base.sync.actions; -import com.google.idea.blaze.base.actions.BlazeToggleAction; +import com.google.idea.blaze.base.actions.BlazeProjectToggleAction; import com.google.idea.blaze.base.settings.BlazeUserSettings; import com.intellij.openapi.actionSystem.AnActionEvent; /** Manages a tick box of whether to show performance warnings. */ -public class ShowPerformanceWarningsToggleAction extends BlazeToggleAction { +public class ShowPerformanceWarningsToggleAction extends BlazeProjectToggleAction { @Override public boolean isSelected(AnActionEvent e) { return BlazeUserSettings.getInstance().getShowPerformanceWarnings();
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java b/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java index 950f473..f72294b 100644 --- a/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java +++ b/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java
@@ -15,25 +15,29 @@ */ package com.google.idea.blaze.base.sync.actions; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.sync.BlazeSyncManager; -import com.google.idea.blaze.base.sync.BlazeSyncParams; -import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; +import com.google.idea.blaze.base.sync.status.BlazeSyncStatus; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.project.Project; -/** Allows a partial sync of the project depending on what's been selected. */ -public class SyncWorkingSetAction extends BlazeAction { - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project != null) { - BlazeSyncParams syncParams = - new BlazeSyncParams.Builder("Sync Working Set", SyncMode.PARTIAL) - .addWorkingSet(true) - .build(); +/** Allows a partial sync of the working set. */ +public class SyncWorkingSetAction extends BlazeProjectAction { - BlazeSyncManager.getInstance(project).requestProjectSync(syncParams); - } + @Override + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { + BlazeSyncManager.getInstance(project).workingSetSync(); + updateStatus(project, e); + } + + @Override + protected void updateForBlazeProject(Project project, AnActionEvent e) { + updateStatus(project, e); + } + + private static void updateStatus(Project project, AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(!BlazeSyncStatus.getInstance(project).syncInProgress()); } }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java index 4051083..c355d5d 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
@@ -16,6 +16,7 @@ package com.google.idea.blaze.base.sync.aspects; import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -73,6 +74,7 @@ BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets, WorkspaceLanguageSettings workspaceLanguageSettings, ArtifactLocationDecoder artifactLocationDecoder, @@ -90,5 +92,19 @@ BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, + List<TargetExpression> targets); + + /** + * Attempts to compile the requested ide artifacts. + * + * <p>Amounts to a build of the ide-compile output group. + */ + BuildResult compileIdeArtifacts( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets); }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java index f44f200..36e29af 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
@@ -36,6 +36,7 @@ import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor; import com.google.idea.blaze.base.metrics.Action; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -91,6 +92,7 @@ BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets, WorkspaceLanguageSettings workspaceLanguageSettings, ArtifactLocationDecoder artifactLocationDecoder, @@ -106,7 +108,7 @@ } // If the aspect strategy has changed, redo everything from scratch - final AspectStrategy aspectStrategy = getAspectStrategy(project); + final AspectStrategy aspectStrategy = getAspectStrategy(project, blazeVersionData); if (prevState != null && !Objects.equal(prevState.aspectStrategyName, aspectStrategy.getName())) { prevState = null; @@ -375,8 +377,38 @@ BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets) { - AspectStrategy aspectStrategy = getAspectStrategy(project); + return resolveIdeArtifacts( + project, context, workspaceRoot, projectViewSet, blazeVersionData, targets, false); + } + + @Override + public BuildResult compileIdeArtifacts( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, + List<TargetExpression> targets) { + boolean ideCompile = hasIdeCompileOutputGroup(blazeVersionData); + return resolveIdeArtifacts( + project, context, workspaceRoot, projectViewSet, blazeVersionData, targets, ideCompile); + } + + private static boolean hasIdeCompileOutputGroup(BlazeVersionData blazeVersionData) { + return blazeVersionData.bazelIsAtLeastVersion(0, 4, 3); + } + + private static BuildResult resolveIdeArtifacts( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, + List<TargetExpression> targets, + boolean useIdeCompileOutputGroup) { + AspectStrategy aspectStrategy = getAspectStrategy(project, blazeVersionData); BlazeCommand.Builder blazeCommandBuilder = BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD) @@ -385,7 +417,11 @@ .addBlazeFlags(BlazeFlags.KEEP_GOING) .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)); - aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder); + if (useIdeCompileOutputGroup) { + aspectStrategy.modifyIdeCompileCommand(blazeCommandBuilder); + } else { + aspectStrategy.modifyIdeResolveCommand(blazeCommandBuilder); + } BlazeCommand blazeCommand = blazeCommandBuilder.build(); @@ -402,9 +438,10 @@ return BuildResult.fromExitCode(retVal); } - private static AspectStrategy getAspectStrategy(Project project) { + private static AspectStrategy getAspectStrategy( + Project project, BlazeVersionData blazeVersionData) { for (AspectStrategyProvider provider : AspectStrategyProvider.EP_NAME.getExtensions()) { - AspectStrategy aspectStrategy = provider.getAspectStrategy(project); + AspectStrategy aspectStrategy = provider.getAspectStrategy(project, blazeVersionData); if (aspectStrategy != null) { return aspectStrategy; }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java index f3f0557..c140587 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
@@ -150,8 +150,7 @@ } private static TargetKey makeTargetKey(IntellijIdeInfo.TargetKey key) { - return TargetKey.forGeneralTarget( - new Label(key.getLabel()), Strings.emptyToNull(key.getAspectId())); + return TargetKey.forGeneralTarget(new Label(key.getLabel()), key.getAspectIdsList()); } private static Dependency makeDependency(IntellijIdeInfo.Dependency dep) {
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java index 598d8b6..d63630e 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java
@@ -28,6 +28,8 @@ void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder); + void modifyIdeCompileCommand(BlazeCommand.Builder blazeCommandBuilder); + String getAspectOutputFileExtension(); IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream) throws IOException;
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java index df88c4f..63787db 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java
@@ -22,6 +22,7 @@ /** Aspect strategy for native. */ public class AspectStrategyNative implements AspectStrategy { + @Override public String getName() { return "NativeAspect"; @@ -42,6 +43,13 @@ } @Override + public void modifyIdeCompileCommand(BlazeCommand.Builder blazeCommandBuilder) { + blazeCommandBuilder + .addBlazeFlags("--aspects=AndroidStudioInfoAspect") + .addBlazeFlags("--output_groups=ide-compile"); + } + + @Override public String getAspectOutputFileExtension() { return ".aswb-build"; }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java index 0fdd2b5..13c7586 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java
@@ -15,6 +15,7 @@ */ package com.google.idea.blaze.base.sync.aspects.strategy; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; import javax.annotation.Nullable; @@ -25,5 +26,5 @@ ExtensionPointName.create("com.google.idea.blaze.AspectStrategyProvider"); @Nullable - AspectStrategy getAspectStrategy(Project project); + AspectStrategy getAspectStrategy(Project project, BlazeVersionData blazeVersionData); }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java index 9042523..f3b1029 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java
@@ -15,14 +15,19 @@ */ package com.google.idea.blaze.base.sync.aspects.strategy; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.common.experiments.BoolExperiment; import com.intellij.openapi.project.Project; class AspectStrategyProviderBazel implements AspectStrategyProvider { - BoolExperiment useSkylarkAspect = new BoolExperiment("use.skylark.aspect.bazel", false); + private static final BoolExperiment useSkylarkAspect = + new BoolExperiment("use.skylark.aspect.bazel", true); @Override - public AspectStrategy getAspectStrategy(Project project) { - return useSkylarkAspect.getValue() ? new AspectStrategySkylark() : new AspectStrategyNative(); + public AspectStrategy getAspectStrategy(Project project, BlazeVersionData blazeVersionData) { + boolean canUseSkylark = + useSkylarkAspect.getValue() && blazeVersionData.bazelIsAtLeastVersion(0, 4, 3); + + return canUseSkylark ? new AspectStrategySkylark() : new AspectStrategyNative(); } }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java index 0139f87..aa3eadc 100644 --- a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java +++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java
@@ -18,6 +18,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.idea.blaze.base.command.BlazeCommand; +import com.google.idea.blaze.base.command.BlazeCommand.Builder; import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo; import com.google.repackaged.protobuf.TextFormat; import java.io.IOException; @@ -33,7 +34,7 @@ } protected String getAspectFlag() { - return "--aspects=@bazel_tools://tools/ide/intellij_info.bzl%intellij_info_aspect"; + return "--aspects=@bazel_tools//tools/ide:intellij_info.bzl%intellij_info_aspect"; } @Override @@ -51,6 +52,13 @@ } @Override + public void modifyIdeCompileCommand(Builder blazeCommandBuilder) { + blazeCommandBuilder + .addBlazeFlags(getAspectFlag()) + .addBlazeFlags("--output_groups=intellij-compile"); + } + + @Override public String getAspectOutputFileExtension() { return ".intellij-info.txt"; }
diff --git a/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java b/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java index 871274e..d8e72f6 100644 --- a/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java +++ b/base/src/com/google/idea/blaze/base/sync/projectstructure/ContentEntryEditor.java
@@ -24,6 +24,7 @@ import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.SourceFolderProvider; import com.google.idea.blaze.base.sync.projectview.ImportRoots; @@ -82,6 +83,15 @@ ImmutableMap<VirtualFile, SourceFolder> sourceFolders = provider.initializeSourceFolders(contentEntry); VirtualFile rootFile = getVirtualFile(root); + if (rootFile == null) { + IssueOutput.warn( + String.format( + "Could not find directory %s. Your 'test_sources' project view " + + "attribute will not have any effect. Please resync.", + workspaceRoot)) + .submit(context); + continue; + } SourceFolder rootSource = sourceFolders.get(rootFile); walkFileSystem( workspaceRoot, @@ -121,11 +131,11 @@ SourceFolder current = sourceFolders.get(file); SourceFolder currentOrParent = current != null ? current : parent; if (isTest != currentOrParent.isTestSource()) { + currentOrParent = + provider.setSourceFolderForLocation(contentEntry, currentOrParent, file, isTest); if (current != null) { contentEntry.removeSourceFolder(current); } - currentOrParent = - provider.setSourceFolderForLocation(contentEntry, currentOrParent, file, isTest); } for (VirtualFile child : file.getChildren()) { walkFileSystem(
diff --git a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java index aa98c7e..a1e3f7c 100644 --- a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java +++ b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatus.java
@@ -30,10 +30,13 @@ SyncStatus getStatus(); + static BlazeSyncStatus getInstance(Project project) { return ServiceManager.getService(project, BlazeSyncStatus.class); } + boolean syncInProgress(); + void setDirty(); void queueAutomaticSyncIfDirty();
diff --git a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java index 396f27d..ee60cd7 100644 --- a/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java +++ b/base/src/com/google/idea/blaze/base/sync/status/BlazeSyncStatusImpl.java
@@ -23,7 +23,6 @@ import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; import com.google.idea.blaze.base.sync.SyncListener.SyncResult; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; @@ -54,7 +53,7 @@ private final Project project; - public final AtomicBoolean syncInProgress = new AtomicBoolean(false); + private final AtomicBoolean syncInProgress = new AtomicBoolean(false); private final AtomicBoolean syncPending = new AtomicBoolean(false); /** has a BUILD file changed since the last sync started */ @@ -86,6 +85,11 @@ return dirty ? SyncStatus.DIRTY : SyncStatus.CLEAN; } + @Override + public boolean syncInProgress() { + return syncInProgress.get(); + } + public void syncStarted() { syncPending.set(false); syncInProgress.set(true); @@ -189,10 +193,9 @@ private void processEvent(@Nullable VirtualFile file) { if (isSyncSensitiveFile(file)) { - FileDocumentManager manager = FileDocumentManager.getInstance(); - Document doc = manager.getCachedDocument(file); - if (doc != null) { - manager.saveDocument(doc); + FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); + if (fileDocumentManager.isFileModified(file)) { + setDirty(); } } }
diff --git a/base/src/com/google/idea/blaze/base/targetmaps/TransitiveDependencyMap.java b/base/src/com/google/idea/blaze/base/targetmaps/TransitiveDependencyMap.java new file mode 100644 index 0000000..f973c8d --- /dev/null +++ b/base/src/com/google/idea/blaze/base/targetmaps/TransitiveDependencyMap.java
@@ -0,0 +1,79 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.targetmaps; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Queues; +import com.google.common.collect.Sets; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + +/** Handy class to find all transitive dependencies of a given target */ +public class TransitiveDependencyMap { + private final Project project; + + public static TransitiveDependencyMap getInstance(Project project) { + return ServiceManager.getService(project, TransitiveDependencyMap.class); + } + + public TransitiveDependencyMap(Project project) { + this.project = project; + } + + public ImmutableCollection<TargetKey> getTransitiveDependencies(TargetKey targetKey) { + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (blazeProjectData == null) { + return ImmutableSet.of(); + } + // TODO: see if we need caching. + return getTransitiveDependencies(targetKey, blazeProjectData.targetMap); + } + + private static ImmutableCollection<TargetKey> getTransitiveDependencies( + TargetKey targetKey, TargetMap targetMap) { + Queue<TargetKey> targetsToVisit = Queues.newArrayDeque(); + Set<TargetKey> transitiveDependencies = Sets.newHashSet(); + targetsToVisit.add(targetKey); + while (!targetsToVisit.isEmpty()) { + TargetIdeInfo currentTarget = targetMap.get(targetsToVisit.remove()); + if (currentTarget == null) { + continue; + } + List<TargetKey> newDependencies = + currentTarget + .dependencies + .stream() + .map(d -> TargetKey.forPlainTarget(d.targetKey.label)) + // Get rid of the ones we've already seen. + .filter(r -> !transitiveDependencies.contains(r)) + .collect(Collectors.toList()); + targetsToVisit.addAll(newDependencies); + transitiveDependencies.addAll(newDependencies); + } + return ImmutableSet.copyOf(transitiveDependencies); + } +}
diff --git a/base/src/com/google/idea/blaze/base/util/SaveUtil.java b/base/src/com/google/idea/blaze/base/util/SaveUtil.java index 6454a7a..949f201 100644 --- a/base/src/com/google/idea/blaze/base/util/SaveUtil.java +++ b/base/src/com/google/idea/blaze/base/util/SaveUtil.java
@@ -15,18 +15,13 @@ */ package com.google.idea.blaze.base.util; +import com.google.idea.sdkcompat.transactions.Transactions; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.util.ui.UIUtil; /** Utility for saving all files. */ public class SaveUtil { public static void saveAllFiles() { - UIUtil.invokeAndWaitIfNeeded( - new Runnable() { - @Override - public void run() { - FileDocumentManager.getInstance().saveAllDocuments(); - } - }); + Transactions.submitTransactionAndWait( + () -> FileDocumentManager.getInstance().saveAllDocuments()); } }
diff --git a/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java b/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java index 16e6909..b05e69a 100644 --- a/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java +++ b/base/src/com/google/idea/blaze/base/vcs/BlazeVcsHandler.java
@@ -20,6 +20,7 @@ import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; import com.google.idea.blaze.base.sync.workspace.WorkingSet; @@ -33,6 +34,18 @@ ExtensionPointName<BlazeVcsHandler> EP_NAME = ExtensionPointName.create("com.google.idea.blaze.VcsHandler"); + @Nullable + static BlazeVcsHandler vcsHandlerForProject(Project project) { + BuildSystem buildSystem = Blaze.getBuildSystem(project); + WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project); + for (BlazeVcsHandler candidate : BlazeVcsHandler.EP_NAME.getExtensions()) { + if (candidate.handlesProject(buildSystem, workspaceRoot)) { + return candidate; + } + } + return null; + } + /** Returns the name of this VCS, eg. "git" or "hg" */ String getVcsName();
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java index 20a7e4d..1739755 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java +++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeWizardOptionProvider.java
@@ -15,16 +15,17 @@ */ package com.google.idea.blaze.base.wizard2; -import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.components.ServiceManager; import java.util.Collection; /** Provides options during the import process. */ public interface BlazeWizardOptionProvider { - ExtensionPointName<BlazeWizardOptionProvider> EP_NAME = - ExtensionPointName.create("com.google.idea.blaze.BlazeWizardOptionProvider"); - Collection<BlazeSelectWorkspaceOption> getSelectWorkspaceOptions(BlazeNewProjectBuilder builder); Collection<BlazeSelectProjectViewOption> getSelectProjectViewOptions( BlazeNewProjectBuilder builder); + + static BlazeWizardOptionProvider getInstance() { + return ServiceManager.getService(BlazeWizardOptionProvider.class); + } }
diff --git a/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java index 62db2c5..ae00949 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java +++ b/base/src/com/google/idea/blaze/base/wizard2/CopyExternalProjectViewOption.java
@@ -35,14 +35,15 @@ import javax.swing.JComponent; import javax.swing.JLabel; -class CopyExternalProjectViewOption implements BlazeSelectProjectViewOption { +/** Copies an external project view from anywhere on the user's file system */ +public class CopyExternalProjectViewOption implements BlazeSelectProjectViewOption { private static final String LAST_WORKSPACE_PATH = "copy-external.last-project-view-path"; final BlazeWizardUserSettings userSettings; final JComponent component; final TextFieldWithStoredHistory projectViewPathField; - CopyExternalProjectViewOption(BlazeNewProjectBuilder builder) { + public CopyExternalProjectViewOption(BlazeNewProjectBuilder builder) { this.userSettings = builder.getUserSettings(); this.projectViewPathField = new TextFieldWithStoredHistory(LAST_WORKSPACE_PATH);
diff --git a/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java index 366e424..3e8ebf7 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java +++ b/base/src/com/google/idea/blaze/base/wizard2/CreateFromScratchProjectViewOption.java
@@ -19,7 +19,8 @@ import javax.annotation.Nullable; import javax.swing.JComponent; -class CreateFromScratchProjectViewOption implements BlazeSelectProjectViewOption { +/** Creates an empty project view */ +public class CreateFromScratchProjectViewOption implements BlazeSelectProjectViewOption { @Override public String getOptionName() { return "create-from-scratch";
diff --git a/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java index e2e41d9..b2edeff 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java +++ b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
@@ -45,7 +45,8 @@ import javax.swing.JComponent; import javax.swing.JLabel; -class GenerateFromBuildFileSelectProjectViewOption implements BlazeSelectProjectViewOption { +/** Generates a project view given a BUILD file */ +public class GenerateFromBuildFileSelectProjectViewOption implements BlazeSelectProjectViewOption { private static final String LAST_WORKSPACE_PATH = "generate-from-build-file.last-workspace-path"; private final BlazeNewProjectBuilder builder; private final BlazeWizardUserSettings userSettings;
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java index c2d47e1..bdec2eb 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java +++ b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
@@ -39,7 +39,8 @@ import javax.swing.JComponent; import javax.swing.JLabel; -class ImportFromWorkspaceProjectViewOption implements BlazeSelectProjectViewOption { +/** Imports a project view from source control */ +public class ImportFromWorkspaceProjectViewOption implements BlazeSelectProjectViewOption { private static final String LAST_WORKSPACE_PATH = "import-from-workspace.last-workspace-path"; final BlazeNewProjectBuilder builder; @@ -47,7 +48,7 @@ final JComponent component; final TextFieldWithStoredHistory projectViewPathField; - ImportFromWorkspaceProjectViewOption(BlazeNewProjectBuilder builder) { + public ImportFromWorkspaceProjectViewOption(BlazeNewProjectBuilder builder) { this.builder = builder; this.userSettings = builder.getUserSettings();
diff --git a/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java index 2b8c058..51af079 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java +++ b/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
@@ -36,12 +36,13 @@ import javax.swing.JComponent; import javax.swing.JLabel; -class UseExistingBazelWorkspaceOption implements BlazeSelectWorkspaceOption { +/** Allows importing an existing bazel workspace */ +public class UseExistingBazelWorkspaceOption implements BlazeSelectWorkspaceOption { private final JComponent component; private final TextFieldWithHistory directoryField; - UseExistingBazelWorkspaceOption(BlazeNewProjectBuilder builder) { + public UseExistingBazelWorkspaceOption(BlazeNewProjectBuilder builder) { this.directoryField = new TextFieldWithHistory(); this.directoryField.setHistory(builder.getWorkspaceHistory(BuildSystem.Bazel)); this.directoryField.setHistorySize(BlazeNewProjectBuilder.HISTORY_SIZE);
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java index 0307b5a..4f3be05 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java +++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
@@ -92,6 +92,7 @@ private JTextField projectNameField; private HashCode paramsHash; private WorkspaceRoot workspaceRoot; + private WorkspacePathResolver workspacePathResolver; public BlazeEditProjectViewControl(BlazeNewProjectBuilder builder, Disposable parentDisposable) { this.projectViewUi = new ProjectViewUi(parentDisposable); @@ -209,6 +210,7 @@ } this.workspaceRoot = workspaceRoot; + this.workspacePathResolver = workspacePathResolver; projectNameField.setText(workspaceName); String defaultDataDir = getDefaultProjectDataDirectory(workspaceName); projectDataDirField.setText(defaultDataDir); @@ -328,7 +330,8 @@ return BlazeValidationResult.failure(projectViewParseError); } - ProjectViewValidator projectViewValidator = new ProjectViewValidator(projectViewSet); + ProjectViewValidator projectViewValidator = + new ProjectViewValidator(workspacePathResolver, projectViewSet); ProgressManager.getInstance() .runProcessWithProgressSynchronously( projectViewValidator, "Validating Project", false, null); @@ -347,12 +350,15 @@ } private static class ProjectViewValidator implements Runnable { + private final WorkspacePathResolver workspacePathResolver; private final ProjectViewSet projectViewSet; private boolean success; List<IssueOutput> errors = Lists.newArrayList(); - ProjectViewValidator(ProjectViewSet projectViewSet) { + ProjectViewValidator( + WorkspacePathResolver workspacePathResolver, ProjectViewSet projectViewSet) { + this.workspacePathResolver = workspacePathResolver; this.projectViewSet = projectViewSet; } @@ -379,8 +385,8 @@ if (workspaceLanguageSettings == null) { return false; } - return ProjectViewVerifier.verifyProjectViewNoDisk( - context, projectViewSet, workspaceLanguageSettings); + return ProjectViewVerifier.verifyProjectView( + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); } }
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java index a2b6467..4dc04cd 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java +++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectProjectViewControl.java
@@ -15,7 +15,6 @@ */ package com.google.idea.blaze.base.wizard2.ui; -import com.google.common.collect.Lists; import com.google.idea.blaze.base.projectview.ProjectViewStorageManager; import com.google.idea.blaze.base.ui.BlazeValidationResult; import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder; @@ -30,11 +29,8 @@ private BlazeSelectOptionControl<BlazeSelectProjectViewOption> selectOptionControl; public BlazeSelectProjectViewControl(BlazeNewProjectBuilder builder) { - Collection<BlazeSelectProjectViewOption> options = Lists.newArrayList(); - for (BlazeWizardOptionProvider optionProvider : - BlazeWizardOptionProvider.EP_NAME.getExtensions()) { - options.addAll(optionProvider.getSelectProjectViewOptions(builder)); - } + Collection<BlazeSelectProjectViewOption> options = + BlazeWizardOptionProvider.getInstance().getSelectProjectViewOptions(builder); this.selectOptionControl = new BlazeSelectOptionControl<BlazeSelectProjectViewOption>(builder, options) {
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java index f0c6b45..e24b446 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java +++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java
@@ -15,7 +15,6 @@ */ package com.google.idea.blaze.base.wizard2.ui; -import com.google.common.collect.Lists; import com.google.idea.blaze.base.ui.BlazeValidationResult; import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder; import com.google.idea.blaze.base.wizard2.BlazeSelectWorkspaceOption; @@ -28,11 +27,8 @@ BlazeSelectOptionControl<BlazeSelectWorkspaceOption> selectOptionControl; public BlazeSelectWorkspaceControl(BlazeNewProjectBuilder builder) { - Collection<BlazeSelectWorkspaceOption> options = Lists.newArrayList(); - for (BlazeWizardOptionProvider optionProvider : - BlazeWizardOptionProvider.EP_NAME.getExtensions()) { - options.addAll(optionProvider.getSelectWorkspaceOptions(builder)); - } + Collection<BlazeSelectWorkspaceOption> options = + BlazeWizardOptionProvider.getInstance().getSelectWorkspaceOptions(builder); this.selectOptionControl = new BlazeSelectOptionControl<BlazeSelectWorkspaceOption>(builder, options) {
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java index 361b0bb..960660e 100644 --- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java +++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
@@ -21,11 +21,12 @@ import com.google.idea.blaze.base.EditorTestHelper; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; -import com.google.idea.blaze.base.sync.workspace.WorkingSet; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import org.junit.Before; @@ -57,12 +58,11 @@ new TargetMap(ImmutableMap.of()), ImmutableMap.of(), fakeRoots, - new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()), + new BlazeVersionData(), workspacePathResolver, artifactLocationDecoder, null, - null, - null, + new SyncState.Builder().build(), null); } }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java index c4f5242..eb8d69f 100644 --- a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java +++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java
@@ -23,6 +23,7 @@ import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor; @@ -31,7 +32,6 @@ import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; -import com.google.idea.blaze.base.sync.workspace.WorkingSet; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import com.intellij.openapi.options.ConfigurationException; @@ -79,12 +79,11 @@ new TargetMap(ImmutableMap.of()), ImmutableMap.of(), fakeRoots, - new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()), + new BlazeVersionData(), workspacePathResolver, artifactLocationDecoder, null, null, - null, null); }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java index 6395e65..237fa7f 100644 --- a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java +++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java
@@ -23,13 +23,13 @@ import com.google.idea.blaze.base.BlazeIntegrationTestCase; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; -import com.google.idea.blaze.base.sync.workspace.WorkingSet; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import com.intellij.execution.RunnerAndConfigurationSettings; @@ -89,12 +89,11 @@ new TargetMap(ImmutableMap.of()), ImmutableMap.of(), fakeRoots, - new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()), + new BlazeVersionData(), workspacePathResolver, artifactLocationDecoder, null, null, - null, null); }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java index 336930a..b9a2b48 100644 --- a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java +++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java
@@ -22,13 +22,13 @@ import com.google.idea.blaze.base.BlazeIntegrationTestCase; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; import com.google.idea.blaze.base.model.primitives.Label; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; -import com.google.idea.blaze.base.sync.workspace.WorkingSet; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import com.intellij.openapi.options.ConfigurationException; @@ -70,12 +70,11 @@ new TargetMap(ImmutableMap.of()), ImmutableMap.of(), fakeRoots, - new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()), + new BlazeVersionData(), workspacePathResolver, artifactLocationDecoder, null, null, - null, null); }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializerTest.java new file mode 100644 index 0000000..fd5896b --- /dev/null +++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/exporter/RunConfigurationSerializerTest.java
@@ -0,0 +1,157 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.exporter; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.idea.blaze.base.BlazeIntegrationTestCase; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; +import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; +import com.google.idea.blaze.base.sync.workspace.BlazeRoots; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.openapi.util.InvalidDataException; +import org.jdom.Element; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test that {@link RunConfigurationSerializer} serializes/deserializes run configurations + * correctly. + */ +@RunWith(JUnit4.class) +public class RunConfigurationSerializerTest extends BlazeIntegrationTestCase { + + private RunManagerImpl runManager; + private BlazeCommandRunConfigurationType type; + private BlazeCommandRunConfiguration configuration; + + @Before + public final void doSetup() { + runManager = RunManagerImpl.getInstanceImpl(getProject()); + // Without BlazeProjectData, the configuration editor is always disabled. + mockBlazeProjectDataManager(getMockBlazeProjectData()); + type = BlazeCommandRunConfigurationType.getInstance(); + + RunnerAndConfigurationSettings runnerAndConfigurationSettings = + runManager.createConfiguration("Blaze Configuration", type.getFactory()); + runManager.addConfiguration(runnerAndConfigurationSettings, false); + configuration = + (BlazeCommandRunConfiguration) runnerAndConfigurationSettings.getConfiguration(); + } + + private BlazeProjectData getMockBlazeProjectData() { + BlazeRoots fakeRoots = + new BlazeRoots( + null, + ImmutableList.of(workspaceRoot.directory()), + new ExecutionRootPath("out/crosstool/bin"), + new ExecutionRootPath("out/crosstool/gen"), + null); + WorkspacePathResolver workspacePathResolver = + new WorkspacePathResolverImpl(workspaceRoot, fakeRoots); + ArtifactLocationDecoder artifactLocationDecoder = + new ArtifactLocationDecoderImpl(fakeRoots, workspacePathResolver); + return new BlazeProjectData( + 0, + new TargetMap(ImmutableMap.of()), + ImmutableMap.of(), + fakeRoots, + new BlazeVersionData(), + workspacePathResolver, + artifactLocationDecoder, + null, + null, + null); + } + + @After + public final void doTeardown() { + clearRunManager(); + } + + private void clearRunManager() { + runManager.clearAll(); + // We don't need to do this at setup, because it is handled by RunManagerImpl's constructor. + // However, clearAll() clears the configuration types, so we need to reinitialize them. + runManager.initializeConfigurationTypes( + ConfigurationType.CONFIGURATION_TYPE_EP.getExtensions()); + } + + @Test + public void testRunConfigurationUnalteredBySerializationRoundTrip() throws InvalidDataException { + configuration.setTarget(new Label("//package:rule")); + configuration.setKeepInSync(true); + + final Element initialElement = runManager.getState(); + + Element element = RunConfigurationSerializer.writeToXml(configuration); + assertThat(RunConfigurationSerializer.findExisting(getProject(), element)).isNotNull(); + + clearRunManager(); // remove configuration from project + RunConfigurationSerializer.loadFromXmlElementIgnoreExisting(getProject(), element); + + final Element newElement = runManager.getState(); + final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat()); + assertThat(xmlOutputter.outputString(newElement)) + .isEqualTo(xmlOutputter.outputString(initialElement)); + } + + @Test + public void testSetKeepInSyncWhenImporting() throws InvalidDataException { + configuration.setTarget(new Label("//package:rule")); + configuration.setKeepInSync(false); + + Element element = RunConfigurationSerializer.writeToXml(configuration); + assertThat(RunConfigurationSerializer.findExisting(getProject(), element)).isNotNull(); + + clearRunManager(); // remove configuration from project + RunConfigurationSerializer.loadFromXmlElementIgnoreExisting(getProject(), element); + + RunConfiguration config = runManager.getAllConfigurations()[0]; + assertThat(config).isInstanceOf(BlazeCommandRunConfiguration.class); + assertThat(((BlazeCommandRunConfiguration) config).getKeepInSync()).isTrue(); + } + + @Test + public void testKeepInSyncRespectedWhenImporting() throws InvalidDataException { + Element element = RunConfigurationSerializer.writeToXml(configuration); + + configuration.setKeepInSync(false); + assertThat(RunConfigurationSerializer.shouldLoadConfiguration(getProject(), element)).isFalse(); + + configuration.setKeepInSync(true); + assertThat(RunConfigurationSerializer.shouldLoadConfiguration(getProject(), element)).isTrue(); + } +}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java b/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java new file mode 100644 index 0000000..e86bd3f --- /dev/null +++ b/base/tests/unittests/com/google/idea/blaze/base/actions/BlazeBuildServiceTest.java
@@ -0,0 +1,145 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.actions; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.model.primitives.TargetExpression; +import com.google.idea.blaze.base.projectview.ProjectView; +import com.google.idea.blaze.base.projectview.ProjectViewManager; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.projectview.section.ListSection; +import com.google.idea.blaze.base.projectview.section.sections.TargetSection; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.BlazeImportSettings; +import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; +import java.io.File; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test cases for {@link BlazeBuildService}. */ +@RunWith(JUnit4.class) +public class BlazeBuildServiceTest extends BlazeTestCase { + BlazeBuildService service; + ProjectViewSet viewSet; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + BlazeImportSettingsManager importSettingsManager = new BlazeImportSettingsManager(project); + importSettingsManager.setImportSettings( + new BlazeImportSettings("", "", "", "", "", Blaze.BuildSystem.Blaze)); + projectServices.register(BlazeImportSettingsManager.class, importSettingsManager); + + ProjectView view = + ProjectView.builder() + .add( + ListSection.builder(TargetSection.KEY) + .add(TargetExpression.fromString("//view/target:one")) + .add(TargetExpression.fromString("//view/target:two"))) + .build(); + viewSet = ProjectViewSet.builder().add(new File("view/target/.blazeproject"), view).build(); + ProjectViewManager viewManager = new MockProjectViewManager(viewSet); + projectServices.register(ProjectViewManager.class, viewManager); + + projectServices.register(BlazeProjectDataManager.class, new MockProjectDataManager()); + + applicationServices.register(BlazeBuildService.class, spy(new BlazeBuildService())); + + service = BlazeBuildService.getInstance(); + assertThat(service).isNotNull(); + + // Can't mock BlazeExecutor.submitTask. + doNothing().when(service).buildTargetExpressions(any(), any(), any(), any(), any()); + } + + @Test + public void testBuildFile() { + ImmutableCollection<Label> labels = + ImmutableList.of(new Label("//foo:bar"), new Label("//foo:baz")); + List<TargetExpression> targets = Lists.newArrayList(labels); + service.buildFile(project, "Foo.java", labels); + verify(service).buildTargetExpressions(eq(project), eq(targets), eq(viewSet), any(), any()); + } + + @Test + public void testBuildProject() { + service.buildProject(project); + List<TargetExpression> targets = + Lists.newArrayList( + TargetExpression.fromString("//view/target:one"), + TargetExpression.fromString("//view/target:two")); + verify(service).buildTargetExpressions(eq(project), eq(targets), eq(viewSet), any(), any()); + } + + private static class MockProjectViewManager extends ProjectViewManager { + private final ProjectViewSet viewSet; + + public MockProjectViewManager(ProjectViewSet viewSet) { + this.viewSet = viewSet; + } + + @Nullable + @Override + public ProjectViewSet getProjectViewSet() { + return viewSet; + } + + @Nullable + @Override + public ProjectViewSet reloadProjectView( + BlazeContext context, WorkspacePathResolver workspacePathResolver) { + return viewSet; + } + } + + private static class MockProjectDataManager implements BlazeProjectDataManager { + private final BlazeProjectData projectData; + + public MockProjectDataManager() { + this.projectData = + new BlazeProjectData(0L, null, null, null, null, null, null, null, null, null); + } + + @Nullable + @Override + public BlazeProjectData getBlazeProjectData() { + return projectData; + } + + @Override + public ModuleEditor editModules() { + return null; + } + } +}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java b/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java new file mode 100644 index 0000000..5860e40 --- /dev/null +++ b/base/tests/unittests/com/google/idea/blaze/base/bazel/BazelVersionTest.java
@@ -0,0 +1,71 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.bazel; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BazelVersion}. */ +@RunWith(JUnit4.class) +public class BazelVersionTest { + + @Test + public void testParseOldVersionFormat() { + BazelVersion version = BazelVersion.parseVersion("release 0.4.1"); + assertThat(version).isNotNull(); + assertThat(version.major).isEqualTo(0); + assertThat(version.minor).isEqualTo(4); + assertThat(version.bugfix).isEqualTo(1); + } + + @Test + public void testParseVersionFormatDistributionPackage() { + BazelVersion version = BazelVersion.parseVersion("release 0.4.3- (@non-git)"); + assertThat(version).isNotNull(); + assertThat(version.major).isEqualTo(0); + assertThat(version.minor).isEqualTo(4); + assertThat(version.bugfix).isEqualTo(3); + } + + @Test + public void testParseVersionFormatManualBuild() { + BazelVersion version = BazelVersion.parseVersion("release 0.4.3- (@c9139896"); + assertThat(version).isNotNull(); + assertThat(version.major).isEqualTo(0); + assertThat(version.minor).isEqualTo(4); + assertThat(version.bugfix).isEqualTo(3); + } + + @Test + public void testParseVersionFormatManualOld() { + BazelVersion version = BazelVersion.parseVersion("development version"); + assertThat(version).isEqualTo(BazelVersion.UNKNOWN); + } + + @Test + public void testIsAtLeast() { + BazelVersion version = BazelVersion.parseVersion("release 0.4.1"); + assertThat(version).isNotNull(); + assertThat(version.isAtLeast(0, 3, 2)).isTrue(); + assertThat(version.isAtLeast(0, 4, 0)).isTrue(); + assertThat(version.isAtLeast(0, 4, 1)).isTrue(); + assertThat(version.isAtLeast(0, 4, 2)).isFalse(); + assertThat(version.isAtLeast(0, 5, 0)).isFalse(); + } +}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java index 40ea19f..ed1fd3a 100644 --- a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java +++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewSetTest.java
@@ -39,6 +39,7 @@ import com.google.idea.blaze.base.projectview.section.sections.ImportSection; import com.google.idea.blaze.base.projectview.section.sections.ImportTargetOutputSection; import com.google.idea.blaze.base.projectview.section.sections.MetricsProjectSection; +import com.google.idea.blaze.base.projectview.section.sections.RunConfigurationsSection; import com.google.idea.blaze.base.projectview.section.sections.Sections; import com.google.idea.blaze.base.projectview.section.sections.TargetSection; import com.google.idea.blaze.base.projectview.section.sections.TestSourceSection; @@ -91,6 +92,9 @@ ListSection.builder(AdditionalLanguagesSection.KEY).add(LanguageClass.JAVA)) .add(ScalarSection.builder(MetricsProjectSection.KEY).set("my project")) .add(TextBlockSection.of(TextBlock.newLine())) + .add( + ListSection.builder(RunConfigurationsSection.KEY) + .add(new WorkspacePath("test"))) .build()) .build();
diff --git a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java index 4d27c8e..fe15957 100644 --- a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java +++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
@@ -33,6 +33,8 @@ import com.google.idea.blaze.base.sync.BlazeSyncPlugin; import com.google.idea.blaze.base.sync.projectview.ImportRoots; import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; +import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import java.io.File; import java.util.Set; import org.jetbrains.annotations.NotNull; @@ -46,6 +48,8 @@ private static final String FAKE_ROOT = "/root"; private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_ROOT)); + private WorkspacePathResolver workspacePathResolver = + new WorkspacePathResolverImpl(workspaceRoot); private MockFileAttributeProvider fileAttributeProvider; private ErrorCollector errorCollector = new ErrorCollector(); private BlazeContext context; @@ -82,7 +86,7 @@ .build(); fileAttributeProvider.addProjectView(projectViewSet); ProjectViewVerifier.verifyProjectView( - context, workspaceRoot, projectViewSet, workspaceLanguageSettings); + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); errorCollector.assertNoIssues(); } @@ -104,7 +108,7 @@ .build(); fileAttributeProvider.addProjectView(projectViewSet); ProjectViewVerifier.verifyProjectView( - context, workspaceRoot, projectViewSet, workspaceLanguageSettings); + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); errorCollector.assertIssues( "java/com/google/android/apps/example is included, " + "but that contradicts java/com/google/android/apps/example which was excluded"); @@ -128,7 +132,7 @@ .build(); fileAttributeProvider.addProjectView(projectViewSet); ProjectViewVerifier.verifyProjectView( - context, workspaceRoot, projectViewSet, workspaceLanguageSettings); + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); errorCollector.assertIssues( "java/com/google/android/apps/example is included, " + "but that contradicts java/com/google/android/apps which was excluded"); @@ -153,7 +157,7 @@ .build(); fileAttributeProvider.addProjectView(projectViewSet); ProjectViewVerifier.verifyProjectView( - context, workspaceRoot, projectViewSet, workspaceLanguageSettings); + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); errorCollector.assertNoIssues(); } @@ -171,11 +175,33 @@ .build()) .build(); ProjectViewVerifier.verifyProjectView( - context, workspaceRoot, projectViewSet, workspaceLanguageSettings); + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); errorCollector.assertIssues( String.format( - "Directory '%s' specified in import roots not found under workspace root '%s'", - "java/com/google/android/apps/example", "/root")); + "Directory '%s' specified in project view not found.", + "java/com/google/android/apps/example")); + } + + @Test + public void testImportRootIsFileResultsInIssue() { + ProjectViewSet projectViewSet = + ProjectViewSet.builder() + .add( + ProjectView.builder() + .add( + ListSection.builder(DirectorySection.KEY) + .add( + DirectoryEntry.include( + new WorkspacePath("java/com/google/android/apps/example")))) + .build()) + .build(); + fileAttributeProvider.addFile(new WorkspacePath("java/com/google/android/apps/example")); + ProjectViewVerifier.verifyProjectView( + context, workspacePathResolver, projectViewSet, workspaceLanguageSettings); + errorCollector.assertIssues( + String.format( + "Directory '%s' specified in project view is a file.", + "java/com/google/android/apps/example")); } static class MockFileAttributeProvider extends FileAttributeProvider { @@ -228,5 +254,10 @@ public boolean exists(File file) { return files.contains(file); } + + @Override + public boolean isDirectory(File file) { + return directories.contains(file); + } } }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java index c8eaeec..5598b49 100644 --- a/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java +++ b/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java
@@ -21,6 +21,7 @@ import com.google.common.collect.Lists; import com.google.idea.blaze.base.BlazeTestCase; import com.google.idea.blaze.base.command.BlazeCommandName; +import com.google.idea.blaze.base.run.DistributedExecutorSupport; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; @@ -28,7 +29,6 @@ import org.jdom.Element; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; -import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -43,8 +43,7 @@ private BlazeCommandRunConfigurationCommonState state; @Override - protected void initTest( - @NotNull Container applicationServices, @NotNull Container projectServices) { + protected void initTest(Container applicationServices, Container projectServices) { super.initTest(applicationServices, projectServices); applicationServices.register(UISettings.class, new UISettings()); @@ -52,7 +51,9 @@ BlazeImportSettingsManager.class, new BlazeImportSettingsManager(project)); BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS); - state = new BlazeCommandRunConfigurationCommonState(Blaze.buildSystemName(project)); + registerExtensionPoint(DistributedExecutorSupport.EP_NAME, DistributedExecutorSupport.class); + + state = new BlazeCommandRunConfigurationCommonState(Blaze.getBuildSystem(project)); } @Test @@ -65,7 +66,7 @@ Element element = new Element("test"); state.writeExternal(element); BlazeCommandRunConfigurationCommonState readState = - new BlazeCommandRunConfigurationCommonState(Blaze.buildSystemName(project)); + new BlazeCommandRunConfigurationCommonState(Blaze.getBuildSystem(project)); readState.readExternal(element); assertThat(readState.getCommand()).isEqualTo(COMMAND); @@ -79,7 +80,7 @@ Element element = new Element("test"); state.writeExternal(element); BlazeCommandRunConfigurationCommonState readState = - new BlazeCommandRunConfigurationCommonState(Blaze.buildSystemName(project)); + new BlazeCommandRunConfigurationCommonState(Blaze.getBuildSystem(project)); readState.readExternal(element); assertThat(readState.getCommand()).isEqualTo(state.getCommand()); @@ -96,7 +97,7 @@ Element element = new Element("test"); state.writeExternal(element); BlazeCommandRunConfigurationCommonState readState = - new BlazeCommandRunConfigurationCommonState(Blaze.buildSystemName(project)); + new BlazeCommandRunConfigurationCommonState(Blaze.getBuildSystem(project)); readState.readExternal(element); assertThat(readState.getBlazeFlags()).containsExactly("hi", "I'm", "Josh").inOrder(); @@ -132,7 +133,7 @@ editor.resetEditorFrom(state); BlazeCommandRunConfigurationCommonState readState = - new BlazeCommandRunConfigurationCommonState(Blaze.buildSystemName(project)); + new BlazeCommandRunConfigurationCommonState(Blaze.getBuildSystem(project)); editor.applyEditorTo(readState); assertThat(readState.getCommand()).isEqualTo(state.getCommand()); @@ -147,7 +148,7 @@ editor.resetEditorFrom(state); BlazeCommandRunConfigurationCommonState readState = - new BlazeCommandRunConfigurationCommonState(Blaze.buildSystemName(project)); + new BlazeCommandRunConfigurationCommonState(Blaze.getBuildSystem(project)); editor.applyEditorTo(readState); assertThat(readState.getCommand()).isEqualTo(state.getCommand());
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/state/RunConfigurationFlagStateTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/state/RunConfigurationFlagStateTest.java new file mode 100644 index 0000000..730ac3b --- /dev/null +++ b/base/tests/unittests/com/google/idea/blaze/base/run/state/RunConfigurationFlagStateTest.java
@@ -0,0 +1,75 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.run.state; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link RunConfigurationFlagsState}. */ +@RunWith(JUnit4.class) +public class RunConfigurationFlagStateTest { + + @Test + public void testEscapedQuotesRetainedAfterReserialization() { + // previously, we were removing escape chars and quotes during ParametersListUtil.parse, then + // not putting them back when converting back to a string. + ImmutableList<String> flags = ImmutableList.of("--flag=\\\"Hello_world!\\\"", "--flag2"); + RunConfigurationFlagsState state = new RunConfigurationFlagsState("tag", "field"); + state.setFlags(flags); + + RunConfigurationStateEditor editor = state.getEditor(null); + editor.resetEditorFrom(state); + editor.applyEditorTo(state); + + assertThat(state.getFlags()).isEqualTo(flags); + } + + @Test + public void testQuotesRetainedAfterReserialization() { + ImmutableList<String> flags = ImmutableList.of("\"--flag=test\""); + RunConfigurationFlagsState state = new RunConfigurationFlagsState("tag", "field"); + state.setFlags(flags); + + RunConfigurationStateEditor editor = state.getEditor(null); + editor.resetEditorFrom(state); + editor.applyEditorTo(state); + + assertThat(state.getFlags()).isEqualTo(flags); + } + + @Test + public void testNormalFlagsAreNotMangled() { + ImmutableList<String> flags = + ImmutableList.of( + "--test_sharding_strategy=disabled", + "--test_strategy=local", + "--experimental_show_artifacts", + "--test_filter=com.google.idea.blaze.base.run.state.RunConfigurationFlagStateTest#", + "--define=ij_product=intellij-latest"); + RunConfigurationFlagsState state = new RunConfigurationFlagsState("tag", "field"); + state.setFlags(flags); + + RunConfigurationStateEditor editor = state.getEditor(null); + editor.resetEditorFrom(state); + editor.applyEditorTo(state); + + assertThat(state.getFlags()).isEqualTo(flags); + } +}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/BlazeSyncManagerTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/BlazeSyncManagerTest.java new file mode 100644 index 0000000..c8cf5ba --- /dev/null +++ b/base/tests/unittests/com/google/idea/blaze/base/sync/BlazeSyncManagerTest.java
@@ -0,0 +1,115 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.sync; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.model.primitives.TargetExpression; +import com.google.idea.blaze.base.settings.BlazeUserSettings; +import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode; +import java.io.IOException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +/** Test cases for {@link BlazeSyncManager}. */ +@RunWith(JUnit4.class) +public class BlazeSyncManagerTest extends BlazeTestCase { + @Spy BlazeSyncManager manager = new BlazeSyncManager(project); + @Captor ArgumentCaptor<BlazeSyncParams> paramsCaptor; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + super.initTest(applicationServices, projectServices); + MockitoAnnotations.initMocks(this); + applicationServices.register(BlazeUserSettings.class, mock(BlazeUserSettings.class)); + doNothing().when(manager).requestProjectSync(any()); + projectServices.register(BlazeSyncManager.class, manager); + assertThat(BlazeSyncManager.getInstance(project)).isSameAs(manager); + } + + @Test + public void testFullProjectSync() throws IOException { + manager.fullProjectSync(); + verify(manager).requestProjectSync(paramsCaptor.capture()); + BlazeSyncParams params = paramsCaptor.getValue(); + assertThat(params).isNotNull(); + assertThat(params.title).isEqualTo("Full Sync"); + assertThat(params.syncMode).isEqualTo(SyncMode.FULL); + assertThat(params.backgroundSync).isFalse(); + assertThat(params.addProjectViewTargets).isTrue(); + assertThat(params.addWorkingSet) + .isEqualTo(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()); + assertThat(params.targetExpressions).isEmpty(); + } + + @Test + public void testIncrementalProjectSync() throws IOException { + manager.incrementalProjectSync(); + verify(manager).requestProjectSync(paramsCaptor.capture()); + BlazeSyncParams params = paramsCaptor.getValue(); + assertThat(params).isNotNull(); + assertThat(params.title).isEqualTo("Sync"); + assertThat(params.syncMode).isEqualTo(SyncMode.INCREMENTAL); + assertThat(params.backgroundSync).isFalse(); + assertThat(params.addProjectViewTargets).isTrue(); + assertThat(params.addWorkingSet) + .isEqualTo(BlazeUserSettings.getInstance().getExpandSyncToWorkingSet()); + assertThat(params.targetExpressions).isEmpty(); + } + + @Test + public void testPartialSync() { + List<TargetExpression> targets = + ImmutableList.of( + TargetExpression.fromString("//foo:bar"), TargetExpression.fromString("//foo:baz")); + manager.partialSync(targets); + verify(manager).requestProjectSync(paramsCaptor.capture()); + BlazeSyncParams params = paramsCaptor.getValue(); + assertThat(params).isNotNull(); + assertThat(params.title).isEqualTo("Partial Sync"); + assertThat(params.syncMode).isEqualTo(SyncMode.PARTIAL); + assertThat(params.backgroundSync).isFalse(); + assertThat(params.addProjectViewTargets).isFalse(); + assertThat(params.addWorkingSet).isFalse(); + assertThat(params.targetExpressions).isEqualTo(targets); + } + + @Test + public void testWorkingSetSync() throws IOException { + manager.workingSetSync(); + verify(manager).requestProjectSync(paramsCaptor.capture()); + BlazeSyncParams params = paramsCaptor.getValue(); + assertThat(params).isNotNull(); + assertThat(params.title).isEqualTo("Sync Working Set"); + assertThat(params.syncMode).isEqualTo(SyncMode.PARTIAL); + assertThat(params.backgroundSync).isFalse(); + assertThat(params.addProjectViewTargets).isFalse(); + assertThat(params.addWorkingSet).isTrue(); + assertThat(params.targetExpressions).isEmpty(); + } +}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/targetmaps/TransitiveDependencyMapTest.java b/base/tests/unittests/com/google/idea/blaze/base/targetmaps/TransitiveDependencyMapTest.java new file mode 100644 index 0000000..3037ff1 --- /dev/null +++ b/base/tests/unittests/com/google/idea/blaze/base/targetmaps/TransitiveDependencyMapTest.java
@@ -0,0 +1,167 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.base.targetmaps; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; +import com.google.idea.blaze.base.ideinfo.TargetKey; +import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.ideinfo.TargetMapBuilder; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.primitives.Label; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin.ModuleEditor; +import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TransitiveDependencyMap}. */ +@RunWith(JUnit4.class) +public class TransitiveDependencyMapTest extends BlazeTestCase { + private TransitiveDependencyMap transitiveDependencyMap; + + @Override + protected void initTest(Container applicationServices, Container projectServices) { + super.initTest(applicationServices, projectServices); + projectServices.register( + BlazeProjectDataManager.class, + new MockBlazeProjectDataManager( + new BlazeProjectData( + 0L, buildTargetMap(), null, null, null, null, null, null, null, null))); + projectServices.register(TransitiveDependencyMap.class, new TransitiveDependencyMap(project)); + transitiveDependencyMap = TransitiveDependencyMap.getInstance(project); + } + + @Test + public void testGetSimpleDependency() { + TargetKey simpleA = TargetKey.forPlainTarget(new Label("//com/google/example/simple:a")); + TargetKey simpleB = TargetKey.forPlainTarget(new Label("//com/google/example/simple:b")); + + assertThat(transitiveDependencyMap.getTransitiveDependencies(simpleA)).containsExactly(simpleB); + assertThat(transitiveDependencyMap.getTransitiveDependencies(simpleB)).isEmpty(); + } + + @Test + public void testGetChainDependencies() { + TargetKey chainA = TargetKey.forPlainTarget(new Label("//com/google/example/chain:a")); + TargetKey chainB = TargetKey.forPlainTarget(new Label("//com/google/example/chain:b")); + TargetKey chainC = TargetKey.forPlainTarget(new Label("//com/google/example/chain:c")); + TargetKey chainD = TargetKey.forPlainTarget(new Label("//com/google/example/chain:d")); + + assertThat(transitiveDependencyMap.getTransitiveDependencies(chainA)) + .containsExactly(chainB, chainC, chainD); + assertThat(transitiveDependencyMap.getTransitiveDependencies(chainB)) + .containsExactly(chainC, chainD); + assertThat(transitiveDependencyMap.getTransitiveDependencies(chainC)).containsExactly(chainD); + assertThat(transitiveDependencyMap.getTransitiveDependencies(chainD)).isEmpty(); + } + + @Test + public void testGetDiamondDependencies() { + TargetKey diamondA = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:a")); + TargetKey diamondB = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:b")); + TargetKey diamondBB = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:bb")); + TargetKey diamondBBB = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:bbb")); + TargetKey diamondC = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:c")); + TargetKey diamondCC = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:cc")); + TargetKey diamondCCC = TargetKey.forPlainTarget(new Label("//com/google/example/diamond:ccc")); + + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondA)) + .containsExactly(diamondB, diamondBB, diamondBBB, diamondC, diamondCC, diamondCCC); + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondB)) + .containsExactly(diamondC); + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondBB)) + .containsExactly(diamondC, diamondCC); + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondBBB)) + .containsExactly(diamondC, diamondCC, diamondCCC); + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondC)).isEmpty(); + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondCC)).isEmpty(); + assertThat(transitiveDependencyMap.getTransitiveDependencies(diamondCCC)).isEmpty(); + } + + @Test + public void testGetDependencyForNonExistentTarget() { + TargetKey bogus = TargetKey.forPlainTarget(new Label("//com/google/fake:target")); + assertThat(transitiveDependencyMap.getTransitiveDependencies(bogus)).isEmpty(); + } + + private static TargetMap buildTargetMap() { + Label simpleA = new Label("//com/google/example/simple:a"); + Label simpleB = new Label("//com/google/example/simple:b"); + Label chainA = new Label("//com/google/example/chain:a"); + Label chainB = new Label("//com/google/example/chain:b"); + Label chainC = new Label("//com/google/example/chain:c"); + Label chainD = new Label("//com/google/example/chain:d"); + Label diamondA = new Label("//com/google/example/diamond:a"); + Label diamondB = new Label("//com/google/example/diamond:b"); + Label diamondBB = new Label("//com/google/example/diamond:bb"); + Label diamondBBB = new Label("//com/google/example/diamond:bbb"); + Label diamondC = new Label("//com/google/example/diamond:c"); + Label diamondCC = new Label("//com/google/example/diamond:cc"); + Label diamondCCC = new Label("//com/google/example/diamond:ccc"); + return TargetMapBuilder.builder() + .addTarget(TargetIdeInfo.builder().setLabel(simpleA).addDependency(simpleB)) + .addTarget(TargetIdeInfo.builder().setLabel(simpleB)) + .addTarget(TargetIdeInfo.builder().setLabel(chainA).addDependency(chainB)) + .addTarget(TargetIdeInfo.builder().setLabel(chainB).addDependency(chainC)) + .addTarget(TargetIdeInfo.builder().setLabel(chainC).addDependency(chainD)) + .addTarget(TargetIdeInfo.builder().setLabel(chainD)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(diamondA) + .addDependency(diamondB) + .addDependency(diamondBB) + .addDependency(diamondBBB)) + .addTarget(TargetIdeInfo.builder().setLabel(diamondB).addDependency(diamondC)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(diamondBB) + .addDependency(diamondC) + .addDependency(diamondCC)) + .addTarget( + TargetIdeInfo.builder() + .setLabel(diamondBBB) + .addDependency(diamondC) + .addDependency(diamondCC) + .addDependency(diamondCCC)) + .addTarget(TargetIdeInfo.builder().setLabel(diamondC)) + .addTarget(TargetIdeInfo.builder().setLabel(diamondCC)) + .addTarget(TargetIdeInfo.builder().setLabel(diamondCCC)) + .build(); + } + + private static class MockBlazeProjectDataManager implements BlazeProjectDataManager { + private final BlazeProjectData blazeProjectData; + + public MockBlazeProjectDataManager(BlazeProjectData blazeProjectData) { + this.blazeProjectData = blazeProjectData; + } + + @Nullable + @Override + public BlazeProjectData getBlazeProjectData() { + return blazeProjectData; + } + + @Override + public ModuleEditor editModules() { + return null; + } + } +}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java b/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java index 06cbe96..36fbdc0 100644 --- a/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java +++ b/base/tests/unittests/com/google/idea/blaze/base/vcs/git/GitStatusLineProcessorTest.java
@@ -18,9 +18,11 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.BlazeTestCase; import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import java.io.File; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -28,6 +30,7 @@ /** Tests for {@link GitStatusLineProcessor} */ @RunWith(JUnit4.class) public class GitStatusLineProcessorTest { + @Rule public BlazeTestCase.IgnoreOnWindowsRule rule = new BlazeTestCase.IgnoreOnWindowsRule(); @Test public void testGitStatusParser() {
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java index 633c9df..81d3e75 100644 --- a/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java +++ b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
@@ -34,6 +34,7 @@ import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.testFramework.EdtTestUtil; import com.intellij.testFramework.IdeaTestUtil; @@ -44,6 +45,7 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import com.intellij.testFramework.fixtures.TestFixtureBuilder; import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl; +import com.intellij.util.ThrowableRunnable; import java.io.File; import java.io.FileNotFoundException; import javax.annotation.Nullable; @@ -51,10 +53,32 @@ import org.junit.Before; import org.junit.Rule; import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; /** Base test class for blaze integration tests. {@link UsefulTestCase} */ public abstract class BlazeIntegrationTestCase { + /** Test rule that ensures tests do not run on Windows (see http://b.android.com/222904) */ + public static class IgnoreOnWindowsRule implements TestRule { + @Override + public Statement apply(Statement base, Description description) { + if (SystemInfo.isWindows) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + System.out.println( + "Test \"" + + description.getDisplayName() + + "\" does not run on Windows (see http://b.android.com/222904)"); + } + }; + } + return base; + } + } + + @Rule public final IgnoreOnWindowsRule rule = new IgnoreOnWindowsRule(); @Rule public final IntellijTestSetupRule setupRule = new IntellijTestSetupRule(); @Rule public final TestRule testRunWrapper = runTestsOnEdt() ? new EdtRule() : null; @@ -74,20 +98,20 @@ testFixture.setUp(); fileSystem = new TestFileSystem(getProject(), testFixture.getTempDirFixture()); - Runnable writeAction = - () -> - ApplicationManager.getApplication() - .runWriteAction( - () -> { - ProjectJdkTable.getInstance().addJdk(IdeaTestUtil.getMockJdk18()); - VirtualFile workspaceRootVirtualFile = - fileSystem.createDirectory("workspace"); - workspaceRoot = - new WorkspaceRoot(new File(workspaceRootVirtualFile.getPath())); - projectDataDirectory = fileSystem.createDirectory("project-data-dir"); - workspace = new WorkspaceFileSystem(workspaceRoot, fileSystem); - }); - EdtTestUtil.runInEdtAndWait(writeAction); + EdtTestUtil.runInEdtAndWait( + (ThrowableRunnable<Throwable>) + () -> + ApplicationManager.getApplication() + .runWriteAction( + () -> { + ProjectJdkTable.getInstance().addJdk(IdeaTestUtil.getMockJdk18()); + VirtualFile workspaceRootVirtualFile = + fileSystem.createDirectory("workspace"); + workspaceRoot = + new WorkspaceRoot(new File(workspaceRootVirtualFile.getPath())); + projectDataDirectory = fileSystem.createDirectory("project-data-dir"); + workspace = new WorkspaceFileSystem(workspaceRoot, fileSystem); + })); BlazeImportSettingsManager.getInstance(getProject()) .setImportSettings(
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java b/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java index e7a2d4e..b4dede2 100644 --- a/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java +++ b/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java
@@ -36,6 +36,7 @@ import com.intellij.testFramework.EditorTestUtil.CaretInfo; import com.intellij.testFramework.EdtTestUtil; import com.intellij.testFramework.fixtures.CodeInsightTestFixture; +import com.intellij.util.ThrowableRunnable; import java.util.Arrays; import javax.annotation.Nullable; @@ -54,7 +55,8 @@ } public Editor openFileInEditor(VirtualFile file) { - EdtTestUtil.runInEdtAndWait((Runnable) () -> testFixture.openFileInEditor(file)); + EdtTestUtil.runInEdtAndWait( + (ThrowableRunnable<Throwable>) () -> testFixture.openFileInEditor(file)); return testFixture.getEditor(); } @@ -125,7 +127,7 @@ public void setCaretPosition(Editor editor, int lineNumber, int columnNumber) { final CaretInfo info = new CaretInfo(new LogicalPosition(lineNumber, columnNumber), null); EdtTestUtil.runInEdtAndWait( - (Runnable) + (ThrowableRunnable<Throwable>) () -> EditorTestUtil.setCaretsAndSelection( editor, new CaretAndSelectionState(ImmutableList.of(info), null)));
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java index efa81a8..78d1ec3 100644 --- a/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java +++ b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
@@ -25,12 +25,13 @@ import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder; import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl; import com.google.idea.blaze.base.sync.workspace.BlazeRoots; -import com.google.idea.blaze.base.sync.workspace.WorkingSet; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import com.intellij.openapi.vfs.VirtualFile; @@ -83,12 +84,11 @@ new TargetMap(ImmutableMap.of()), ImmutableMap.of(), fakeRoots, - new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()), + new BlazeVersionData(), workspacePathResolver, artifactLocationDecoder, null, - null, - null, + new SyncState.Builder().build(), null); } }
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java index b197413..a0ac661 100644 --- a/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java +++ b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
@@ -29,6 +29,7 @@ import com.google.idea.blaze.base.command.info.BlazeInfo; import com.google.idea.blaze.base.ideinfo.ArtifactLocation; import com.google.idea.blaze.base.ideinfo.TargetMap; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.TargetExpression; import com.google.idea.blaze.base.model.primitives.WorkspacePath; @@ -53,6 +54,7 @@ import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl; import com.google.idea.blaze.base.vcs.BlazeVcsHandler; +import com.intellij.ide.IdeEventQueue; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ModifiableRootModel; @@ -60,6 +62,8 @@ import com.intellij.openapi.vfs.VirtualFile; import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import javax.annotation.Nullable; import org.junit.Before; @@ -182,7 +186,20 @@ project, BlazeImportSettingsManager.getInstance(project).getImportSettings(), syncParams); - syncTask.syncProject(context); + + // We need to run sync off EDT to keep IntelliJ's transaction system happy + // Because the sync task itself wants to run occasional EDT tasks, we'll have + // to keep flushing the event queue. + Future<?> future = + Executors.newSingleThreadExecutor().submit(() -> syncTask.syncProject(context)); + while (!future.isDone()) { + IdeEventQueue.getInstance().flushQueue(); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } } private static class MockProjectViewManager extends ProjectViewManager { @@ -282,6 +299,7 @@ BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets, WorkspaceLanguageSettings workspaceLanguageSettings, ArtifactLocationDecoder artifactLocationDecoder, @@ -297,6 +315,18 @@ BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, + List<TargetExpression> targets) { + return BuildResult.SUCCESS; + } + + @Override + public BuildResult compileIdeArtifacts( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, List<TargetExpression> targets) { return BuildResult.SUCCESS; }
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java b/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java index 428b180..874fd28 100644 --- a/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java +++ b/base/tests/utils/unit/com/google/idea/blaze/base/BlazeTestCase.java
@@ -19,17 +19,22 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.extensions.DefaultPluginDescriptor; -import com.intellij.openapi.extensions.ExtensionPoint; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.extensions.impl.ExtensionPointImpl; import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; import org.picocontainer.MutablePicoContainer; /** @@ -40,6 +45,27 @@ * <p>Provides a mock application and a mock project. */ public class BlazeTestCase { + /** Test rule that ensures tests do not run on Windows (see http://b.android.com/222904) */ + public static class IgnoreOnWindowsRule implements TestRule { + @NotNull + @Override + public Statement apply(Statement base, Description description) { + if (SystemInfo.isWindows) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + System.out.println( + "Test \"" + + description.getDisplayName() + + "\" does not run on Windows (see http://b.android.com/222904)"); + } + }; + } + return base; + } + } + + @Rule public IgnoreOnWindowsRule rule = new IgnoreOnWindowsRule(); protected Project project; private ExtensionsAreaImpl extensionsArea; @@ -94,16 +120,9 @@ protected <T> ExtensionPointImpl<T> registerExtensionPoint( @NotNull ExtensionPointName<T> name, @NotNull Class<T> type) { - ExtensionPointImpl<T> extensionPoint = - new ExtensionPointImpl<T>( - name.getName(), - type.getName(), - ExtensionPoint.Kind.INTERFACE, - extensionsArea, - null, - new Extensions.SimpleLogProvider(), - new DefaultPluginDescriptor(PluginId.getId(type.getName()), type.getClassLoader())); - extensionsArea.registerExtensionPoint(extensionPoint); - return extensionPoint; + PluginDescriptor pluginDescriptor = + new DefaultPluginDescriptor(PluginId.getId(type.getName()), type.getClassLoader()); + extensionsArea.registerExtensionPoint(name.getName(), type.getName(), pluginDescriptor); + return extensionsArea.getExtensionPoint(name.getName()); } }
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java b/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java index 4f30237..c4b6762 100644 --- a/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java +++ b/base/tests/utils/unit/com/google/idea/blaze/base/run/MockBlazeCommandRunConfigurationHandlerProvider.java
@@ -81,6 +81,9 @@ public JComponent createComponent() { return null; } + + @Override + public void setComponentEnabled(boolean enabled) {} }; } }
diff --git a/build_defs/build_defs.bzl b/build_defs/build_defs.bzl index f836204..3104e0f 100644 --- a/build_defs/build_defs.bzl +++ b/build_defs/build_defs.bzl
@@ -191,22 +191,16 @@ "chmod +w $@", "mkdir -p META-INF", "cp $(location {plugin_xml}) META-INF/plugin.xml".format(plugin_xml=plugin_xml), - "$(location {zip_tool}) -u $@ META-INF/plugin.xml >/dev/null".format(zip_tool=zip_tool), ] - srcs = [ + srcs = meta_inf_files + [ plugin_xml, deploy_jar, ] for meta_inf_file in meta_inf_files: - cmd.append("cp $(location {meta_inf_file}) META-INF/$$(basename $(location {meta_inf_file}))".format( - meta_inf_file=meta_inf_file, - )) - cmd.append("$(location {zip_tool}) -u $@ META-INF/$$(basename $(location {meta_inf_file})) >/dev/null".format( - zip_tool=zip_tool, - meta_inf_file=meta_inf_file, - )) - srcs.append(meta_inf_file) + cmd.append("meta_inf_files='$(locations {meta_inf_file})'".format(meta_inf_file=meta_inf_file)) + cmd.append("for f in $$meta_inf_files; do cp $$f META-INF/; done") + cmd.append("$(location {zip_tool}) -u $@ META-INF/* >/dev/null".format(zip_tool=zip_tool)) native.genrule( name = name + "_genrule", @@ -236,28 +230,38 @@ tags = ["intellij-plugin-bundle"], ) -def repackage_jar(name, src_rule, out, - rules = [ - "com.google.common.** com.google.repackaged.common.@1", - "com.google.gson.** com.google.repackaged.gson.@1", - "com.google.protobuf.** com.google.repackaged.protobuf.@1", - "com.google.thirdparty.** com.google.repackaged.thirdparty.@1", - ], **kwargs): +def repackaged_jar(name, deps, rules, launcher=None, **kwargs): """Repackages classes in a jar, to avoid collisions in the classpath. Args: name: the name of this target - src_rule: a java_binary label with the create_executable attribute set to 0 - out: the output jarfile + deps: The dependencies repackage rules: the rules to apply in the repackaging - Only repackage some of com.google.** from proto_deps.jar. - We do not repackage: + Do not repackage: - com.google.net.** because that has JNI files which use FindClass(JNIEnv *, const char *) with hard-coded native string literals that jarjar doesn't rewrite. - com.google.errorprone packages (rewriting will throw off blaze build). + launcher: The launcher arg to pass to java_binary **kwargs: Any additional arguments to pass to the final target. """ + java_binary_name = name + "_deploy_jar" + out = name + ".jar" + native.java_binary( + name = java_binary_name, + create_executable = 0, + stamp = 0, + launcher = launcher, + runtime_deps = deps, + ) + _repackage_jar(name, java_binary_name, out, rules, **kwargs) + +def repackage_jar(name, src_rule, out, rules, **kwargs): + print("repackage_jar is deprecated. Please switch to repackaged_jar.") + _repackage_jar(name, src_rule, out, rules, **kwargs) + +def _repackage_jar(name, src_rule, out, rules, **kwargs): + """Repackages classes in a jar, to avoid collisions in the classpath.""" repackage_tool = "@jarjar//jar" deploy_jar = "{src_rule}_deploy.jar".format(src_rule=src_rule) script_lines = []
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationHandler.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationHandler.java index 29aea18..c5ded30 100644 --- a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationHandler.java +++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationHandler.java
@@ -23,6 +23,7 @@ import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.execution.Executor; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.RunConfiguration; @@ -38,8 +39,9 @@ private final BlazeCommandRunConfigurationCommonState state; public BlazeCidrRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { - this.buildSystemName = Blaze.buildSystemName(configuration.getProject()); - this.state = new BlazeCommandRunConfigurationCommonState(buildSystemName); + BuildSystem buildSystem = Blaze.getBuildSystem(configuration.getProject()); + this.buildSystemName = buildSystem.getName(); + this.state = new BlazeCommandRunConfigurationCommonState(buildSystem); } @Override
diff --git a/common/actionhelper/BUILD b/common/actionhelper/BUILD new file mode 100644 index 0000000..0da6dd5 --- /dev/null +++ b/common/actionhelper/BUILD
@@ -0,0 +1,11 @@ +licenses(["notice"]) # Apache 2.0 + +java_library( + name = "actionhelper", + srcs = glob(["src/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//intellij_platform_sdk:plugin_api", + "@jsr305_annotations//jar", + ], +)
diff --git a/common/actionhelper/src/com/google/idea/common/actionhelper/ActionPresentationHelper.java b/common/actionhelper/src/com/google/idea/common/actionhelper/ActionPresentationHelper.java new file mode 100644 index 0000000..96a7882 --- /dev/null +++ b/common/actionhelper/src/com/google/idea/common/actionhelper/ActionPresentationHelper.java
@@ -0,0 +1,175 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.common.actionhelper; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.vfs.VirtualFile; +import java.util.List; +import javax.annotation.Nullable; + +/** Helps setting the presentation enabled/visible/text states. */ +public class ActionPresentationHelper { + + private final Presentation presentation; + private boolean enabled = true; + private boolean visible = true; + private boolean disableWithoutSubject; + private boolean hasSubject; + private String text; + private String subjectText; + + /** Converts a subject to a string */ + @FunctionalInterface + public interface SubjectToString<T> { + String subjectToString(T subject); + } + + private ActionPresentationHelper(Presentation presentation) { + this.presentation = presentation; + } + + public static ActionPresentationHelper of(AnActionEvent e) { + return new ActionPresentationHelper(e.getPresentation()); + } + + /** Disables the action if the condition is true. */ + public ActionPresentationHelper disableIf(boolean disableCondition) { + this.enabled = this.enabled && !disableCondition; + return this; + } + + /** Hides the action if the condition is true. */ + public ActionPresentationHelper hideIf(boolean hideCondition) { + this.visible = this.visible && !hideCondition; + return this; + } + + /** Disables the action if no subject has been provided. */ + public ActionPresentationHelper disableWithoutSubject() { + this.disableWithoutSubject = true; + return this; + } + + /** Sets the text of the presentation. */ + public ActionPresentationHelper setText(String text) { + this.text = text; + return this; + } + + /** + * Sets the text depending on the subject. + * + * @param noSubjectText Text to set if there is no subject, or if the action is disabled. + * @param subjectText Text to set if there is a subject. If %s exists in the subject text, + * String.format is used with the quoted file name. + * @param file The subject. May be null. + */ + public ActionPresentationHelper setTextWithSubject( + String noSubjectText, String subjectText, @Nullable VirtualFile file) { + return setTextWithSubject( + noSubjectText, subjectText, file, ActionPresentationHelper::quoteFileName); + } + + /** + * Sets the text depending on the subject. + * + * @param noSubjectText Text to set if there is no subject, or if the action is disabled. + * @param subjectText Text to set if there is a subject. If %s exists in the subject text, + * String.format is used with the subject passed through subjectToString + * @param subject The subject. May be null. + * @param subjectToString Method used to convert the subject to a string. + */ + public <T> ActionPresentationHelper setTextWithSubject( + String noSubjectText, + String subjectText, + @Nullable T subject, + SubjectToString<T> subjectToString) { + this.text = noSubjectText; + if (subject != null) { + this.subjectText = + subjectText.contains("%s") + ? String.format(subjectText, subjectToString.subjectToString(subject)) + : subjectText; + this.hasSubject = true; + } + return this; + } + + /** + * Sets the text depending on the subjects. + * + * @param noSubjectText Text to set if there is no subject, or if the action is disabled. + * @param singleSubjectText Text to set if there is a single subject. If %s exists in the subject + * text, String.format is used with the quoted single file name. + * @param multipleSubjectText Text to use if there are multiple subjects. + */ + public ActionPresentationHelper setTextWithSubjects( + String noSubjectText, + String singleSubjectText, + String multipleSubjectText, + List<VirtualFile> files) { + return setTextWithSubjects( + noSubjectText, + singleSubjectText, + multipleSubjectText, + files, + ActionPresentationHelper::quoteFileName); + } + + /** + * Sets the text depending on the subjects. + * + * @param noSubjectText Text to set if there is no subject, or if the action is disabled. + * @param singleSubjectText Text to set if there is a single subject. If %s exists in the subject + * text, String.format is used with the subject passed through subjectToString. + * @param multipleSubjectText Text to use if there are multiple subjects. + */ + public <T> ActionPresentationHelper setTextWithSubjects( + String noSubjectText, + String singleSubjectText, + String multipleSubjectText, + List<T> subjects, + SubjectToString<T> subjectToString) { + if (subjects.size() > 1) { + this.text = noSubjectText; + this.subjectText = multipleSubjectText; + this.hasSubject = true; + return this; + } else { + T subject = !subjects.isEmpty() ? subjects.get(0) : null; + return setTextWithSubject(noSubjectText, singleSubjectText, subject, subjectToString); + } + } + + private static String quoteFileName(VirtualFile file) { + return "\"" + file.getName() + "\""; + } + + public void commit() { + boolean enabled = this.enabled; + if (disableWithoutSubject) { + enabled = enabled && hasSubject; + } + presentation.setEnabled(enabled); + presentation.setVisible(visible); + + String text = enabled && hasSubject ? subjectText : this.text; + if (text != null) { + presentation.setText(text); + } + } +}
diff --git a/common/formatter/BUILD b/common/formatter/BUILD index 839a469..1e45774 100644 --- a/common/formatter/BUILD +++ b/common/formatter/BUILD
@@ -6,6 +6,7 @@ visibility = ["//visibility:public"], deps = [ "//intellij_platform_sdk:plugin_api", + "//sdkcompat", "@jsr305_annotations//jar", ], )
diff --git a/common/formatter/src/com/google/idea/common/formatter/DelegatingCodeStyleManager.java b/common/formatter/src/com/google/idea/common/formatter/DelegatingCodeStyleManager.java index aa2460f..354fea4 100644 --- a/common/formatter/src/com/google/idea/common/formatter/DelegatingCodeStyleManager.java +++ b/common/formatter/src/com/google/idea/common/formatter/DelegatingCodeStyleManager.java
@@ -15,6 +15,7 @@ */ package com.google.idea.common.formatter; +import com.google.idea.sdkcompat.codestyle.CodeStyleManagerSdkCompatAdapter; import com.intellij.lang.ASTNode; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileTypes.FileType; @@ -31,7 +32,7 @@ import javax.annotation.Nullable; /** A delegating {@link CodeStyleManager}. */ -public abstract class DelegatingCodeStyleManager extends CodeStyleManager { +public abstract class DelegatingCodeStyleManager extends CodeStyleManagerSdkCompatAdapter { private final CodeStyleManager delegate;
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java index c066fc4..1c64fbc 100644 --- a/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java +++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCSyncPlugin.java
@@ -27,11 +27,9 @@ import com.google.idea.blaze.base.sync.BlazeSyncPlugin; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModifiableRootModel; import com.jetbrains.cidr.lang.workspace.OCWorkspace; import com.jetbrains.cidr.lang.workspace.OCWorkspaceManager; import java.util.Set; -import javax.annotation.Nullable; final class BlazeCSyncPlugin extends BlazeSyncPlugin.Adapter { @Override @@ -43,16 +41,13 @@ } @Override - public void updateProjectStructure( + public void updateInMemoryState( Project project, BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet, BlazeProjectData blazeProjectData, - @Nullable BlazeProjectData oldBlazeProjectData, - ModuleEditor moduleEditor, - Module workspaceModule, - ModifiableRootModel workspaceModifiableModel) { + Module workspaceModule) { if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.C)) { return; }
diff --git a/golang/BUILD b/golang/BUILD new file mode 100644 index 0000000..16a486c --- /dev/null +++ b/golang/BUILD
@@ -0,0 +1,38 @@ +licenses(["notice"]) # Apache 2.0 + +java_library( + name = "golang", + srcs = glob(["src/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//base", + "//intellij_platform_sdk:plugin_api", + "//sdkcompat", + "@jsr305_annotations//jar", + ], +) + +filegroup( + name = "plugin_xml", + srcs = ["src/META-INF/golang.xml"], + visibility = ["//visibility:public"], +) + +load( + "//testing:test_defs.bzl", + "intellij_unit_test_suite", +) + +intellij_unit_test_suite( + name = "unit_tests", + srcs = glob(["tests/unittests/**/*.java"]), + test_package_root = "com.google.idea.blaze.golang", + deps = [ + ":golang", + "//base", + "//base:unit_test_utils", + "//intellij_platform_sdk:plugin_api_for_tests", + "@jsr305_annotations//jar", + "@junit//jar", + ], +)
diff --git a/golang/src/META-INF/golang.xml b/golang/src/META-INF/golang.xml new file mode 100644 index 0000000..32948ee --- /dev/null +++ b/golang/src/META-INF/golang.xml
@@ -0,0 +1,7 @@ +<idea-plugin> + + <extensions defaultExtensionNs="com.google.idea.blaze"> + <SyncPlugin implementation="com.google.idea.blaze.golang.sync.BlazeGoSyncPlugin"/> + </extensions> + +</idea-plugin>
diff --git a/golang/src/com/google/idea/blaze/golang/sdk/GoSdkUtil.java b/golang/src/com/google/idea/blaze/golang/sdk/GoSdkUtil.java new file mode 100644 index 0000000..ba42571 --- /dev/null +++ b/golang/src/com/google/idea/blaze/golang/sdk/GoSdkUtil.java
@@ -0,0 +1,59 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.golang.sdk; + +import com.intellij.execution.configurations.PathEnvironmentVariableUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import java.io.File; +import java.io.IOException; +import javax.annotation.Nullable; + +/** + * Go-lang SDK utility methods. + * + * <p>TODO: Remove this, and reference go-lang plugin source code directly. + */ +public class GoSdkUtil { + + @Nullable + public static VirtualFile suggestSdkDirectory() { + String fromEnv = suggestSdkDirectoryPathFromEnv(); + if (fromEnv != null) { + return LocalFileSystem.getInstance().findFileByPath(fromEnv); + } + return LocalFileSystem.getInstance().findFileByPath("/usr/local/go"); + } + + @Nullable + private static String suggestSdkDirectoryPathFromEnv() { + File fileFromPath = PathEnvironmentVariableUtil.findInPath("go"); + if (fileFromPath != null) { + File canonicalFile; + try { + canonicalFile = fileFromPath.getCanonicalFile(); + String path = canonicalFile.getPath(); + if (path.endsWith("bin/go")) { + return StringUtil.trimEnd(path, "bin/go"); + } + } catch (IOException e) { + // if it can't be found, just silently return null + } + } + return null; + } +}
diff --git a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java new file mode 100644 index 0000000..693c5f7 --- /dev/null +++ b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoLibrarySource.java
@@ -0,0 +1,38 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.golang.sync; + +import com.google.idea.blaze.base.sync.libraries.LibrarySource; +import com.intellij.openapi.roots.libraries.Library; +import java.util.function.Predicate; + +/** Prevents garbage collection of Go libraries */ +class BlazeGoLibrarySource extends LibrarySource.Adapter { + + static final BlazeGoLibrarySource INSTANCE = new BlazeGoLibrarySource(); + + private BlazeGoLibrarySource() {} + + @Override + public Predicate<Library> getGcRetentionFilter() { + return BlazeGoLibrarySource::isGoLibrary; + } + + static boolean isGoLibrary(Library library) { + String name = library.getName(); + return name != null && name.startsWith(BlazeGoSyncPlugin.GO_LIBRARY_PREFIX); + } +}
diff --git a/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java new file mode 100644 index 0000000..7f2a5c7 --- /dev/null +++ b/golang/src/com/google/idea/blaze/golang/sync/BlazeGoSyncPlugin.java
@@ -0,0 +1,243 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.golang.sync; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; +import com.google.idea.blaze.base.model.primitives.WorkspaceType; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.output.IssueOutput; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin; +import com.google.idea.blaze.base.sync.GenericSourceFolderProvider; +import com.google.idea.blaze.base.sync.SourceFolderProvider; +import com.google.idea.blaze.base.sync.data.BlazeDataStorage; +import com.google.idea.blaze.base.sync.libraries.LibrarySource; +import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; +import com.google.idea.blaze.golang.sdk.GoSdkUtil; +import com.google.idea.sdkcompat.transactions.Transactions; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.module.ModuleTypeManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkType; +import com.intellij.openapi.projectRoots.SdkTypeId; +import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PluginsAdvertiser; +import com.intellij.openapi.util.EmptyRunnable; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.NavigatableAdapter; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +/** Supports golang. */ +public class BlazeGoSyncPlugin extends BlazeSyncPlugin.Adapter { + + static final String GO_LIBRARY_PREFIX = "GOPATH"; + private static final String GO_MODULE_TYPE_ID = "GO_MODULE"; + private static final String GO_PLUGIN_ID = "ro.redeul.google.go"; + private static final String GO_SDK_TYPE_ID = "Go SDK"; + + @Nullable + @Override + public ModuleType<?> getWorkspaceModuleType(WorkspaceType workspaceType) { + if (workspaceType == WorkspaceType.GO) { + return ModuleTypeManager.getInstance().findByID(GO_MODULE_TYPE_ID); + } + return null; + } + + @Override + public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() { + return ImmutableList.of(WorkspaceType.GO); + } + + @Override + public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) { + if (workspaceType == WorkspaceType.GO) { + return ImmutableSet.of(LanguageClass.GO); + } + return ImmutableSet.of(); + } + + @Nullable + @Override + public WorkspaceType getDefaultWorkspaceType() { + return WorkspaceType.GO; + } + + @Nullable + @Override + public SourceFolderProvider getSourceFolderProvider(BlazeProjectData projectData) { + if (!projectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.GO)) { + return null; + } + return GenericSourceFolderProvider.INSTANCE; + } + + @Override + public void updateProjectStructure( + Project project, + BlazeContext context, + WorkspaceRoot workspaceRoot, + ProjectViewSet projectViewSet, + BlazeProjectData blazeProjectData, + @Nullable BlazeProjectData oldBlazeProjectData, + ModuleEditor moduleEditor, + Module workspaceModule, + ModifiableRootModel workspaceModifiableModel) { + if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)) { + return; + } + for (Library lib : getGoLibraries(project)) { + if (workspaceModifiableModel.findLibraryOrderEntry(lib) == null) { + workspaceModifiableModel.addLibraryEntry(lib); + } + } + } + + private static List<Library> getGoLibraries(Project project) { + List<Library> libraries = Lists.newArrayList(); + LibraryTablesRegistrar registrar = LibraryTablesRegistrar.getInstance(); + for (Library lib : registrar.getLibraryTable().getLibraries()) { + if (BlazeGoLibrarySource.isGoLibrary(lib)) { + libraries.add(lib); + } + } + + String moduleLibraryName = + String.format("%s <%s>", GO_LIBRARY_PREFIX, BlazeDataStorage.WORKSPACE_MODULE_NAME); + Library goModuleLibrary = + registrar.getLibraryTable(project).getLibraryByName(moduleLibraryName); + if (goModuleLibrary != null) { + libraries.add(goModuleLibrary); + } + return libraries; + } + + /** + * By default the Go plugin will create duplicate copies of project libraries, one for each + * module. We only care about library associated with the workspace module. + */ + static boolean isGoLibraryForModule(Library library, String moduleName) { + String name = library.getName(); + return name != null && name.equals("GOPATH <" + moduleName + ">"); + } + + @Nullable + @Override + public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) { + if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)) { + return null; + } + return BlazeGoLibrarySource.INSTANCE; + } + + @Override + public boolean validateProjectView( + BlazeContext context, + ProjectViewSet projectViewSet, + WorkspaceLanguageSettings workspaceLanguageSettings) { + if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.GO)) { + return true; + } + if (!isPluginEnabled()) { + IssueOutput.error("Go plugin needed for Go language support.") + .navigatable( + new NavigatableAdapter() { + @Override + public void navigate(boolean requestFocus) { + if (isPluginInstalled()) { + PluginManager.enablePlugin(GO_PLUGIN_ID); + } else { + PluginsAdvertiser.installAndEnablePlugins( + ImmutableSet.of(GO_PLUGIN_ID), EmptyRunnable.INSTANCE); + } + } + }) + .submit(context); + return false; + } + return true; + } + + private static boolean isPluginInstalled() { + return PluginManager.isPluginInstalled(PluginId.getId(GO_PLUGIN_ID)); + } + + private static boolean isPluginEnabled() { + IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId(GO_PLUGIN_ID)); + return plugin != null && plugin.isEnabled(); + } + + @Override + public void updateProjectSdk( + Project project, + BlazeContext context, + ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, + BlazeProjectData blazeProjectData) { + if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.GO)) { + return; + } + Sdk currentSdk = ProjectRootManager.getInstance(project).getProjectSdk(); + if (currentSdk != null && currentSdk.getSdkType().getName().equals(GO_SDK_TYPE_ID)) { + return; + } + Sdk sdk = getOrCreateGoSdk(); + if (sdk != null) { + setProjectSdk(project, sdk); + } + } + + @Nullable + private static Sdk getOrCreateGoSdk() { + ProjectJdkTable sdkTable = ProjectJdkTable.getInstance(); + SdkTypeId type = sdkTable.getSdkTypeByName(GO_SDK_TYPE_ID); + List<Sdk> sdk = sdkTable.getSdksOfType(type); + if (!sdk.isEmpty()) { + return sdk.get(0); + } + VirtualFile defaultSdk = GoSdkUtil.suggestSdkDirectory(); + if (defaultSdk != null) { + return SdkConfigurationUtil.createAndAddSDK(defaultSdk.getPath(), (SdkType) type); + } + return null; + } + + private static void setProjectSdk(Project project, Sdk sdk) { + Transactions.submitTransactionAndWait( + () -> + ApplicationManager.getApplication() + .runWriteAction(() -> ProjectRootManager.getInstance(project).setProjectSdk(sdk))); + } +}
diff --git a/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java b/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java new file mode 100644 index 0000000..e563a27 --- /dev/null +++ b/golang/tests/unittests/com/google/idea/blaze/golang/sync/BlazeGoSyncPluginTest.java
@@ -0,0 +1,116 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.golang.sync; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.base.model.primitives.LanguageClass; +import com.google.idea.blaze.base.model.primitives.WorkspaceType; +import com.google.idea.blaze.base.projectview.ProjectView; +import com.google.idea.blaze.base.projectview.ProjectViewSet; +import com.google.idea.blaze.base.projectview.section.ListSection; +import com.google.idea.blaze.base.projectview.section.ScalarSection; +import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection; +import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection; +import com.google.idea.blaze.base.scope.BlazeContext; +import com.google.idea.blaze.base.scope.ErrorCollector; +import com.google.idea.blaze.base.scope.output.IssueOutput; +import com.google.idea.blaze.base.sync.BlazeSyncPlugin; +import com.google.idea.blaze.base.sync.projectview.LanguageSupport; +import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings; +import com.intellij.openapi.extensions.impl.ExtensionPointImpl; +import java.util.Set; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BlazeGoSyncPlugin} */ +@RunWith(JUnit4.class) +public class BlazeGoSyncPluginTest extends BlazeTestCase { + + private final ErrorCollector errorCollector = new ErrorCollector(); + private BlazeContext context; + private ExtensionPointImpl<BlazeSyncPlugin> syncPluginEp; + + @Override + protected void initTest( + @NotNull Container applicationServices, @NotNull Container projectServices) { + super.initTest(applicationServices, projectServices); + + syncPluginEp = registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class); + syncPluginEp.registerExtension(new BlazeGoSyncPlugin()); + context = new BlazeContext(); + context.addOutputSink(IssueOutput.class, errorCollector); + } + + @Test + public void testGoWorkspaceTypeSupported() { + ProjectViewSet projectViewSet = + ProjectViewSet.builder() + .add( + ProjectView.builder() + .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.GO)) + .build()) + .build(); + WorkspaceLanguageSettings workspaceLanguageSettings = + LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet); + errorCollector.assertNoIssues(); + assertThat(workspaceLanguageSettings) + .isEqualTo( + new WorkspaceLanguageSettings( + WorkspaceType.GO, ImmutableSet.of(LanguageClass.GENERIC, LanguageClass.GO))); + } + + @Test + public void testGoNotAValidAdditionalLanguage() { + // add a java sync plugin so we have another workspace type available + syncPluginEp.registerExtension( + new BlazeSyncPlugin.Adapter() { + @Override + public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() { + return ImmutableList.of(WorkspaceType.JAVA); + } + + @Override + public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) { + return ImmutableSet.of(LanguageClass.JAVA); + } + + @Nullable + @Override + public WorkspaceType getDefaultWorkspaceType() { + return WorkspaceType.JAVA; + } + }); + + ProjectViewSet projectViewSet = + ProjectViewSet.builder() + .add( + ProjectView.builder() + .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.JAVA)) + .add(ListSection.builder(AdditionalLanguagesSection.KEY).add(LanguageClass.GO)) + .build()) + .build(); + LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet); + errorCollector.assertIssueContaining( + "Language 'go' is not supported for this plugin with workspace type: 'java'"); + } +}
diff --git a/ijwb/BUILD b/ijwb/BUILD index 0ec5aad..55b2b72 100644 --- a/ijwb/BUILD +++ b/ijwb/BUILD
@@ -17,6 +17,7 @@ srcs = [ "src/META-INF/ijwb.xml", "//base:plugin_xml", + "//golang:plugin_xml", "//java:plugin_xml", "//plugin_dev:plugin_xml", ], @@ -50,6 +51,7 @@ ], deps = [ "//base", + "//golang", "//intellij_platform_sdk:plugin_api", "//java", "@jsr305_annotations//jar",
diff --git a/ijwb/ijwb.bazelproject b/ijwb/ijwb.bazelproject index e8fc755..6bf0977 100644 --- a/ijwb/ijwb.bazelproject +++ b/ijwb/ijwb.bazelproject
@@ -11,6 +11,9 @@ workspace_type: intellij_plugin +build_flags: + --define=ij_product=intellij-latest + test_sources: */tests/unittests* */tests/integrationtests*
diff --git a/intellij_platform_sdk/BUILD b/intellij_platform_sdk/BUILD index 2f68436..9edcbbf 100644 --- a/intellij_platform_sdk/BUILD +++ b/intellij_platform_sdk/BUILD
@@ -62,6 +62,14 @@ }, ) +# Android Studio 2.3.0.4 +config_setting( + name = "android-studio-2.3.0.4", + values = { + "define": "ij_product=android-studio-2.3.0.4", + }, +) + config_setting( name = "clion-latest", values = { @@ -69,6 +77,14 @@ }, ) +# CLion 2016.3.2 +config_setting( + name = "clion-2016.3.2", + values = { + "define": "ij_product=clion-2016.3.2", + }, +) + # CLion 2016.2.2 config_setting( name = "clion-162.1967.7", @@ -77,16 +93,13 @@ }, ) -# CLion 16 (2016.2.1) -config_setting( - name = "clion-162.1628.20", - values = { - "define": "ij_product=clion-162.1628.20", - }, +load( + ":build_defs.bzl", + "select_for_ide", + "select_for_plugin_api", + "select_from_plugin_api_directory", ) -load(":build_defs.bzl", "select_from_plugin_api_directory", "select_for_ide") - # The purpose of this rule is to hide the versioning # complexity from users of this api. # There will be additional versions added in the future @@ -96,6 +109,7 @@ android_studio = [ ":sdk", ":android_plugin", + ":test_recorder", ], clion = [":sdk"], intellij = [":sdk"], @@ -128,7 +142,7 @@ java_library( name = "plugin_api_for_grammar_kit", visibility = ["//third_party/java/jetbrains/grammar_kit:__pkg__"], - exports = ["//intellij_platform_sdk/IC_162_2032_8:sdk"], + exports = ["//intellij_platform_sdk/intellij_ce_2016_3_1:sdk"], ) # Used to support IntelliJ plugin development in our plugin @@ -172,7 +186,25 @@ android_studio = [":bundled_plugins"], clion = [":bundled_plugins"], intellij = [":bundled_plugins"], - ), + ) + [":missing_test_classes"], +) + +# Certain plugin API releases don't contain test classes, so any testServiceImplemention +# in an upstream plugin will cause an error when the plugin is loaded for integration tests. +# Here we have dummy versions of these missing classes. +java_library( + name = "missing_test_classes", + srcs = select_for_plugin_api({ + "android-studio-2.3.0.3": [ + "missing/tests/com/jetbrains/cidr/modulemap/resolve/MockModuleMapManagerImpl.java", + ], + "android-studio-2.3.0.4": [ + "missing/tests/com/jetbrains/cidr/modulemap/resolve/MockModuleMapManagerImpl.java", + ], + "default": [], + }) + ["missing/src/dummy/pkg/DummyClassToAvoidAnEmptyJavaLibrary.java"], + tags = ["intellij-missing-test-classes"], + deps = [":plugin_api"], ) filegroup( @@ -189,6 +221,7 @@ srcs = select_for_ide( android_studio = ["android_studio_application_info_name.txt"], clion = ["clion_application_info_name.txt"], + default = ["intellij_application_info_name.txt"], intellij = ["intellij_application_info_name.txt"], ), )
diff --git a/intellij_platform_sdk/BUILD.clion b/intellij_platform_sdk/BUILD.clion index e395d09..c83e150 100644 --- a/intellij_platform_sdk/BUILD.clion +++ b/intellij_platform_sdk/BUILD.clion
@@ -26,4 +26,4 @@ filegroup( name = "application_info_jar", srcs = glob(["clion-*/lib/clion.jar"]), -) +) \ No newline at end of file
diff --git a/intellij_platform_sdk/build_defs.bzl b/intellij_platform_sdk/build_defs.bzl index 57dbe76..e7c5c2d 100644 --- a/intellij_platform_sdk/build_defs.bzl +++ b/intellij_platform_sdk/build_defs.bzl
@@ -2,7 +2,7 @@ # The current indirect ij_product mapping (eg. "intellij-latest") INDIRECT_IJ_PRODUCTS = { - "intellij-latest": "intellij-162.2032.8", + "intellij-latest": "intellij-2016.3.1", "android-studio-latest": "android-studio-145.1617.8", "android-studio-beta": "android-studio-2.3.0.3", "clion-latest": "clion-162.1967.7", @@ -25,14 +25,18 @@ ide="android-studio", directory="android_studio_2_3_0_3", ), - "clion-162.1628.20": struct( - ide="clion", - directory="CL_162_1628_20", + "android-studio-2.3.0.4": struct( + ide="android-studio", + directory="android_studio_2_3_0_4", ), "clion-162.1967.7": struct( ide="clion", directory="CL_162_1967_7", ), + "clion-2016.3.2": struct( + ide="clion", + directory="clion_2016_3_2", + ), } # BUILD_VARS for each IDE corresponding to indirect ij_products, eg. "intellij-latest" @@ -50,11 +54,8 @@ You may only include direct ij_products here, not indirects (eg. intellij-latest). Returns: - A select statement on all plugin_apis. Unless you include a "default" - clause any other matched plugin_api will return "None". - - A build without an ij_product is considered equivalent to building with - "intellij-latest". + A select statement on all plugin_apis. Unless you include a "default", + a non-matched plugin_api will result in an error. Example: java_library( @@ -65,6 +66,9 @@ }), ) """ + if not params: + fail("Empty select_for_plugin_api") + for indirect_ij_product in INDIRECT_IJ_PRODUCTS: if indirect_ij_product in params: error_message = "".join([ @@ -72,43 +76,27 @@ "Instead, select on an exact ij_product."]) fail(error_message) - # To make the select work with "intellij-latest" and friends, - # we find if the user is currently selecting on what intellij-latest - # is resolving to, and copy that. Example: + expanded_params = dict(**params) + + # Expand all indirect plugin_apis to point to their + # corresponding direct plugin_api. # - # {"intellij-2016.3.1": "stuff"} -> - # {"intellij-2016.3.1": "stuff", "intellij-latest": "stuff"} - params = dict(**params) + # {"intellij-2016.3.1": "foo"} -> + # {"intellij-2016.3.1": "foo", "intellij-latest": "foo"} for indirect_ij_product, resolved_plugin_api in INDIRECT_IJ_PRODUCTS.items(): if resolved_plugin_api in params: - params[indirect_ij_product] = params[resolved_plugin_api] + expanded_params[indirect_ij_product] = params[resolved_plugin_api] - if "default" not in params: - # If "intellij-latest" is supported, we set "default" to that - # This supports building with an empty command line. Example: - # - # {"intellij-2016.3.1": "stuff", "intellij-latest": "stuff"} -> - # {"intellij-2016.3.1": "stuff", "intellij-latest": "stuff", "default": "stuff"} - if "intellij-latest" in params: - params["default"] = params["intellij-latest"] - - # Add the other indirect ij_products returning None rather than default - for ij_product in INDIRECT_IJ_PRODUCTS: - if ij_product not in params: - params[ij_product] = None - for ij_product in DIRECT_IJ_PRODUCTS: - if ij_product not in params: - params[ij_product] = None - - # Map to the actual targets + # Map the shorthand ij_products to full config_setting targets. # This makes it more convenient so the user doesn't have to # fully specify the path to the plugin_apis select_params = dict() - for ij_product, value in params.items(): + for ij_product, value in expanded_params.items(): if ij_product == "default": select_params["//conditions:default"] = value else: select_params["//intellij_platform_sdk:" + ij_product] = value + return select(select_params) def select_for_ide(intellij=None, android_studio=None, clion=None, default=None): @@ -134,7 +122,6 @@ intellij = intellij or default android_studio = android_studio or default clion = clion or default - default = default or intellij ide_to_value = { "intellij" : intellij, @@ -166,4 +153,8 @@ params = dict() for ij_product, value in DIRECT_IJ_PRODUCTS.items(): params[ij_product] = [_plugin_api_directory(value) + item for item in ide_to_value[value.ide]] + + # No ij_product == intellij-latest + params["default"] = params[INDIRECT_IJ_PRODUCTS["intellij-latest"]] + return select_for_plugin_api(params)
diff --git a/java/BUILD b/java/BUILD index 80799c3..ac9f772 100644 --- a/java/BUILD +++ b/java/BUILD
@@ -6,10 +6,12 @@ visibility = ["//visibility:public"], deps = [ "//base", + "//common/actionhelper", "//common/experiments", "//intellij_platform_sdk:junit", "//intellij_platform_sdk:plugin_api", "//proto_deps", + "//sdkcompat", "@jsr305_annotations//jar", ], )
diff --git a/java/src/META-INF/blaze-java.xml b/java/src/META-INF/blaze-java.xml index ae7ca4d..5466c27 100644 --- a/java/src/META-INF/blaze-java.xml +++ b/java/src/META-INF/blaze-java.xml
@@ -19,31 +19,34 @@ <actions> <action class="com.google.idea.blaze.java.libraries.ExcludeLibraryAction" - id="Blaze.ExcludeLibraryAction" - icon="BlazeIcons.Blaze" - text="Exclude Library and Resync"> - <add-to-group group-id="Blaze.ProjectViewPopupMenu"/> + id="Blaze.ExcludeLibraryAction" + text="Exclude Library and Resync"> </action> <action class="com.google.idea.blaze.java.libraries.AttachSourceJarAction" - id="Blaze.AttachSourceJarAction" - icon="BlazeIcons.Blaze" - text="Attach Source Jar"> - <add-to-group group-id="Blaze.ProjectViewPopupMenu"/> + id="Blaze.AttachSourceJarAction" + text="Attach Source Jar"> </action> <action class="com.google.idea.blaze.java.libraries.AddLibraryTargetDirectoryToProjectViewAction" - id="Blaze.AddLibraryTargetDirectoryToProjectView" - icon="BlazeIcons.Blaze" - text="Add Library Target Directory To Project View"> - <add-to-group group-id="Blaze.ProjectViewPopupMenu"/> + id="Blaze.AddLibraryTargetDirectoryToProjectView" + text="Add Library Target Directory to Project View"> + </action> + <action class="com.google.idea.blaze.java.libraries.DetachAllSourceJarsAction" + id="Blaze.DetachAllSourceJars" + text="Detach All Blaze Source Jars"> </action> - <group> - <action class="com.google.idea.blaze.java.libraries.DetachAllSourceJarsAction" - id="Blaze.DetachAllSourceJars" - text="Detach All Blaze Source Jars"> - </action> - <separator/> - <add-to-group group-id="Blaze.MainMenuActionGroup" relative-to-action="Blaze.EditProjectView" anchor="before"/> + <group id="Blaze.Java.ProjectViewPopupMenu"> + <add-to-group group-id="Blaze.ProjectViewPopupMenu"/> + <reference id="Blaze.ExcludeLibraryAction"/> + <reference id="Blaze.AttachSourceJarAction"/> + <reference id="Blaze.AddLibraryTargetDirectoryToProjectView"/> + </group> + + <group id="Blaze.JavaMenuGroup.Outer"> + <add-to-group group-id="Blaze.MainMenuActionGroup" relative-to-action="Blaze.MenuFooter" anchor="after"/> + <group id="Blaze.JavaMenuGroup" text="Java"> + <reference id="Blaze.DetachAllSourceJars"/> + </group> </group> <!-- IntelliJ specific actions --> @@ -92,6 +95,9 @@ <projectService serviceImplementation="com.google.idea.blaze.java.libraries.SourceJarManager"/> <refactoring.safeDeleteProcessor id="build_file_safe_delete" order="before javaProcessor" implementation="com.google.idea.blaze.java.lang.build.BuildFileSafeDeleteProcessor"/> + <!--duplicated here in case the Kotlin plugin is present, as it also tries to replace javaProcessor--> + <refactoring.safeDeleteProcessor id="build_file_safe_delete_copy" order="before kotlinProcessor" + implementation="com.google.idea.blaze.java.lang.build.BuildFileSafeDeleteProcessor"/> <projectService serviceImplementation="com.google.idea.blaze.java.libraries.JarCache"/> <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider"/>
diff --git a/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java index 935fded..a954942 100644 --- a/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java +++ b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
@@ -18,7 +18,7 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.model.BlazeProjectData; @@ -44,14 +44,12 @@ import java.io.File; import java.util.List; import java.util.Set; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import javax.annotation.Nullable; -class AddLibraryTargetDirectoryToProjectViewAction extends BlazeAction { +class AddLibraryTargetDirectoryToProjectViewAction extends BlazeProjectAction { + @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - assert project != null; + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { Library library = LibraryActionHelper.findLibraryForAction(e); if (library != null) { addDirectoriesToProjectView(project, ImmutableList.of(library)); @@ -59,18 +57,15 @@ } @Override - protected void doUpdate(@NotNull AnActionEvent e) { + protected void updateForBlazeProject(Project project, AnActionEvent e) { Presentation presentation = e.getPresentation(); boolean visible = false; boolean enabled = false; - Project project = e.getProject(); - if (project != null) { - Library library = LibraryActionHelper.findLibraryForAction(e); - if (library != null) { - visible = true; - if (getDirectoryToAddForLibrary(project, library) != null) { - enabled = true; - } + Library library = LibraryActionHelper.findLibraryForAction(e); + if (library != null) { + visible = true; + if (getDirectoryToAddForLibrary(project, library) != null) { + enabled = true; } } presentation.setVisible(visible);
diff --git a/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java b/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java index 7e18d28..9961f6f 100644 --- a/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java +++ b/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
@@ -15,7 +15,7 @@ */ package com.google.idea.blaze.java.libraries; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.ideinfo.LibraryArtifact; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; @@ -30,13 +30,11 @@ import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.ui.Messages; -import org.jetbrains.annotations.NotNull; -class AttachSourceJarAction extends BlazeAction { +class AttachSourceJarAction extends BlazeProjectAction { + @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - assert project != null; + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData == null) { @@ -77,28 +75,25 @@ } @Override - protected void doUpdate(@NotNull AnActionEvent e) { + protected void updateForBlazeProject(Project project, AnActionEvent e) { Presentation presentation = e.getPresentation(); String text = "Attach Source Jar"; boolean visible = false; boolean enabled = false; - Project project = e.getProject(); - if (project != null) { - BlazeProjectData blazeProjectData = - BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); - if (blazeProjectData != null) { - Library library = LibraryActionHelper.findLibraryForAction(e); - if (library != null) { - visible = true; + BlazeProjectData blazeProjectData = + BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); + if (blazeProjectData != null) { + Library library = LibraryActionHelper.findLibraryForAction(e); + if (library != null) { + visible = true; - BlazeJarLibrary blazeLibrary = - LibraryActionHelper.findLibraryFromIntellijLibrary( - e.getProject(), blazeProjectData, library); - if (blazeLibrary != null && blazeLibrary.libraryArtifact.sourceJar != null) { - enabled = true; - if (SourceJarManager.getInstance(project).hasSourceJarAttached(blazeLibrary.key)) { - text = "Detach Source Jar"; - } + BlazeJarLibrary blazeLibrary = + LibraryActionHelper.findLibraryFromIntellijLibrary( + e.getProject(), blazeProjectData, library); + if (blazeLibrary != null && blazeLibrary.libraryArtifact.sourceJar != null) { + enabled = true; + if (SourceJarManager.getInstance(project).hasSourceJarAttached(blazeLibrary.key)) { + text = "Detach Source Jar"; } } }
diff --git a/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java b/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java index 05759ba..b5305b3 100644 --- a/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java +++ b/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
@@ -26,6 +26,7 @@ import com.google.idea.blaze.base.sync.libraries.LibraryEditor; import com.google.idea.blaze.java.settings.BlazeJavaUserSettings; import com.google.idea.blaze.java.sync.model.BlazeJarLibrary; +import com.google.idea.sdkcompat.transactions.Transactions; import com.intellij.codeInsight.AttachSourcesProvider; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; @@ -35,7 +36,6 @@ import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.util.ActionCallback; import com.intellij.psi.PsiFile; -import com.intellij.util.ui.UIUtil; import java.util.Collection; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -85,7 +85,8 @@ * corresponding user setting is active. */ if (BlazeJavaUserSettings.getInstance().getAttachSourcesOnDemand()) { - UIUtil.invokeLaterIfNeeded( + Transactions.submitTransaction( + project, () -> { attachSources(project, blazeProjectData, librariesToAttachSourceTo); });
diff --git a/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java b/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java index 10f70a3..37188f6 100644 --- a/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java +++ b/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java
@@ -16,7 +16,7 @@ package com.google.idea.blaze.java.libraries; import com.google.common.collect.Lists; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.LibraryKey; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; @@ -30,12 +30,10 @@ import com.intellij.openapi.roots.libraries.LibraryTable; import java.util.List; -class DetachAllSourceJarsAction extends BlazeAction { - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - assert project != null; +class DetachAllSourceJarsAction extends BlazeProjectAction { + @Override + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData == null) {
diff --git a/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java b/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java index 53d4ae8..48ab1d2 100644 --- a/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java +++ b/java/src/com/google/idea/blaze/java/libraries/ExcludeLibraryAction.java
@@ -15,7 +15,7 @@ */ package com.google.idea.blaze.java.libraries; -import com.google.idea.blaze.base.actions.BlazeAction; +import com.google.idea.blaze.base.actions.BlazeProjectAction; import com.google.idea.blaze.base.ideinfo.LibraryArtifact; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.projectview.ProjectViewEdit; @@ -33,13 +33,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.ui.Messages; -import org.jetbrains.annotations.NotNull; -class ExcludeLibraryAction extends BlazeAction { +class ExcludeLibraryAction extends BlazeProjectAction { + @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - assert project != null; + protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(project).getBlazeProjectData(); if (blazeProjectData == null) { @@ -88,7 +86,7 @@ } @Override - protected void doUpdate(@NotNull AnActionEvent e) { + protected void updateForBlazeProject(Project project, AnActionEvent e) { Presentation presentation = e.getPresentation(); boolean enabled = LibraryActionHelper.findLibraryForAction(e) != null; presentation.setVisible(enabled);
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.java index 001f26e..33a6540 100644 --- a/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.java +++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaDebuggerRunner.java
@@ -17,7 +17,7 @@ import com.google.idea.blaze.base.model.primitives.Kind; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; -import com.intellij.debugger.impl.GenericDebuggerRunner; +import com.google.idea.sdkcompat.debugger.GenericDebuggerRunnerSdkCompatAdapter; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.JavaParameters; import com.intellij.execution.configurations.RemoteConnection; @@ -27,19 +27,21 @@ import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.ui.RunContentDescriptor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import javax.annotation.Nullable; /** A runner that adapts the GenericDebuggerRunner to work with Blaze run configurations. */ -public class BlazeJavaDebuggerRunner extends GenericDebuggerRunner { +public class BlazeJavaDebuggerRunner extends GenericDebuggerRunnerSdkCompatAdapter { + + // wait 10 minutes for the blaze build to complete before connecting + private static final long POLL_TIMEOUT_MILLIS = 10 * 60 * 1000; + @Override - @NotNull public String getRunnerId() { return "Blaze-Debug"; } @Override - public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) { + public boolean canRun(final String executorId, final RunProfile profile) { if (executorId.equals(DefaultDebugExecutor.EXECUTOR_ID) && profile instanceof BlazeCommandRunConfiguration) { BlazeCommandRunConfiguration configuration = (BlazeCommandRunConfiguration) profile; @@ -61,13 +63,12 @@ @Override @Nullable public RunContentDescriptor createContentDescriptor( - @NotNull RunProfileState state, @NotNull ExecutionEnvironment environment) - throws ExecutionException { + RunProfileState state, ExecutionEnvironment environment) throws ExecutionException { if (!(state instanceof BlazeJavaRunProfileState)) { return null; } BlazeJavaRunProfileState blazeState = (BlazeJavaRunProfileState) state; RemoteConnection connection = blazeState.getRemoteConnection(); - return attachVirtualMachine(state, environment, connection, true /* pollConnection */); + return attachVirtualMachine(state, environment, connection, POLL_TIMEOUT_MILLIS); } }
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandler.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandler.java index fd26ffd..479b964 100644 --- a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandler.java +++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationHandler.java
@@ -22,6 +22,7 @@ import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.base.settings.Blaze; +import com.google.idea.blaze.base.settings.Blaze.BuildSystem; import com.intellij.execution.Executor; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunProfileState; @@ -38,8 +39,9 @@ private final BlazeCommandRunConfigurationCommonState state; public BlazeJavaRunConfigurationHandler(BlazeCommandRunConfiguration configuration) { - this.buildSystemName = Blaze.buildSystemName(configuration.getProject()); - this.state = new BlazeCommandRunConfigurationCommonState(buildSystemName); + BuildSystem buildSystem = Blaze.getBuildSystem(configuration.getProject()); + this.buildSystemName = buildSystem.getName(); + this.state = new BlazeCommandRunConfigurationCommonState(buildSystem); } @Override
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java index a712e65..47b971b 100644 --- a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java +++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunProfileState.java
@@ -28,8 +28,10 @@ import com.google.idea.blaze.base.projectview.ProjectViewManager; import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; +import com.google.idea.blaze.base.run.DistributedExecutorSupport; import com.google.idea.blaze.base.run.processhandler.LineProcessingProcessAdapter; import com.google.idea.blaze.base.run.processhandler.ScopedBlazeProcessHandler; +import com.google.idea.blaze.base.run.smrunner.SmRunnerUtils; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.scopes.IdeaLogScope; @@ -38,14 +40,22 @@ import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.settings.BlazeImportSettings; import com.google.idea.blaze.base.settings.BlazeImportSettingsManager; +import com.google.idea.common.experiments.BoolExperiment; +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.RemoteConnection; import com.intellij.execution.configurations.RemoteState; import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.WrappingRunConfiguration; +import com.intellij.execution.filters.TextConsoleBuilderImpl; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessListener; import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.project.Project; /** @@ -54,6 +64,10 @@ * when using a debug executor. */ final class BlazeJavaRunProfileState extends CommandLineState implements RemoteState { + + private static final BoolExperiment smRunnerUiEnabled = + new BoolExperiment("use.smrunner.ui.java", true); + // Blaze seems to always use this port for --java_debug. // TODO(joshgiles): Look at manually identifying and setting port. private static final int DEBUG_PORT = 5005; @@ -64,12 +78,18 @@ public BlazeJavaRunProfileState(ExecutionEnvironment environment, boolean debug) { super(environment); - RunProfile runProfile = environment.getRunProfile(); - assert runProfile instanceof BlazeCommandRunConfiguration; - configuration = (BlazeCommandRunConfiguration) runProfile; + this.configuration = getConfiguration(environment); this.debug = debug; } + private static BlazeCommandRunConfiguration getConfiguration(ExecutionEnvironment environment) { + RunProfile runProfile = environment.getRunProfile(); + if (runProfile instanceof WrappingRunConfiguration) { + runProfile = ((WrappingRunConfiguration) runProfile).getPeer(); + } + return (BlazeCommandRunConfiguration) runProfile; + } + @Override protected ProcessHandler startProcess() throws ExecutionException { Project project = configuration.getProject(); @@ -80,7 +100,25 @@ ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet(); assert projectViewSet != null; - BlazeCommand blazeCommand = getBlazeCommand(project, configuration, projectViewSet, debug); + BlazeCommand blazeCommand; + if (useTestUi()) { + BlazeJavaTestEventsHandler eventsHandler = new BlazeJavaTestEventsHandler(); + blazeCommand = + getBlazeCommand( + project, configuration, projectViewSet, eventsHandler.getBlazeFlags(), debug); + setConsoleBuilder( + new TextConsoleBuilderImpl(project) { + @Override + protected ConsoleView createConsole() { + return SmRunnerUtils.getConsoleView( + project, configuration, getEnvironment().getExecutor(), eventsHandler); + } + }); + } else { + blazeCommand = + getBlazeCommand(project, configuration, projectViewSet, ImmutableList.of(), debug); + } + WorkspaceRoot workspaceRoot = WorkspaceRoot.fromImportSettings(importSettings); return new ScopedBlazeProcessHandler( project, @@ -106,6 +144,24 @@ } @Override + public ExecutionResult execute(Executor executor, ProgramRunner runner) + throws ExecutionException { + DefaultExecutionResult result = (DefaultExecutionResult) super.execute(executor, runner); + return SmRunnerUtils.attachRerunFailedTestsAction(result); + } + + private boolean useTestUi() { + if (!smRunnerUiEnabled.getValue()) { + return false; + } + BlazeCommandRunConfigurationCommonState state = + configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class); + return state != null + && BlazeCommandName.TEST.equals(state.getCommand()) + && !Boolean.TRUE.equals(state.getRunOnDistributedExecutor()); + } + + @Override public RemoteConnection getRemoteConnection() { if (!debug) { return null; @@ -122,6 +178,7 @@ Project project, BlazeCommandRunConfiguration configuration, ProjectViewSet projectViewSet, + ImmutableList<String> extraBlazeFlags, boolean debug) { BlazeCommandRunConfigurationCommonState handlerState = @@ -135,6 +192,7 @@ .setBlazeBinary(handlerState.getBlazeBinary()) .addTargets(configuration.getTarget()) .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)) + .addBlazeFlags(extraBlazeFlags) .addBlazeFlags(handlerState.getBlazeFlags()); if (debug) { @@ -145,6 +203,10 @@ } else { command.addBlazeFlags(BlazeFlags.JAVA_TEST_DEBUG); } + } else { + command.addBlazeFlags( + DistributedExecutorSupport.getBlazeFlags( + project, handlerState.getRunOnDistributedExecutor())); } command.addExeFlags(handlerState.getExeFlags());
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java new file mode 100644 index 0000000..9af9b64 --- /dev/null +++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestEventsHandler.java
@@ -0,0 +1,94 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.java.run; + +import com.google.idea.blaze.base.command.BlazeFlags; +import com.google.idea.blaze.base.run.smrunner.BlazeTestEventsHandler; +import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags; +import com.intellij.execution.Location; +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.execution.testframework.JavaTestLocator; +import com.intellij.execution.testframework.sm.runner.SMTestLocator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.io.URLUtil; +import java.util.List; +import javax.annotation.Nullable; + +/** Provides java-specific methods needed by the SM-runner test UI. */ +public class BlazeJavaTestEventsHandler extends BlazeTestEventsHandler { + + public BlazeJavaTestEventsHandler() { + super("Blaze Java Test"); + } + + @Override + public SMTestLocator getTestLocator() { + return JavaTestLocator.INSTANCE; + } + + @Override + public String suiteLocationUrl(String name) { + return JavaTestLocator.SUITE_PROTOCOL + URLUtil.SCHEME_SEPARATOR + name; + } + + @Override + public String testLocationUrl(String name, @Nullable String classname) { + if (classname == null) { + return null; + } + return JavaTestLocator.TEST_PROTOCOL + URLUtil.SCHEME_SEPARATOR + classname + "." + name; + } + + @Override + public String suiteDisplayName(String rawName) { + String name = StringUtil.trimEnd(rawName, '.'); + int lastPointIx = name.lastIndexOf('.'); + return lastPointIx != -1 ? name.substring(lastPointIx + 1, name.length()) : name; + } + + @Nullable + @Override + public String getTestFilter(Project project, List<AbstractTestProxy> failedTests) { + GlobalSearchScope projectScope = GlobalSearchScope.allScope(project); + MultiMap<PsiClass, PsiMethod> failedMethodsPerClass = new MultiMap<>(); + for (AbstractTestProxy test : failedTests) { + appendTest(failedMethodsPerClass, test.getLocation(project, projectScope)); + } + String filter = BlazeJUnitTestFilterFlags.testFilterForClassesAndMethods(failedMethodsPerClass); + return filter != null ? BlazeFlags.TEST_FILTER + "=" + filter : null; + } + + private static void appendTest( + MultiMap<PsiClass, PsiMethod> testMap, @Nullable Location<?> testLocation) { + if (testLocation == null) { + return; + } + PsiElement method = testLocation.getPsiElement(); + if (!(method instanceof PsiMethod)) { + return; + } + PsiClass psiClass = ((PsiMethod) method).getContainingClass(); + if (psiClass != null) { + testMap.putValue(psiClass, (PsiMethod) method); + } + } +}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java index 2c6a6aa..8f2290f 100644 --- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java +++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java
@@ -16,76 +16,147 @@ package com.google.idea.blaze.java.run.producers; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; -import com.google.idea.blaze.base.command.BlazeFlags; -import com.google.idea.common.experiments.BoolExperiment; +import com.google.common.collect.ImmutableList; +import com.intellij.execution.junit.JUnitUtil; +import com.intellij.execution.junit2.PsiMemberParameterizedLocation; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.util.containers.MultiMap; import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; +import java.util.stream.Collectors; import javax.annotation.Nullable; /** Utilities for building test filter flags for JUnit tests. */ public final class BlazeJUnitTestFilterFlags { - private static final BoolExperiment enableParameterizedSupport = - new BoolExperiment("enable.parameterized.test.support", true); - /** A version of JUnit to generate test filter flags for. */ public enum JUnitVersion { JUNIT_3, JUNIT_4 } - public static String testFilterFlagForClass(String className, JUnitVersion jUnitVersion) { - return testFilterFlagForClassAndMethod( - className, null, jUnitVersion, /* parameterized doesn't matter for a class */ false); + /** + * Builds the JUnit test filter corresponding to the given class.<br> + * Returns null if no class name can be found. + */ + @Nullable + public static String testFilterForClass(PsiClass psiClass) { + return testFilterForClassAndMethods(psiClass, ImmutableList.of()); } - public static String testFilterFlagForClassAndMethod( + /** + * Builds the JUnit test filter corresponding to the given class and methods.<br> + * Returns null if no class name can be found. + */ + @Nullable + public static String testFilterForClassAndMethods( + PsiClass psiClass, Collection<PsiMethod> methods) { + JUnitVersion version = + JUnitUtil.isJUnit4TestClass(psiClass) ? JUnitVersion.JUNIT_4 : JUnitVersion.JUNIT_3; + return testFilterForClassAndMethods(psiClass, version, methods); + } + + @Nullable + public static String testFilterForClassesAndMethods( + MultiMap<PsiClass, PsiMethod> methodsPerClass) { + // Note: this could be incorrect if there are no JUnit4 classes in this sample, but some in the + // java_test target they're run from. + JUnitVersion version = + hasJUnit4Test(methodsPerClass.keySet()) ? JUnitVersion.JUNIT_4 : JUnitVersion.JUNIT_3; + return testFilterForClassesAndMethods(methodsPerClass, version); + } + + @Nullable + public static String testFilterForClassesAndMethods( + MultiMap<PsiClass, PsiMethod> methodsPerClass, JUnitVersion version) { + StringBuilder output = new StringBuilder(); + for (Entry<PsiClass, Collection<PsiMethod>> entry : methodsPerClass.entrySet()) { + String filter = testFilterForClassAndMethods(entry.getKey(), version, entry.getValue()); + if (filter != null) { + output.append(filter); + } + } + return Strings.emptyToNull(output.toString()); + } + + private static boolean hasJUnit4Test(Collection<PsiClass> classes) { + for (PsiClass psiClass : classes) { + if (JUnitUtil.isJUnit4TestClass(psiClass)) { + return true; + } + } + return false; + } + + /** + * Builds the JUnit test filter corresponding to the given class and methods.<br> + * Returns null if no class name can be found. + */ + @Nullable + private static String testFilterForClassAndMethods( + PsiClass psiClass, JUnitVersion version, Collection<PsiMethod> methods) { + String className = psiClass.getQualifiedName(); + if (className == null) { + return null; + } + // Sort so multiple configurations created with different selection orders are the same. + List<String> methodNames = + methods.stream().map(PsiMethod::getName).sorted().collect(Collectors.toList()); + return testFilterForClassAndMethods(className, methodNames, version, isParameterized(psiClass)); + } + + private static boolean isParameterized(PsiClass testClass) { + return PsiMemberParameterizedLocation.getParameterizedLocation(testClass, null) != null; + } + + /** + * Builds the blaze test_filter flag for JUnit tests. Excludes the "--test_filter" component of + * the flag, so that multiple test classes can be combined. + */ + @VisibleForTesting + static String testFilterForClassAndMethods( String className, - @Nullable String methodName, + List<String> methodNames, JUnitVersion jUnitVersion, boolean parameterized) { - StringBuilder output = new StringBuilder(BlazeFlags.TEST_FILTER); - output.append('='); - output.append(className); - - if (!Strings.isNullOrEmpty(methodName)) { - output.append('#'); - output.append(methodName); - // JUnit 4 test filters are regexes, and must be terminated to avoid matching - // unintended classes/methods. JUnit 3 test filters do not need or support this syntax. + StringBuilder output = new StringBuilder(className); + String methodNamePattern = concatenateMethodNames(methodNames, jUnitVersion); + if (Strings.isNullOrEmpty(methodNamePattern)) { if (jUnitVersion == JUnitVersion.JUNIT_4) { - // parameterized tests include their parameters between brackets after the method name - if (parameterized && enableParameterizedSupport.getValue()) { - output.append("(\\[.+\\])?"); - } - output.append("$"); + output.append('#'); } - } else if (jUnitVersion == JUnitVersion.JUNIT_4) { - output.append('#'); + return output.toString(); } - + output.append('#').append(methodNamePattern); + // JUnit 4 test filters are regexes, and must be terminated to avoid matching + // unintended classes/methods. JUnit 3 test filters do not need or support this syntax. + if (jUnitVersion == JUnitVersion.JUNIT_3) { + return output.toString(); + } + // parameterized tests include their parameters between brackets after the method name + if (parameterized) { + output.append("(\\[.+\\])?"); + } + output.append('$'); return output.toString(); } - public static String testFilterFlagForClassAndMethods( - String className, - Collection<String> methodNames, - JUnitVersion jUnitVersion, - boolean parameterized) { - if (methodNames.size() == 0) { - return testFilterFlagForClass(className, jUnitVersion); - } else if (methodNames.size() == 1) { - return testFilterFlagForClassAndMethod( - className, methodNames.iterator().next(), jUnitVersion, parameterized); + @Nullable + private static String concatenateMethodNames( + List<String> methodNames, JUnitVersion jUnitVersion) { + if (methodNames.isEmpty()) { + return null; } - String methodNamePattern; - if (jUnitVersion == JUnitVersion.JUNIT_4) { - methodNamePattern = String.format("(%s)", String.join("|", methodNames)); - } else { - methodNamePattern = String.join(",", methodNames); + if (methodNames.size() == 1) { + return methodNames.get(0); } - return testFilterFlagForClassAndMethod( - className, methodNamePattern, jUnitVersion, parameterized); + return jUnitVersion == JUnitVersion.JUNIT_4 + ? String.format("(%s)", String.join("|", methodNames)) + : String.join(",", methodNames); } private BlazeJUnitTestFilterFlags() {}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java index 3eb8ab3..95bc4ee 100644 --- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java +++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
@@ -26,7 +26,6 @@ import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.java.run.RunUtil; -import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags.JUnitVersion; import com.intellij.execution.JavaExecutionUtil; import com.intellij.execution.Location; import com.intellij.execution.actions.ConfigurationContext; @@ -36,7 +35,6 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; -import java.util.List; import java.util.Objects; import org.jetbrains.annotations.NotNull; @@ -90,13 +88,10 @@ ImmutableList.Builder<String> flags = ImmutableList.builder(); - final String qualifiedName = testClass.getQualifiedName(); - if (qualifiedName != null) { - final JUnitVersion jUnitVersion = - JUnitUtil.isJUnit4TestClass(testClass) ? JUnitVersion.JUNIT_4 : JUnitVersion.JUNIT_3; - flags.add(BlazeJUnitTestFilterFlags.testFilterFlagForClass(qualifiedName, jUnitVersion)); + String testFilter = BlazeJUnitTestFilterFlags.testFilterForClass(testClass); + if (testFilter != null) { + flags.add(BlazeFlags.TEST_FILTER + "=" + testFilter); } - flags.add(BlazeFlags.TEST_OUTPUT_STREAMED); flags.addAll(handlerState.getBlazeFlags()); @@ -147,12 +142,7 @@ if (!Objects.equals(handlerState.getCommand(), BlazeCommandName.TEST)) { return false; } - List<String> flags = handlerState.getBlazeFlags(); - - final JUnitVersion jUnitVersion = - JUnitUtil.isJUnit4TestClass(testClass) ? JUnitVersion.JUNIT_4 : JUnitVersion.JUNIT_3; - return flags.contains( - BlazeJUnitTestFilterFlags.testFilterFlagForClass( - testClass.getQualifiedName(), jUnitVersion)); + String filter = BlazeJUnitTestFilterFlags.testFilterForClass(testClass); + return Objects.equals(filter, handlerState.getTestFilterFlag()); } }
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java index 2fc899f..1a7e149 100644 --- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java +++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
@@ -26,18 +26,14 @@ import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; import com.google.idea.blaze.java.run.RunUtil; -import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags.JUnitVersion; import com.intellij.execution.actions.ConfigurationContext; -import com.intellij.execution.junit.JUnitUtil; -import com.intellij.execution.junit2.PsiMemberParameterizedLocation; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; /** Producer for run configurations related to Java test methods in Blaze. */ @@ -152,29 +148,15 @@ return null; } } - - final List<String> methodNames = new ArrayList<>(); - for (PsiMethod method : selectedMethods) { - methodNames.add(method.getName()); - } - // Sort so multiple configurations created with different selection orders are the same. - Collections.sort(methodNames); - - final String qualifiedName = containingClass.getQualifiedName(); - if (qualifiedName == null) { + String testFilter = + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods(containingClass, selectedMethods); + if (testFilter == null) { return null; } - final JUnitVersion jUnitVersion = - JUnitUtil.isJUnit4TestClass(containingClass) ? JUnitVersion.JUNIT_4 : JUnitVersion.JUNIT_3; - boolean parameterized = isParameterized(containingClass); - final String testFilterFlag = - BlazeJUnitTestFilterFlags.testFilterFlagForClassAndMethods( - qualifiedName, methodNames, jUnitVersion, parameterized); - + // Sort so multiple configurations created with different selection orders are the same. + List<String> methodNames = + selectedMethods.stream().map(PsiMethod::getName).sorted().collect(Collectors.toList()); + final String testFilterFlag = BlazeFlags.TEST_FILTER + "=" + testFilter; return new SelectedMethodInfo(firstMethod, containingClass, methodNames, testFilterFlag); } - - private static boolean isParameterized(PsiClass testClass) { - return PsiMemberParameterizedLocation.getParameterizedLocation(testClass, null) != null; - } }
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java index 65151a6..b971b94 100644 --- a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java +++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
@@ -22,6 +22,7 @@ import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.model.BlazeLibrary; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.SyncState; import com.google.idea.blaze.base.model.primitives.LanguageClass; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -56,6 +57,7 @@ import com.google.idea.blaze.java.sync.projectstructure.JavaSourceFolderProvider; import com.google.idea.blaze.java.sync.projectstructure.Jdks; import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet; +import com.google.idea.sdkcompat.transactions.Transactions; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.StdModuleTypes; @@ -64,7 +66,6 @@ import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.openapi.roots.ex.ProjectRootManagerEx; import com.intellij.pom.java.LanguageLevel; -import com.intellij.util.ui.UIUtil; import java.util.Collection; import java.util.Set; import javax.annotation.Nullable; @@ -170,6 +171,7 @@ Project project, BlazeContext context, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, BlazeProjectData blazeProjectData) { if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(WorkspaceType.JAVA)) { return; @@ -212,19 +214,18 @@ private static void setProjectSdkAndLanguageLevel( final Project project, final Sdk sdk, final LanguageLevel javaLanguageLevel) { - UIUtil.invokeAndWaitIfNeeded( - (Runnable) - () -> - ApplicationManager.getApplication() - .runWriteAction( - () -> { - ProjectRootManagerEx rootManager = - ProjectRootManagerEx.getInstanceEx(project); - rootManager.setProjectSdk(sdk); - LanguageLevelProjectExtension ext = - LanguageLevelProjectExtension.getInstance(project); - ext.setLanguageLevel(javaLanguageLevel); - })); + Transactions.submitTransactionAndWait( + () -> + ApplicationManager.getApplication() + .runWriteAction( + () -> { + ProjectRootManagerEx rootManager = + ProjectRootManagerEx.getInstanceEx(project); + rootManager.setProjectSdk(sdk); + LanguageLevelProjectExtension ext = + LanguageLevelProjectExtension.getInstance(project); + ext.setLanguageLevel(javaLanguageLevel); + })); } @Override
diff --git a/java/src/com/google/idea/blaze/java/sync/projectstructure/JavaSourceFolderProvider.java b/java/src/com/google/idea/blaze/java/sync/projectstructure/JavaSourceFolderProvider.java index 5855655..abe5804 100644 --- a/java/src/com/google/idea/blaze/java/sync/projectstructure/JavaSourceFolderProvider.java +++ b/java/src/com/google/idea/blaze/java/sync/projectstructure/JavaSourceFolderProvider.java
@@ -23,6 +23,7 @@ import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData; import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.SourceFolder; import com.intellij.openapi.util.io.FileUtil; @@ -42,6 +43,7 @@ /** Edits source folders in IntelliJ content entries */ public class JavaSourceFolderProvider implements SourceFolderProvider { + private static final Logger logger = Logger.getInstance(JavaSourceFolderProvider.class); private final ImmutableMap<File, BlazeContentEntry> blazeContentEntries; @@ -106,6 +108,7 @@ private static String derivePackagePrefix(VirtualFile file, SourceFolder parentFolder) { String parentPackagePrefix = parentFolder.getPackagePrefix(); + logger.assertTrue(parentFolder.getFile() != null); String relativePath = VfsUtilCore.getRelativePath(file, parentFolder.getFile(), '.'); if (Strings.isNullOrEmpty(relativePath)) { return parentPackagePrefix;
diff --git a/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java index 3f3aede..10e43eb 100644 --- a/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java +++ b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
@@ -29,6 +29,7 @@ import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration; import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType; +import com.google.idea.blaze.base.run.DistributedExecutorSupport; import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandlerProvider; import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider; import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState; @@ -68,6 +69,7 @@ applicationServices.register(TargetFinder.class, new MockTargetFinder()); applicationServices.register(BlazeUserSettings.class, new BlazeUserSettings()); registerExtensionPoint(BuildFlagsProvider.EP_NAME, BuildFlagsProvider.class); + registerExtensionPoint(DistributedExecutorSupport.EP_NAME, DistributedExecutorSupport.class); ExtensionPointImpl<BlazeCommandRunConfigurationHandlerProvider> handlerProviderEp = registerExtensionPoint( BlazeCommandRunConfigurationHandlerProvider.EP_NAME, @@ -87,7 +89,11 @@ handlerState.setBlazeFlags(ImmutableList.of("--flag1", "--flag2")); assertThat( BlazeJavaRunProfileState.getBlazeCommand( - project, configuration, ProjectViewSet.builder().build(), false /* debug */) + project, + configuration, + ProjectViewSet.builder().build(), + ImmutableList.of(), + false /* debug */) .toList()) .isEqualTo( ImmutableList.of( @@ -108,7 +114,11 @@ handlerState.setCommand(BlazeCommandName.fromString("command")); assertThat( BlazeJavaRunProfileState.getBlazeCommand( - project, configuration, ProjectViewSet.builder().build(), true /* debug */) + project, + configuration, + ProjectViewSet.builder().build(), + ImmutableList.of(), + true /* debug */) .toList()) .isEqualTo( ImmutableList.of( @@ -128,7 +138,11 @@ handlerState.setCommand(BlazeCommandName.fromString("command")); assertThat( BlazeJavaRunProfileState.getBlazeCommand( - project, configuration, ProjectViewSet.builder().build(), true /* debug */) + project, + configuration, + ProjectViewSet.builder().build(), + ImmutableList.of(), + true /* debug */) .toList()) .isEqualTo( ImmutableList.of(
diff --git a/java/tests/unittests/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlagsTest.java b/java/tests/unittests/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlagsTest.java new file mode 100644 index 0000000..d5d2cde --- /dev/null +++ b/java/tests/unittests/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlagsTest.java
@@ -0,0 +1,130 @@ +/* + * Copyright 2016 The Bazel Authors. All rights reserved. + * + * 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.google.idea.blaze.java.run.producers; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.idea.blaze.base.BlazeTestCase; +import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags.JUnitVersion; +import com.google.idea.common.experiments.ExperimentService; +import com.google.idea.common.experiments.MockExperimentService; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BlazeJUnitTestFilterFlags}. */ +@RunWith(JUnit4.class) +public class BlazeJUnitTestFilterFlagsTest extends BlazeTestCase { + + @Override + protected void initTest( + @NotNull Container applicationServices, @NotNull Container projectServices) { + ExperimentService experimentService = new MockExperimentService(); + applicationServices.register(ExperimentService.class, experimentService); + } + + @Test + public void testSingleJUnit4ClassFilter() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", ImmutableList.of(), JUnitVersion.JUNIT_4, false)) + .isEqualTo("com.google.idea.ClassName#"); + } + + @Test + public void testSingleJUnit3ClassFilter() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", ImmutableList.of(), JUnitVersion.JUNIT_3, false)) + .isEqualTo("com.google.idea.ClassName"); + } + + @Test + public void testParameterizedIgnoredForSingleClass() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", ImmutableList.of(), JUnitVersion.JUNIT_4, true)) + .isEqualTo("com.google.idea.ClassName#"); + } + + @Test + public void testJUnit4ClassAndSingleMethod() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", + ImmutableList.of("testMethod1"), + JUnitVersion.JUNIT_4, + false)) + .isEqualTo("com.google.idea.ClassName#testMethod1$"); + } + + @Test + public void testJUnit3ClassAndSingleMethod() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", + ImmutableList.of("testMethod1"), + JUnitVersion.JUNIT_3, + false)) + .isEqualTo("com.google.idea.ClassName#testMethod1"); + } + + @Test + public void testJUnit4ClassAndMultipleMethods() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", + ImmutableList.of("testMethod1", "testMethod2"), + JUnitVersion.JUNIT_4, + false)) + .isEqualTo("com.google.idea.ClassName#(testMethod1|testMethod2)$"); + } + + @Test + public void testJUnit4ParametrizedClassAndMultipleMethods() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", + ImmutableList.of("testMethod1", "testMethod2"), + JUnitVersion.JUNIT_4, + true)) + .isEqualTo("com.google.idea.ClassName#(testMethod1|testMethod2)(\\[.+\\])?$"); + } + + @Test + public void testJUnit3ClassAndMultipleMethods() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", + ImmutableList.of("testMethod1", "testMethod2"), + JUnitVersion.JUNIT_3, + false)) + .isEqualTo("com.google.idea.ClassName#testMethod1,testMethod2"); + } + + @Test + public void testParameterizedIgnoredForJUnit3() { + assertThat( + BlazeJUnitTestFilterFlags.testFilterForClassAndMethods( + "com.google.idea.ClassName", + ImmutableList.of("testMethod1", "testMethod2"), + JUnitVersion.JUNIT_3, + true)) + .isEqualTo("com.google.idea.ClassName#testMethod1,testMethod2"); + } +}
diff --git a/plugin_dev/BUILD b/plugin_dev/BUILD index 48eb552..1c56953 100644 --- a/plugin_dev/BUILD +++ b/plugin_dev/BUILD
@@ -9,6 +9,7 @@ "//intellij_platform_sdk:devkit", "//intellij_platform_sdk:plugin_api", "//java", + "//sdkcompat", "@jsr305_annotations//jar", ], )
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java index 00cbccb..a68f89b 100644 --- a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java +++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
@@ -35,6 +35,8 @@ import com.google.idea.blaze.base.projectview.ProjectViewSet; import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; import com.google.idea.blaze.base.run.BlazeRunConfiguration; +import com.google.idea.blaze.base.run.state.RunConfigurationFlagsState; +import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor; import com.google.idea.blaze.base.run.targetfinder.TargetFinder; import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager; @@ -47,6 +49,7 @@ import com.intellij.execution.configurations.JavaCommandLineState; import com.intellij.execution.configurations.JavaParameters; import com.intellij.execution.configurations.LocatableConfigurationBase; +import com.intellij.execution.configurations.LogFileOptions; import com.intellij.execution.configurations.ModuleRunConfiguration; import com.intellij.execution.configurations.ParametersList; import com.intellij.execution.configurations.RunProfileState; @@ -75,13 +78,15 @@ import com.intellij.openapi.util.WriteExternalException; import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.RawCommandLineEditor; +import com.intellij.ui.components.JBCheckBox; import com.intellij.util.PlatformUtils; -import com.intellij.util.execution.ParametersListUtil; import java.awt.BorderLayout; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Nullable; @@ -89,7 +94,6 @@ import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; -import javax.swing.JTextArea; import org.jdom.Element; /** @@ -105,16 +109,20 @@ private static final String SDK_ATTR = "blaze-plugin-sdk"; private static final String VM_PARAMS_ATTR = "blaze-vm-params"; private static final String PROGRAM_PARAMS_ATTR = "blaze-program-params"; + private static final String KEEP_IN_SYNC_TAG = "keep-in-sync"; private final String buildSystem; @Nullable private Label target; - private ImmutableList<String> blazeFlags = ImmutableList.of(); - private ImmutableList<String> exeFlags = ImmutableList.of(); + private final RunConfigurationFlagsState blazeFlags; + private final RunConfigurationFlagsState exeFlags; @Nullable private Sdk pluginSdk; @Nullable String vmParameters; @Nullable private String programParameters; + // for keeping imported configurations in sync with their source XML + @Nullable private Boolean keepInSync = null; + public BlazeIntellijPluginConfiguration( Project project, ConfigurationFactory factory, @@ -129,6 +137,19 @@ if (initialTarget != null) { target = initialTarget.key.label; } + blazeFlags = new RunConfigurationFlagsState(USER_BLAZE_FLAG_TAG, buildSystem + " flags:"); + exeFlags = new RunConfigurationFlagsState(USER_EXE_FLAG_TAG, "Executable flags:"); + } + + @Override + public void setKeepInSync(@Nullable Boolean keepInSync) { + this.keepInSync = keepInSync; + } + + @Override + @Nullable + public Boolean getKeepInSync() { + return keepInSync; } @Override @@ -147,6 +168,19 @@ } } + @Override + public ArrayList<LogFileOptions> getAllLogFiles() { + ArrayList<LogFileOptions> result = new ArrayList<>(); + if (pluginSdk == null) { + return result; + } + String sandboxHome = IdeaJdkHelper.getSandboxHome(pluginSdk); + String logFile = Paths.get(sandboxHome, "system", "log", "idea.log").toString(); + LogFileOptions logFileOptions = new LogFileOptions("idea.log", logFile, true, true, true); + result.add(logFileOptions); + return result; + } + private ImmutableList<File> findPluginJars() throws ExecutionException { BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData(); @@ -353,8 +387,8 @@ } else { target = null; } - blazeFlags = loadUserFlags(element, USER_BLAZE_FLAG_TAG); - exeFlags = loadUserFlags(element, USER_EXE_FLAG_TAG); + blazeFlags.readExternal(element); + exeFlags.readExternal(element); String sdkName = element.getAttributeValue(SDK_ATTR); if (!Strings.isNullOrEmpty(sdkName)) { @@ -362,25 +396,9 @@ } vmParameters = Strings.emptyToNull(element.getAttributeValue(VM_PARAMS_ATTR)); programParameters = Strings.emptyToNull(element.getAttributeValue(PROGRAM_PARAMS_ATTR)); - } - private static ImmutableList<String> loadUserFlags(Element root, String tag) { - ImmutableList.Builder<String> flagsBuilder = ImmutableList.builder(); - for (Element e : root.getChildren(tag)) { - String flag = e.getTextTrim(); - if (flag != null && !flag.isEmpty()) { - flagsBuilder.add(flag); - } - } - return flagsBuilder.build(); - } - - private static void saveUserFlags(Element root, List<String> flags, String tag) { - for (String flag : flags) { - Element child = new Element(tag); - child.setText(flag); - root.addContent(child); - } + String keepInSyncString = element.getAttributeValue(KEEP_IN_SYNC_TAG); + keepInSync = keepInSyncString != null ? Boolean.parseBoolean(keepInSyncString) : null; } @Override @@ -392,8 +410,8 @@ targetElement.setText(target.toString()); element.addContent(targetElement); } - saveUserFlags(element, blazeFlags, USER_BLAZE_FLAG_TAG); - saveUserFlags(element, exeFlags, USER_EXE_FLAG_TAG); + blazeFlags.writeExternal(element); + exeFlags.writeExternal(element); if (pluginSdk != null) { element.setAttribute(SDK_ATTR, pluginSdk.getName()); } @@ -403,6 +421,9 @@ if (programParameters != null) { element.setAttribute(PROGRAM_PARAMS_ATTR, programParameters); } + if (keepInSync != null) { + element.setAttribute(KEEP_IN_SYNC_TAG, Boolean.toString(keepInSync)); + } } @Override @@ -410,11 +431,12 @@ final BlazeIntellijPluginConfiguration configuration = (BlazeIntellijPluginConfiguration) super.clone(); configuration.target = target; - configuration.blazeFlags = blazeFlags; - configuration.exeFlags = exeFlags; + configuration.blazeFlags.setFlags(blazeFlags.getFlags()); + configuration.exeFlags.setFlags(exeFlags.getFlags()); configuration.pluginSdk = pluginSdk; configuration.vmParameters = vmParameters; configuration.programParameters = programParameters; + configuration.keepInSync = keepInSync; return configuration; } @@ -423,8 +445,8 @@ BlazeCommand.builder(Blaze.getBuildSystem(getProject()), BlazeCommandName.BUILD) .addTargets(getTarget()) .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)) - .addBlazeFlags(blazeFlags) - .addExeFlags(exeFlags); + .addBlazeFlags(blazeFlags.getFlags()) + .addExeFlags(exeFlags.getFlags()); return command.build(); } @@ -436,7 +458,8 @@ for (TargetIdeInfo target : javaTargets) { javaLabels.add(target.key.label); } - return new BlazeIntellijPluginConfigurationSettingsEditor(buildSystem, javaLabels); + return new BlazeIntellijPluginConfigurationSettingsEditor( + javaLabels, blazeFlags.getEditor(getProject()), exeFlags.getEditor(getProject())); } @Override @@ -456,21 +479,23 @@ @VisibleForTesting static class BlazeIntellijPluginConfigurationSettingsEditor extends SettingsEditor<BlazeIntellijPluginConfiguration> { - private final String buildSystemName; - private final ComboBox targetCombo; - private final JTextArea blazeFlagsField = new JTextArea(5, 0); - private final JTextArea exeFlagsField = new JTextArea(5, 0); + private final ComboBox<Label> targetCombo; + private final RunConfigurationStateEditor blazeFlagsEditor; + private final RunConfigurationStateEditor exeFlagsEditor; private final JdkComboBox sdkCombo; private final LabeledComponent<RawCommandLineEditor> vmParameters = new LabeledComponent<>(); private final LabeledComponent<RawCommandLineEditor> programParameters = new LabeledComponent<>(); + private final JBCheckBox keepInSyncCheckBox; public BlazeIntellijPluginConfigurationSettingsEditor( - String buildSystemName, List<Label> javaLabels) { - this.buildSystemName = buildSystemName; + List<Label> javaLabels, + RunConfigurationStateEditor blazeFlagsEditor, + RunConfigurationStateEditor exeFlagsEditor) { targetCombo = - new ComboBox( - new DefaultComboBoxModel(Ordering.usingToString().sortedCopy(javaLabels).toArray())); + new ComboBox<>( + new DefaultComboBoxModel<>( + Ordering.usingToString().sortedCopy(javaLabels).toArray(new Label[0]))); targetCombo.setRenderer( new ListCellRendererWrapper<Label>() { @Override @@ -479,18 +504,35 @@ setText(value == null ? null : value.toString()); } }); - + this.blazeFlagsEditor = blazeFlagsEditor; + this.exeFlagsEditor = exeFlagsEditor; ProjectSdksModel sdksModel = new ProjectSdksModel(); sdksModel.reset(null); sdkCombo = new JdkComboBox(sdksModel, IdeaJdkHelper::isIdeaJdkType); + + keepInSyncCheckBox = new JBCheckBox("Keep in sync with source XML"); + keepInSyncCheckBox.addItemListener(e -> updateEnabledStatus()); + } + + private void updateEnabledStatus() { + setEnabled(!keepInSyncCheckBox.isVisible() || !keepInSyncCheckBox.isSelected()); + } + + private void setEnabled(boolean enabled) { + targetCombo.setEnabled(enabled); + sdkCombo.setEnabled(enabled); + vmParameters.getComponent().setEnabled(enabled); + programParameters.getComponent().setEnabled(enabled); + blazeFlagsEditor.setComponentEnabled(enabled); + exeFlagsEditor.setComponentEnabled(enabled); } @VisibleForTesting @Override public void resetEditorFrom(BlazeIntellijPluginConfiguration s) { targetCombo.setSelectedItem(s.getTarget()); - blazeFlagsField.setText(ParametersListUtil.join(s.blazeFlags)); - exeFlagsField.setText(ParametersListUtil.join(s.exeFlags)); + blazeFlagsEditor.resetEditorFrom(s.blazeFlags); + exeFlagsEditor.resetEditorFrom(s.exeFlags); if (s.pluginSdk != null) { sdkCombo.setSelectedJdk(s.pluginSdk); } else { @@ -502,6 +544,10 @@ if (s.programParameters != null) { programParameters.getComponent().setText(s.programParameters); } + keepInSyncCheckBox.setVisible(s.keepInSync != null); + if (s.keepInSync != null) { + keepInSyncCheckBox.setSelected(s.keepInSync); + } } @VisibleForTesting @@ -512,15 +558,12 @@ } catch (ClassCastException e) { throw new ConfigurationException("Invalid label specified."); } - s.blazeFlags = - ImmutableList.copyOf( - ParametersListUtil.parse(Strings.nullToEmpty(blazeFlagsField.getText()))); - s.exeFlags = - ImmutableList.copyOf( - ParametersListUtil.parse(Strings.nullToEmpty(exeFlagsField.getText()))); + blazeFlagsEditor.applyEditorTo(s.blazeFlags); + exeFlagsEditor.applyEditorTo(s.exeFlags); s.pluginSdk = sdkCombo.getSelectedJdk(); s.vmParameters = vmParameters.getComponent().getText(); s.programParameters = programParameters.getComponent().getText(); + s.keepInSync = keepInSyncCheckBox.isVisible() ? keepInSyncCheckBox.isSelected() : null; } @Override @@ -544,10 +587,9 @@ vmParameters.getComponent(), programParameters.getLabel(), programParameters.getComponent(), - new JLabel(buildSystemName + " flags:"), - blazeFlagsField, - new JLabel("Executable flags:"), - exeFlagsField); + blazeFlagsEditor.createComponent(), + exeFlagsEditor.createComponent(), + keepInSyncCheckBox); } } }
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java index 34b06df..e2eeb8e 100644 --- a/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java +++ b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.idea.blaze.base.model.BlazeProjectData; +import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.LanguageClass; import com.google.idea.blaze.base.model.primitives.WorkspaceType; import com.google.idea.blaze.base.projectview.ProjectViewSet; @@ -27,13 +28,13 @@ import com.google.idea.blaze.java.sync.JavaLanguageLevelHelper; import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData; import com.google.idea.blaze.java.sync.projectstructure.JavaSourceFolderProvider; +import com.google.idea.sdkcompat.transactions.Transactions; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.pom.java.LanguageLevel; -import com.intellij.util.ui.UIUtil; import java.util.Set; import javax.annotation.Nullable; @@ -79,6 +80,7 @@ Project project, BlazeContext context, ProjectViewSet projectViewSet, + BlazeVersionData blazeVersionData, BlazeProjectData blazeProjectData) { if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType( WorkspaceType.INTELLIJ_PLUGIN)) { @@ -90,15 +92,14 @@ projectViewSet, blazeProjectData, LanguageLevel.JDK_1_7); // Leave the SDK, but set the language level - UIUtil.invokeAndWaitIfNeeded( - (Runnable) - () -> - ApplicationManager.getApplication() - .runWriteAction( - () -> { - LanguageLevelProjectExtension ext = - LanguageLevelProjectExtension.getInstance(project); - ext.setLanguageLevel(javaLanguageLevel); - })); + Transactions.submitTransactionAndWait( + () -> + ApplicationManager.getApplication() + .runWriteAction( + () -> { + LanguageLevelProjectExtension ext = + LanguageLevelProjectExtension.getInstance(project); + ext.setLanguageLevel(javaLanguageLevel); + })); } }
diff --git a/proto_deps/proto_deps.jar b/proto_deps/proto_deps.jar index 1a140af..6f79519 100755 --- a/proto_deps/proto_deps.jar +++ b/proto_deps/proto_deps.jar Binary files differ
diff --git a/sdkcompat/BUILD b/sdkcompat/BUILD new file mode 100644 index 0000000..ce19ce5 --- /dev/null +++ b/sdkcompat/BUILD
@@ -0,0 +1,25 @@ +# Description: Indirections for SDK changes to the underlying platform library. + +licenses(["notice"]) # Apache 2.0 + +load("//intellij_platform_sdk:build_defs.bzl", "select_for_plugin_api") + +java_library( + name = "sdkcompat", + srcs = select_for_plugin_api({ + "android-studio-145.1617.8": glob(["v145/**"]), + "android-studio-2.3.0.3": glob(["v162/**"]), + "android-studio-2.3.0.4": glob(["v162/**"]), + "intellij-2016.3.1": glob(["v163/**"]), + "intellij-162.2032.8": glob(["v162/**"]), + "clion-162.1967.7": glob( + ["v162/**"], + exclude = ["v162/com/google/idea/sdkcompat/debugger/**"], + ), + }), + visibility = ["//visibility:public"], + deps = [ + "//intellij_platform_sdk:plugin_api", + "@jsr305_annotations//jar", + ], +)
diff --git a/sdkcompat/v145/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java b/sdkcompat/v145/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java new file mode 100644 index 0000000..6051cc9 --- /dev/null +++ b/sdkcompat/v145/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java
@@ -0,0 +1,6 @@ +package com.google.idea.sdkcompat.codestyle; + +import com.intellij.psi.codeStyle.CodeStyleManager; + +/** Adapter to extend two bridge different IntelliJ SDK versions. */ +public abstract class CodeStyleManagerSdkCompatAdapter extends CodeStyleManager {}
diff --git a/sdkcompat/v145/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java b/sdkcompat/v145/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java new file mode 100644 index 0000000..6dfc995 --- /dev/null +++ b/sdkcompat/v145/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java
@@ -0,0 +1,25 @@ +package com.google.idea.sdkcompat.debugger; + +import com.intellij.debugger.impl.GenericDebuggerRunner; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RemoteConnection; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.RunContentDescriptor; +import javax.annotation.Nullable; + +/** SDK compatibility for {@link GenericDebuggerRunner}. */ +public class GenericDebuggerRunnerSdkCompatAdapter extends GenericDebuggerRunner { + + @Nullable + protected RunContentDescriptor attachVirtualMachine( + RunProfileState state, + ExecutionEnvironment env, + RemoteConnection connection, + long pollTimeout) + throws ExecutionException { + // no timeout available until 2016.2 onwards + return super.attachVirtualMachine( + state, env, connection, pollTimeout != 0 /* pollConnection */); + } +}
diff --git a/sdkcompat/v145/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java b/sdkcompat/v145/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java new file mode 100644 index 0000000..c4bd9a7 --- /dev/null +++ b/sdkcompat/v145/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
@@ -0,0 +1,13 @@ +package com.google.idea.sdkcompat.smrunner; + +import com.intellij.execution.testframework.sm.runner.events.TestFailedEvent; +import javax.annotation.Nullable; + +/** Handles SM-runner methods which have changed between our supported versions. */ +public class SmRunnerCompatUtils { + + public static TestFailedEvent getTestFailedEvent( + String name, @Nullable String message, @Nullable String content, long duration) { + return new TestFailedEvent(name, -1, message, content, true, null, null, null, duration); + } +}
diff --git a/sdkcompat/v145/com/google/idea/sdkcompat/transactions/Transactions.java b/sdkcompat/v145/com/google/idea/sdkcompat/transactions/Transactions.java new file mode 100644 index 0000000..94ab905 --- /dev/null +++ b/sdkcompat/v145/com/google/idea/sdkcompat/transactions/Transactions.java
@@ -0,0 +1,16 @@ +package com.google.idea.sdkcompat.transactions; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; + +/** Created by tomlu on 12/22/16. */ +public class Transactions { + public static void submitTransactionAndWait(Runnable runnable) { + ApplicationManager.getApplication().invokeAndWait(runnable, ModalityState.any()); + } + + public static void submitTransaction(Disposable disposable, Runnable runnable) { + ApplicationManager.getApplication().invokeLater(runnable); + } +}
diff --git a/sdkcompat/v145/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java b/sdkcompat/v145/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java new file mode 100644 index 0000000..df055ea --- /dev/null +++ b/sdkcompat/v145/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java
@@ -0,0 +1,12 @@ +package com.google.idea.sdkcompat.vcs; + +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.util.continuation.ContinuationPause; + +/** SDK adapter for change list interface. */ +public abstract class ChangeListManagerSdkCompatAdapter extends ChangeListManager { + @Override + public void freeze(ContinuationPause context, String reason) { + throw new UnsupportedOperationException("ChangeListManager#freeze()"); + } +}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java new file mode 100644 index 0000000..6051cc9 --- /dev/null +++ b/sdkcompat/v162/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java
@@ -0,0 +1,6 @@ +package com.google.idea.sdkcompat.codestyle; + +import com.intellij.psi.codeStyle.CodeStyleManager; + +/** Adapter to extend two bridge different IntelliJ SDK versions. */ +public abstract class CodeStyleManagerSdkCompatAdapter extends CodeStyleManager {}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java new file mode 100644 index 0000000..ed822b3 --- /dev/null +++ b/sdkcompat/v162/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java
@@ -0,0 +1,6 @@ +package com.google.idea.sdkcompat.debugger; + +import com.intellij.debugger.impl.GenericDebuggerRunner; + +/** SDK compatibility for {@link GenericDebuggerRunner}. */ +public class GenericDebuggerRunnerSdkCompatAdapter extends GenericDebuggerRunner {}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java b/sdkcompat/v162/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java new file mode 100644 index 0000000..1e9b474 --- /dev/null +++ b/sdkcompat/v162/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
@@ -0,0 +1,13 @@ +package com.google.idea.sdkcompat.smrunner; + +import com.intellij.execution.testframework.sm.runner.events.TestFailedEvent; +import javax.annotation.Nullable; + +/** Handles SM-runner methods which have changed between our supported versions. */ +public class SmRunnerCompatUtils { + + public static TestFailedEvent getTestFailedEvent( + String name, @Nullable String message, @Nullable String content, long duration) { + return new TestFailedEvent(name, null, message, content, true, null, null, null, duration); + } +}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java b/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java new file mode 100644 index 0000000..94ab905 --- /dev/null +++ b/sdkcompat/v162/com/google/idea/sdkcompat/transactions/Transactions.java
@@ -0,0 +1,16 @@ +package com.google.idea.sdkcompat.transactions; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; + +/** Created by tomlu on 12/22/16. */ +public class Transactions { + public static void submitTransactionAndWait(Runnable runnable) { + ApplicationManager.getApplication().invokeAndWait(runnable, ModalityState.any()); + } + + public static void submitTransaction(Disposable disposable, Runnable runnable) { + ApplicationManager.getApplication().invokeLater(runnable); + } +}
diff --git a/sdkcompat/v162/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java b/sdkcompat/v162/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java new file mode 100644 index 0000000..df055ea --- /dev/null +++ b/sdkcompat/v162/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java
@@ -0,0 +1,12 @@ +package com.google.idea.sdkcompat.vcs; + +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.util.continuation.ContinuationPause; + +/** SDK adapter for change list interface. */ +public abstract class ChangeListManagerSdkCompatAdapter extends ChangeListManager { + @Override + public void freeze(ContinuationPause context, String reason) { + throw new UnsupportedOperationException("ChangeListManager#freeze()"); + } +}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java new file mode 100644 index 0000000..33d3ad4 --- /dev/null +++ b/sdkcompat/v163/com/google/idea/sdkcompat/codestyle/CodeStyleManagerSdkCompatAdapter.java
@@ -0,0 +1,22 @@ +package com.google.idea.sdkcompat.codestyle; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.ChangedRangesInfo; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.util.IncorrectOperationException; +import java.util.ArrayList; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** Adapter to extend two bridge different IntelliJ SDK versions. */ +public abstract class CodeStyleManagerSdkCompatAdapter extends CodeStyleManager { + @Override + public void reformatTextWithContext(@NotNull PsiFile file, @NotNull ChangedRangesInfo info) + throws IncorrectOperationException { + List<TextRange> ranges = new ArrayList<>(); + ranges.addAll(info.insertedRanges); + ranges.addAll(info.allChangedRanges); + this.reformatTextWithContext(file, ranges); + } +}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java new file mode 100644 index 0000000..ed822b3 --- /dev/null +++ b/sdkcompat/v163/com/google/idea/sdkcompat/debugger/GenericDebuggerRunnerSdkCompatAdapter.java
@@ -0,0 +1,6 @@ +package com.google.idea.sdkcompat.debugger; + +import com.intellij.debugger.impl.GenericDebuggerRunner; + +/** SDK compatibility for {@link GenericDebuggerRunner}. */ +public class GenericDebuggerRunnerSdkCompatAdapter extends GenericDebuggerRunner {}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java b/sdkcompat/v163/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java new file mode 100644 index 0000000..1e9b474 --- /dev/null +++ b/sdkcompat/v163/com/google/idea/sdkcompat/smrunner/SmRunnerCompatUtils.java
@@ -0,0 +1,13 @@ +package com.google.idea.sdkcompat.smrunner; + +import com.intellij.execution.testframework.sm.runner.events.TestFailedEvent; +import javax.annotation.Nullable; + +/** Handles SM-runner methods which have changed between our supported versions. */ +public class SmRunnerCompatUtils { + + public static TestFailedEvent getTestFailedEvent( + String name, @Nullable String message, @Nullable String content, long duration) { + return new TestFailedEvent(name, null, message, content, true, null, null, null, duration); + } +}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/transactions/Transactions.java b/sdkcompat/v163/com/google/idea/sdkcompat/transactions/Transactions.java new file mode 100644 index 0000000..8862aa8 --- /dev/null +++ b/sdkcompat/v163/com/google/idea/sdkcompat/transactions/Transactions.java
@@ -0,0 +1,15 @@ +package com.google.idea.sdkcompat.transactions; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.TransactionGuard; + +/** SDK adapter to use transaction guards. */ +public class Transactions { + public static void submitTransactionAndWait(Runnable runnable) { + TransactionGuard.getInstance().submitTransactionAndWait(runnable); + } + + public static void submitTransaction(Disposable disposable, Runnable runnable) { + TransactionGuard.submitTransaction(disposable, runnable); + } +}
diff --git a/sdkcompat/v163/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java b/sdkcompat/v163/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java new file mode 100644 index 0000000..1e525c1 --- /dev/null +++ b/sdkcompat/v163/com/google/idea/sdkcompat/vcs/ChangeListManagerSdkCompatAdapter.java
@@ -0,0 +1,6 @@ +package com.google.idea.sdkcompat.vcs; + +import com.intellij.openapi.vcs.changes.ChangeListManager; + +/** SDK adapter to for changelist interface. */ +public abstract class ChangeListManagerSdkCompatAdapter extends ChangeListManager {}
diff --git a/version.bzl b/version.bzl index 0f144e5..1bcca22 100644 --- a/version.bzl +++ b/version.bzl
@@ -1,3 +1,3 @@ """Version of the blaze plugin.""" -VERSION = "2016.12.05.6" +VERSION = "2017.01.09.1"