Java tutorial
/* * Copyright 2017 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.run; import com.intellij.execution.*; import com.intellij.execution.configurations.CommandLineState; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.executors.DefaultRunExecutor; import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.GenericProgramRunner; import com.intellij.execution.runners.ProgramRunner; import com.intellij.execution.runners.RunContentBuilder; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.Separator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.xdebugger.XDebugProcess; import com.intellij.xdebugger.XDebugProcessStarter; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; import com.jetbrains.lang.dart.ide.runner.DartExecutionHelper; import com.jetbrains.lang.dart.util.DartUrlResolver; import io.flutter.FlutterInitializer; import io.flutter.actions.RestartFlutterApp; import io.flutter.dart.DartPlugin; import io.flutter.logging.FlutterLog; import io.flutter.logging.FlutterLogView; import io.flutter.run.bazel.BazelRunConfig; import io.flutter.run.daemon.*; import io.flutter.view.OpenFlutterViewAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Launches a flutter app, showing it in the console. * <p> * Normally creates a debugging session, which is needed for hot reload. */ public class LaunchState extends CommandLineState { // We use the profile launch type, contributed by the Android IntelliJ plugins // in 2017.3 and Android Studio 3.0, if it's available. This allows us to support // their 'profile' launch button, next to the regular run and debug ones. public static final String ANDROID_PROFILER_EXECUTOR_ID = "Android Profiler"; private final @NotNull VirtualFile workDir; /** * The file or directory holding the Flutter app's source code. * This determines how the analysis server resolves URI's (for breakpoints, etc). * <p> * If a file, this should be the file containing the main() method. */ private final @NotNull VirtualFile sourceLocation; private final @NotNull RunConfig runConfig; private final @NotNull Callback callback; public LaunchState(@NotNull ExecutionEnvironment env, @NotNull VirtualFile workDir, @NotNull VirtualFile sourceLocation, @NotNull RunConfig runConfig, @NotNull Callback callback) { super(env); this.workDir = workDir; this.sourceLocation = sourceLocation; this.runConfig = runConfig; this.callback = callback; DaemonConsoleView.install(this, env, workDir); } @Nullable protected ConsoleView createConsole(@NotNull final Executor executor) throws ExecutionException { if (FlutterLog.isLoggingEnabled()) { final FlutterApp app = FlutterApp.fromEnv(getEnvironment()); assert app != null; return new FlutterLogView(app); } else { return super.createConsole(executor); } } private RunContentDescriptor launch(@NotNull ExecutionEnvironment env) throws ExecutionException { FileDocumentManager.getInstance().saveAllDocuments(); // Set our FlutterLaunchMode up in the ExecutionEnvironment. if (RunMode.fromEnv(env).isProfiling()) { FlutterLaunchMode.addToEnvironment(env, FlutterLaunchMode.PROFILE); } final Project project = getEnvironment().getProject(); final FlutterDevice device = DeviceService.getInstance(project).getSelectedDevice(); final FlutterApp app = callback.createApp(device); if (device == null) { Messages.showDialog(project, "No connected devices found; please connect a device, or see flutter.io/setup for getting started instructions.", "No Connected Devices Found", new String[] { Messages.OK_BUTTON }, 0, AllIcons.General.InformationDialog); return null; } // Cache for use in console configuration. FlutterApp.addToEnvironment(env, app); // Remember the run configuration that started this process. app.getProcessHandler().putUserData(FLUTTER_RUN_CONFIG_KEY, runConfig); final ExecutionResult result = setUpConsoleAndActions(app); // For Bazel run configurations, // where the console is not null, // and we find the expected process handler type, // print the command line command to the console. if (runConfig instanceof BazelRunConfig && app.getConsole() != null && app.getProcessHandler() instanceof OSProcessHandler) { final String commandLineString = ((OSProcessHandler) app.getProcessHandler()).getCommandLine().trim(); if (StringUtil.isNotEmpty(commandLineString)) { app.getConsole().print(commandLineString + "\n", ConsoleViewContentType.NORMAL_OUTPUT); } } device.bringToFront(); // Check for and display any analysis errors when we launch an app. if (env.getRunProfile() instanceof SdkRunConfig) { final Class dartExecutionHelper = classForName( "com.jetbrains.lang.dart.ide.runner.DartExecutionHelper"); if (dartExecutionHelper != null) { final String message = ("<a href='open.dart.analysis'>Analysis issues</a> may affect " + "the execution of '" + env.getRunProfile().getName() + "'."); final SdkRunConfig config = (SdkRunConfig) env.getRunProfile(); final SdkFields sdkFields = config.getFields(); final MainFile mainFile = MainFile.verify(sdkFields.getFilePath(), env.getProject()).get(); DartExecutionHelper.displayIssues(project, mainFile.getFile(), message, env.getRunProfile().getIcon()); } } final FlutterLaunchMode launchMode = FlutterLaunchMode.fromEnv(env); if (launchMode.supportsDebugConnection()) { return createDebugSession(env, app, result).getRunContentDescriptor(); } else { return new RunContentBuilder(result, env).showRunContent(env.getContentToReuse()); } } private static Class classForName(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { return null; } } @NotNull private XDebugSession createDebugSession(@NotNull final ExecutionEnvironment env, @NotNull final FlutterApp app, @NotNull final ExecutionResult executionResult) throws ExecutionException { final DartUrlResolver resolver = DartUrlResolver.getInstance(env.getProject(), sourceLocation); final PositionMapper mapper = createPositionMapper(env, app, resolver); final XDebuggerManager manager = XDebuggerManager.getInstance(env.getProject()); final XDebugSession session = manager.startSession(env, new XDebugProcessStarter() { @Override @NotNull public XDebugProcess start(@NotNull final XDebugSession session) { return new FlutterDebugProcess(app, env, session, executionResult, resolver, mapper); } }); if (app.getMode() != RunMode.DEBUG) { session.setBreakpointMuted(true); } return session; } @NotNull private PositionMapper createPositionMapper(@NotNull ExecutionEnvironment env, @NotNull FlutterApp app, @NotNull DartUrlResolver resolver) { final PositionMapper.Analyzer analyzer; if (app.getMode() == RunMode.DEBUG) { analyzer = PositionMapper.Analyzer.create(env.getProject(), sourceLocation); } else { analyzer = null; // Don't need analysis server just to run. } // Choose source root containing the Dart application. // TODO(skybrian) for bazel, we probably should pass in three source roots here (for bazel-bin, bazel-genfiles, etc). final VirtualFile pubspec = resolver.getPubspecYamlFile(); final VirtualFile sourceRoot = pubspec != null ? pubspec.getParent() : workDir; return new PositionMapper(env.getProject(), sourceRoot, resolver, analyzer); } @NotNull private ExecutionResult setUpConsoleAndActions(@NotNull FlutterApp app) throws ExecutionException { final ConsoleView console = createConsole(getEnvironment().getExecutor()); if (console != null) { app.setConsole(console); console.attachToProcess(app.getProcessHandler()); } // Add observatory actions. // These actions are effectively added only to the Run tool window. // For Debug see FlutterDebugProcess.registerAdditionalActions() final Computable<Boolean> observatoryAvailable = () -> !app.getProcessHandler().isProcessTerminated() && app.getConnector().getBrowserUrl() != null; final List<AnAction> actions = new ArrayList<>(Arrays .asList(super.createActions(console, app.getProcessHandler(), getEnvironment().getExecutor()))); actions.add(new Separator()); actions.add(new OpenObservatoryAction(app.getConnector(), observatoryAvailable)); actions.add(new OpenTimelineViewAction(app.getConnector(), observatoryAvailable)); actions.add(new Separator()); actions.add(new OpenFlutterViewAction(() -> !app.getProcessHandler().isProcessTerminated())); return new DefaultExecutionResult(console, app.getProcessHandler(), actions.toArray(new AnAction[0])); } @Override public @NotNull ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { throw new ExecutionException("not implemented"); // Not used; launch() does this. } @Override protected @NotNull ProcessHandler startProcess() throws ExecutionException { // This can happen if there isn't a custom runner defined in plugin.xml. // The runner should extend LaunchState.Runner (below). throw new ExecutionException("need to implement LaunchState.Runner for " + runConfig.getClass()); } /** * Starts the process and wraps it in a FlutterApp. * <p> * The callback knows the appropriate command line arguments (bazel versus non-bazel). */ public interface Callback { FlutterApp createApp(@Nullable FlutterDevice device) throws ExecutionException; } /** * A run configuration that works with Launcher. */ public interface RunConfig extends RunProfile { Project getProject(); @NotNull LaunchState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException; @NotNull GeneralCommandLine getCommand(ExecutionEnvironment environment, FlutterDevice device) throws ExecutionException; } /** * A runner that automatically invokes {@link #launch}. */ public static abstract class Runner<C extends RunConfig> extends GenericProgramRunner { private final Class<C> runConfigClass; public Runner(Class<C> runConfigClass) { this.runConfigClass = runConfigClass; } @SuppressWarnings("SimplifiableIfStatement") @Override public final boolean canRun(final @NotNull String executorId, final @NotNull RunProfile profile) { if (!DefaultRunExecutor.EXECUTOR_ID.equals(executorId) && !DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) && !ANDROID_PROFILER_EXECUTOR_ID.equals(executorId)) { return false; } if (!(profile instanceof RunConfig)) { return false; } // If the app is running and the launch mode is the same, then we can run. final RunConfig config = (RunConfig) profile; final ProcessHandler process = getRunningAppProcess(config); if (process != null) { final FlutterApp app = FlutterApp.fromProcess(process); if (app == null) { return false; } final String selectedDeviceId = getSelectedDeviceId(config.getProject()); // Only continue checks for this app if the launched device is the same as the selected one. if (StringUtil.equals(app.deviceId(), selectedDeviceId)) { // Disable if no app or this isn't the mode that app was launched in. if (!executorId.equals(app.getMode().mode())) { return false; } // Disable the run/debug buttons if the app is starting up. if (app.getState() == FlutterApp.State.STARTING || app.getState() == FlutterApp.State.RELOADING || app.getState() == FlutterApp.State.RESTARTING) { return false; } } } if (DartPlugin.getDartSdk(config.getProject()) == null) { return false; } return runConfigClass.isInstance(profile) && canRun(runConfigClass.cast(profile)); } /** * Subclass hook for additional checks. */ protected boolean canRun(C config) { return true; } @Override protected final RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull ExecutionEnvironment env) throws ExecutionException { if (!(state instanceof LaunchState)) { LOG.error("unexpected RunProfileState: " + state.getClass()); return null; } final LaunchState launchState = (LaunchState) state; final String executorId = env.getExecutor().getId(); // See if we should issue a hot-reload. final List<RunContentDescriptor> runningProcesses = ExecutionManager.getInstance(env.getProject()) .getContentManager().getAllDescriptors(); final ProcessHandler process = getRunningAppProcess(launchState.runConfig); if (process != null) { final FlutterApp app = FlutterApp.fromProcess(process); final String selectedDeviceId = getSelectedDeviceId(env.getProject()); if (app != null && StringUtil.equals(app.deviceId(), selectedDeviceId)) { if (executorId.equals(app.getMode().mode())) { if (!identicalCommands(app.getCommand(), launchState.runConfig.getCommand(env, app.device()))) { // To be safe, relaunch as the arguments to launch have changed. try { // TODO(jacobr): ideally we shouldn't be synchronously waiting // for futures like this but I don't see a better option. // In practice this seems fine. app.shutdownAsync().get(); } catch (InterruptedException | java.util.concurrent.ExecutionException e) { LOG.error(e); } return launchState.launch(env); } final FlutterLaunchMode launchMode = FlutterLaunchMode.fromEnv(env); if (launchMode.supportsReload() && app.isStarted()) { // Map a re-run action to a flutter hot restart. FileDocumentManager.getInstance().saveAllDocuments(); FlutterInitializer.sendAnalyticsAction(RestartFlutterApp.class.getSimpleName()); app.performRestartApp(); } } return null; } } // Else, launch the app. return launchState.launch(env); } private static boolean identicalCommands(GeneralCommandLine a, GeneralCommandLine b) { return a.getParametersList().getList().equals(b.getParametersList().getList()); } @Nullable private String getSelectedDeviceId(@NotNull Project project) { final FlutterDevice selectedDevice = DeviceService.getInstance(project).getSelectedDevice(); return selectedDevice == null ? null : selectedDevice.deviceId(); } } /** * Returns the currently running app for the given RunConfig, if any. */ @Nullable public static ProcessHandler getRunningAppProcess(RunConfig config) { final Project project = config.getProject(); final List<RunContentDescriptor> runningProcesses = ExecutionManager.getInstance(project) .getContentManager().getAllDescriptors(); for (RunContentDescriptor descriptor : runningProcesses) { final ProcessHandler process = descriptor.getProcessHandler(); if (process != null && !process.isProcessTerminated() && process.getUserData(FLUTTER_RUN_CONFIG_KEY) == config) { return process; } } return null; } private static final Key<RunConfig> FLUTTER_RUN_CONFIG_KEY = new Key<>("FLUTTER_RUN_CONFIG_KEY"); private static final Logger LOG = Logger.getInstance(LaunchState.class); }