diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 831984cac9..2f1a9602a7 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -927,6 +927,9 @@ + diff --git a/resources/META-INF/plugin_template.xml b/resources/META-INF/plugin_template.xml index 7000ec86e1..3c56175113 100644 --- a/resources/META-INF/plugin_template.xml +++ b/resources/META-INF/plugin_template.xml @@ -290,6 +290,9 @@ + diff --git a/src/io/flutter/FlutterInitializer.java b/src/io/flutter/FlutterInitializer.java index 2cdcccb1ac..0b9f6f65bd 100644 --- a/src/io/flutter/FlutterInitializer.java +++ b/src/io/flutter/FlutterInitializer.java @@ -23,6 +23,7 @@ import io.flutter.analytics.Analytics; import io.flutter.analytics.ToolWindowTracker; import io.flutter.android.IntelliJAndroidSdk; +import io.flutter.devtools.WebDevManager; import io.flutter.editor.FlutterSaveActionsManager; import io.flutter.perf.FlutterWidgetPerfManager; import io.flutter.pub.PubRoot; @@ -32,6 +33,8 @@ import io.flutter.run.daemon.DeviceService; import io.flutter.samples.FlutterSampleManager; import io.flutter.sdk.FlutterPluginsLibraryManager; +import io.flutter.sdk.FlutterSdk; +import io.flutter.sdk.FlutterSdkManager; import io.flutter.settings.FlutterSettings; import io.flutter.utils.FlutterModuleUtils; import io.flutter.view.FlutterPerfViewFactory; @@ -82,14 +85,19 @@ public void runActivity(@NotNull Project project) { // If the project declares a Flutter dependency, do some extra initialization. boolean hasFlutterModule = false; + boolean hasFlutterWebModule = false; for (Module module : ModuleManager.getInstance(project).getModules()) { - if (!FlutterModuleUtils.declaresFlutter(module)) { + final boolean declaresFlutter = FlutterModuleUtils.declaresFlutter(module); + final boolean declaresFlutterWeb = FlutterModuleUtils.declaresFlutterWeb(module); + + hasFlutterModule = hasFlutterModule || declaresFlutter; + hasFlutterWebModule = hasFlutterWebModule || declaresFlutterWeb; + + if (!declaresFlutter && !declaresFlutterWeb) { continue; } - hasFlutterModule = true; - // Ensure SDKs are configured; needed for clean module import. FlutterModuleUtils.enableDartSDK(module); @@ -119,6 +127,11 @@ public void runActivity(@NotNull Project project) { performAndroidStudioCanaryCheck(); } + // Check if the project is a flutter web project; if so, install webdev. + if (hasFlutterWebModule) { + installWebDev(project); + } + FlutterRunNotifications.init(project); // Start the widget perf manager. @@ -188,6 +201,38 @@ public void actionPerformed(@NotNull AnActionEvent event) { } } + private void installWebDev(@NotNull Project project) { + final FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(project); + + final WebDevManager webDevManager = WebDevManager.getInstance(project); + + if (flutterSdk != null) { + if (!webDevManager.hasInstalledWebDev()) { + webDevManager.installWebdev(); + } + } + else { + // Listen for sdk changes; on a valid flutter sdk, attempt to install webdev. + FlutterSdkManager.getInstance(project).addListener(new FlutterSdkManager.Listener() { + boolean installAttempted = false; + + @Override + public void flutterSdkAdded() { + final FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(project); + if (flutterSdk == null) { + return; + } + + if (!installAttempted && !webDevManager.hasInstalledWebDev()) { + installAttempted = true; + + webDevManager.installWebdev(); + } + } + }); + } + } + /** * Automatically set Android SDK based on ANDROID_HOME. */ diff --git a/src/io/flutter/devtools/DevToolsManager.java b/src/io/flutter/devtools/DevToolsManager.java index e58864d20d..ca5525635b 100644 --- a/src/io/flutter/devtools/DevToolsManager.java +++ b/src/io/flutter/devtools/DevToolsManager.java @@ -12,6 +12,7 @@ import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessOutput; import com.intellij.ide.browsers.BrowserLauncher; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.progress.ProgressIndicator; @@ -19,6 +20,7 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; +import io.flutter.console.FlutterConsoles; import io.flutter.pub.PubRoot; import io.flutter.pub.PubRoots; import io.flutter.sdk.FlutterCommand; @@ -69,34 +71,40 @@ public CompletableFuture installDevTools() { final ProgressManager progressManager = ProgressManager.getInstance(); progressManager.run(new Task.Backgroundable(project, "Installing DevTools...", true) { - OSProcessHandler processHandler; + Process process; @Override public void run(@NotNull ProgressIndicator indicator) { indicator.setText(getTitle()); + indicator.setIndeterminate(true); - processHandler = command.startInConsole(project); + process = command.start((ProcessOutput output) -> { + if (output.getExitCode() != 0) { + final String message = (output.getStdout() + "\n" + output.getStderr()).trim(); + FlutterConsoles.displayMessage(project, null, message, true); + } + }, null); try { - final boolean value = processHandler.waitFor(); - if (value) { + final int resultCode = process.waitFor(); + if (resultCode == 0) { installedDevTools = true; } - result.complete(value); + result.complete(resultCode == 0); } - catch (RuntimeException re) { + catch (RuntimeException | InterruptedException re) { if (!result.isDone()) { result.complete(false); } } - processHandler = null; + process = null; } @Override public void onCancel() { - if (processHandler != null && !processHandler.isProcessTerminated()) { - processHandler.destroyProcess(); + if (process != null && process.isAlive()) { + process.destroy(); if (!result.isDone()) { result.complete(false); } @@ -158,6 +166,9 @@ public static void startServer( Callback onClose ) { final FlutterCommand command = sdk.flutterPackagesPub(pubRoot, "global", "run", "devtools", "--machine", "--port=0"); + + // TODO(devoncarew): Refactor this so that we don't use the console to display output - this steals + // focus away from the Run (or Debug) view. final OSProcessHandler processHandler = command.startInConsole(project); if (processHandler == null) { @@ -170,7 +181,7 @@ public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType final String text = event.getText().trim(); if (text.startsWith("{") && text.endsWith("}")) { - // {"method":"server.started","params":{"host":"127.0.0.1","port":9100}} + // {"event":"server.started","params":{"host":"127.0.0.1","port":9100}} try { final JsonParser jsonParser = new JsonParser(); diff --git a/src/io/flutter/devtools/WebDevManager.java b/src/io/flutter/devtools/WebDevManager.java new file mode 100644 index 0000000000..c337f1d2b4 --- /dev/null +++ b/src/io/flutter/devtools/WebDevManager.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.devtools; + +import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import io.flutter.console.FlutterConsoles; +import io.flutter.pub.PubRoot; +import io.flutter.pub.PubRoots; +import io.flutter.sdk.FlutterCommand; +import io.flutter.sdk.FlutterSdk; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Manage installing the webdev cli. + */ +public class WebDevManager { + public static WebDevManager getInstance(@NotNull Project project) { + return ServiceManager.getService(project, WebDevManager.class); + } + + @NotNull private final Project project; + + private boolean installedWebdev = false; + + private WebDevManager(@NotNull Project project) { + this.project = project; + } + + public boolean hasInstalledWebDev() { + return installedWebdev; + } + + public CompletableFuture installWebdev() { + final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + if (sdk == null) { + return createCompletedFuture(false); + } + + final List pubRoots = PubRoots.forProject(project); + if (pubRoots.isEmpty()) { + return createCompletedFuture(false); + } + + final CompletableFuture result = new CompletableFuture<>(); + final FlutterCommand command = sdk.flutterPackagesPub(pubRoots.get(0), "global", "activate", "webdev"); + + final ProgressManager progressManager = ProgressManager.getInstance(); + //noinspection DialogTitleCapitalization + progressManager.run(new Task.Backgroundable(project, "Installing webdev...", true) { + Process process; + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setText(getTitle()); + indicator.setIndeterminate(true); + + process = command.start((ProcessOutput output) -> { + if (output.getExitCode() != 0) { + final String message = (output.getStdout() + "\n" + output.getStderr()).trim(); + FlutterConsoles.displayMessage(project, null, message, true); + } + }, null); + + try { + final int resultCode = process.waitFor(); + if (resultCode == 0) { + installedWebdev = true; + } + result.complete(resultCode == 0); + } + catch (RuntimeException | InterruptedException re) { + if (!result.isDone()) { + result.complete(false); + } + } + + process = null; + } + + @Override + public void onCancel() { + if (process != null && process.isAlive()) { + process.destroy(); + if (!result.isDone()) { + result.complete(false); + } + } + } + }); + + return result; + } + + private CompletableFuture createCompletedFuture(boolean value) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(value); + return result; + } +} diff --git a/src/io/flutter/pub/PubRoot.java b/src/io/flutter/pub/PubRoot.java index 227b499dcd..3f269b5a14 100644 --- a/src/io/flutter/pub/PubRoot.java +++ b/src/io/flutter/pub/PubRoot.java @@ -241,6 +241,13 @@ public boolean declaresFlutter() { return FlutterUtils.declaresFlutter(pubspec); } + /** + * Returns true if the pubspec declares a flutter web dependency. + */ + public boolean declaresFlutterWeb() { + return FlutterUtils.declaresFlutterWeb(pubspec); + } + /** * Returns true if the pubspec indicates that it is a Flutter plugin. */ diff --git a/src/io/flutter/run/SdkFields.java b/src/io/flutter/run/SdkFields.java index 4fb4322bc9..c46c89b333 100644 --- a/src/io/flutter/run/SdkFields.java +++ b/src/io/flutter/run/SdkFields.java @@ -125,13 +125,17 @@ public GeneralCommandLine createFlutterSdkRunCommand(@NotNull Project project, throw new ExecutionException("Entrypoint isn't within a Flutter pub root"); } + final FlutterCommand command; String[] args = additionalArgs == null ? new String[]{ } : additionalArgs.split(" "); - if (buildFlavor != null) { - args = ArrayUtil.append(args, "--flavor=" + buildFlavor); + if (FlutterUtils.declaresFlutterWeb(root.getPubspec())) { + command = flutterSdk.flutterRunWeb(root, runMode, args); + } + else { + if (buildFlavor != null) { + args = ArrayUtil.append(args, "--flavor=" + buildFlavor); + } + command = flutterSdk.flutterRun(root, main.getFile(), device, runMode, flutterLaunchMode, project, args); } - final FlutterCommand command = FlutterUtils.declaresFlutterWeb(root.getPubspec()) ? - flutterSdk.flutterRunWeb(root, runMode) : - flutterSdk.flutterRun(root, main.getFile(), device, runMode, flutterLaunchMode, project, args); return command.createGeneralCommandLine(project); } diff --git a/src/io/flutter/sdk/FlutterSdk.java b/src/io/flutter/sdk/FlutterSdk.java index aa99e1edbe..e7bd8b5977 100644 --- a/src/io/flutter/sdk/FlutterSdk.java +++ b/src/io/flutter/sdk/FlutterSdk.java @@ -277,15 +277,13 @@ else if (flutterLaunchMode == FlutterLaunchMode.RELEASE) { return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.ATTACH, args.toArray(new String[]{ })); } - public FlutterCommand flutterRunWeb(@NotNull PubRoot root, @NotNull RunMode mode) { - // TODO(devoncarew): We need to provision the webdev cli here. - + public FlutterCommand flutterRunWeb(@NotNull PubRoot root, @NotNull RunMode mode, String... additionalArgs) { // TODO(jwren): After debugging is supported by webdev, this should be modified to check for debug and add // any additional needed flags: i.e. if (mode == RunMode.DEBUG) { args.add("--debug"); } // See https://github.com/flutter/flutter-intellij/issues/3349. // flutter packages pub global run webdev daemon - return new FlutterWebCommand(this, root.getRoot(), FlutterCommand.Type.FLUTTER_WEB_RUN, new String[]{ }); + return new FlutterWebCommand(this, root.getRoot(), FlutterCommand.Type.FLUTTER_WEB_RUN, additionalArgs); } public FlutterCommand flutterRunOnTester(@NotNull PubRoot root, @NotNull String mainPath) { diff --git a/src/io/flutter/sdk/FlutterWebCommand.java b/src/io/flutter/sdk/FlutterWebCommand.java index b8db7645ac..c2d76c1bd3 100644 --- a/src/io/flutter/sdk/FlutterWebCommand.java +++ b/src/io/flutter/sdk/FlutterWebCommand.java @@ -32,7 +32,6 @@ public GeneralCommandLine createGeneralCommandLine(@Nullable Project project) { line.setExePath(FileUtil.toSystemDependentName(sdk.getHomePath() + "/bin/" + FlutterSdkUtil.flutterScriptName())); // flutter packages pub line.addParameters(Type.PACKAGES_PUB.subCommand); - // TODO(devoncarew): We need to provision webdev locally. line.addParameters("global", "run", "webdev", "daemon"); line.addParameters(args); return line; diff --git a/src/io/flutter/utils/FlutterModuleUtils.java b/src/io/flutter/utils/FlutterModuleUtils.java index 1b896144a2..f7f3c472d8 100644 --- a/src/io/flutter/utils/FlutterModuleUtils.java +++ b/src/io/flutter/utils/FlutterModuleUtils.java @@ -174,7 +174,7 @@ private static VirtualFile findPreferedXcodeMetadataFile(@Nullable VirtualFile i @NotNull public static Module[] getModules(@NotNull Project project) { // A disposed project has no modules. - if (project.isDisposed()) return new Module[]{}; + if (project.isDisposed()) return new Module[]{ }; return ModuleManager.getInstance(project).getModules(); } @@ -262,8 +262,7 @@ public static void autoShowMain(@NotNull Project project, @NotNull PubRoot root) } /** - * Introspect into the module's content roots, looking for flutter.yaml or a pubspec.yaml that - * references flutter. + * Introspect into the module's content roots, looking for a pubspec.yaml that references flutter. *

* True is returned if any of the PubRoots associated with the {@link Module} have a pubspec that declares flutter. */ @@ -276,6 +275,18 @@ public static boolean declaresFlutter(@NotNull Module module) { return false; } + /** + * Introspect into the module's content roots, looking for a pubspec.yaml that references flutter web. + */ + public static boolean declaresFlutterWeb(@NotNull Module module) { + for (PubRoot root : PubRoots.forModule(module)) { + if (root.declaresFlutterWeb()) { + return true; + } + } + return false; + } + /** * Find flutter modules. *