[flutter_tool] Build a Fuchsia package (#32519)
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 6db4f0e..c841d54 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -392,11 +392,16 @@ return fs.path.join(getBuildDirectory(), 'web'); } -/// Returns the linux build output directory. +/// Returns the Linux build output directory. String getLinuxBuildDirectory() { return fs.path.join(getBuildDirectory(), 'linux'); } +/// Returns the Fuchsia build output directory. +String getFuchsiaBuildDirectory() { + return fs.path.join(getBuildDirectory(), 'fuchsia'); +} + /// Returns directory used by incremental compiler (IKG - incremental kernel /// generator) to store cached intermediate state. String getIncrementalCompilerByteStoreDirectory() {
diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index e97e7f3..1691b57 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart
@@ -13,6 +13,7 @@ import 'build_apk.dart'; import 'build_appbundle.dart'; import 'build_bundle.dart'; +import 'build_fuchsia.dart'; import 'build_ios.dart'; import 'build_web.dart'; @@ -27,6 +28,7 @@ addSubcommand(BuildMacosCommand()); addSubcommand(BuildLinuxCommand()); addSubcommand(BuildWindowsCommand()); + addSubcommand(BuildFuchsiaCommand(verboseHelp: verboseHelp)); } @override
diff --git a/packages/flutter_tools/lib/src/commands/build_fuchsia.dart b/packages/flutter_tools/lib/src/commands/build_fuchsia.dart new file mode 100644 index 0000000..473f943 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_fuchsia.dart
@@ -0,0 +1,66 @@ +// 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/file_system.dart'; +import '../base/platform.dart'; +import '../build_info.dart'; +import '../cache.dart'; +import '../fuchsia/fuchsia_build.dart'; +import '../project.dart'; +import '../runner/flutter_command.dart' show FlutterCommandResult; +import 'build.dart'; + +/// A command to build a Fuchsia target. +class BuildFuchsiaCommand extends BuildSubCommand { + BuildFuchsiaCommand({bool verboseHelp = false}) { + usesTargetOption(); + addBuildModeFlags(verboseHelp: verboseHelp); + } + + @override + final String name = 'fuchsia'; + + @override + bool isExperimental = true; + + @override + bool hidden = true; + + @override + Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{ + DevelopmentArtifact.fuchsia, + DevelopmentArtifact.universal, + }; + + @override + String get description => 'build the Fuchsia target (Experimental).'; + + @override + Future<FlutterCommandResult> runCommand() async { + Cache.releaseLockEarly(); + final BuildInfo buildInfo = getBuildInfo(); + final FlutterProject flutterProject = FlutterProject.current(); + if (!platform.isLinux && !platform.isMacOS) { + throwToolExit('"build Fuchsia" only supported on Linux and MacOS hosts.'); + } + if (!flutterProject.fuchsia.existsSync()) { + throwToolExit('No Fuchsia project configured.'); + } + final String appName = flutterProject.fuchsia.project.manifest.appName; + final String cmxPath = fs.path.join( + flutterProject.fuchsia.meta.path, '$appName.cmx'); + final File cmxFile = fs.file(cmxPath); + if (!cmxFile.existsSync()) { + throwToolExit('Fuchsia build requires a .cmx file at $cmxPath for the app'); + } + await buildFuchsia( + fuchsiaProject: flutterProject.fuchsia, + target: targetFile, + buildInfo: buildInfo); + return null; + } +}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index eb7dea5..f69b872 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -27,6 +27,8 @@ import 'device.dart'; import 'doctor.dart'; import 'emulator.dart'; +import 'fuchsia/fuchsia_kernel_compiler.dart'; +import 'fuchsia/fuchsia_pm.dart'; import 'fuchsia/fuchsia_sdk.dart'; import 'fuchsia/fuchsia_workflow.dart'; import 'ios/cocoapods.dart'; @@ -52,36 +54,38 @@ body: runner, overrides: overrides, fallbacks: <Type, Generator>{ + AndroidLicenseValidator: () => AndroidLicenseValidator(), AndroidSdk: AndroidSdk.locateAndroidSdk, AndroidStudio: AndroidStudio.latestValid, - AndroidWorkflow: () => AndroidWorkflow(), AndroidValidator: () => AndroidValidator(), - AndroidLicenseValidator: () => AndroidLicenseValidator(), + AndroidWorkflow: () => AndroidWorkflow(), ApplicationPackageFactory: () => ApplicationPackageFactory(), Artifacts: () => CachedArtifacts(), AssetBundleFactory: () => AssetBundleFactory.defaultInstance, BotDetector: () => const BotDetector(), Cache: () => Cache(), + ChromeLauncher: () => const ChromeLauncher(), CocoaPods: () => CocoaPods(), CocoaPodsValidator: () => const CocoaPodsValidator(), Config: () => Config(), - ChromeLauncher: () => const ChromeLauncher(), DevFSConfig: () => DevFSConfig(), DeviceManager: () => DeviceManager(), Doctor: () => const Doctor(), DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, EmulatorManager: () => EmulatorManager(), - FuchsiaSdk: () => FuchsiaSdk(), - FuchsiaArtifacts: () => FuchsiaArtifacts.find(), - FuchsiaWorkflow: () => FuchsiaWorkflow(), Flags: () => const EmptyFlags(), FlutterVersion: () => FlutterVersion(const SystemClock()), + FuchsiaArtifacts: () => FuchsiaArtifacts.find(), + FuchsiaKernelCompiler: () => FuchsiaKernelCompiler(), + FuchsiaPM: () => FuchsiaPM(), + FuchsiaSdk: () => FuchsiaSdk(), + FuchsiaWorkflow: () => FuchsiaWorkflow(), GenSnapshot: () => const GenSnapshot(), HotRunnerConfig: () => HotRunnerConfig(), IMobileDevice: () => const IMobileDevice(), IOSSimulatorUtils: () => IOSSimulatorUtils(), - IOSWorkflow: () => const IOSWorkflow(), IOSValidator: () => const IOSValidator(), + IOSWorkflow: () => const IOSWorkflow(), KernelCompilerFactory: () => const KernelCompilerFactory(), LinuxWorkflow: () => const LinuxWorkflow(), Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(), @@ -89,13 +93,13 @@ OperatingSystemUtils: () => OperatingSystemUtils(), PlistBuddy: () => const PlistBuddy(), SimControl: () => SimControl(), - SystemClock: () => const SystemClock(), Stdio: () => const Stdio(), + SystemClock: () => const SystemClock(), TimeoutConfiguration: () => const TimeoutConfiguration(), Usage: () => Usage(), UserMessages: () => UserMessages(), - WindowsWorkflow: () => const WindowsWorkflow(), WebCompiler: () => const WebCompiler(), + WindowsWorkflow: () => const WindowsWorkflow(), Xcode: () => Xcode(), XcodeProjectInterpreter: () => XcodeProjectInterpreter(), },
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart new file mode 100644 index 0000000..c39b83b --- /dev/null +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
@@ -0,0 +1,111 @@ +// 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:meta/meta.dart'; + +import '../asset.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../build_info.dart'; +import '../bundle.dart'; +import '../devfs.dart'; +import '../project.dart'; + +import 'fuchsia_kernel_compiler.dart'; +import 'fuchsia_pm.dart'; + +// Building a Fuchsia package has a few steps: +// 1. Do the custom kernel compile using the kernel compiler from the Fuchsia +// SDK. This produces .dilp files (among others) and a manifest file. +// 2. Create a manifest file for assets. +// 3. Using these manifests, use the Fuchsia SDK 'pm' tool to create the +// Fuchsia package. +Future<void> buildFuchsia( + {@required FuchsiaProject fuchsiaProject, + @required String target, // E.g., lib/main.dart + BuildInfo buildInfo = BuildInfo.debug}) async { + final Directory outDir = fs.directory(getFuchsiaBuildDirectory()); + if (!outDir.existsSync()) { + outDir.createSync(recursive: true); + } + + await fuchsiaKernelCompiler.build( + fuchsiaProject: fuchsiaProject, target: target, buildInfo: buildInfo); + await _buildAssets(fuchsiaProject, target, buildInfo); + await _buildPackage(fuchsiaProject, target, buildInfo); +} + +Future<void> _buildAssets( + FuchsiaProject fuchsiaProject, + String target, // lib/main.dart + BuildInfo buildInfo) async { + final String assetDir = getAssetBuildDirectory(); + final AssetBundle assets = await buildAssets( + manifestPath: fuchsiaProject.project.pubspecFile.path, + packagesPath: fuchsiaProject.project.packagesFile.path, + assetDirPath: assetDir, + includeDefaultFonts: false, + ); + + final Map<String, DevFSContent> assetEntries = + Map<String, DevFSContent>.from(assets.entries); + await writeBundle(fs.directory(assetDir), assetEntries); + + final String appName = fuchsiaProject.project.manifest.appName; + final String outDir = getFuchsiaBuildDirectory(); + final String assetManifest = fs.path.join(outDir, '${appName}_pkgassets'); + + final File destFile = fs.file(assetManifest); + await destFile.create(recursive: true); + final IOSink outFile = destFile.openWrite(); + + for (String path in assets.entries.keys) { + outFile.write('data/$appName/$path=$assetDir/$path\n'); + } + await outFile.flush(); + await outFile.close(); +} + +// TODO(zra): Allow supplying a signing key. +Future<void> _buildPackage( + FuchsiaProject fuchsiaProject, + String target, // lib/main.dart + BuildInfo buildInfo) async { + final String outDir = getFuchsiaBuildDirectory(); + final String pkgDir = fs.path.join(outDir, 'pkg'); + final String appName = fuchsiaProject.project.manifest.appName; + final String dilpmanifest = fs.path.join(outDir, '$appName.dilpmanifest'); + final String pkgassets = fs.path.join(outDir, '${appName}_pkgassets'); + final String packageManifest = fs.path.join(pkgDir, 'package_manifest'); + final String devKeyPath = fs.path.join(pkgDir, 'development.key'); + + final Directory pkg = fs.directory(pkgDir); + if (!pkg.existsSync()) { + pkg.createSync(recursive: true); + } + + // Concatenate dilpmanifest and pkgassets into package_manifest. + final File manifestFile = fs.file(packageManifest); + manifestFile.writeAsStringSync(fs.file(dilpmanifest).readAsStringSync()); + manifestFile.writeAsStringSync(fs.file(pkgassets).readAsStringSync(), + mode: FileMode.append); + manifestFile.writeAsStringSync( + 'meta/$appName.cmx=${fuchsiaProject.meta.path}/$appName.cmx\n', + mode: FileMode.append); + manifestFile.writeAsStringSync('meta/package=$pkgDir/meta/package\n', + mode: FileMode.append); + + if (!await fuchsiaPM.init(pkgDir, appName)) { + return; + } + if (!await fuchsiaPM.genkey(pkgDir, devKeyPath)) { + return; + } + if (!await fuchsiaPM.build(pkgDir, devKeyPath, packageManifest)) { + return; + } + if (!await fuchsiaPM.archive(pkgDir, devKeyPath, packageManifest)) { + return; + } +}
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart new file mode 100644 index 0000000..e31a5bf --- /dev/null +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_kernel_compiler.dart
@@ -0,0 +1,114 @@ +// 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:meta/meta.dart'; + +import '../artifacts.dart'; +import '../base/common.dart'; +import '../base/context.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../convert.dart'; +import '../globals.dart'; +import '../project.dart'; + +import 'fuchsia_sdk.dart'; + +/// The [FuchsiaKernelCompiler] instance. +FuchsiaKernelCompiler get fuchsiaKernelCompiler => + context.get<FuchsiaKernelCompiler>(); + +class FuchsiaKernelCompiler { + /// Compiles the [fuchsiaProject] with entrypoint [target] to a collection of + /// .dilp files (consisting of the app split along package: boundaries, but + /// the Flutter tool should make no use of that fact), and a manifest that + /// refers to them. + Future<void> build({ + @required FuchsiaProject fuchsiaProject, + @required String target, // E.g., lib/main.dart + BuildInfo buildInfo = BuildInfo.debug, + }) async { + // TODO(zra): Use filesystem root and scheme information from buildInfo. + const String multiRootScheme = 'main-root'; + final String packagesFile = fuchsiaProject.project.packagesFile.path; + final String outDir = getFuchsiaBuildDirectory(); + final String appName = fuchsiaProject.project.manifest.appName; + final String fsRoot = fuchsiaProject.project.directory.path; + final String relativePackagesFile = + fs.path.relative(packagesFile, from: fsRoot); + final String manifestPath = fs.path.join(outDir, '$appName.dilpmanifest'); + List<String> flags = <String>[ + // https://github.com/dart-lang/sdk/issues/36639: + // Remove when new constant eval supports dilp files. + '--enable-experiment=no-constant-update-2018', + '--target', 'flutter_runner', + '--platform', fuchsiaArtifacts.platformKernelDill.path, + '--filesystem-scheme', 'main-root', + '--filesystem-root', fsRoot, + '--packages', '$multiRootScheme:///$relativePackagesFile', + '--output', fs.path.join(outDir, '$appName.dil'), + '--no-link-platform', + '--split-output-by-packages', + '--manifest', manifestPath, + '--component-name', appName, + ]; + + if (buildInfo.isDebug) { + flags += <String>[ + '--embed-sources', + ]; + } else if (buildInfo.isProfile) { + flags += <String>[ + '--no-embed-sources', + '-Ddart.vm.profile=true', + '--gen-bytecode', + '--drop-ast', + ]; + } else if (buildInfo.isRelease) { + flags += <String>[ + '--no-embed-sources', + '-Ddart.vm.release=true', + '--gen-bytecode', + '--drop-ast', + ]; + } else { + throwToolExit('Expected build type to be debug, profile, or release'); + } + + flags += <String>[ + '$multiRootScheme:///$target', + ]; + + final List<String> command = <String>[ + artifacts.getArtifactPath(Artifact.engineDartBinary), + fuchsiaArtifacts.kernelCompiler.path, + ]..addAll(flags); + printTrace("Running: '${command.join(' ')}'"); + final Process process = await processManager.start(command); + final Status status = logger.startProgress( + 'Building Fuchsia application...', + timeout: null, + ); + int result; + try { + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printError); + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printTrace); + result = await process.exitCode; + } finally { + status.cancel(); + } + if (result != 0) { + throwToolExit('Build process failed'); + } + } +}
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart new file mode 100644 index 0000000..755e08d --- /dev/null +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_pm.dart
@@ -0,0 +1,134 @@ +// 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 '../base/context.dart'; +import '../base/io.dart'; +import '../base/process_manager.dart'; +import '../globals.dart'; + +import 'fuchsia_sdk.dart'; + +/// The [FuchsiaPM] instance. +FuchsiaPM get fuchsiaPM => context.get<FuchsiaPM>(); + +/// This is a basic wrapper class for the Fuchsia SDK's `pm` tool. +class FuchsiaPM { + /// Initializes the staging area at [buildPath] for creating the Fuchsia + /// package for the app named [appName]. + /// + /// When successful, this creates a file under [buildPath] at `meta/package`. + /// + /// NB: The [buildPath] should probably be e.g. `build/fuchsia/pkg`, and the + /// [appName] should probably be the name of the app from the pubspec file. + Future<bool> init(String buildPath, String appName) async { + final List<String> command = <String>[ + fuchsiaArtifacts.pm.path, + '-o', + buildPath, + '-n', + appName, + 'init', + ]; + printTrace("Running: '${command.join(' ')}'"); + final ProcessResult result = await processManager.run(command); + if (result.exitCode != 0) { + printError('Error initializing Fuchsia package for $appName: '); + printError(result.stdout); + printError(result.stderr); + return false; + } + return true; + } + + /// Generates a new private key to be used to sign a Fuchsia package. + /// + /// [buildPath] should be the same [buildPath] passed to [init]. + Future<bool> genkey(String buildPath, String outKeyPath) async { + final List<String> command = <String>[ + fuchsiaArtifacts.pm.path, + '-o', + buildPath, + '-k', + outKeyPath, + 'genkey', + ]; + printTrace("Running: '${command.join(' ')}'"); + final ProcessResult result = await processManager.run(command); + if (result.exitCode != 0) { + printError('Error generating key for Fuchsia package: '); + printError(result.stdout); + printError(result.stderr); + return false; + } + return true; + } + + /// Updates, signs, and seals a Fuchsia package. + /// + /// [buildPath] should be the same [buildPath] passed to [init]. + /// [manifestPath] must be a file containing lines formatted as follows: + /// + /// data/path/to/file/in/the/package=/path/to/file/on/the/host + /// + /// which describe the contents of the Fuchsia package. It must also contain + /// two other entries: + /// + /// meta/$APPNAME.cmx=/path/to/cmx/on/the/host/$APPNAME.cmx + /// meta/package=/path/to/package/file/from/init/package + /// + /// where $APPNAME is the same [appName] passed to [init], and meta/package + /// is set up to be the file `meta/package` created by [init]. + Future<bool> build( + String buildPath, String keyPath, String manifestPath) async { + final List<String> command = <String>[ + fuchsiaArtifacts.pm.path, + '-o', + buildPath, + '-k', + keyPath, + '-m', + manifestPath, + 'build', + ]; + printTrace("Running: '${command.join(' ')}'"); + final ProcessResult result = await processManager.run(command); + if (result.exitCode != 0) { + printError('Error building Fuchsia package: '); + printError(result.stdout); + printError(result.stderr); + return false; + } + return true; + } + + /// Constructs a .far representation of the Fuchsia package. + /// + /// When successful, creates a file `app_name-0.far` under [buildPath], which + /// is the Fuchsia package. + /// + /// [buildPath] should be the same path passed to [init], and [manfiestPath] + /// should be the same manifest passed to [build]. + Future<bool> archive( + String buildPath, String keyPath, String manifestPath) async { + final List<String> command = <String>[ + fuchsiaArtifacts.pm.path, + '-o', + buildPath, + '-k', + keyPath, + '-m', + manifestPath, + 'archive', + ]; + printTrace("Running: '${command.join(' ')}'"); + final ProcessResult result = await processManager.run(command); + if (result.exitCode != 0) { + printError('Error archiving Fuchsia package: '); + printError(result.stdout); + printError(result.stderr); + return false; + } + return true; + } +}
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart index d7d3a51..256eae2 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
@@ -86,6 +86,8 @@ this.devFinder, this.platformKernelDill, this.flutterPatchedSdk, + this.kernelCompiler, + this.pm, }); /// Creates a new [FuchsiaArtifacts] using the cached Fuchsia SDK. @@ -116,6 +118,9 @@ dartPrebuilts, 'flutter_runner', 'platform_strong.dill')), flutterPatchedSdk: fs.file(fs.path.join( dartPrebuilts, 'flutter_runner')), + kernelCompiler: fs.file(fs.path.join( + dartPrebuilts, 'kernel_compiler.snapshot')), + pm: fs.file(fs.path.join(tools, 'pm')), ); } @@ -135,4 +140,10 @@ /// The directory containing [platformKernelDill]. final File flutterPatchedSdk; + + /// The snapshot of the Fuchsia kernel compiler. + final File kernelCompiler; + + /// The pm tool. + final File pm; }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index ae8e405..2decfcf 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -91,22 +91,32 @@ } /// The iOS sub project of this project. - IosProject get ios => IosProject.fromFlutter(this); + IosProject _ios; + IosProject get ios => _ios ??= IosProject.fromFlutter(this); /// The Android sub project of this project. - AndroidProject get android => AndroidProject._(this); + AndroidProject _android; + AndroidProject get android => _android ??= AndroidProject._(this); /// The web sub project of this project. - WebProject get web => WebProject._(this); + WebProject _web; + WebProject get web => _web ??= WebProject._(this); - /// The macos sub project of this project. - MacOSProject get macos => MacOSProject._(this); + /// The MacOS sub project of this project. + MacOSProject _macos; + MacOSProject get macos => _macos ??= MacOSProject._(this); - /// The linux sub project of this project. - LinuxProject get linux => LinuxProject._(this); + /// The Linux sub project of this project. + LinuxProject _linux; + LinuxProject get linux => _linux ??= LinuxProject._(this); - /// The windows sub project of this project. - WindowsProject get windows => WindowsProject._(this); + /// The Windows sub project of this project. + WindowsProject _windows; + WindowsProject get windows => _windows ??= WindowsProject._(this); + + /// The Fuchsia sub project of this project. + FuchsiaProject _fuchsia; + FuchsiaProject get fuchsia => _fuchsia ??= FuchsiaProject._(this); /// The `pubspec.yaml` file of this project. File get pubspecFile => directory.childFile('pubspec.yaml'); @@ -606,3 +616,20 @@ /// The Linux project makefile. File get makeFile => editableHostAppDirectory.childFile('Makefile'); } + +/// The Fuchisa sub project +class FuchsiaProject { + FuchsiaProject._(this.project); + + final FlutterProject project; + + Directory _editableHostAppDirectory; + Directory get editableHostAppDirectory => + _editableHostAppDirectory ??= project.directory.childDirectory('fuchsia'); + + bool existsSync() => editableHostAppDirectory.existsSync(); + + Directory _meta; + Directory get meta => + _meta ??= editableHostAppDirectory.childDirectory('meta'); +}
diff --git a/packages/flutter_tools/test/commands/build_fuchsia_test.dart b/packages/flutter_tools/test/commands/build_fuchsia_test.dart new file mode 100644 index 0000000..a31f596 --- /dev/null +++ b/packages/flutter_tools/test/commands/build_fuchsia_test.dart
@@ -0,0 +1,191 @@ +// 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/platform.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/build.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_kernel_compiler.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_pm.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:meta/meta.dart'; +import 'package:mockito/mockito.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; +import '../src/mocks.dart'; + +void main() { + Cache.disableLocking(); + + MemoryFileSystem memoryFileSystem; + MockPlatform linuxPlatform; + MockPlatform windowsPlatform; + MockFuchsiaPM fuchsiaPM; + MockFuchsiaKernelCompiler fuchsiaKernelCompiler; + + setUp(() { + memoryFileSystem = MemoryFileSystem(); + linuxPlatform = MockPlatform(); + windowsPlatform = MockPlatform(); + fuchsiaPM = MockFuchsiaPM(); + fuchsiaKernelCompiler = MockFuchsiaKernelCompiler(); + + when(linuxPlatform.isLinux).thenReturn(true); + when(windowsPlatform.isWindows).thenReturn(true); + when(windowsPlatform.isLinux).thenReturn(false); + when(windowsPlatform.isMacOS).thenReturn(false); + }); + + testUsingContext('Fuchsia build fails when there is no fuchsia project', + () async { + final BuildCommand command = BuildCommand(); + applyMocksToCommand(command); + expect( + createTestCommandRunner(command) + .run(const <String>['build', 'fuchsia']), + throwsA(isInstanceOf<ToolExit>())); + }, overrides: <Type, Generator>{ + Platform: () => linuxPlatform, + FileSystem: () => memoryFileSystem, + }); + + testUsingContext('Fuchsia build fails when there is no cmx file', () async { + final BuildCommand command = BuildCommand(); + applyMocksToCommand(command); + fs.directory('fuchsia').createSync(recursive: true); + fs.file('.packages').createSync(); + fs.file('pubspec.yaml').createSync(); + + expect( + createTestCommandRunner(command) + .run(const <String>['build', 'fuchsia']), + throwsA(isInstanceOf<ToolExit>())); + }, overrides: <Type, Generator>{ + Platform: () => linuxPlatform, + FileSystem: () => memoryFileSystem, + }); + + testUsingContext('Fuchsia build fails on Windows platform', () async { + final BuildCommand command = BuildCommand(); + applyMocksToCommand(command); + const String appName = 'app_name'; + fs + .file(fs.path.join('fuchsia', 'meta', '$appName.cmx')) + .createSync(recursive: true); + fs.file('.packages').createSync(); + final File pubspecFile = fs.file('pubspec.yaml')..createSync(); + pubspecFile.writeAsStringSync('name: $appName'); + + expect( + createTestCommandRunner(command) + .run(const <String>['build', 'fuchsia']), + throwsA(isInstanceOf<ToolExit>())); + }, overrides: <Type, Generator>{ + Platform: () => windowsPlatform, + FileSystem: () => memoryFileSystem, + }); + + testUsingContext('Fuchsia build parts fit together right', () async { + final BuildCommand command = BuildCommand(); + applyMocksToCommand(command); + const String appName = 'app_name'; + fs + .file(fs.path.join('fuchsia', 'meta', '$appName.cmx')) + .createSync(recursive: true); + fs.file('.packages').createSync(); + fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true); + final File pubspecFile = fs.file('pubspec.yaml')..createSync(); + pubspecFile.writeAsStringSync('name: $appName'); + + await createTestCommandRunner(command) + .run(const <String>['build', 'fuchsia']); + final String farPath = + fs.path.join(getFuchsiaBuildDirectory(), 'pkg', 'app_name-0.far'); + expect(fs.file(farPath).existsSync(), isTrue); + }, overrides: <Type, Generator>{ + Platform: () => linuxPlatform, + FileSystem: () => memoryFileSystem, + FuchsiaPM: () => fuchsiaPM, + FuchsiaKernelCompiler: () => fuchsiaKernelCompiler, + }); +} + +class MockPlatform extends Mock implements Platform { + @override + Map<String, String> environment = <String, String>{ + 'FLUTTER_ROOT': '/', + }; +} + +class MockFuchsiaPM extends Mock implements FuchsiaPM { + String _appName; + + @override + Future<bool> init(String buildPath, String appName) async { + if (!fs.directory(buildPath).existsSync()) { + return false; + } + fs + .file(fs.path.join(buildPath, 'meta', 'package')) + .createSync(recursive: true); + _appName = appName; + return true; + } + + @override + Future<bool> genkey(String buildPath, String outKeyPath) async { + if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync()) { + return false; + } + fs.file(outKeyPath).createSync(recursive: true); + return true; + } + + @override + Future<bool> build( + String buildPath, String keyPath, String manifestPath) async { + if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() || + !fs.file(keyPath).existsSync() || + !fs.file(manifestPath).existsSync()) { + return false; + } + fs.file(fs.path.join(buildPath, 'meta.far')).createSync(recursive: true); + return true; + } + + @override + Future<bool> archive( + String buildPath, String keyPath, String manifestPath) async { + if (!fs.file(fs.path.join(buildPath, 'meta', 'package')).existsSync() || + !fs.file(keyPath).existsSync() || + !fs.file(manifestPath).existsSync()) { + return false; + } + if (_appName == null) { + return false; + } + fs + .file(fs.path.join(buildPath, '$_appName-0.far')) + .createSync(recursive: true); + return true; + } +} + +class MockFuchsiaKernelCompiler extends Mock implements FuchsiaKernelCompiler { + @override + Future<void> build({ + @required FuchsiaProject fuchsiaProject, + @required String target, // E.g., lib/main.dart + BuildInfo buildInfo = BuildInfo.debug, + }) async { + final String outDir = getFuchsiaBuildDirectory(); + final String appName = fuchsiaProject.project.manifest.appName; + final String manifestPath = fs.path.join(outDir, '$appName.dilpmanifest'); + fs.file(manifestPath).createSync(recursive: true); + } +}