Add desktop projects and build commands (experimental) (#31205)

This commit is contained in:
Jonah Williams 2019-04-17 12:16:55 -07:00 committed by GitHub
parent 1e2e96dbe4
commit 86c938b5b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 589 additions and 0 deletions

View file

@ -4,6 +4,10 @@
import 'dart:async';
import '../commands/build_linux.dart';
import '../commands/build_macos.dart';
import '../commands/build_windows.dart';
import '../runner/flutter_command.dart';
import 'build_aot.dart';
import 'build_apk.dart';
@ -22,6 +26,9 @@ class BuildCommand extends FlutterCommand {
addSubcommand(BuildFlxCommand());
addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
addSubcommand(BuildWebCommand());
addSubcommand(BuildMacosCommand());
addSubcommand(BuildLinuxCommand());
addSubcommand(BuildWindowsCommand());
}
@override

View file

@ -0,0 +1,84 @@
// 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.
import 'dart:async';
import '../base/common.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../build_info.dart';
import '../cache.dart';
import '../convert.dart';
import '../globals.dart';
import '../project.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart';
/// A command to build a linux desktop target through a build shell script.
class BuildLinuxCommand extends BuildSubCommand {
BuildLinuxCommand() {
argParser.addFlag('debug',
negatable: false,
help: 'Build a debug version of your app.',
);
argParser.addFlag('profile',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.'
);
argParser.addFlag('release',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.',
);
}
@override
final String name = 'linux';
@override
bool isExperimental = true;
@override
bool hidden = true;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.linux,
DevelopmentArtifact.universal,
};
@override
String get description => 'build the Linux desktop target (Experimental).';
@override
Future<FlutterCommandResult> runCommand() async {
Cache.releaseLockEarly();
final BuildInfo buildInfo = getBuildInfo();
final FlutterProject flutterProject = await FlutterProject.current();
if (!platform.isLinux) {
throwToolExit('"build linux" only supported on Linux hosts.');
}
if (!flutterProject.linux.existsSync()) {
throwToolExit('No Linux desktop project configured.');
}
final Process process = await processManager.start(<String>[
flutterProject.linux.buildScript.path,
Cache.flutterRoot,
buildInfo.isDebug ? 'debug' : 'release',
], runInShell: true);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printError);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printStatus);
final int result = await process.exitCode;
if (result != 0) {
throwToolExit('Build process failed');
}
return null;
}
}

View file

@ -0,0 +1,84 @@
// 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.
import 'dart:async';
import '../base/common.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../build_info.dart';
import '../cache.dart';
import '../convert.dart';
import '../globals.dart';
import '../project.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart';
/// A command to build a macos desktop target through a build shell script.
class BuildMacosCommand extends BuildSubCommand {
BuildMacosCommand() {
argParser.addFlag('debug',
negatable: false,
help: 'Build a debug version of your app.',
);
argParser.addFlag('profile',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.'
);
argParser.addFlag('release',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.',
);
}
@override
final String name = 'macos';
@override
bool isExperimental = true;
@override
bool hidden = true;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.macOS,
DevelopmentArtifact.universal,
};
@override
String get description => 'build the macOS desktop target (Experimental).';
@override
Future<FlutterCommandResult> runCommand() async {
Cache.releaseLockEarly();
final BuildInfo buildInfo = getBuildInfo();
final FlutterProject flutterProject = await FlutterProject.current();
if (!platform.isMacOS) {
throwToolExit('"build macos" only supported on macOS hosts.');
}
if (!flutterProject.macos.existsSync()) {
throwToolExit('No macOS desktop project configured.');
}
final Process process = await processManager.start(<String>[
flutterProject.macos.buildScript.path,
Cache.flutterRoot,
buildInfo.isDebug ? 'debug' : 'release',
], runInShell: true);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printError);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printStatus);
final int result = await process.exitCode;
if (result != 0) {
throwToolExit('Build process failed');
}
return null;
}
}

View file

@ -0,0 +1,84 @@
// 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.
import 'dart:async';
import '../base/common.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../build_info.dart';
import '../cache.dart';
import '../convert.dart';
import '../globals.dart';
import '../project.dart';
import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart';
/// A command to build a windows desktop target through a build shell script.
class BuildWindowsCommand extends BuildSubCommand {
BuildWindowsCommand() {
argParser.addFlag('debug',
negatable: false,
help: 'Build a debug version of your app.',
);
argParser.addFlag('profile',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.'
);
argParser.addFlag('release',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.',
);
}
@override
final String name = 'windows';
@override
bool isExperimental = true;
@override
bool hidden = true;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.windows,
DevelopmentArtifact.universal,
};
@override
String get description => 'build the desktop Windows target (Experimental).';
@override
Future<FlutterCommandResult> runCommand() async {
Cache.releaseLockEarly();
final FlutterProject flutterProject = await FlutterProject.current();
final BuildInfo buildInfo = getBuildInfo();
if (!platform.isWindows) {
throwToolExit('"build windows" only supported on Windows hosts.');
}
if (!flutterProject.windows.existsSync()) {
throwToolExit('No Windows desktop project configured.');
}
final Process process = await processManager.start(<String>[
flutterProject.windows.buildScript.path,
Cache.flutterRoot,
buildInfo.isDebug ? 'debug' : 'release',
], runInShell: true);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printError);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printStatus);
final int result = await process.exitCode;
if (result != 0) {
throwToolExit('Build process failed');
}
return null;
}
}

View file

@ -99,6 +99,15 @@ class FlutterProject {
/// The web sub project of this project.
WebProject get web => WebProject._(this);
/// The macos sub project of this project.
MacOSProject get macos => MacOSProject._(this);
/// The linux sub project of this project.
LinuxProject get linux => LinuxProject._(this);
/// The windows sub project of this project.
WindowsProject get windows => WindowsProject._(this);
/// The `pubspec.yaml` file of this project.
File get pubspecFile => directory.childFile('pubspec.yaml');
@ -518,3 +527,39 @@ Match _firstMatchInFile(File file, RegExp regExp) {
}
return null;
}
/// The macOS sub project.
class MacOSProject {
MacOSProject._(this.project);
final FlutterProject project;
bool existsSync() => project.directory.childDirectory('macos').existsSync();
// Note: The build script file exists as a temporary shim.
File get buildScript => project.directory.childDirectory('macos').childFile('build.sh');
}
/// The Windows sub project
class WindowsProject {
WindowsProject._(this.project);
final FlutterProject project;
bool existsSync() => project.directory.childDirectory('windows').existsSync();
// Note: The build script file exists as a temporary shim.
File get buildScript => project.directory.childDirectory('windows').childFile('build.bat');
}
/// The Linux sub project.
class LinuxProject {
LinuxProject._(this.project);
final FlutterProject project;
bool existsSync() => project.directory.childDirectory('linux').existsSync();
// Note: The build script file exists as a temporary shim.
File get buildScript => project.directory.childDirectory('linux').childFile('build.sh');
}

View file

@ -0,0 +1,94 @@
// 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.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
void main() {
Cache.disableLocking();
final MockProcessManager mockProcessManager = MockProcessManager();
final MemoryFileSystem memoryFilesystem = MemoryFileSystem();
final MockProcess mockProcess = MockProcess();
final MockPlatform linuxPlatform = MockPlatform();
final MockPlatform notLinuxPlatform = MockPlatform();
when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
return 0;
});
when(mockProcess.stderr).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(mockProcess.stdout).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(linuxPlatform.isLinux).thenReturn(true);
when(notLinuxPlatform.isLinux).thenReturn(false);
testUsingContext('Linux build fails when there is no linux project', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
expect(createTestCommandRunner(command).run(
const <String>['build', 'linux']
), throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => linuxPlatform,
});
testUsingContext('Linux build fails on non-linux platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file('linux/build.sh').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
expect(createTestCommandRunner(command).run(
const <String>['build', 'linux']
), throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => notLinuxPlatform,
});
testUsingContext('Linux build invokes build script', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file('linux/build.sh').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
when(mockProcessManager.start(<String>[
'/linux/build.sh',
'/',
'release'
], runInShell: true)).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run(
const <String>['build', 'linux']
);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFilesystem,
ProcessManager: () => mockProcessManager,
Platform: () => linuxPlatform,
});
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}
class MockPlatform extends Mock implements Platform {
@override
Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': '/',
};
}

View file

@ -0,0 +1,95 @@
// 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.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
void main() {
Cache.disableLocking();
final MockProcessManager mockProcessManager = MockProcessManager();
final MemoryFileSystem memoryFilesystem = MemoryFileSystem();
final MockProcess mockProcess = MockProcess();
final MockPlatform macosPlatform = MockPlatform();
final MockPlatform notMacosPlatform = MockPlatform();
when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
return 0;
});
when(mockProcess.stderr).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(mockProcess.stdout).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(macosPlatform.isMacOS).thenReturn(true);
when(notMacosPlatform.isMacOS).thenReturn(false);
testUsingContext('macOS build fails when there is no macos project', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
expect(createTestCommandRunner(command).run(
const <String>['build', 'macos']
), throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => macosPlatform,
});
testUsingContext('macOS build fails on non-macOS platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file('macos/build.sh').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
expect(createTestCommandRunner(command).run(
const <String>['build', 'macos']
), throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => notMacosPlatform,
FileSystem: () => memoryFilesystem,
});
testUsingContext('macOS build invokes build script', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file('macos/build.sh').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
when(mockProcessManager.start(<String>[
'/macos/build.sh',
'/',
'release',
], runInShell: true)).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run(
const <String>['build', 'macos']
);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFilesystem,
ProcessManager: () => mockProcessManager,
Platform: () => macosPlatform,
});
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}
class MockPlatform extends Mock implements Platform {
@override
Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': '/',
};
}

View file

@ -0,0 +1,96 @@
// 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.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
void main() {
Cache.disableLocking();
final MockProcessManager mockProcessManager = MockProcessManager();
final MemoryFileSystem memoryFilesystem = MemoryFileSystem(style: FileSystemStyle.windows);
final MockProcess mockProcess = MockProcess();
final MockPlatform windowsPlatform = MockPlatform();
final MockPlatform notWindowsPlatform = MockPlatform();
when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
return 0;
});
when(mockProcess.stderr).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(mockProcess.stdout).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(windowsPlatform.isWindows).thenReturn(true);
when(notWindowsPlatform.isWindows).thenReturn(false);
testUsingContext('Windows build fails when there is no windows project', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
expect(createTestCommandRunner(command).run(
const <String>['build', 'windows']
), throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => windowsPlatform,
FileSystem: () => memoryFilesystem,
});
testUsingContext('Windows build fails on non windows platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file(r'windows\build.bat').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
expect(createTestCommandRunner(command).run(
const <String>['build', 'windows']
), throwsA(isInstanceOf<ToolExit>()));
}, overrides: <Type, Generator>{
Platform: () => notWindowsPlatform,
FileSystem: () => memoryFilesystem,
});
testUsingContext('Windows build invokes build script', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file(r'windows\build.bat').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
when(mockProcessManager.start(<String>[
r'C:\windows\build.bat',
r'C:\',
'release',
], runInShell: true)).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run(
const <String>['build', 'windows']
);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFilesystem,
ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform,
});
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}
class MockPlatform extends Mock implements Platform {
@override
Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': r'C:\',
};
}