Unverified Commit e91b98a4 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add initial implementation of flutter assemble (#32816)

parent fb9ff929
......@@ -14,6 +14,7 @@ import 'src/build_runner/web_compilation_delegate.dart';
import 'src/codegen.dart';
import 'src/commands/analyze.dart';
import 'src/commands/assemble.dart';
import 'src/commands/attach.dart';
import 'src/commands/build.dart';
import 'src/commands/channel.dart';
......@@ -61,6 +62,7 @@ Future<void> main(List<String> args) async {
await runner.run(args, <FlutterCommand>[
AnalyzeCommand(verboseHelp: verboseHelp),
AssembleCommand(),
AttachCommand(verboseHelp: verboseHelp),
BuildCommand(verboseHelp: verboseHelp),
ChannelCommand(verboseHelp: verboseHelp),
......
......@@ -14,10 +14,13 @@ import 'dart/sdk.dart';
import 'globals.dart';
enum Artifact {
/// The tool which compiles a dart kernel file into native code.
genSnapshot,
/// The flutter tester binary.
flutterTester,
snapshotDart,
flutterFramework,
/// The framework directory of the macOS desktop.
flutterMacOSFramework,
vmSnapshotData,
isolateSnapshotData,
......@@ -25,12 +28,24 @@ enum Artifact {
platformLibrariesJson,
flutterPatchedSdkPath,
frontendServerSnapshotForEngineDartSdk,
/// The root directory of the dartk SDK.
engineDartSdkPath,
/// The dart binary used to execute any of the required snapshots.
engineDartBinary,
/// The dart snapshot of the dart2js compiler.
dart2jsSnapshot,
/// The dart snapshot of the dartdev compiler.
dartdevcSnapshot,
/// The dart snpashot of the kernel worker compiler.
kernelWorkerSnapshot,
/// The root of the web implementation of the dart SDK.
flutterWebSdk,
/// The root of the Linux desktop sources.
linuxDesktopPath,
/// The root of the Windows desktop sources.
windowsDesktopPath,
/// The root of the sky_engine package
skyEnginePath,
}
String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMode mode ]) {
......@@ -47,6 +62,10 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo
case Artifact.flutterFramework:
return 'Flutter.framework';
case Artifact.flutterMacOSFramework:
if (platform != TargetPlatform.darwin_x64) {
throw Exception('${getNameForTargetPlatform(platform)} does not support'
' macOS desktop development');
}
return 'FlutterMacOS.framework';
case Artifact.vmSnapshotData:
return 'vm_isolate_snapshot.bin';
......@@ -74,6 +93,20 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo
return 'dartdevc.dart.snapshot';
case Artifact.kernelWorkerSnapshot:
return 'kernel_worker.dart.snapshot';
case Artifact.linuxDesktopPath:
if (platform != TargetPlatform.linux_x64) {
throw Exception('${getNameForTargetPlatform(platform)} does not support'
' Linux desktop development');
}
return '';
case Artifact.windowsDesktopPath:
if (platform != TargetPlatform.windows_x64) {
throw Exception('${getNameForTargetPlatform(platform)} does not support'
' Windows desktop development');
}
return '';
case Artifact.skyEnginePath:
return 'sky_engine';
}
assert(false, 'Invalid artifact $artifact.');
return null;
......@@ -209,9 +242,14 @@ class CachedArtifacts extends Artifacts {
case Artifact.kernelWorkerSnapshot:
return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
case Artifact.flutterMacOSFramework:
case Artifact.linuxDesktopPath:
case Artifact.windowsDesktopPath:
final String engineArtifactsPath = cache.getArtifactDirectory('engine').path;
final String platformDirName = getNameForTargetPlatform(platform);
return fs.path.join(engineArtifactsPath, platformDirName, _artifactToFileName(artifact, platform, mode));
case Artifact.skyEnginePath:
final Directory dartPackageDirectory = cache.getCacheDir('pkg');
return fs.path.join(dartPackageDirectory.path, _artifactToFileName(artifact));
default:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
......@@ -302,6 +340,12 @@ class LocalEngineArtifacts extends Artifacts {
return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
case Artifact.kernelWorkerSnapshot:
return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact));
case Artifact.linuxDesktopPath:
return fs.path.join(_hostEngineOutPath, _artifactToFileName(artifact));
case Artifact.windowsDesktopPath:
return fs.path.join(_hostEngineOutPath, _artifactToFileName(artifact));
case Artifact.skyEnginePath:
return fs.path.join(_hostEngineOutPath, 'gen', 'dart-pkg', _artifactToFileName(artifact));
}
assert(false, 'Invalid artifact $artifact.');
return null;
......
......@@ -9,7 +9,6 @@ import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../build_info.dart';
import '../bundle.dart';
import '../cache.dart';
import '../compile.dart';
import '../dart/package_map.dart';
import '../globals.dart';
......@@ -95,10 +94,6 @@ class AOTSnapshotter {
IOSArch iosArch,
List<String> extraGenSnapshotOptions = const <String>[],
}) async {
FlutterProject flutterProject;
if (fs.file('pubspec.yaml').existsSync()) {
flutterProject = FlutterProject.current();
}
if (!_isValidAotPlatform(platform, buildMode)) {
printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
return 1;
......@@ -122,8 +117,6 @@ class AOTSnapshotter {
final List<String> inputPaths = <String>[uiPath, vmServicePath, mainPath];
final Set<String> outputPaths = <String>{};
final String depfilePath = fs.path.join(outputDir.path, 'snapshot.d');
final List<String> genSnapshotArgs = <String>[
'--deterministic',
];
......@@ -165,26 +158,6 @@ class AOTSnapshotter {
return 1;
}
// If inputs and outputs have not changed since last run, skip the build.
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: '$depfilePath.fingerprint',
paths: <String>[mainPath, ...inputPaths, ...outputPaths],
properties: <String, String>{
'buildMode': buildMode.toString(),
'targetPlatform': platform.toString(),
'entryPoint': mainPath,
'extraGenSnapshotOptions': extraGenSnapshotOptions.join(' '),
'engineHash': Cache.instance.engineRevision,
'buildersUsed': '${flutterProject != null && flutterProject.hasBuilders}',
},
depfilePaths: <String>[],
);
// TODO(jonahwilliams): re-enable once this can be proved correct.
// if (await fingerprinter.doesFingerprintMatch()) {
// printTrace('Skipping AOT snapshot build. Fingerprint match.');
// return 0;
// }
final SnapshotType snapshotType = SnapshotType(platform, buildMode);
final int genSnapshotExitCode =
await _timedStep('snapshot(CompileTime)', 'aot-snapshot',
......@@ -210,9 +183,6 @@ class AOTSnapshotter {
if (result.exitCode != 0)
return result.exitCode;
}
// Compute and record build fingerprint.
await fingerprinter.writeFingerprint();
return 0;
}
......
......@@ -115,6 +115,32 @@ enum BuildMode {
release,
}
const List<String> _kBuildModes = <String>[
'debug',
'profile',
'release',
'dynamic-profile',
'dynamic-release',
];
/// Return the name for the build mode, or "any" if null.
String getNameForBuildMode(BuildMode buildMode) {
return _kBuildModes[buildMode.index];
}
/// Returns the [BuildMode] for a particular `name`.
BuildMode getBuildModeForName(String name) {
switch (name) {
case 'debug':
return BuildMode.debug;
case 'profile':
return BuildMode.profile;
case 'release':
return BuildMode.release;
}
return null;
}
String validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String buildNumber) {
if (buildNumber == null) {
return null;
......
This diff is collapsed.
// 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/file_system.dart';
import 'build_system.dart';
/// An exception thrown when a rule declares an input that does not exist on
/// disk.
class MissingInputException implements Exception {
const MissingInputException(this.missing, this.target);
/// The file or directory we expected to find.
final List<File> missing;
/// The name of the target this file should have been output from.
final String target;
@override
String toString() {
final String files = missing.map((File file) => file.path).join(', ');
return '$files were declared as an inputs, but did not exist. '
'Check the definition of target:$target for errors';
}
}
/// An exception thrown if we detect a cycle in the dependencies of a target.
class CycleException implements Exception {
CycleException(this.targets);
final Set<Target> targets;
@override
String toString() => 'Dependency cycle detected in build: '
'${targets.map((Target target) => target.name).join(' -> ')}';
}
/// An exception thrown when a pattern is invalid.
class InvalidPatternException implements Exception {
InvalidPatternException(this.pattern);
final String pattern;
@override
String toString() => 'The pattern "$pattern" is not valid';
}
/// An exception thrown when a rule declares an output that was not produced
/// by the invocation.
class MissingOutputException implements Exception {
const MissingOutputException(this.missing, this.target);
/// The files we expected to find.
final List<File> missing;
/// The name of the target this file should have been output from.
final String target;
@override
String toString() {
final String files = missing.map((File file) => file.path).join(', ');
return '$files were declared as outputs, but were not generated by '
'the action. Check the definition of target:$target for errors';
}
}
/// An exception thrown when in output is placed outside of
/// [Environment.buildDir].
class MisplacedOutputException implements Exception {
MisplacedOutputException(this.path, this.target);
final String path;
final String target;
@override
String toString() {
return 'Target $target produced an output at $path'
' which is outside of the current build or project directory';
}
}
/// An exception thrown if a build action is missing a required define.
class MissingDefineException implements Exception {
MissingDefineException(this.define, this.target);
final String define;
final String target;
@override
String toString() {
return 'Target $target required define $define '
'but it was not provided';
}
}
// 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 'dart:collection';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import '../base/file_system.dart';
import '../globals.dart';
import 'build_system.dart';
import 'filecache.pb.dart' as pb;
/// A globally accessible cache of file hashes.
///
/// In cases where multiple targets read the same source files as inputs, we
/// avoid recomputing or storing multiple copies of hashes by delegating
/// through this class. All file hashes are held in memory during a build
/// operation, and persisted to cache in the root build directory.
///
/// The format of the file store is subject to change and not part of its API.
///
/// To regenerate the protobuf entries used to construct the cache:
/// 1. If not already installed, https://developers.google.com/protocol-buffers/docs/downloads
/// 2. pub global active `protoc-gen-dart`
/// 3. protoc -I=lib/src/build_system/ --dart_out=lib/src/build_system/ lib/src/build_system/filecache.proto
/// 4. Add licenses headers to the newly generated file and check-in.
///
/// See also: https://developers.google.com/protocol-buffers/docs/darttutorial
// TODO(jonahwilliams): find a better way to clear out old entries, perhaps
// track the last access or modification date?
class FileHashStore {
FileHashStore(this.environment);
final Environment environment;
final HashMap<String, String> previousHashes = HashMap<String, String>();
final HashMap<String, String> currentHashes = HashMap<String, String>();
// The name of the file which stores the file hashes.
static const String _kFileCache = '.filecache';
// The current version of the file cache storage format.
static const int _kVersion = 1;
/// Read file hashes from disk.
void initialize() {
printTrace('Initializing file store');
if (!_cacheFile.existsSync()) {
return;
}
final List<int> data = _cacheFile.readAsBytesSync();
final pb.FileStorage fileStorage = pb.FileStorage.fromBuffer(data);
if (fileStorage.version != _kVersion) {
_cacheFile.deleteSync();
return;
}
for (pb.FileHash fileHash in fileStorage.files) {
previousHashes[fileHash.path] = fileHash.hash;
}
printTrace('Done initializing file store');
}
/// Persist file hashes to disk.
void persist() {
printTrace('Persisting file store');
final pb.FileStorage fileStorage = pb.FileStorage();
fileStorage.version = _kVersion;
final File file = _cacheFile;
if (!file.existsSync()) {
file.createSync();
}
for (MapEntry<String, String> entry in currentHashes.entries) {
previousHashes[entry.key] = entry.value;
}
for (MapEntry<String, String> entry in previousHashes.entries) {
final pb.FileHash fileHash = pb.FileHash();
fileHash.path = entry.key;
fileHash.hash = entry.value;
fileStorage.files.add(fileHash);
}
final Uint8List buffer = fileStorage.writeToBuffer();
file.writeAsBytesSync(buffer);
printTrace('Done persisting file store');
}
/// Computes a hash of the provided files and returns a list of entities
/// that were dirty.
// TODO(jonahwilliams): compare hash performance with md5 tool on macOS and
// linux and certutil on Windows, as well as dividing up computation across
// isolates. This also related to the current performance issue with checking
// APKs before installing them on device.
Future<List<File>> hashFiles(List<File> files) async {
final List<File> dirty = <File>[];
for (File file in files) {
final String absolutePath = file.resolveSymbolicLinksSync();
final String previousHash = previousHashes[absolutePath];
final List<int> bytes = file.readAsBytesSync();
final String currentHash = md5.convert(bytes).toString();
if (currentHash != previousHash) {
dirty.add(file);
}
currentHashes[absolutePath] = currentHash;
}
return dirty;
}
File get _cacheFile => environment.rootBuildDir.childFile(_kFileCache);
}
// 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.
///
// Generated code. Do not modify.
// source: lib/src/build_system/filecache.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name, sort_constructors_first
import 'dart:core' as $core show bool, Deprecated, double, int, List, Map, override, pragma, String, dynamic;
import 'package:protobuf/protobuf.dart' as $pb;
class FileHash extends $pb.GeneratedMessage {
factory FileHash() => create();
static final $pb.BuilderInfo _i = $pb.BuilderInfo('FileHash', package: const $pb.PackageName('flutter_tools'))
..aOS(1, 'path')
..aOS(2, 'hash')
..hasRequiredFields = false;
FileHash._() : super();
factory FileHash.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory FileHash.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.override
FileHash clone() => FileHash()..mergeFromMessage(this);
@$core.override
FileHash copyWith(void Function(FileHash) updates) => super.copyWith(($core.dynamic message) => updates(message as FileHash));
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static FileHash create() => FileHash._();
@$core.override
FileHash createEmptyInstance() => create();
static $pb.PbList<FileHash> createRepeated() => $pb.PbList<FileHash>();
static FileHash getDefault() => _defaultInstance ??= create()..freeze();
static FileHash _defaultInstance;
$core.String get path => $_getS(0, '');
set path($core.String v) { $_setString(0, v); }
$core.bool hasPath() => $_has(0);
void clearPath() => clearField(1);
$core.String get hash => $_getS(1, '');
set hash($core.String v) { $_setString(1, v); }
$core.bool hasHash() => $_has(1);
void clearHash() => clearField(2);
}
class FileStorage extends $pb.GeneratedMessage {
factory FileStorage() => create();
static final $pb.BuilderInfo _i = $pb.BuilderInfo('FileHashStore', package: const $pb.PackageName('flutter_tools'))
..a<$core.int>(1, 'version', $pb.PbFieldType.O3)
..pc<FileHash>(2, 'files', $pb.PbFieldType.PM,FileHash.create)
..hasRequiredFields = false;
FileStorage._() : super();
factory FileStorage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory FileStorage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.override
FileStorage clone() => FileStorage()..mergeFromMessage(this);
@$core.override
FileStorage copyWith(void Function(FileStorage) updates) => super.copyWith(($core.dynamic message) => updates(message as FileStorage));
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static FileStorage create() => FileStorage._();
@$core.override
FileStorage createEmptyInstance() => create();
static $pb.PbList<FileStorage> createRepeated() => $pb.PbList<FileStorage>();
static FileStorage getDefault() => _defaultInstance ??= create()..freeze();
static FileStorage _defaultInstance;
$core.int get version => $_get(0, 0);
set version($core.int v) { $_setSignedInt32(0, v); }
$core.bool hasVersion() => $_has(0);
void clearVersion() => clearField(1);
$core.List<FileHash> get files => $_getList(1);
}
// 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.
///
// Generated code. Do not modify.
// source: lib/src/build_system/filecache.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
const Map<String, Object> FileHash$json = <String, Object>{
'1': 'FileHash',
'2': <Map<String, Object>>[
<String, Object>{'1': 'path', '3': 1, '4': 1, '5': 9, '10': 'path'},
<String, Object>{'1': 'hash', '3': 2, '4': 1, '5': 9, '10': 'hash'},
],
};
const Map<String, Object> FileStorage$json = <String, Object>{
'1': 'FileHashStore',
'2': <Map<String, Object>>[
<String, Object>{'1': 'version', '3': 1, '4': 1, '5': 5, '10': 'version'},
<String, Object>{'1': 'files', '3': 2, '4': 3, '5': 11, '6': '.flutter_tools.FileHash', '10': 'files'},
],
};
syntax = "proto3";
package flutter_tools;
message FileHash {
// The absolute path to the file on disk.
string path = 1;
// The last computed file hash.
string hash = 2;
}
message FileStorage {
// The current version of the file store.
int32 version = 1;
// All currently stored files.
repeated FileHash files = 2;
}
// 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 '../artifacts.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../globals.dart';
import 'build_system.dart';
import 'exceptions.dart';
/// An input function produces a list of additional input files for an
/// [Environment].
typedef InputFunction = List<File> Function(Environment environment);
/// Collects sources for a [Target] into a single list of [FileSystemEntities].
class SourceVisitor {
/// Create a new [SourceVisitor] from an [Environment].
SourceVisitor(this.environment, [this.inputs = true]);
/// The current environment.
final Environment environment;
/// Whether we are visiting inputs or outputs.
///
/// Defaults to `true`.
final bool inputs;
/// The entities are populated after visiting each source.
final List<File> sources = <File>[];
/// Visit a [Source] which contains a function.
///
/// The function is expected to produce a list of [FileSystemEntities]s.
void visitFunction(InputFunction function) {
sources.addAll(function(environment));
}
/// Visit a [Source] which contains a file uri.
///
/// The uri may that may include constants defined in an [Environment].
void visitPattern(String pattern) {
// perform substitution of the environmental values and then
// of the local values.
final List<String> segments = <String>[];
final List<String> rawParts = pattern.split('/');
final bool hasWildcard = rawParts.last.contains('*');
String wildcardFile;
if (hasWildcard) {
wildcardFile = rawParts.removeLast();
}
// If the pattern does not start with an env variable, then we have nothing
// to resolve it to, error out.
switch (rawParts.first) {
case Environment.kProjectDirectory:
segments.addAll(
fs.path.split(environment.projectDir.resolveSymbolicLinksSync()));
break;
case Environment.kBuildDirectory:
segments.addAll(fs.path.split(
environment.buildDir.resolveSymbolicLinksSync()));
break;
case Environment.kCacheDirectory:
segments.addAll(
fs.path.split(environment.cacheDir.resolveSymbolicLinksSync()));
break;
case Environment.kFlutterRootDirectory:
segments.addAll(
fs.path.split(environment.cacheDir.resolveSymbolicLinksSync()));
break;
default:
throw InvalidPatternException(pattern);
}
rawParts.skip(1).forEach(segments.add);
final String filePath = fs.path.joinAll(segments);
if (hasWildcard) {
// Perform a simple match by splitting the wildcard containing file one
// the `*`. For example, for `/*.dart`, we get [.dart]. We then check
// that part of the file matches. If there are values before and after
// the `*` we need to check that both match without overlapping. For
// example, `foo_*_.dart`. We want to match `foo_b_.dart` but not
// `foo_.dart`. To do so, we first subtract the first section from the
// string if the first segment matches.
final List<String> segments = wildcardFile.split('*');
if (segments.length > 2) {
throw InvalidPatternException(pattern);
}
if (!fs.directory(filePath).existsSync()) {
throw Exception('$filePath does not exist!');
}
for (FileSystemEntity entity in fs.directory(filePath).listSync()) {
final String filename = fs.path.basename(entity.path);
if (segments.isEmpty) {
sources.add(fs.file(entity.absolute));
} else if (segments.length == 1) {
if (filename.startsWith(segments[0]) ||
filename.endsWith(segments[0])) {
sources.add(entity.absolute);
}
} else if (filename.startsWith(segments[0])) {
if (filename.substring(segments[0].length).endsWith(segments[1])) {
sources.add(entity.absolute);
}
}
}
} else {
sources.add(fs.file(fs.path.normalize(filePath)));
}
}
/// Visit a [Source] which contains a [SourceBehavior].
void visitBehavior(SourceBehavior sourceBehavior) {
if (inputs) {
sources.addAll(sourceBehavior.inputs(environment));
} else {
sources.addAll(sourceBehavior.outputs(environment));
}
}
/// Visit a [Source] which is defined by an [Artifact] from the flutter cache.
///
/// If the [Artifact] points to a directory then all child files are included.
void visitArtifact(Artifact artifact, TargetPlatform platform, BuildMode mode) {
final String path = artifacts.getArtifactPath(artifact, platform: platform, mode: mode);
if (fs.isDirectorySync(path)) {
sources.addAll(<File>[
for (FileSystemEntity entity in fs.directory(path).listSync(recursive: true))
if (entity is File)
entity
]);
} else {
sources.add(fs.file(path));
}
}
}
/// A description of an input or output of a [Target].
abstract class Source {
/// This source is a file-uri which contains some references to magic
/// environment variables.
const factory Source.pattern(String pattern) = _PatternSource;
/// This source is produced by invoking the provided function.
const factory Source.function(InputFunction function) = _FunctionSource;
/// This source is produced by the [SourceBehavior] class.
const factory Source.behavior(SourceBehavior behavior) = _SourceBehavior;
/// The source is provided by an [Artifact].
///
/// If [artifact] points to a directory then all child files are included.
const factory Source.artifact(Artifact artifact, {TargetPlatform platform,
BuildMode mode}) = _ArtifactSource;
/// Visit the particular source type.
void accept(SourceVisitor visitor);
/// Whether the output source provided can be known before executing the rule.
///
/// This does not apply to inputs, which are always explicit and must be
/// evaluated before the build.
///
/// For example, [Source.pattern] and [Source.version] are not implicit
/// provided they do not use any wildcards. [Source.behavior] and
/// [Source.function] are always implicit.
bool get implicit;
}
/// An interface for describing input and output copies together.
abstract class SourceBehavior {
const SourceBehavior();
/// The inputs for a particular target.
List<File> inputs(Environment environment);
/// The outputs for a particular target.
List<File> outputs(Environment environment);
}
class _SourceBehavior implements Source {
const _SourceBehavior(this.value);
final SourceBehavior value;
@override
void accept(SourceVisitor visitor) => visitor.visitBehavior(value);
@override
bool get implicit => true;
}
class _FunctionSource implements Source {
const _FunctionSource(this.value);
final InputFunction value;
@override
void accept(SourceVisitor visitor) => visitor.visitFunction(value);
@override
bool get implicit => true;
}
class _PatternSource implements Source {
const _PatternSource(this.value);
final String value;
@override
void accept(SourceVisitor visitor) => visitor.visitPattern(value);
@override
bool get implicit => value.contains('*');
}
class _ArtifactSource implements Source {
const _ArtifactSource(this.artifact, { this.platform, this.mode });
final Artifact artifact;
final TargetPlatform platform;
final BuildMode mode;
@override
void accept(SourceVisitor visitor) => visitor.visitArtifact(artifact, platform, mode);
@override
bool get implicit => false;
}
// 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:pool/pool.dart';
import '../../asset.dart';
import '../../base/file_system.dart';
import '../../devfs.dart';
import '../build_system.dart';
/// The copying logic for flutter assets.
// TODO(jonahwilliams): combine the asset bundle logic with this rule so that
// we can compute the key for deleted assets. This is required to remove assets
// from build directories that are no longer part of the manifest and to unify
// the update/diff logic.
class AssetBehavior extends SourceBehavior {
const AssetBehavior();
@override
List<File> inputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
final List<File> results = <File>[];
final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
for (DevFSFileContent devFsContent in files) {
results.add(fs.file(devFsContent.file.path));
}
return results;
}
@override
List<File> outputs(Environment environment) {
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
final List<File> results = <File>[];
for (MapEntry<String, DevFSContent> entry in assetBundle.entries.entries) {
final File file = fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', entry.key));
results.add(file);
}
return results;
}
}
/// Copies the asset files from the [copyAssets] rule into place.
Future<void> copyAssetsInvocation(Map<String, ChangeType> updates, Environment environment) async {
final Directory output = environment
.buildDir
.childDirectory('flutter_assets');
if (output.existsSync()) {
output.deleteSync(recursive: true);
}
output.createSync(recursive: true);
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build(
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
packagesPath: environment.projectDir.childFile('.packages').path,
);
// Limit number of open files to avoid running out of file descriptors.
final Pool pool = Pool(64);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
final File file = fs.file(fs.path.join(output.path, entry.key));
file.parent.createSync(recursive: true);
await file.writeAsBytes(await entry.value.contentsAsBytes());
} finally {
resource.release();
}
}));
}
/// Copy the assets used in the application into a build directory.
const Target copyAssets = Target(
name: 'copy_assets',
inputs: <Source>[
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
Source.behavior(AssetBehavior()),
],
outputs: <Source>[
Source.pattern('{BUILD_DIR}/flutter_assets/AssetManifest.json'),
Source.pattern('{BUILD_DIR}/flutter_assets/FontManifest.json'),
Source.pattern('{BUILD_DIR}/flutter_assets/LICENSE'),
Source.behavior(AssetBehavior()), // <- everything in this subdirectory.
],
dependencies: <Target>[],
buildAction: copyAssetsInvocation,
);
This diff is collapsed.
// 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 '../build_system.dart';
import 'assets.dart';
import 'dart.dart';
/// Create an iOS debug application.
const Target debugIosApplication = Target(
name: 'debug_ios_application',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
dependencies: <Target>[
copyAssets,
kernelSnapshot,
]
);
/// Create an iOS profile application.
const Target profileIosApplication = Target(
name: 'profile_ios_application',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
dependencies: <Target>[
copyAssets,
aotAssemblyProfile,
]
);
/// Create an iOS debug application.
const Target releaseIosApplication = Target(
name: 'release_ios_application',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
dependencies: <Target>[
copyAssets,
aotAssemblyRelease,
]
);
// 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 '../../artifacts.dart';
import '../../base/file_system.dart';
import '../../globals.dart';
import '../build_system.dart';
// Copies all of the input files to the correct copy dir.
Future<void> copyLinuxAssets(Map<String, ChangeType> updates,
Environment environment) async {
final String basePath = artifacts.getArtifactPath(Artifact.linuxDesktopPath);
for (String input in updates.keys) {
final String outputPath = fs.path.join(
environment.projectDir.path,
'linux',
'flutter',
fs.path.relative(input, from: basePath),
);
final File destinationFile = fs.file(outputPath);
if (!destinationFile.parent.existsSync()) {
destinationFile.parent.createSync(recursive: true);
}
fs.file(input).copySync(destinationFile.path);
}
}
/// Copies the Linux desktop embedding files to the copy directory.
const Target unpackLinux = Target(
name: 'unpack_linux',
inputs: <Source>[
Source.artifact(Artifact.linuxDesktopPath),
],
outputs: <Source>[
Source.pattern('{PROJECT_DIR}/linux/flutter/libflutter_linux.so'),
Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_export.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_messenger.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_plugin_registrar.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/flutter_glfw.h'),
Source.pattern('{PROJECT_DIR}/linux/flutter/icudtl.dat'),
Source.pattern('{PROJECT_DIR}/linux/flutter/cpp_client_wrapper/*'),
],
dependencies: <Target>[],
buildAction: copyLinuxAssets,
);
// 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 '../../artifacts.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/process_manager.dart';
import '../../globals.dart';
import '../build_system.dart';
import 'assets.dart';
import 'dart.dart';
/// Copy the macOS framework to the correct copy dir by invoking 'cp -R'.
///
/// The shelling out is done to avoid complications with preserving special
/// files (e.g., symbolic links) in the framework structure.
///
/// Removes any previous version of the framework that already exists in the
/// target directory.
// TODO(jonahwilliams): remove shell out.
Future<void> copyFramework(Map<String, ChangeType> updates,
Environment environment) async {
final String basePath = artifacts.getArtifactPath(Artifact.flutterMacOSFramework);
final Directory targetDirectory = environment
.projectDir
.childDirectory('macos')
.childDirectory('Flutter')
.childDirectory('FlutterMacOS.framework');
if (targetDirectory.existsSync()) {
targetDirectory.deleteSync(recursive: true);
}
final ProcessResult result = processManager
.runSync(<String>['cp', '-R', basePath, targetDirectory.path]);
if (result.exitCode != 0) {
throw Exception(
'Failed to copy framework (exit ${result.exitCode}:\n'
'${result.stdout}\n---\n${result.stderr}',
);
}
}
const String _kOutputPrefix = '{PROJECT_DIR}/macos/Flutter/FlutterMacOS.framework';
/// Copies the macOS desktop framework to the copy directory.
const Target unpackMacos = Target(
name: 'unpack_macos',
inputs: <Source>[
Source.artifact(Artifact.flutterMacOSFramework),
],
outputs: <Source>[
Source.pattern('$_kOutputPrefix/FlutterMacOS'),
// Headers
Source.pattern('$_kOutputPrefix/Headers/FLEOpenGLContextHandling.h'),
Source.pattern('$_kOutputPrefix/Headers/FLEReshapeListener.h'),
Source.pattern('$_kOutputPrefix/Headers/FLEView.h'),
Source.pattern('$_kOutputPrefix/Headers/FLEViewController.h'),
Source.pattern('$_kOutputPrefix/Headers/FlutterBinaryMessenger.h'),
Source.pattern('$_kOutputPrefix/Headers/FlutterChannels.h'),
Source.pattern('$_kOutputPrefix/Headers/FlutterCodecs.h'),
Source.pattern('$_kOutputPrefix/Headers/FlutterMacOS.h'),
Source.pattern('$_kOutputPrefix/Headers/FlutterPluginMacOS.h'),
Source.pattern('$_kOutputPrefix/Headers/FlutterPluginRegistrarMacOS.h'),
// Modules
Source.pattern('$_kOutputPrefix/Modules/module.modulemap'),
// Resources
Source.pattern('$_kOutputPrefix/Resources/icudtl.dat'),
Source.pattern('$_kOutputPrefix/Resources/info.plist'),
// Ignore Versions folder for now
],
dependencies: <Target>[],
buildAction: copyFramework,
);
/// Build a macOS application.
const Target macosApplication = Target(
name: 'debug_macos_application',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
dependencies: <Target>[
unpackMacos,
kernelSnapshot,
copyAssets,
]
);
/// Build a macOS release application.
const Target macoReleaseApplication = Target(
name: 'release_macos_application',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
dependencies: <Target>[
unpackMacos,
aotElfRelease,
copyAssets,
]
);
// 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 '../../artifacts.dart';
import '../../base/file_system.dart';
import '../../globals.dart';
import '../build_system.dart';
/// Copies all of the input files to the correct copy dir.
Future<void> copyWindowsAssets(Map<String, ChangeType> updates,
Environment environment) async {
// This path needs to match the prefix in the rule below.
final String basePath = artifacts.getArtifactPath(Artifact.windowsDesktopPath);
for (String input in updates.keys) {
final String outputPath = fs.path.join(
environment.projectDir.path,
'windows',
'flutter',
fs.path.relative(input, from: basePath),
);
final File destinationFile = fs.file(outputPath);
if (!destinationFile.parent.existsSync()) {
destinationFile.parent.createSync(recursive: true);
}
fs.file(input).copySync(destinationFile.path);
}
}
/// Copies the Windows desktop embedding files to the copy directory.
const Target unpackWindows = Target(
name: 'unpack_windows',
inputs: <Source>[
Source.artifact(Artifact.windowsDesktopPath),
],
outputs: <Source>[
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll.exp'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll.lib'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_windows.dll.pdb'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_export.h'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_messenger.h'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_plugin_registrar.h'),
Source.pattern('{PROJECT_DIR}/windows/flutter/flutter_glfw.h'),
Source.pattern('{PROJECT_DIR}/windows/flutter/icudtl.dat'),
Source.pattern('{PROJECT_DIR}/windows/flutter/cpp_client_wrapper/*'),
],
dependencies: <Target>[],
buildAction: copyWindowsAssets,
);
// 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/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../build_system/build_system.dart';
import '../convert.dart';
import '../globals.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
/// The [BuildSystem] instance.
BuildSystem get buildSystem => context.get<BuildSystem>();
/// Assemble provides a low level API to interact with the flutter tool build
/// system.
class AssembleCommand extends FlutterCommand {
AssembleCommand() {
addSubcommand(AssembleRun());
addSubcommand(AssembleDescribe());
addSubcommand(AssembleListInputs());
addSubcommand(AssembleBuildDirectory());
}
@override
String get description => 'Assemble and build flutter resources.';
@override
String get name => 'assemble';
@override
bool get isExperimental => true;
@override
Future<FlutterCommandResult> runCommand() {
return null;
}
}
abstract class AssembleBase extends FlutterCommand {
AssembleBase() {
argParser.addMultiOption(
'define',
abbr: 'd',
help: 'Allows passing configuration to a target with --define=target=key=value.'
);
argParser.addOption(
'build-mode',
allowed: const <String>[
'debug',
'profile',
'release',
],
);
argParser.addOption(
'resource-pool-size',
help: 'The maximum number of concurrent tasks the build system will run.'
);
}
/// Returns the provided target platform.
///
/// Throws a [ToolExit] if none is provided. This intentionally has no
/// default.
TargetPlatform get targetPlatform {
final String value = argResults['target-platform'] ?? 'darwin-x64';
if (value == null) {
throwToolExit('--target-platform is required for flutter assemble.');
}
return getTargetPlatformForName(value);
}
/// Returns the provided build mode.
///
/// Throws a [ToolExit] if none is provided. This intentionally has no
/// default.
BuildMode get buildMode {
final String value = argResults['build-mode'] ?? 'debug';
if (value == null) {
throwToolExit('--build-mode is required for flutter assemble.');
}
return getBuildModeForName(value);
}
/// The name of the target we are describing or building.
String get targetName {
if (argResults.rest.isEmpty) {
throwToolExit('missing target name for flutter assemble.');
}
return argResults.rest.first;
}
/// The environmental configuration for a build invocation.
Environment get environment {
final FlutterProject flutterProject = FlutterProject.current();
final Environment result = Environment(
buildDir: fs.directory(getBuildDirectory()),
projectDir: flutterProject.directory,
defines: _parseDefines(argResults['define']),
);
return result;
}
static Map<String, String> _parseDefines(List<String> values) {
final Map<String, String> results = <String, String>{};
for (String chunk in values) {
final List<String> parts = chunk.split('=');
if (parts.length != 2) {
throwToolExit('Improperly formatted define flag: $chunk');
}
final String key = parts[0];
final String value = parts[1];
results[key] = value;
}
return results;
}
}
/// Execute a build starting from a target action.
class AssembleRun extends AssembleBase {
@override
String get description => 'Execute the stages for a specified target.';
@override
String get name => 'run';
@override
bool get isExperimental => true;
@override
Future<FlutterCommandResult> runCommand() async {
final BuildResult result = await buildSystem.build(targetName, environment, BuildSystemConfig(
resourcePoolSize: argResults['resource-pool-size'],
));
if (!result.success) {
for (MapEntry<String, ExceptionMeasurement> data in result.exceptions.entries) {
printError('Target ${data.key} failed: ${data.value.exception}');
printError('${data.value.exception}');
}
throwToolExit('build failed');
} else {
printStatus('build succeeded');
}
return null;
}
}
/// Fully describe a target and its dependencies.
class AssembleDescribe extends AssembleBase {
@override
String get description => 'List the stages for a specified target.';
@override
String get name => 'describe';
@override
bool get isExperimental => true;
@override
Future<FlutterCommandResult> runCommand() {
try {
printStatus(
json.encode(buildSystem.describe(targetName, environment))
);
} on Exception catch (err, stackTrace) {
printTrace(stackTrace.toString());
throwToolExit(err.toString());
}
return null;
}
}
/// List input files for a target.
class AssembleListInputs extends AssembleBase {
@override
String get description => 'List the inputs for a particular target.';
@override
String get name => 'inputs';
@override
bool get isExperimental => true;
@override
Future<FlutterCommandResult> runCommand() {
try {
final List<Map<String, Object>> results = buildSystem.describe(targetName, environment);
for (Map<String, Object> result in results) {
if (result['name'] == targetName) {
final List<String> inputs = result['inputs'];
inputs.forEach(printStatus);
}
}
} on Exception catch (err, stackTrace) {
printTrace(stackTrace.toString());
throwToolExit(err.toString());
}
return null;
}
}
/// Return the build directory for a configuiration.
class AssembleBuildDirectory extends AssembleBase {
@override
String get description => 'List the inputs for a particular target.';
@override
String get name => 'build-dir';
@override
bool get isExperimental => true;
@override
Future<FlutterCommandResult> runCommand() {
printStatus(environment.buildDir.path);
return null;
}
}
......@@ -21,6 +21,7 @@ import 'base/platform.dart';
import 'base/time.dart';
import 'base/user_messages.dart';
import 'base/utils.dart';
import 'build_system/build_system.dart';
import 'cache.dart';
import 'compile.dart';
import 'devfs.dart';
......@@ -67,6 +68,7 @@ Future<T> runInContext<T>(
Artifacts: () => CachedArtifacts(),
AssetBundleFactory: () => AssetBundleFactory.defaultInstance,
BotDetector: () => const BotDetector(),
BuildSystem: () => BuildSystem(),
Cache: () => Cache(),
ChromeLauncher: () => const ChromeLauncher(),
CocoaPods: () => CocoaPods(),
......
......@@ -38,6 +38,7 @@ dependencies:
yaml: 2.1.16
flutter_goldens_client:
path: ../flutter_goldens_client
protobuf: 0.13.15
# We depend on very specific internal implementation details of the
# 'test' package, which change between versions, so when upgrading
......@@ -83,7 +84,6 @@ dependencies:
pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
protobuf: 0.13.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pubspec_parse: 0.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
scratch_space: 0.0.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......
// 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/exceptions.dart';
import '../src/common.dart';
void main() {
test('Exceptions', () {
final MissingInputException missingInputException = MissingInputException(
<File>[fs.file('foo'), fs.file('bar')], 'example');
final CycleException cycleException = CycleException(const <Target>{
Target(
name: 'foo',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
),
Target(
name: 'bar',
buildAction: null,
inputs: <Source>[],
outputs: <Source>[],
)
});
final InvalidPatternException invalidPatternException = InvalidPatternException(
'ABC'
);
final MissingOutputException missingOutputException = MissingOutputException(
<File>[ fs.file('foo'), fs.file('bar') ],
'example'
);
final MisplacedOutputException misplacedOutputException = MisplacedOutputException(
'foo',
'example',
);
final MissingDefineException missingDefineException = MissingDefineException(
'foobar',
'example',
);
expect(
missingInputException.toString(),
'foo, bar were declared as an inputs, '
'but did not exist. Check the definition of target:example for errors');
expect(
cycleException.toString(),
'Dependency cycle detected in build: foo -> bar'
);
expect(
invalidPatternException.toString(),
'The pattern "ABC" is not valid'
);
expect(
missingOutputException.toString(),
'foo, bar were declared as outputs, but were not generated by the '
'action. Check the definition of target:example for errors'
);
expect(
misplacedOutputException.toString(),
'Target example produced an output at foo which is outside of the '
'current build or project directory',
);
expect(
missingDefineException.toString(),
'Target example required define foobar but it was not provided'
);
});
}
// 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/assets.dart';
import '../../src/common.dart';
import '../../src/testbed.dart';
void main() {
group('copy_assets', () {
Testbed testbed;
BuildSystem buildSystem;
Environment environment;
setUp(() {
testbed = Testbed(setup: () {
environment = Environment(
projectDir: fs.currentDirectory,
);
buildSystem = BuildSystem(<String, Target>{
copyAssets.name: copyAssets,
});
fs.file(fs.path.join('assets', 'foo', 'bar.png'))
..createSync(recursive: true);
fs.file('.packages')
..createSync();
fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync('''
name: example
flutter:
assets:
- assets/foo/bar.png
''');
});
});
test('Copies files to correct asset directory', () => testbed.run(() async {
await buildSystem.build('copy_assets', environment, const BuildSystemConfig());
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'AssetManifest.json')).existsSync(), true);
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'FontManifest.json')).existsSync(), true);
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'LICENSE')).existsSync(), true);
// See https://github.com/flutter/flutter/issues/35293
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true);
}));
test('Does not leave stale files in build directory', () => testbed.run(() async {
await buildSystem.build('copy_assets', environment, const BuildSystemConfig());
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true);
// Modify manifest to remove asset.
fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync('''
name: example
flutter:
''');
await buildSystem.build('copy_assets', environment, const BuildSystemConfig());
// See https://github.com/flutter/flutter/issues/35293
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), false);
}));
});
}
// 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:flutter_tools/src/base/build.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/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/exceptions.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/mocks.dart';
import '../../src/testbed.dart';
void main() {
group('dart rules', () {
Testbed testbed;
BuildSystem buildSystem;
Environment androidEnvironment;
Environment iosEnvironment;
MockProcessManager mockProcessManager;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
mockProcessManager = MockProcessManager();
testbed = Testbed(setup: () {
androidEnvironment = Environment(
projectDir: fs.currentDirectory,
defines: <String, String>{
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
}
);
iosEnvironment = Environment(
projectDir: fs.currentDirectory,
defines: <String, String>{
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
}
);
buildSystem = BuildSystem();
HostPlatform hostPlatform;
if (platform.isWindows) {
hostPlatform = HostPlatform.windows_x64;
} else if (platform.isLinux) {
hostPlatform = HostPlatform.linux_x64;
} else if (platform.isMacOS) {
hostPlatform = HostPlatform.darwin_x64;
} else {
assert(false);
}
final String skyEngineLine = platform.isWindows
? r'sky_engine:file:///C:/bin/cache/pkg/sky_engine/lib/'
: 'sky_engine:file:///bin/cache/pkg/sky_engine/lib/';
fs.file('.packages')
..createSync()
..writeAsStringSync('''
# Generated
$skyEngineLine
flutter_tools:lib/''');
final String engineArtifacts = fs.path.join('bin', 'cache',
'artifacts', 'engine');
final List<String> paths = <String>[
fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui',
'ui.dart'),
fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'sdk_ext',
'vmservice_io.dart'),
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
fs.path.join(engineArtifacts, getNameForHostPlatform(hostPlatform),
'frontend_server.dart.snapshot'),
fs.path.join(engineArtifacts, 'android-arm-profile',
getNameForHostPlatform(hostPlatform), 'gen_snapshot'),
fs.path.join(engineArtifacts, 'ios-profile', 'gen_snapshot'),
fs.path.join(engineArtifacts, 'common', 'flutter_patched_sdk',
'platform_strong.dill'),
fs.path.join('lib', 'foo.dart'),
fs.path.join('lib', 'bar.dart'),
fs.path.join('lib', 'fizz'),
];
for (String path in paths) {
fs.file(path).createSync(recursive: true);
}
}, overrides: <Type, Generator>{
KernelCompilerFactory: () => FakeKernelCompilerFactory(),
GenSnapshot: () => FakeGenSnapshot(),
});
});
test('kernel_snapshot Produces correct output directory', () => testbed.run(() async {
await buildSystem.build('kernel_snapshot', androidEnvironment, const BuildSystemConfig());
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path,'main.app.dill')).existsSync(), true);
}));
test('kernel_snapshot throws error if missing build mode', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('kernel_snapshot',
androidEnvironment..defines.remove(kBuildMode), const BuildSystemConfig());
expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
}));
test('aot_elf_profile Produces correct output directory', () => testbed.run(() async {
await buildSystem.build('aot_elf_profile', androidEnvironment, const BuildSystemConfig());
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'main.app.dill')).existsSync(), true);
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'app.so')).existsSync(), true);
}));
test('aot_elf_profile throws error if missing build mode', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('aot_elf_profile',
androidEnvironment..defines.remove(kBuildMode), const BuildSystemConfig());
expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
}));
test('aot_elf_profile throws error if missing target platform', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('aot_elf_profile',
androidEnvironment..defines.remove(kTargetPlatform), const BuildSystemConfig());
expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
}));
test('aot_assembly_profile throws error if missing build mode', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('aot_assembly_profile',
iosEnvironment..defines.remove(kBuildMode), const BuildSystemConfig());
expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
}));
test('aot_assembly_profile throws error if missing target platform', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('aot_assembly_profile',
iosEnvironment..defines.remove(kTargetPlatform), const BuildSystemConfig());
expect(result.exceptions.values.single.exception, isInstanceOf<MissingDefineException>());
}));
test('aot_assembly_profile throws error if built for non-iOS platform', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('aot_assembly_profile',
androidEnvironment, const BuildSystemConfig());
expect(result.exceptions.values.single.exception, isInstanceOf<Exception>());
}));
test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async {
iosEnvironment.defines[kIosArchs] ='armv7,arm64';
when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
.createSync(recursive: true);
return FakeProcessResult(
stdout: '',
stderr: '',
);
});
final BuildResult result = await buildSystem.build('aot_assembly_profile',
iosEnvironment, const BuildSystemConfig());
expect(result.success, true);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
}));
});
}
class MockProcessManager extends Mock implements ProcessManager {}
class FakeGenSnapshot implements GenSnapshot {
@override
Future<int> run({SnapshotType snapshotType, IOSArch iosArch, Iterable<String> additionalArgs = const <String>[]}) async {
final Directory out = fs.file(additionalArgs.last).parent;
if (iosArch == null) {
out.childFile('app.so').createSync();
out.childFile('gen_snapshot.d').createSync();
return 0;
}
out.childDirectory('App.framework').childFile('App').createSync(recursive: true);
out.childFile('snapshot_assembly.S').createSync();
out.childFile('snapshot_assembly.o').createSync();
return 0;
}
}
class FakeKernelCompilerFactory implements KernelCompilerFactory {
FakeKernelCompiler kernelCompiler = FakeKernelCompiler();
@override
Future<KernelCompiler> create(FlutterProject flutterProject) async {
return kernelCompiler;
}
}
class FakeKernelCompiler implements KernelCompiler {
@override
Future<CompilerOutput> compile({
String sdkRoot,
String mainPath,
String outputFilePath,
String depFilePath,
TargetModel targetModel = TargetModel.flutter,
bool linkPlatformKernelIn = false,
bool aot = false,
bool trackWidgetCreation,
List<String> extraFrontEndOptions,
String incrementalCompilerByteStorePath,
String packagesPath,
List<String> fileSystemRoots,
String fileSystemScheme,
bool targetProductVm = false,
String initializeFromDill}) async {
fs.file(outputFilePath).createSync(recursive: true);
return CompilerOutput(outputFilePath, 0, null);
}
}
// 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/linux.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/testbed.dart';
void main() {
group('unpack_linux', () {
Testbed testbed;
BuildSystem buildSystem;
Environment environment;
MockPlatform mockPlatform;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
mockPlatform = MockPlatform();
when(mockPlatform.isWindows).thenReturn(false);
when(mockPlatform.isMacOS).thenReturn(false);
when(mockPlatform.isLinux).thenReturn(true);
testbed = Testbed(setup: () {
Cache.flutterRoot = '';
environment = Environment(
projectDir: fs.currentDirectory,
);
buildSystem = BuildSystem(<String, Target>{
unpackLinux.name: unpackLinux,
});
fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').createSync(recursive: true);
fs.file('bin/cache/artifacts/engine/linux-x64/flutter_export.h').createSync();
fs.file('bin/cache/artifacts/engine/linux-x64/flutter_messenger.h').createSync();
fs.file('bin/cache/artifacts/engine/linux-x64/flutter_plugin_registrar.h').createSync();
fs.file('bin/cache/artifacts/engine/linux-x64/flutter_glfw.h').createSync();
fs.file('bin/cache/artifacts/engine/linux-x64/icudtl.dat').createSync();
fs.file('bin/cache/artifacts/engine/linux-x64/cpp_client_wrapper/foo').createSync(recursive: true);
fs.directory('linux').createSync();
}, overrides: <Type, Generator>{
Platform: () => mockPlatform,
});
});
test('Copies files to correct cache directory', () => testbed.run(() async {
final BuildResult result = await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
expect(result.hasException, false);
expect(fs.file('linux/flutter/libflutter_linux.so').existsSync(), true);
expect(fs.file('linux/flutter/flutter_export.h').existsSync(), true);
expect(fs.file('linux/flutter/flutter_messenger.h').existsSync(), true);
expect(fs.file('linux/flutter/flutter_plugin_registrar.h').existsSync(), true);
expect(fs.file('linux/flutter/flutter_glfw.h').existsSync(), true);
expect(fs.file('linux/flutter/icudtl.dat').existsSync(), true);
expect(fs.file('linux/flutter/cpp_client_wrapper/foo').existsSync(), true);
}));
test('Does not re-copy files unecessarily', () => testbed.run(() async {
await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
final DateTime modified = fs.file('linux/flutter/libflutter_linux.so').statSync().modified;
await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
expect(fs.file('linux/flutter/libflutter_linux.so').statSync().modified, equals(modified));
}));
test('Detects changes in input cache files', () => testbed.run(() async {
await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').writeAsStringSync('asd'); // modify cache.
await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
expect(fs.file('linux/flutter/libflutter_linux.so').readAsStringSync(), 'asd');
}));
});
}
class MockPlatform extends Mock implements Platform {}
// 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: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/base/process_manager.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/macos.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/testbed.dart';
void main() {
group('unpack_macos', () {
Testbed testbed;
BuildSystem buildSystem;
Environment environment;
MockPlatform mockPlatform;
setUp(() {
mockPlatform = MockPlatform();
when(mockPlatform.isWindows).thenReturn(false);
when(mockPlatform.isMacOS).thenReturn(true);
when(mockPlatform.isLinux).thenReturn(false);
testbed = Testbed(setup: () {
environment = Environment(
projectDir: fs.currentDirectory,
);
buildSystem = BuildSystem(<String, Target>{
unpackMacos.name: unpackMacos,
});
final List<File> inputs = <File>[
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/FlutterMacOS'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEReshapeListener.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEView.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEViewController.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterChannels.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterCodecs.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterMacOS.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Modules/module.modulemap'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Resources/icudtl.dat'),
fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Resources/info.plist'),
];
for (File input in inputs) {
input.createSync(recursive: true);
}
when(processManager.runSync(any)).thenAnswer((Invocation invocation) {
final List<String> arguments = invocation.positionalArguments.first;
final Directory source = fs.directory(arguments[arguments.length - 2]);
final Directory target = fs.directory(arguments.last)
..createSync(recursive: true);
for (FileSystemEntity entity in source.listSync(recursive: true)) {
if (entity is File) {
final String relative = fs.path.relative(entity.path, from: source.path);
final String destination = fs.path.join(target.path, relative);
if (!fs.file(destination).parent.existsSync()) {
fs.file(destination).parent.createSync();
}
entity.copySync(destination);
}
}
return FakeProcessResult()..exitCode = 0;
});
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
Platform: () => mockPlatform,
});
});
test('Copies files to correct cache directory', () => testbed.run(() async {
await buildSystem.build('unpack_macos', environment, const BuildSystemConfig());
expect(fs.directory('macos/Flutter/FlutterMacOS.framework').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/FlutterMacOS').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEReshapeListener.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEView.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEViewController.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterChannels.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterCodecs.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterMacOS.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Modules/module.modulemap').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Resources/icudtl.dat').existsSync(), true);
expect(fs.file('macos/Flutter/FlutterMacOS.framework/Resources/info.plist').existsSync(), true);
}));
});
}
class MockPlatform extends Mock implements Platform {}
class MockProcessManager extends Mock implements ProcessManager {}
class FakeProcessResult implements ProcessResult {
@override
int exitCode;
@override
int pid = 0;
@override
String stderr = '';
@override
String stdout = '';
}
// 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/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/windows.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/testbed.dart';
void main() {
group('unpack_windows', () {
Testbed testbed;
BuildSystem buildSystem;
Environment environment;
Platform platform;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
Cache.flutterRoot = '';
platform = MockPlatform();
when(platform.isWindows).thenReturn(true);
when(platform.isMacOS).thenReturn(false);
when(platform.isLinux).thenReturn(false);
when(platform.pathSeparator).thenReturn(r'\');
testbed = Testbed(setup: () {
environment = Environment(
projectDir: fs.currentDirectory,
);
buildSystem = BuildSystem(<String, Target>{
unpackWindows.name: unpackWindows,
});
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').createSync(recursive: true);
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\lutter_export.h').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_glfw.h').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat').createSync();
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\cpp_client_wrapper\foo').createSync(recursive: true);
fs.directory('windows').createSync();
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
Platform: () => platform,
});
});
test('Copies files to correct cache directory', () => testbed.run(() async {
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
expect(fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_windows.dll').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.exp').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.lib').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.pdb').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_plugin_registrar.h').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\flutter_glfw.h').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\icudtl.dat').existsSync(), true);
expect(fs.file(r'C:\windows\flutter\cpp_client_wrapper\foo').existsSync(), true);
}));
test('Does not re-copy files unecessarily', () => testbed.run(() async {
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
final DateTime modified = fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified;
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, equals(modified));
}));
test('Detects changes in input cache files', () => testbed.run(() async {
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
final DateTime modified = fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified;
fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').writeAsStringSync('asd'); // modify cache.
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, isNot(modified));
}));
});
}
class MockPlatform extends Mock implements Platform {}
// 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:args/command_runner.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/assemble.dart';
import 'package:flutter_tools/src/globals.dart';
import 'package:mockito/mockito.dart';
import '../src/common.dart';
import '../src/testbed.dart';
void main() {
group('Assemble', () {
Testbed testbed;
MockBuildSystem mockBuildSystem;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
mockBuildSystem = MockBuildSystem();
testbed = Testbed(overrides: <Type, Generator>{
BuildSystem: () => mockBuildSystem,
});
});
test('Can list the output directory relative to project root', () => testbed.run(() async {
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
await commandRunner.run(<String>['assemble', '--flutter-root=.', 'build-dir', '-dBuildMode=debug']);
final BufferLogger bufferLogger = logger;
final Environment environment = Environment(
defines: <String, String>{
'BuildMode': 'debug'
}, projectDir: fs.currentDirectory,
buildDir: fs.directory(getBuildDirectory()),
);
expect(bufferLogger.statusText.trim(),
fs.path.relative(environment.buildDir.path, from: fs.currentDirectory.path));
}));
test('Can describe a target', () => testbed.run(() async {
when(mockBuildSystem.describe('foobar', any)).thenReturn(<Map<String, Object>>[
<String, Object>{'fizz': 'bar'},
]);
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
await commandRunner.run(<String>['assemble', '--flutter-root=.', 'describe', 'foobar']);
final BufferLogger bufferLogger = logger;
expect(bufferLogger.statusText.trim(), '[{"fizz":"bar"}]');
}));
test('Can describe a target\'s inputs', () => testbed.run(() async {
when(mockBuildSystem.describe('foobar', any)).thenReturn(<Map<String, Object>>[
<String, Object>{'name': 'foobar', 'inputs': <String>['bar', 'baz']},
]);
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
await commandRunner.run(<String>['assemble', '--flutter-root=.', 'inputs', 'foobar']);
final BufferLogger bufferLogger = logger;
expect(bufferLogger.statusText.trim(), 'bar\nbaz');
}));
test('Can run a build', () => testbed.run(() async {
when(mockBuildSystem.build('foobar', any, any)).thenAnswer((Invocation invocation) async {
return BuildResult(true, const <String, ExceptionMeasurement>{}, const <String, PerformanceMeasurement>{});
});
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
await commandRunner.run(<String>['assemble', 'run', 'foobar']);
final BufferLogger bufferLogger = logger;
expect(bufferLogger.statusText.trim(), 'build succeeded');
}));
});
}
class MockBuildSystem extends Mock implements BuildSystem {}
......@@ -76,7 +76,7 @@ class Testbed {
: _setup = setup,
_overrides = overrides;
final Future<void> Function() _setup;
final FutureOr<void> Function() _setup;
final Map<Type, Generator> _overrides;
/// Runs `test` within a tool zone.
......
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