From 58d6c425e452ca48caa00be13bc8cc5bdb83aee5 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 8 Jan 2020 12:39:49 -0800 Subject: [PATCH] Check for desktop project files before building (#48350) Moves the checks that projects have been configured for desktop to a lower level, where they will cover more codepaths (e.g., 'run'), and improves them to check for native build projects, rather than just directories, to catch cases where the directory exists (e.g., due to accidental creation of generated files). Also adds links to the error messages pointing to instructions on adding desktop support to a project. Fixes #47145 --- .../lib/src/commands/build_linux.dart | 3 -- .../lib/src/commands/build_macos.dart | 3 -- .../lib/src/commands/build_windows.dart | 3 -- .../lib/src/linux/build_linux.dart | 6 +++ .../lib/src/macos/build_macos.dart | 6 +++ .../lib/src/windows/build_windows.dart | 7 ++++ .../hermetic/build_linux_test.dart | 14 +++++-- .../hermetic/build_macos_test.dart | 16 ++++++-- .../hermetic/build_windows_test.dart | 38 ++++++++++--------- 9 files changed, 61 insertions(+), 35 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart index b3e9b78061..81f096ac77 100644 --- a/packages/flutter_tools/lib/src/commands/build_linux.dart +++ b/packages/flutter_tools/lib/src/commands/build_linux.dart @@ -46,9 +46,6 @@ class BuildLinuxCommand extends BuildSubCommand { if (!globals.platform.isLinux) { throwToolExit('"build linux" only supported on Linux hosts.'); } - if (!flutterProject.linux.existsSync()) { - throwToolExit('No Linux desktop project configured.'); - } await buildLinux(flutterProject.linux, buildInfo, target: targetFile); return null; } diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart index c966f3cf11..8d39a3a633 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos.dart @@ -46,9 +46,6 @@ class BuildMacosCommand extends BuildSubCommand { if (!globals.platform.isMacOS) { throwToolExit('"build macos" only supported on macOS hosts.'); } - if (!flutterProject.macos.existsSync()) { - throwToolExit('No macOS desktop project configured.'); - } await buildMacOS( flutterProject: flutterProject, buildInfo: buildInfo, diff --git a/packages/flutter_tools/lib/src/commands/build_windows.dart b/packages/flutter_tools/lib/src/commands/build_windows.dart index 7be232b781..3dd6ff705f 100644 --- a/packages/flutter_tools/lib/src/commands/build_windows.dart +++ b/packages/flutter_tools/lib/src/commands/build_windows.dart @@ -46,9 +46,6 @@ class BuildWindowsCommand extends BuildSubCommand { if (!globals.platform.isWindows) { throwToolExit('"build windows" only supported on Windows hosts.'); } - if (!flutterProject.windows.existsSync()) { - throwToolExit('No Windows desktop project configured.'); - } await buildWindows(flutterProject.windows, buildInfo, target: targetFile); return null; } diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart index 92e896a03a..c34a4f8e98 100644 --- a/packages/flutter_tools/lib/src/linux/build_linux.dart +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart @@ -14,6 +14,12 @@ import '../reporting/reporting.dart'; /// Builds the Linux project through the Makefile. Future buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async { + if (!linuxProject.makeFile.existsSync()) { + throwToolExit('No Linux desktop project configured. See ' + 'https://github.com/flutter/flutter/wiki/Desktop-shells#create ' + 'to learn about adding Linux support to a project.'); + } + final StringBuffer buffer = StringBuffer(''' # Generated code do not commit. export FLUTTER_ROOT=${Cache.flutterRoot} diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index da6de5e990..d0c528a4fe 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -20,6 +20,12 @@ Future buildMacOS({ BuildInfo buildInfo, String targetOverride, }) async { + if (!flutterProject.macos.xcodeWorkspace.existsSync()) { + throwToolExit('No macOS desktop project configured. ' + 'See https://flutter.dev/desktop#add-desktop-support-to-an-existing-flutter-project ' + 'to learn about adding macOS support to a project.'); + } + final Directory flutterBuildDir = globals.fs.directory(getMacOSBuildDirectory()); if (!flutterBuildDir.existsSync()) { flutterBuildDir.createSync(recursive: true); diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 55e4b77a39..d4b3c50a2d 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -16,6 +16,13 @@ import 'visual_studio.dart'; /// Builds the Windows project using msbuild. Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {String target}) async { + if (!windowsProject.solutionFile.existsSync()) { + throwToolExit( + 'No Windows desktop project configured. ' + 'See https://github.com/flutter/flutter/wiki/Desktop-shells#create ' + 'to learn about adding Windows support to a project.'); + } + final Map environment = { 'FLUTTER_ROOT': Cache.flutterRoot, 'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart index d2d93543d4..e6a7244a7d 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart @@ -55,14 +55,19 @@ void main() { when(notLinuxPlatform.isWindows).thenReturn(false); }); - // Creates the mock files necessary to run a build. - void setUpMockProjectFilesForBuild() { - globals.fs.file('linux/build.sh').createSync(recursive: true); + // Creates the mock files necessary to look like a Flutter project. + void setUpMockCoreProjectFiles() { globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); } + // Creates the mock files necessary to run a build. + void setUpMockProjectFilesForBuild() { + globals.fs.file(globals.fs.path.join('linux', 'Makefile')).createSync(recursive: true); + setUpMockCoreProjectFiles(); + } + // Sets up mock expectation for running 'make'. void expectMakeInvocationWithMode(String buildModeName) { when(mockProcessManager.start([ @@ -78,9 +83,10 @@ void main() { testUsingContext('Linux build fails when there is no linux project', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); + setUpMockCoreProjectFiles(); expect(createTestCommandRunner(command).run( const ['build', 'linux'] - ), throwsA(isInstanceOf())); + ), throwsToolExit(message: 'No Linux desktop project configured')); }, overrides: { Platform: () => linuxPlatform, FileSystem: () => MemoryFileSystem(), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart index 7f6d74fb0f..18ad8d42f3 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart @@ -67,14 +67,19 @@ void main() { when(notMacosPlatform.isWindows).thenReturn(false); }); - // Sets up the minimal mock project files necessary for macOS builds to succeed. - void createMinimalMockProjectFiles() { - globals.fs.directory('macos').createSync(); + // Sets up the minimal mock project files necessary to look like a Flutter project. + void createCoreMockProjectFiles() { globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); } + // Sets up the minimal mock project files necessary for macOS builds to succeed. + void createMinimalMockProjectFiles() { + globals.fs.directory(globals.fs.path.join('macos', 'Runner.xcworkspace')).createSync(recursive: true); + createCoreMockProjectFiles(); + } + // Mocks the process manager to handle an xcodebuild call to build the app // in the given configuration. void setUpMockXcodeBuildHandler(String configuration) { @@ -102,11 +107,14 @@ void main() { testUsingContext('macOS build fails when there is no macos project', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); + createCoreMockProjectFiles(); expect(createTestCommandRunner(command).run( const ['build', 'macos'] - ), throwsA(isInstanceOf())); + ), throwsToolExit(message: 'No macOS desktop project configured')); }, overrides: { Platform: () => macosPlatform, + FileSystem: () => MemoryFileSystem(), + ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), }); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index 9a81864207..5b2f571b83 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -58,10 +58,23 @@ void main() { when(notWindowsPlatform.isWindows).thenReturn(false); }); + // Creates the mock files necessary to look like a Flutter project. + void setUpMockCoreProjectFiles() { + globals.fs.file('pubspec.yaml').createSync(); + globals.fs.file('.packages').createSync(); + globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); + } + + // Creates the mock files necessary to run a build. + void setUpMockProjectFilesForBuild() { + globals.fs.file(solutionPath).createSync(recursive: true); + setUpMockCoreProjectFiles(); + } + testUsingContext('Windows build fails when there is no vcvars64.bat', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); - globals.fs.file(solutionPath).createSync(recursive: true); + setUpMockProjectFilesForBuild(); expect(createTestCommandRunner(command).run( const ['build', 'windows'] ), throwsA(isInstanceOf())); @@ -76,10 +89,11 @@ void main() { testUsingContext('Windows build fails when there is no windows project', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); + setUpMockCoreProjectFiles(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); expect(createTestCommandRunner(command).run( const ['build', 'windows'] - ), throwsA(isInstanceOf())); + ), throwsToolExit(message: 'No Windows desktop project configured')); }, overrides: { Platform: () => windowsPlatform, FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), @@ -91,11 +105,8 @@ void main() { testUsingContext('Windows build fails on non windows platform', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); - globals.fs.file(solutionPath).createSync(recursive: true); + setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); expect(createTestCommandRunner(command).run( const ['build', 'windows'] @@ -111,11 +122,8 @@ void main() { testUsingContext('Windows build does not spew stdout to status logger', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); - globals.fs.file(solutionPath).createSync(recursive: true); + setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); when(mockProcessManager.start([ r'C:\packages\flutter_tools\bin\vs_build.bat', @@ -142,11 +150,8 @@ void main() { testUsingContext('Windows build invokes msbuild and writes generated files', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); - globals.fs.file(solutionPath).createSync(recursive: true); + setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); when(mockProcessManager.start([ r'C:\packages\flutter_tools\bin\vs_build.bat', @@ -179,11 +184,8 @@ void main() { testUsingContext('Release build prints an under-construction warning', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); - globals.fs.file(solutionPath).createSync(recursive: true); + setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); when(mockProcessManager.start([ r'C:\packages\flutter_tools\bin\vs_build.bat',