Unverified Commit 58d6c425 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

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
parent 67c843f7
...@@ -46,9 +46,6 @@ class BuildLinuxCommand extends BuildSubCommand { ...@@ -46,9 +46,6 @@ class BuildLinuxCommand extends BuildSubCommand {
if (!globals.platform.isLinux) { if (!globals.platform.isLinux) {
throwToolExit('"build linux" only supported on Linux hosts.'); 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); await buildLinux(flutterProject.linux, buildInfo, target: targetFile);
return null; return null;
} }
......
...@@ -46,9 +46,6 @@ class BuildMacosCommand extends BuildSubCommand { ...@@ -46,9 +46,6 @@ class BuildMacosCommand extends BuildSubCommand {
if (!globals.platform.isMacOS) { if (!globals.platform.isMacOS) {
throwToolExit('"build macos" only supported on macOS hosts.'); throwToolExit('"build macos" only supported on macOS hosts.');
} }
if (!flutterProject.macos.existsSync()) {
throwToolExit('No macOS desktop project configured.');
}
await buildMacOS( await buildMacOS(
flutterProject: flutterProject, flutterProject: flutterProject,
buildInfo: buildInfo, buildInfo: buildInfo,
......
...@@ -46,9 +46,6 @@ class BuildWindowsCommand extends BuildSubCommand { ...@@ -46,9 +46,6 @@ class BuildWindowsCommand extends BuildSubCommand {
if (!globals.platform.isWindows) { if (!globals.platform.isWindows) {
throwToolExit('"build windows" only supported on Windows hosts.'); 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); await buildWindows(flutterProject.windows, buildInfo, target: targetFile);
return null; return null;
} }
......
...@@ -14,6 +14,12 @@ import '../reporting/reporting.dart'; ...@@ -14,6 +14,12 @@ import '../reporting/reporting.dart';
/// Builds the Linux project through the Makefile. /// Builds the Linux project through the Makefile.
Future<void> buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async { Future<void> 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(''' final StringBuffer buffer = StringBuffer('''
# Generated code do not commit. # Generated code do not commit.
export FLUTTER_ROOT=${Cache.flutterRoot} export FLUTTER_ROOT=${Cache.flutterRoot}
......
...@@ -20,6 +20,12 @@ Future<void> buildMacOS({ ...@@ -20,6 +20,12 @@ Future<void> buildMacOS({
BuildInfo buildInfo, BuildInfo buildInfo,
String targetOverride, String targetOverride,
}) async { }) 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()); final Directory flutterBuildDir = globals.fs.directory(getMacOSBuildDirectory());
if (!flutterBuildDir.existsSync()) { if (!flutterBuildDir.existsSync()) {
flutterBuildDir.createSync(recursive: true); flutterBuildDir.createSync(recursive: true);
......
...@@ -16,6 +16,13 @@ import 'visual_studio.dart'; ...@@ -16,6 +16,13 @@ import 'visual_studio.dart';
/// Builds the Windows project using msbuild. /// Builds the Windows project using msbuild.
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {String target}) async { Future<void> 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<String, String> environment = <String, String>{ final Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': Cache.flutterRoot, 'FLUTTER_ROOT': Cache.flutterRoot,
'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path, 'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path,
......
...@@ -55,14 +55,19 @@ void main() { ...@@ -55,14 +55,19 @@ void main() {
when(notLinuxPlatform.isWindows).thenReturn(false); when(notLinuxPlatform.isWindows).thenReturn(false);
}); });
// Creates the mock files necessary to run a build. // Creates the mock files necessary to look like a Flutter project.
void setUpMockProjectFilesForBuild() { void setUpMockCoreProjectFiles() {
globals.fs.file('linux/build.sh').createSync(recursive: true);
globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync(); globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); 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'. // Sets up mock expectation for running 'make'.
void expectMakeInvocationWithMode(String buildModeName) { void expectMakeInvocationWithMode(String buildModeName) {
when(mockProcessManager.start(<String>[ when(mockProcessManager.start(<String>[
...@@ -78,9 +83,10 @@ void main() { ...@@ -78,9 +83,10 @@ void main() {
testUsingContext('Linux build fails when there is no linux project', () async { testUsingContext('Linux build fails when there is no linux project', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockCoreProjectFiles();
expect(createTestCommandRunner(command).run( expect(createTestCommandRunner(command).run(
const <String>['build', 'linux'] const <String>['build', 'linux']
), throwsA(isInstanceOf<ToolExit>())); ), throwsToolExit(message: 'No Linux desktop project configured'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => linuxPlatform, Platform: () => linuxPlatform,
FileSystem: () => MemoryFileSystem(), FileSystem: () => MemoryFileSystem(),
......
...@@ -67,14 +67,19 @@ void main() { ...@@ -67,14 +67,19 @@ void main() {
when(notMacosPlatform.isWindows).thenReturn(false); when(notMacosPlatform.isWindows).thenReturn(false);
}); });
// Sets up the minimal mock project files necessary for macOS builds to succeed. // Sets up the minimal mock project files necessary to look like a Flutter project.
void createMinimalMockProjectFiles() { void createCoreMockProjectFiles() {
globals.fs.directory('macos').createSync();
globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync(); globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); 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 // Mocks the process manager to handle an xcodebuild call to build the app
// in the given configuration. // in the given configuration.
void setUpMockXcodeBuildHandler(String configuration) { void setUpMockXcodeBuildHandler(String configuration) {
...@@ -102,11 +107,14 @@ void main() { ...@@ -102,11 +107,14 @@ void main() {
testUsingContext('macOS build fails when there is no macos project', () async { testUsingContext('macOS build fails when there is no macos project', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
createCoreMockProjectFiles();
expect(createTestCommandRunner(command).run( expect(createTestCommandRunner(command).run(
const <String>['build', 'macos'] const <String>['build', 'macos']
), throwsA(isInstanceOf<ToolExit>())); ), throwsToolExit(message: 'No macOS desktop project configured'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => macosPlatform, Platform: () => macosPlatform,
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
}); });
......
...@@ -58,10 +58,23 @@ void main() { ...@@ -58,10 +58,23 @@ void main() {
when(notWindowsPlatform.isWindows).thenReturn(false); 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 { testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
globals.fs.file(solutionPath).createSync(recursive: true); setUpMockProjectFilesForBuild();
expect(createTestCommandRunner(command).run( expect(createTestCommandRunner(command).run(
const <String>['build', 'windows'] const <String>['build', 'windows']
), throwsA(isInstanceOf<ToolExit>())); ), throwsA(isInstanceOf<ToolExit>()));
...@@ -76,10 +89,11 @@ void main() { ...@@ -76,10 +89,11 @@ void main() {
testUsingContext('Windows build fails when there is no windows project', () async { testUsingContext('Windows build fails when there is no windows project', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockCoreProjectFiles();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
expect(createTestCommandRunner(command).run( expect(createTestCommandRunner(command).run(
const <String>['build', 'windows'] const <String>['build', 'windows']
), throwsA(isInstanceOf<ToolExit>())); ), throwsToolExit(message: 'No Windows desktop project configured'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
...@@ -91,11 +105,8 @@ void main() { ...@@ -91,11 +105,8 @@ void main() {
testUsingContext('Windows build fails on non windows platform', () async { testUsingContext('Windows build fails on non windows platform', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
globals.fs.file(solutionPath).createSync(recursive: true); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); 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( expect(createTestCommandRunner(command).run(
const <String>['build', 'windows'] const <String>['build', 'windows']
...@@ -111,11 +122,8 @@ void main() { ...@@ -111,11 +122,8 @@ void main() {
testUsingContext('Windows build does not spew stdout to status logger', () async { testUsingContext('Windows build does not spew stdout to status logger', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
globals.fs.file(solutionPath).createSync(recursive: true); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); 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(<String>[ when(mockProcessManager.start(<String>[
r'C:\packages\flutter_tools\bin\vs_build.bat', r'C:\packages\flutter_tools\bin\vs_build.bat',
...@@ -142,11 +150,8 @@ void main() { ...@@ -142,11 +150,8 @@ void main() {
testUsingContext('Windows build invokes msbuild and writes generated files', () async { testUsingContext('Windows build invokes msbuild and writes generated files', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
globals.fs.file(solutionPath).createSync(recursive: true); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); 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(<String>[ when(mockProcessManager.start(<String>[
r'C:\packages\flutter_tools\bin\vs_build.bat', r'C:\packages\flutter_tools\bin\vs_build.bat',
...@@ -179,11 +184,8 @@ void main() { ...@@ -179,11 +184,8 @@ void main() {
testUsingContext('Release build prints an under-construction warning', () async { testUsingContext('Release build prints an under-construction warning', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
globals.fs.file(solutionPath).createSync(recursive: true); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); 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(<String>[ when(mockProcessManager.start(<String>[
r'C:\packages\flutter_tools\bin\vs_build.bat', r'C:\packages\flutter_tools\bin\vs_build.bat',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment